/******************************************************************************* * Copyright (c) 2007, 2016 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation * Semantum Oy - index based searching and graph manipulation (#4255) *******************************************************************************/ package org.simantics.debug.ui; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IMemento; import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.common.primitiverequest.PossibleAdapter; import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.common.request.BinaryRead; import org.simantics.db.common.request.ObjectsWithType; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.uri.UnescapedChildMapOfResource; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.migration.OntologiesFromLibrary; import org.simantics.db.layer0.variable.Variables.Role; import org.simantics.db.request.Read; import org.simantics.db.service.SerialisationSupport; import org.simantics.debug.ui.ResourceSearch.IResourceFilter; import org.simantics.debug.ui.internal.Activator; import org.simantics.debug.ui.internal.DebugUtils; import org.simantics.layer0.Layer0; import org.simantics.operation.Layer0X; import org.simantics.scl.runtime.function.Function; import org.simantics.ui.selection.ResourceWorkbenchSelectionElement; import org.simantics.ui.workbench.dialogs.ResourceLabelProvider; import org.simantics.utils.Container; import org.simantics.utils.ui.BundleUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * TODO Add Debugger Composite as preview! */ public class SearchResourceDialog extends FilteredItemsSelectionDialog { private static final Logger LOGGER = LoggerFactory.getLogger(SearchResourceDialog.class); /** * The default maximum amount of Dependencies index hits to produce as results. */ private static final int DEFAULT_MAX_INDEX_HITS = 1000; private static final Pattern ID_PATTERN = Pattern.compile("\\$([0-9]+)"); private static final String SEARCH_RESOURCE_DIALOG = "SearchResourceDialog"; //$NON-NLS-1$ private static final int SHOW_IN_BROWSER_ID = IDialogConstants.CLIENT_ID + 1; private static final String SHOW_IN_BROWSER_LABEL = "Show In Browser"; private Session session; @SuppressWarnings("unused") private IStructuredSelection selection; private ResourceManager resourceManager; private IResourceFilter resourceFilter = ResourceSearch.FILTER_ALL; LabelProvider detailsLabelProvider = new LabelProvider() { @Override public String getText(Object element) { if (element == null) return "null"; // This may happen if multiple choice is enabled if (element instanceof String) return (String) element; @SuppressWarnings("unchecked") Container rc = (Container) element; final Resource r = rc.get(); try { return session.syncRequest(new Read() { @Override public String perform(ReadGraph g) throws DatabaseException { String name = NameUtils.getSafeName(g, r); String uri = DebugUtils.getPossibleRootRelativePath(g, r); return "[" + r.getResourceId() + "] - " + name + (uri != null ? " - " : "") + (uri != null ? uri : ""); } }); } catch (DatabaseException e) { Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Resource label provider failed unexpectedly.", e)); return ""; } } }; static class ElementLabelProvider extends ResourceLabelProvider { public ElementLabelProvider(Display display) { super(display); } @Override public String getText(Object element) { if (element==null) return "null"; return element.toString(); } @Override public Image getImage(Object element) { if (element == null) return null; @SuppressWarnings("unchecked") Container rc = (Container) element; final Resource r = rc.get(); return super.getImage(r); } }; ElementLabelProvider labelProvider; public SearchResourceDialog(Session s, boolean multi, Shell shell, String title) { this(s, multi, shell, title, null); } public SearchResourceDialog(Session s, boolean multi, Shell shell, String title, IStructuredSelection selection) { super(shell, multi); this.session = s; this.selection = selection; this.labelProvider = new ElementLabelProvider(shell.getDisplay()); setMessage("Enter name, resource URI or ID"); setListLabelProvider(labelProvider); setDetailsLabelProvider(detailsLabelProvider); setTitle(title); //setInitialPattern("*", FilteredItemsSelectionDialog.FULL_SELECTION); setSelectionHistory(new ResourceSelectionHistory()); setSeparatorLabel("Previously selected above, others below"); } @Override protected void configureShell(Shell shell) { this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), shell); setImage((Image) resourceManager.get(BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/cog_blue.png"))); super.configureShell(shell); } @Override protected void createButtonsForButtonBar(Composite parent) { createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); createButton(parent, SHOW_IN_BROWSER_ID, SHOW_IN_BROWSER_LABEL, true); createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); } @Override protected void buttonPressed(int buttonId) { if (buttonId == SHOW_IN_BROWSER_ID) { okPressed(); LabeledResource lr = (LabeledResource) getFirstResult(); ShowInBrowser.defaultExecute(new StructuredSelection(new ResourceWorkbenchSelectionElement(lr.resource))); return; } super.buttonPressed(buttonId); } class ResourceSelectionHistory extends FilteredItemsSelectionDialog.SelectionHistory { @Override protected Object restoreItemFromMemento(IMemento memento) { String data = memento.getTextData(); try { SerialisationSupport support = Simantics.getSession().getService(SerialisationSupport.class); Resource r = support.getResource(Long.parseLong(data)); if (r == null) return null; String name = session.syncRequest(new UniqueRead() { @Override public String perform(ReadGraph g) throws DatabaseException { if (!resourceFilter.acceptResource(g, r)) return null; try { try { return DebugUtils.getSafeLabel(g, r); } catch (Exception ex) { System.out.println("Exception thrown from restoreItemFromMemento"); } } catch (Throwable t) {} return "" + r.getResourceId(); } }); if (name==null) return null; return new LabeledResource(name, r); } catch (NumberFormatException | DatabaseException e) { LOGGER.info("Search memento restoration failed.", e); return null; } } @SuppressWarnings("unchecked") @Override protected void storeItemToMemento(Object item, IMemento memento) { if(item instanceof Container) { try { SerialisationSupport support = Simantics.getSession().getService(SerialisationSupport.class); memento.putTextData(String.valueOf(support.getRandomAccessId(((Container)item).get()))); } catch (DatabaseException e) { e.printStackTrace(); } } } }; @Override protected Control createExtendedContentArea(Composite parent) { return null; } @Override protected ItemsFilter createFilter() { // NOTE: filter must be created here. return new ItemsFilter() { @Override public boolean matchItem(Object item) { //return matches(item.toString()); return true; } // If this method returns true, it means fillContentProvider will // not be called again, but the existing results are just re-filtered. @Override public boolean isSubFilter(ItemsFilter filter) { //System.out.println(getPattern() + " vs. " + filter.getPattern()); return false; } @Override public boolean isConsistentItem(Object item) { return true; } @Override public boolean equalsFilter(ItemsFilter filter) { return false; } }; } @Override protected void fillContentProvider(final AbstractContentProvider contentProvider, final ItemsFilter itemsFilter, final IProgressMonitor progressMonitor) throws CoreException { final String pattern = itemsFilter.getPattern(); final boolean findUris = pattern.trim().startsWith("http:/"); final long referencedResourceId = referencedResourceId(pattern); final boolean findIds = referencedResourceId != 0; //progressMonitor.beginTask("Searching", IProgressMonitor.UNKNOWN); try { session.syncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { // Find by ID first. if (findIds) { try { Resource r = graph.getService(SerialisationSupport.class).getResource(referencedResourceId); contentProvider.add(new LabeledResource(DebugUtils.getSafeLabel(graph, r), r), itemsFilter); //contentProvider.add(new LabeledResource(pattern, r), itemsFilter); } catch (DatabaseException e) { // No resource for specified id. } } if (findUris) { String uri = pattern; if (uri.endsWith(Role.CHILD.getIdentifier())) { uri = uri.substring(0, uri.length() - 1); } Resource r = graph.getPossibleResource(uri); if (r != null) { contentProvider.add(new LabeledResource(DebugUtils.getSafeURI(graph, r), r), itemsFilter ); Map children = graph.syncRequest(new UnescapedChildMapOfResource(r)); for (Resource child : children.values()) { contentProvider.add(new LabeledResource(DebugUtils.getSafeURI(graph, child), child), itemsFilter ); } } } else { Resource project = Simantics.peekProjectResource(); if (project != null) { IResourceFilter rf = resourceFilter; String filter = getFilterForResourceFilter(rf); if (!filter.isEmpty()) filter += " AND "; filter += "Name:" + pattern + "*"; Layer0 L0 = Layer0.getInstance(graph); Set indexRoots = new HashSet<>(); indexRoots.addAll(graph.syncRequest(new ObjectsWithType(project, L0.ConsistsOf, L0.IndexRoot))); indexRoots.addAll(graph.syncRequest(new OntologiesFromLibrary(graph.getRootLibrary()))); for (Resource indexRoot : indexRoots) { Collection hits = find(graph, indexRoot, filter); for (Resource r : hits) { if (rf != null && !rf.acceptResource(graph, r)) continue; contentProvider.add(new LabeledResource(DebugUtils.getSafeLabel(graph, r), r), itemsFilter); } } } } } public Collection find(ReadGraph graph, Resource index, String filter) throws DatabaseException { //TimeLogger.resetTimeAndLog("find(" + graph.getURI(index) + ", " + filter + ")"); Collection indexResult = graph.syncRequest(new QueryIndex(index, filter), TransientCacheListener.>instance()); //TimeLogger.log("found " + indexResult.size()); return indexResult; } }); } catch (DatabaseException ex) { Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, ex.getMessage(), ex)); } progressMonitor.done(); } /** * A (cacheable) query to optimize single index queries for immutable * indexes such as ontologies. */ static class QueryIndex extends BinaryRead> { public QueryIndex(Resource index, String filter) { super(index, filter); } @Override public Collection perform(ReadGraph graph) throws DatabaseException { Layer0X L0X = Layer0X.getInstance(graph); @SuppressWarnings({ "unchecked", "rawtypes" }) Function dependencies = graph.syncRequest(new PossibleAdapter(L0X.DependencyResources, Function.class), TransientCacheListener.instance()); if (dependencies == null) return Collections.emptyList(); @SuppressWarnings("unchecked") List results = (List) dependencies.apply(graph, parameter, parameter2, DEFAULT_MAX_INDEX_HITS); if (results == null || results.isEmpty()) return Collections.emptyList(); // TreeSet to keep the results in deterministic order and to prevent duplicates. Set resultSet = new TreeSet<>(); for (Resource res : results) { if (res != null && !resultSet.contains(res)) resultSet.add(res); } return new ArrayList(resultSet); } } private long referencedResourceId(String pattern) { Matcher m = ID_PATTERN.matcher(pattern); if (m.matches()) { String id = m.group(1); try { return Long.parseLong(id); } catch (NumberFormatException nfe) { } } return 0; } @Override protected IDialogSettings getDialogSettings() { IDialogSettings settings = Activator.getDefault().getDialogSettings() .getSection(SEARCH_RESOURCE_DIALOG); if (settings == null) { settings = Activator.getDefault().getDialogSettings() .addNewSection(SEARCH_RESOURCE_DIALOG); } return settings; } @SuppressWarnings("unchecked") @Override public String getElementName(Object item) { return ((Container)item).get().getResourceId()+""; //return item.toString(); } @Override protected Comparator getItemsComparator() { return (arg0, arg1) -> { return arg0.toString().compareTo(arg1.toString()); }; } @Override protected IStatus validateItem(Object item) { return Status.OK_STATUS; } public IResourceFilter getResourceFilter() { return resourceFilter; } public void setResourceFilter(IResourceFilter resourceFilter) { this.resourceFilter = resourceFilter; } private String getFilterForResourceFilter(IResourceFilter filter) { if (filter == null || filter == ResourceSearch.FILTER_ALL) return ""; if (filter == ResourceSearch.FILTER_RELATIONS) return "Types:Relation"; if (filter == ResourceSearch.FILTER_TYPES) return "Types:Type"; return ""; } }