8532be1ae28a6cce6b97a280d399d399a2040897
[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]+)"); //$NON-NLS-1$
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 = Messages.SearchResourceDialog_ShowInBrowser;
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"; //$NON-NLS-1$
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() + "] - " //$NON-NLS-1$ //$NON-NLS-2$
121                                 + name
122                                 + (uri != null ? " - " : "") //$NON-NLS-1$ //$NON-NLS-2$
123                                 + (uri != null ? uri : ""); //$NON-NLS-1$
124                     }
125                 });
126             } catch (DatabaseException e) {
127                 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.SearchResourceDialog_ActivatorResourceLabelProviderFailed, e));
128                 return ""; //$NON-NLS-1$
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"; //$NON-NLS-1$
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(Messages.SearchResourceDialog_EnterNameResURIOrId);
166         setListLabelProvider(labelProvider);
167         setDetailsLabelProvider(detailsLabelProvider);
168         setTitle(title);
169         //setInitialPattern("*", FilteredItemsSelectionDialog.FULL_SELECTION);
170         setSelectionHistory(new ResourceSelectionHistory());
171         setSeparatorLabel(Messages.SearchResourceDialog_SeperatorLblPreviouslySelected);
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"))); //$NON-NLS-1$
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             if (lr != null) {
197                 ShowInBrowser.defaultExecute(new StructuredSelection(new ResourceWorkbenchSelectionElement(lr.resource)));
198             }
199             return;
200         }
201         super.buttonPressed(buttonId);
202     }
203
204     class ResourceSelectionHistory extends FilteredItemsSelectionDialog.SelectionHistory {
205
206         @Override
207         protected Object restoreItemFromMemento(IMemento memento) {
208             String data = memento.getTextData();
209             try {
210                 SerialisationSupport support = Simantics.getSession().getService(SerialisationSupport.class);
211                 Resource r = support.getResource(Long.parseLong(data));
212                 if (r == null)
213                     return null;
214
215                 String name = session.syncRequest(new UniqueRead<String>() {
216                     @Override
217                     public String perform(ReadGraph g) throws DatabaseException {
218                         if (!resourceFilter.acceptResource(g, r))
219                             return null;
220                         try {
221                             try {
222                                 return DebugUtils.getSafeLabel(g, r);
223                             } catch (Exception ex) {
224                                 System.out.println("Exception thrown from restoreItemFromMemento"); //$NON-NLS-1$
225                             }
226                         } catch (Throwable t) {}
227                         return "" + r.getResourceId(); //$NON-NLS-1$
228                     }
229                 });
230                 if (name==null) return null;
231                 return new LabeledResource(name, r);
232             } catch (NumberFormatException | DatabaseException e) {
233                 LOGGER.info("Search memento restoration failed.", e); //$NON-NLS-1$
234                 return null;
235             }
236         }
237
238         @SuppressWarnings("unchecked")
239         @Override
240         protected void storeItemToMemento(Object item, IMemento memento) {
241             if(item instanceof Container) {
242                 try {
243                     SerialisationSupport support = Simantics.getSession().getService(SerialisationSupport.class);
244                     memento.putTextData(String.valueOf(support.getRandomAccessId(((Container<Resource>)item).get())));
245                 } catch (DatabaseException e) {
246                     e.printStackTrace();
247                 }
248             }
249         }
250     };
251
252     @Override
253     protected Control createExtendedContentArea(Composite parent) {
254         return null;
255     }
256
257     @Override
258     protected ItemsFilter createFilter() {
259         // NOTE: filter must be created here.
260         return new ItemsFilterWithSearchResults();
261     }
262
263     private class ItemsFilterWithSearchResults extends ItemsFilter {
264         private Set<Object> searchResults = new HashSet<Object>();
265
266         public ItemsFilterWithSearchResults() {
267             
268             final String pattern = getPattern();
269             final boolean findUris = pattern.trim().startsWith("http:/"); //$NON-NLS-1$
270             final long referencedResourceId = referencedResourceId(pattern);
271             final boolean findIds = referencedResourceId != 0;
272
273             searchResults.clear();
274             if (pattern.isEmpty()) return;
275             //progressMonitor.beginTask("Searching", IProgressMonitor.UNKNOWN);
276
277             try {
278                 session.syncRequest(new ReadRequest() {
279                     @Override
280                     public void run(ReadGraph graph) throws DatabaseException {
281                         // Find by ID first.
282                         if (findIds) {
283                             try {
284                                 Resource r = graph.getService(SerialisationSupport.class).getResource(referencedResourceId);
285                                 searchResults.add(new LabeledResource(DebugUtils.getSafeLabel(graph, r), r));
286                             } catch (DatabaseException e) {
287                                 // No resource for specified id.
288                             }
289                         }
290                         if (findUris) {
291                             String uri = pattern;
292                             if (uri.endsWith(Role.CHILD.getIdentifier())) {
293                                 uri = uri.substring(0, uri.length() - 1);
294                             }
295                             Resource r = graph.getPossibleResource(uri);
296                             if (r != null) {
297                                 searchResults.add(new LabeledResource(DebugUtils.getSafeURI(graph, r), r));
298
299                                 Map<String, Resource> children = graph.syncRequest(new UnescapedChildMapOfResource(r));
300                                 for (Resource child : children.values()) {
301                                         searchResults.add(new LabeledResource(DebugUtils.getSafeURI(graph, child), child));
302                                 }
303                             }
304                         } else {
305                             Resource project = Simantics.peekProjectResource();
306                             if (project != null) {
307                                 IResourceFilter rf = resourceFilter;
308                                 String filter = getFilterForResourceFilter(rf);
309                                 if (!filter.isEmpty())
310                                     filter += " AND "; //$NON-NLS-1$
311                                 filter += "Name:" + pattern + "*"; //$NON-NLS-1$ //$NON-NLS-2$
312
313                                 Layer0 L0 = Layer0.getInstance(graph);
314
315                                 Set<Resource> indexRoots = new HashSet<>();
316                                 indexRoots.addAll(graph.syncRequest(new ObjectsWithType(project, L0.ConsistsOf, L0.IndexRoot)));
317                                 indexRoots.addAll(graph.syncRequest(new OntologiesFromLibrary(graph.getRootLibrary())));
318                                 for (Resource indexRoot : indexRoots) {
319                                     Collection<Resource> hits = find(graph, indexRoot, filter);
320                                     for (Resource r : hits) {
321                                         if (rf != null && !rf.acceptResource(graph, r))
322                                             continue;
323                                         searchResults.add(new LabeledResource(DebugUtils.getSafeLabel(graph, r), r));
324                                     }
325                                 }
326                             }
327                         }
328                     }
329
330                     public Collection<Resource> find(ReadGraph graph, Resource index, String filter) throws DatabaseException {
331                         //TimeLogger.resetTimeAndLog("find(" + graph.getURI(index) + ", " + filter + ")");
332                         Collection<Resource> indexResult = graph.syncRequest(new QueryIndex(index, filter), TransientCacheListener.<Collection<Resource>>instance());
333                         //TimeLogger.log("found " + indexResult.size());
334                         return indexResult;
335                     }
336
337                 });
338             } catch (DatabaseException ex) {
339                 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, ex.getMessage(), ex));
340             }
341
342         }
343
344         @Override
345         public boolean matchItem(Object item) {
346             return searchResults.contains(item);
347         }
348
349         @Override
350         public boolean isSubFilter(ItemsFilter filter) {
351             return false;
352         }
353         
354         @Override
355         public boolean isConsistentItem(Object item) {
356             return true;
357         }
358         
359         @Override
360         public boolean equalsFilter(ItemsFilter filter) {
361             return false;
362         }
363         
364         public void fillContentProvider(final AbstractContentProvider contentProvider) {
365                 for (Object item : searchResults) {
366                         contentProvider.add(item, this);
367                 }
368         }
369     }
370     
371     @Override
372     protected void fillContentProvider(final AbstractContentProvider contentProvider,
373             final ItemsFilter itemsFilter, final IProgressMonitor progressMonitor)
374                     throws CoreException {
375         ((ItemsFilterWithSearchResults) itemsFilter).fillContentProvider(contentProvider);
376         progressMonitor.done();
377     }
378
379     /**
380      * A (cacheable) query to optimize single index queries for immutable
381      * indexes such as ontologies.
382      */
383     static class QueryIndex extends BinaryRead<Resource, String, Collection<Resource>> {
384
385         public QueryIndex(Resource index, String filter) {
386             super(index, filter);
387         }
388
389         @Override
390         public Collection<Resource> perform(ReadGraph graph)
391                 throws DatabaseException {
392             Layer0X L0X = Layer0X.getInstance(graph);
393
394             @SuppressWarnings({ "unchecked", "rawtypes" })
395             Function dependencies = graph.syncRequest(new PossibleAdapter(L0X.DependencyResources, Function.class), TransientCacheListener.<Function>instance());
396             if (dependencies == null)
397                 return Collections.emptyList();
398
399             @SuppressWarnings("unchecked")
400             List<Resource> results = (List<Resource>) dependencies.apply(graph, parameter, parameter2, DEFAULT_MAX_INDEX_HITS);
401             if (results == null || results.isEmpty())
402                 return Collections.emptyList();
403
404             // TreeSet to keep the results in deterministic order and to prevent duplicates.
405             Set<Resource> resultSet = new TreeSet<>();
406             for (Resource res : results) {
407                 if (res != null && !resultSet.contains(res))
408                     resultSet.add(res);
409             }
410             return new ArrayList<Resource>(resultSet);
411         }
412
413     }
414
415     private long referencedResourceId(String pattern) {
416         Matcher m = ID_PATTERN.matcher(pattern);
417         if (m.matches()) {
418            String id = m.group(1);
419            try {
420                return Long.parseLong(id);
421            } catch (NumberFormatException nfe) {
422            }
423         }
424         return 0;
425     }
426
427     @Override
428     protected IDialogSettings getDialogSettings() {
429         IDialogSettings settings = Activator.getDefault().getDialogSettings()
430         .getSection(SEARCH_RESOURCE_DIALOG);
431         if (settings == null) {
432             settings = Activator.getDefault().getDialogSettings()
433             .addNewSection(SEARCH_RESOURCE_DIALOG);
434         }
435         return settings;
436     }
437
438     @SuppressWarnings("unchecked")
439     @Override
440     public String getElementName(Object item) {
441         return ((Container<Resource>)item).get().getResourceId()+""; //$NON-NLS-1$
442         //return item.toString();
443     }
444
445     @Override
446     protected Comparator<?> getItemsComparator() {
447         return (arg0, arg1) -> {
448             return arg0.toString().compareTo(arg1.toString());
449         };
450     }
451
452     @Override
453     protected IStatus validateItem(Object item) {
454         return Status.OK_STATUS;
455     }
456
457     public IResourceFilter getResourceFilter() {
458         return resourceFilter;
459     }
460
461     public void setResourceFilter(IResourceFilter resourceFilter) {
462         this.resourceFilter = resourceFilter;
463     }
464
465     private String getFilterForResourceFilter(IResourceFilter filter) {
466         if (filter == null || filter == ResourceSearch.FILTER_ALL)
467             return ""; //$NON-NLS-1$
468         if (filter == ResourceSearch.FILTER_RELATIONS)
469             return "Types:Relation";  //$NON-NLS-1$
470         if (filter == ResourceSearch.FILTER_TYPES)
471             return "Types:Type";  //$NON-NLS-1$
472         return ""; //$NON-NLS-1$
473     }
474
475 }