Filter also history items in resource search dialog
[simantics/platform.git] / bundles / org.simantics.debug.ui / src / org / simantics / debug / ui / SearchResourceDialog.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2016 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *     Semantum Oy - index based searching and graph manipulation (#4255)
12  *******************************************************************************/
13 package org.simantics.debug.ui;
14
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Comparator;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.TreeSet;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27 import org.eclipse.core.runtime.CoreException;
28 import org.eclipse.core.runtime.IProgressMonitor;
29 import org.eclipse.core.runtime.IStatus;
30 import org.eclipse.core.runtime.Status;
31 import org.eclipse.jface.dialogs.IDialogConstants;
32 import org.eclipse.jface.dialogs.IDialogSettings;
33 import org.eclipse.jface.resource.JFaceResources;
34 import org.eclipse.jface.resource.LocalResourceManager;
35 import org.eclipse.jface.resource.ResourceManager;
36 import org.eclipse.jface.viewers.IStructuredSelection;
37 import org.eclipse.jface.viewers.LabelProvider;
38 import org.eclipse.jface.viewers.StructuredSelection;
39 import org.eclipse.swt.graphics.Image;
40 import org.eclipse.swt.widgets.Composite;
41 import org.eclipse.swt.widgets.Control;
42 import org.eclipse.swt.widgets.Display;
43 import org.eclipse.swt.widgets.Shell;
44 import org.eclipse.ui.IMemento;
45 import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog;
46 import org.simantics.Simantics;
47 import org.simantics.db.ReadGraph;
48 import org.simantics.db.Resource;
49 import org.simantics.db.Session;
50 import org.simantics.db.common.primitiverequest.PossibleAdapter;
51 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
52 import org.simantics.db.common.request.BinaryRead;
53 import org.simantics.db.common.request.ObjectsWithType;
54 import org.simantics.db.common.request.ReadRequest;
55 import org.simantics.db.common.request.UniqueRead;
56 import org.simantics.db.common.uri.UnescapedChildMapOfResource;
57 import org.simantics.db.common.utils.NameUtils;
58 import org.simantics.db.exception.DatabaseException;
59 import org.simantics.db.layer0.migration.OntologiesFromLibrary;
60 import org.simantics.db.layer0.variable.Variables.Role;
61 import org.simantics.db.request.Read;
62 import org.simantics.db.service.SerialisationSupport;
63 import org.simantics.debug.ui.ResourceSearch.IResourceFilter;
64 import org.simantics.debug.ui.internal.Activator;
65 import org.simantics.debug.ui.internal.DebugUtils;
66 import org.simantics.layer0.Layer0;
67 import org.simantics.operation.Layer0X;
68 import org.simantics.scl.runtime.function.Function;
69 import org.simantics.ui.selection.ResourceWorkbenchSelectionElement;
70 import org.simantics.ui.workbench.dialogs.ResourceLabelProvider;
71 import org.simantics.utils.Container;
72 import org.simantics.utils.ui.BundleUtils;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76 /**
77  * TODO Add Debugger Composite as preview!
78  */
79 public class SearchResourceDialog extends FilteredItemsSelectionDialog {
80
81     private static final Logger LOGGER = LoggerFactory.getLogger(SearchResourceDialog.class);
82
83     /**
84      * The default maximum amount of Dependencies index hits to produce as results.
85      */
86     private static final int DEFAULT_MAX_INDEX_HITS = 1000;
87
88     private static final Pattern ID_PATTERN = Pattern.compile("\\$([0-9]+)");
89
90     private static final String SEARCH_RESOURCE_DIALOG = "SearchResourceDialog"; //$NON-NLS-1$
91
92     private static final int SHOW_IN_BROWSER_ID = IDialogConstants.CLIENT_ID + 1;
93
94     private static final String SHOW_IN_BROWSER_LABEL = "Show In Browser";
95
96     private Session session;
97     @SuppressWarnings("unused")
98     private IStructuredSelection selection;
99     private ResourceManager resourceManager;
100     private IResourceFilter resourceFilter = ResourceSearch.FILTER_ALL;
101
102     LabelProvider detailsLabelProvider = new LabelProvider() {
103         @Override
104         public String getText(Object element) {
105             if (element == null)
106                 return "null";
107             // This may happen if multiple choice is enabled
108             if (element instanceof String)
109                 return (String) element;
110             @SuppressWarnings("unchecked")
111             Container<Resource> rc = (Container<Resource>) element;
112             final Resource r = rc.get();
113             try {
114                 return session.syncRequest(new Read<String>() {
115                     @Override
116                     public String perform(ReadGraph g) throws DatabaseException {
117                         String name = NameUtils.getSafeName(g, r);
118                         String uri = DebugUtils.getPossibleRootRelativePath(g, r);
119                         return
120                                 "[" + r.getResourceId() + "] - "
121                                 + name
122                                 + (uri != null ? " - " : "")
123                                 + (uri != null ? uri : "");
124                     }
125                 });
126             } catch (DatabaseException e) {
127                 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Resource label provider failed unexpectedly.", e));
128                 return "";
129             }
130         }
131     };
132
133     static class ElementLabelProvider extends ResourceLabelProvider {
134         public ElementLabelProvider(Display display) {
135             super(display);
136         }
137         @Override
138         public String getText(Object element) {
139             if (element==null)
140                 return "null";
141             return element.toString();
142         }
143         @Override
144         public Image getImage(Object element) {
145             if (element == null)
146                 return null;
147             @SuppressWarnings("unchecked")
148             Container<Resource> rc = (Container<Resource>) element;
149             final Resource r = rc.get();
150             return super.getImage(r);
151         }
152     };
153
154     ElementLabelProvider labelProvider;
155
156     public SearchResourceDialog(Session s, boolean multi, Shell shell, String title) {
157         this(s, multi, shell, title, null);
158     }
159
160     public SearchResourceDialog(Session s, boolean multi, Shell shell, String title, IStructuredSelection selection) {
161         super(shell, multi);
162         this.session = s;
163         this.selection = selection;
164         this.labelProvider = new ElementLabelProvider(shell.getDisplay());
165         setMessage("Enter name, resource URI or ID");
166         setListLabelProvider(labelProvider);
167         setDetailsLabelProvider(detailsLabelProvider);
168         setTitle(title);
169         //setInitialPattern("*", FilteredItemsSelectionDialog.FULL_SELECTION);
170         setSelectionHistory(new ResourceSelectionHistory());
171         setSeparatorLabel("Previously selected above, others below");
172     }
173
174     @Override
175     protected void configureShell(Shell shell) {
176         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), shell);
177         setImage((Image) resourceManager.get(BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/cog_blue.png")));
178         super.configureShell(shell);
179     }
180
181     @Override
182     protected void createButtonsForButtonBar(Composite parent) {
183         createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
184                 true);
185         createButton(parent, SHOW_IN_BROWSER_ID, SHOW_IN_BROWSER_LABEL,
186                 true);
187         createButton(parent, IDialogConstants.CANCEL_ID,
188                 IDialogConstants.CANCEL_LABEL, false);
189     }
190
191     @Override
192     protected void buttonPressed(int buttonId) {
193         if (buttonId == SHOW_IN_BROWSER_ID) {
194             okPressed();
195             LabeledResource lr = (LabeledResource) getFirstResult();
196             ShowInBrowser.defaultExecute(new StructuredSelection(new ResourceWorkbenchSelectionElement(lr.resource)));
197             return;
198         }
199         super.buttonPressed(buttonId);
200     }
201
202     class ResourceSelectionHistory extends FilteredItemsSelectionDialog.SelectionHistory {
203
204         @Override
205         protected Object restoreItemFromMemento(IMemento memento) {
206             String data = memento.getTextData();
207             try {
208                 SerialisationSupport support = Simantics.getSession().getService(SerialisationSupport.class);
209                 Resource r = support.getResource(Long.parseLong(data));
210                 if (r == null)
211                     return null;
212
213                 String name = session.syncRequest(new UniqueRead<String>() {
214                     @Override
215                     public String perform(ReadGraph g) throws DatabaseException {
216                         if (!resourceFilter.acceptResource(g, r))
217                             return null;
218                         try {
219                             try {
220                                 return DebugUtils.getSafeLabel(g, r);
221                             } catch (Exception ex) {
222                                 System.out.println("Exception thrown from restoreItemFromMemento");
223                             }
224                         } catch (Throwable t) {}
225                         return "" + r.getResourceId();
226                     }
227                 });
228                 if (name==null) return null;
229                 return new LabeledResource(name, r);
230             } catch (NumberFormatException | DatabaseException e) {
231                 LOGGER.info("Search memento restoration failed.", e);
232                 return null;
233             }
234         }
235
236         @SuppressWarnings("unchecked")
237         @Override
238         protected void storeItemToMemento(Object item, IMemento memento) {
239             if(item instanceof Container) {
240                 try {
241                     SerialisationSupport support = Simantics.getSession().getService(SerialisationSupport.class);
242                     memento.putTextData(String.valueOf(support.getRandomAccessId(((Container<Resource>)item).get())));
243                 } catch (DatabaseException e) {
244                     e.printStackTrace();
245                 }
246             }
247         }
248     };
249
250     @Override
251     protected Control createExtendedContentArea(Composite parent) {
252         return null;
253     }
254
255     @Override
256     protected ItemsFilter createFilter() {
257         // NOTE: filter must be created here.
258         return new ItemsFilterWithSearchResults();
259     }
260
261     private class ItemsFilterWithSearchResults extends ItemsFilter {
262         private Set<Object> searchResults = new HashSet<Object>();
263
264         public ItemsFilterWithSearchResults() {
265             
266             final String pattern = getPattern();
267             final boolean findUris = pattern.trim().startsWith("http:/");
268             final long referencedResourceId = referencedResourceId(pattern);
269             final boolean findIds = referencedResourceId != 0;
270
271             searchResults.clear();
272             if (pattern.isEmpty()) return;
273             //progressMonitor.beginTask("Searching", IProgressMonitor.UNKNOWN);
274
275             try {
276                 session.syncRequest(new ReadRequest() {
277                     @Override
278                     public void run(ReadGraph graph) throws DatabaseException {
279                         // Find by ID first.
280                         if (findIds) {
281                             try {
282                                 Resource r = graph.getService(SerialisationSupport.class).getResource(referencedResourceId);
283                                 searchResults.add(new LabeledResource(DebugUtils.getSafeLabel(graph, r), r));
284                             } catch (DatabaseException e) {
285                                 // No resource for specified id.
286                             }
287                         }
288                         if (findUris) {
289                             String uri = pattern;
290                             if (uri.endsWith(Role.CHILD.getIdentifier())) {
291                                 uri = uri.substring(0, uri.length() - 1);
292                             }
293                             Resource r = graph.getPossibleResource(uri);
294                             if (r != null) {
295                                 searchResults.add(new LabeledResource(DebugUtils.getSafeURI(graph, r), r));
296
297                                 Map<String, Resource> children = graph.syncRequest(new UnescapedChildMapOfResource(r));
298                                 for (Resource child : children.values()) {
299                                         searchResults.add(new LabeledResource(DebugUtils.getSafeURI(graph, child), child));
300                                 }
301                             }
302                         } else {
303                             Resource project = Simantics.peekProjectResource();
304                             if (project != null) {
305                                 IResourceFilter rf = resourceFilter;
306                                 String filter = getFilterForResourceFilter(rf);
307                                 if (!filter.isEmpty())
308                                     filter += " AND ";
309                                 filter += "Name:" + pattern + "*";
310
311                                 Layer0 L0 = Layer0.getInstance(graph);
312
313                                 Set<Resource> indexRoots = new HashSet<>();
314                                 indexRoots.addAll(graph.syncRequest(new ObjectsWithType(project, L0.ConsistsOf, L0.IndexRoot)));
315                                 indexRoots.addAll(graph.syncRequest(new OntologiesFromLibrary(graph.getRootLibrary())));
316                                 for (Resource indexRoot : indexRoots) {
317                                     Collection<Resource> hits = find(graph, indexRoot, filter);
318                                     for (Resource r : hits) {
319                                         if (rf != null && !rf.acceptResource(graph, r))
320                                             continue;
321                                         searchResults.add(new LabeledResource(DebugUtils.getSafeLabel(graph, r), r));
322                                     }
323                                 }
324                             }
325                         }
326                     }
327
328                     public Collection<Resource> find(ReadGraph graph, Resource index, String filter) throws DatabaseException {
329                         //TimeLogger.resetTimeAndLog("find(" + graph.getURI(index) + ", " + filter + ")");
330                         Collection<Resource> indexResult = graph.syncRequest(new QueryIndex(index, filter), TransientCacheListener.<Collection<Resource>>instance());
331                         //TimeLogger.log("found " + indexResult.size());
332                         return indexResult;
333                     }
334
335                 });
336             } catch (DatabaseException ex) {
337                 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, ex.getMessage(), ex));
338             }
339
340         }
341
342         @Override
343         public boolean matchItem(Object item) {
344             return searchResults.contains(item);
345         }
346
347         @Override
348         public boolean isSubFilter(ItemsFilter filter) {
349             return false;
350         }
351         
352         @Override
353         public boolean isConsistentItem(Object item) {
354             return true;
355         }
356         
357         @Override
358         public boolean equalsFilter(ItemsFilter filter) {
359             return false;
360         }
361         
362         public void fillContentProvider(final AbstractContentProvider contentProvider) {
363                 for (Object item : searchResults) {
364                         contentProvider.add(item, this);
365                 }
366         }
367     }
368     
369     @Override
370     protected void fillContentProvider(final AbstractContentProvider contentProvider,
371             final ItemsFilter itemsFilter, final IProgressMonitor progressMonitor)
372                     throws CoreException {
373         ((ItemsFilterWithSearchResults) itemsFilter).fillContentProvider(contentProvider);
374         progressMonitor.done();
375     }
376
377     /**
378      * A (cacheable) query to optimize single index queries for immutable
379      * indexes such as ontologies.
380      */
381     static class QueryIndex extends BinaryRead<Resource, String, Collection<Resource>> {
382
383         public QueryIndex(Resource index, String filter) {
384             super(index, filter);
385         }
386
387         @Override
388         public Collection<Resource> perform(ReadGraph graph)
389                 throws DatabaseException {
390             Layer0X L0X = Layer0X.getInstance(graph);
391
392             @SuppressWarnings({ "unchecked", "rawtypes" })
393             Function dependencies = graph.syncRequest(new PossibleAdapter(L0X.DependencyResources, Function.class), TransientCacheListener.<Function>instance());
394             if (dependencies == null)
395                 return Collections.emptyList();
396
397             @SuppressWarnings("unchecked")
398             List<Resource> results = (List<Resource>) dependencies.apply(graph, parameter, parameter2, DEFAULT_MAX_INDEX_HITS);
399             if (results == null || results.isEmpty())
400                 return Collections.emptyList();
401
402             // TreeSet to keep the results in deterministic order and to prevent duplicates.
403             Set<Resource> resultSet = new TreeSet<>();
404             for (Resource res : results) {
405                 if (res != null && !resultSet.contains(res))
406                     resultSet.add(res);
407             }
408             return new ArrayList<Resource>(resultSet);
409         }
410
411     }
412
413     private long referencedResourceId(String pattern) {
414         Matcher m = ID_PATTERN.matcher(pattern);
415         if (m.matches()) {
416            String id = m.group(1);
417            try {
418                return Long.parseLong(id);
419            } catch (NumberFormatException nfe) {
420            }
421         }
422         return 0;
423     }
424
425     @Override
426     protected IDialogSettings getDialogSettings() {
427         IDialogSettings settings = Activator.getDefault().getDialogSettings()
428         .getSection(SEARCH_RESOURCE_DIALOG);
429         if (settings == null) {
430             settings = Activator.getDefault().getDialogSettings()
431             .addNewSection(SEARCH_RESOURCE_DIALOG);
432         }
433         return settings;
434     }
435
436     @SuppressWarnings("unchecked")
437     @Override
438     public String getElementName(Object item) {
439         return ((Container<Resource>)item).get().getResourceId()+"";
440         //return item.toString();
441     }
442
443     @Override
444     protected Comparator<?> getItemsComparator() {
445         return (arg0, arg1) -> {
446             return arg0.toString().compareTo(arg1.toString());
447         };
448     }
449
450     @Override
451     protected IStatus validateItem(Object item) {
452         return Status.OK_STATUS;
453     }
454
455     public IResourceFilter getResourceFilter() {
456         return resourceFilter;
457     }
458
459     public void setResourceFilter(IResourceFilter resourceFilter) {
460         this.resourceFilter = resourceFilter;
461     }
462
463     private String getFilterForResourceFilter(IResourceFilter filter) {
464         if (filter == null || filter == ResourceSearch.FILTER_ALL)
465             return "";
466         if (filter == ResourceSearch.FILTER_RELATIONS)
467             return "Types:Relation"; 
468         if (filter == ResourceSearch.FILTER_TYPES)
469             return "Types:Type"; 
470         return "";
471     }
472
473 }