]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/browsecontexts/BrowseContext.java
Added transient caching for BrowseContext construction
[simantics/platform.git] / bundles / org.simantics.browsing.ui.model / src / org / simantics / browsing / ui / model / browsecontexts / BrowseContext.java
1 /*******************************************************************************
2  * Copyright (c) 2010, 2011 Association for Decentralized Information Management in
3  * 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  *******************************************************************************/
12 package org.simantics.browsing.ui.model.browsecontexts;
13
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23
24 import org.eclipse.jface.resource.ImageDescriptor;
25 import org.eclipse.swt.widgets.Event;
26 import org.simantics.browsing.ui.BuiltinKeys;
27 import org.simantics.browsing.ui.CheckedState;
28 import org.simantics.browsing.ui.NodeContext;
29 import org.simantics.browsing.ui.common.ColumnKeys;
30 import org.simantics.browsing.ui.common.NodeContextBuilder;
31 import org.simantics.browsing.ui.content.CompositeImageDecorator;
32 import org.simantics.browsing.ui.content.CompositeLabelDecorator;
33 import org.simantics.browsing.ui.content.ImageDecorator;
34 import org.simantics.browsing.ui.content.LabelDecorator;
35 import org.simantics.browsing.ui.content.Labeler.Modifier;
36 import org.simantics.browsing.ui.model.InvalidContribution;
37 import org.simantics.browsing.ui.model.actions.ActionBrowseContext;
38 import org.simantics.browsing.ui.model.check.CheckedStateContribution;
39 import org.simantics.browsing.ui.model.children.ChildContribution;
40 import org.simantics.browsing.ui.model.imagedecorators.ImageDecorationContribution;
41 import org.simantics.browsing.ui.model.images.ImageContribution;
42 import org.simantics.browsing.ui.model.labeldecorators.LabelDecorationContribution;
43 import org.simantics.browsing.ui.model.labels.LabelContribution;
44 import org.simantics.browsing.ui.model.modifiers.ModifierContribution;
45 import org.simantics.browsing.ui.model.modifiers.NoModifierRule;
46 import org.simantics.browsing.ui.model.nodetypes.EntityNodeType;
47 import org.simantics.browsing.ui.model.nodetypes.NodeType;
48 import org.simantics.browsing.ui.model.nodetypes.NodeTypeMultiMap;
49 import org.simantics.browsing.ui.model.nodetypes.OrderedNodeTypeMultiMap;
50 import org.simantics.browsing.ui.model.nodetypes.SpecialNodeType;
51 import org.simantics.browsing.ui.model.sorters.AlphanumericSorter;
52 import org.simantics.browsing.ui.model.sorters.Sorter;
53 import org.simantics.browsing.ui.model.sorters.SorterContribution;
54 import org.simantics.browsing.ui.model.tooltips.TooltipContribution;
55 import org.simantics.browsing.ui.model.visuals.FlatNodeContribution;
56 import org.simantics.browsing.ui.model.visuals.VisualsContribution;
57 import org.simantics.db.ReadGraph;
58 import org.simantics.db.RequestProcessor;
59 import org.simantics.db.Resource;
60 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
61 import org.simantics.db.common.request.ResourceRead;
62 import org.simantics.db.common.request.UnaryRead;
63 import org.simantics.db.exception.DatabaseException;
64 import org.simantics.db.exception.ResourceNotFoundException;
65 import org.simantics.db.layer0.variable.Variable;
66 import org.simantics.scl.reflection.OntologyVersions;
67 import org.simantics.viewpoint.ontology.ViewpointResource;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 /**
72  * BrowseContext holds all contributions related to given set of browse contexts.
73  * 
74  * @author Hannu Niemistö
75  */
76 public class BrowseContext {
77
78     private static final Logger LOGGER = LoggerFactory.getLogger(BrowseContext.class);
79         public static final boolean DEBUG = false;
80         
81     NodeTypeMultiMap<ChildContribution> childContributions = new NodeTypeMultiMap<>();
82     NodeTypeMultiMap<ChildContribution> parentContributions = new NodeTypeMultiMap<>();
83     OrderedNodeTypeMultiMap<LabelContribution> labelContributions = new OrderedNodeTypeMultiMap<>();
84     OrderedNodeTypeMultiMap<ImageContribution> imageContributions = new OrderedNodeTypeMultiMap<>();
85     OrderedNodeTypeMultiMap<CheckedStateContribution> checkedStateContributions = new OrderedNodeTypeMultiMap<>();
86     OrderedNodeTypeMultiMap<LabelDecorationContribution> labelDecorationContributions = new OrderedNodeTypeMultiMap<>();
87     OrderedNodeTypeMultiMap<ImageDecorationContribution> imageDecorationContributions = new OrderedNodeTypeMultiMap<>();
88     OrderedNodeTypeMultiMap<ModifierContribution> modifierContributions = new OrderedNodeTypeMultiMap<>();
89     OrderedNodeTypeMultiMap<SorterContribution> sorterContributions = new OrderedNodeTypeMultiMap<>();
90     OrderedNodeTypeMultiMap<FlatNodeContribution> flatNodeContributions = new OrderedNodeTypeMultiMap<>();
91     OrderedNodeTypeMultiMap<TooltipContribution> tooltipContributions = new OrderedNodeTypeMultiMap<>();
92
93     private final String[] uris; 
94
95     private BrowseContext(String... uris) {
96         if (uris == null)
97             throw new NullPointerException("null URIs");
98         this.uris = uris;
99     }
100
101     public String[] getURIs() {
102         return uris;
103     }
104
105     public static BrowseContext get(ReadGraph graph,NodeContext context,BrowseContext defaultContext, boolean useNodeBrowseContexts) throws DatabaseException {
106         if(!useNodeBrowseContexts) return defaultContext;
107         BrowseContext mbc = graph.syncRequest(new ResolveBrowseContext(context));
108         if(mbc != null) return mbc;
109         BrowseContext parentContext = (BrowseContext)context.getConstant(BuiltinKeys.BROWSE_CONTEXT);
110         if(parentContext != null) return parentContext;
111         return defaultContext;
112     }
113
114     private static BrowseContext loadCachedVisuals(ReadGraph g, Resource visualsContributionResource) throws DatabaseException, InvalidContribution {
115         try {
116             return g.syncRequest(new ResourceRead<BrowseContext>(visualsContributionResource) {
117                 @Override
118                 public BrowseContext perform(ReadGraph graph) throws DatabaseException {
119                     try {
120                         BrowseContext bc = new BrowseContext();
121                         VisualsContribution.load(g, visualsContributionResource,
122                                 bc.labelContributions,
123                                 bc.imageContributions,
124                                 bc.checkedStateContributions,
125                                 bc.labelDecorationContributions,
126                                 bc.imageDecorationContributions,
127                                 bc.modifierContributions,
128                                 bc.sorterContributions,
129                                 bc.flatNodeContributions,
130                                 bc.tooltipContributions
131                                 );
132                         return bc;
133                     } catch (InvalidContribution e) {
134                         throw new DatabaseException(e);
135                     }
136                 }
137             }, TransientCacheAsyncListener.instance());
138         } catch (DatabaseException e) {
139             Throwable c = e.getCause();
140             if (c instanceof InvalidContribution)
141                 throw (InvalidContribution) c;
142             throw e;
143         }
144     }
145
146     /**
147      * Creates a new BrowseContext for the given Collection of {@link Resource}s.
148      * 
149      * @param g
150      * @param browseContextResources
151      * @return new BrowseContext
152      * @throws DatabaseException
153      * @throws InvalidContribution
154      */
155     public static BrowseContext create(ReadGraph g, Collection<Resource> browseContextResources) throws DatabaseException, InvalidContribution {
156         ViewpointResource vr = ViewpointResource.getInstance(g);
157         BrowseContext browseContext = new BrowseContext( BrowseContexts.toSortedURIs(g, browseContextResources) );
158         for(Resource browseContextResource : findSubcontexts(g, browseContextResources)) {
159             
160             for(Resource childContributionResource : 
161                 g.getObjects(browseContextResource, vr.BrowseContext_HasChildContribution)) {
162                 ChildContribution contribution = ChildContribution.createCached(g, childContributionResource);
163                 browseContext.childContributions.put(contribution.getParentNodeType(), contribution);
164                 browseContext.parentContributions.put(contribution.getChildNodeType(), contribution);
165             }
166             
167             for(Resource visualsContributionResource : 
168                 g.getObjects(browseContextResource, vr.BrowseContext_HasVisualsContribution)) {
169                 BrowseContext visuals = loadCachedVisuals(g, visualsContributionResource);
170                 visuals.labelContributions.appendTo(browseContext.labelContributions);
171                 visuals.imageContributions.appendTo(browseContext.imageContributions);
172                 visuals.checkedStateContributions.appendTo(browseContext.checkedStateContributions);
173                 visuals.labelDecorationContributions.appendTo(browseContext.labelDecorationContributions);
174                 visuals.imageDecorationContributions.appendTo(browseContext.imageDecorationContributions);
175                 visuals.modifierContributions.appendTo(browseContext.modifierContributions);
176                 visuals.sorterContributions.appendTo(browseContext.sorterContributions);
177                 visuals.flatNodeContributions.appendTo(browseContext.flatNodeContributions);
178                 visuals.tooltipContributions.appendTo(browseContext.tooltipContributions);
179             }
180         }
181         //browseContext.visualize();
182         return browseContext;
183     }
184
185     public static Set<String> getBrowseContextClosure(RequestProcessor processor, final Set<String> browseContexts) throws DatabaseException {
186         return processor.syncRequest(new UnaryRead<Set<String>, Set<String>>(browseContexts) {
187             @Override
188             public Set<String> perform(ReadGraph graph) throws DatabaseException {
189                 Collection<Resource> browseContextResources = new ArrayList<>(parameter.size());
190                 for (String browseContext : parameter) {
191                     try {
192                         browseContextResources.add(graph.getResource(browseContext));
193                     } catch (ResourceNotFoundException e) {
194                         LOGGER.error("Didn't find " + browseContext + " while loading model browser.", e);
195                     }
196                 }
197                 Collection<Resource> allBrowseContextResources = BrowseContext.findSubcontexts(graph, browseContextResources);
198                 Set<String> result = new HashSet<>();
199                 for (Resource r : allBrowseContextResources)
200                     result.add(graph.getURI(r));
201                 return result;
202             }
203         }, TransientCacheAsyncListener.instance());
204     }
205
206     public static Collection<Resource> findSubcontexts(ReadGraph g, Collection<Resource> browseContexts)
207             throws DatabaseException {
208         return g.syncRequest(new UnaryRead<Collection<Resource>, Collection<Resource>>(browseContexts) {
209             @Override
210             public Collection<Resource> perform(ReadGraph graph) throws DatabaseException {
211                 return findSubcontexts0(graph, parameter);
212             }
213         }, TransientCacheAsyncListener.instance());
214     }
215
216     private static Collection<Resource> findSubcontexts0(ReadGraph g,
217             Collection<Resource> browseContexts) throws DatabaseException {
218         ViewpointResource vr = ViewpointResource.getInstance(g);
219         HashSet<Resource> result = new HashSet<>(browseContexts);
220         ArrayList<Resource> stack = new ArrayList<>(browseContexts);
221         while(!stack.isEmpty()) {
222             Resource cur = stack.remove(stack.size()-1);
223             for(Resource sc : g.getObjects(cur, vr.BrowseContext_Includes))
224                 if(result.add(sc))
225                     stack.add(sc);
226         }
227         return result;
228     }
229
230     /**
231      * Finds the possible children of the given {@link NodeContext} parameter.
232      * 
233      * @param graph
234      * @param parent
235      * @return Collection of children or an empty collection in case node has no children
236      * @throws DatabaseException
237      */
238     public Collection<NodeContext> getChildren(ReadGraph graph, NodeContext parent) throws DatabaseException {
239         if(isFlattened(graph, parent))
240             return Collections.emptyList();
241         else
242             return getChildrenImpl(graph, parent);
243     }
244     
245     private Collection<NodeContext> getChildrenImpl(ReadGraph graph, NodeContext parent) throws DatabaseException {
246         NodeType nodeType = getNodeType(graph, parent);
247         if(nodeType == null)
248             return Collections.emptyList();
249         ArrayList<NodeContext> result = new ArrayList<NodeContext>();
250         Collection<ChildContribution> contributions = childContributions.get(graph, nodeType);
251         if(contributions.size() > 1) {
252             Map<String,ChildContribution> contribs = new HashMap<>(); 
253             for(ChildContribution contribution : contributions) {
254                 String identifier = contribution.getIdentifier();
255                 ChildContribution current = contribs.get(identifier);
256                 if(current != null && current.getPriority() > contribution.getPriority()) continue;
257                 contribs.put(identifier, contribution);
258             }
259             contributions = contribs.values();
260         }
261         for(ChildContribution contribution : contributions) {
262                 Collection<NodeContext> children = contribution.getChildren(graph, parent);
263             result.addAll(children);
264             if(DEBUG) {
265                     LOGGER.info("contribution: " + contribution.getIdentifier());
266                     for(NodeContext ctx : children)
267                         LOGGER.info("-" + ctx);
268             }
269         }
270         
271         // Sorting the result
272         if(!result.isEmpty()) {            
273             for(SorterContribution contribution : sorterContributions.get(graph, nodeType)) { 
274                 Sorter sorter = contribution.getSorter(graph, parent);
275                 if(sorter != null) {
276                     sorter.sort(graph, this, result);
277                     return result;
278                 }
279             }
280             AlphanumericSorter.INSTANCE.sort(graph, this, result);
281         }
282         
283         result = flatten(graph, result);
284         //result = augment(graph, result);
285         
286         return result;
287     }
288     
289     private ArrayList<NodeContext> flatten(ReadGraph graph, ArrayList<NodeContext> result) throws DatabaseException {
290         ArrayList<NodeContext> flattened = new ArrayList<NodeContext>();
291         for(NodeContext node : result)
292             if(isFlattened(graph, node)) {
293                 flattened.add(node);
294                 flattened.addAll(getChildrenImpl(graph, node));
295             }
296             else
297                 flattened.add(node);
298         return flattened;
299     }
300     
301     public static ArrayList<NodeContext> augment(ReadGraph graph, BrowseContext bc, Collection<NodeContext> contexts, boolean resolveABC) throws DatabaseException {
302         ArrayList<NodeContext> result = new ArrayList<NodeContext>();
303         for(NodeContext context : contexts) {
304                 ActionBrowseContext abc = null;
305                 if(resolveABC) {
306                         abc = graph.syncRequest(new ResolveActionBrowseContext(context));
307                         if(abc == null) abc = (ActionBrowseContext)context.getConstant(BuiltinKeys.ACTION_BROWSE_CONTEXT);
308                 }
309             result.add(NodeContextBuilder.buildWithData(NodeType.KEY_SEQUENCE_EXT,
310                     new Object[] {
311                     context.getConstant(BuiltinKeys.INPUT), 
312                     context.getConstant(NodeType.TYPE),
313                     context.getConstant(BuiltinKeys.UI_CONTEXT),
314                     bc, abc}));
315         }
316         return result;
317     }
318     
319     private boolean isFlattened(ReadGraph graph, NodeContext node) throws DatabaseException {
320         NodeType nodeType = getNodeType(graph, node);
321         return nodeType != null && !flatNodeContributions.get(graph, nodeType).isEmpty();
322     }
323
324     /**
325      * Finds the possible parents of the given {@link NodeContext} parameter.
326      * 
327      * @param graph
328      * @param child
329      * @return Collection of parents or an empty Collection in case node has no parents.
330      * @throws DatabaseException
331      */
332     public Collection<NodeContext> getParents(ReadGraph graph, NodeContext child) throws DatabaseException {
333         NodeType nodeType = getNodeType(graph, child);
334         if(nodeType == null)
335             return Collections.emptyList();
336         ArrayList<NodeContext> result = new ArrayList<NodeContext>();
337         for(ChildContribution contribution : parentContributions.get(graph, nodeType)) {
338             result.addAll(contribution.getParents(graph, child));
339         }
340         return result;
341     }
342     
343     public boolean hasChildren(ReadGraph graph, NodeContext parent) throws DatabaseException {
344         NodeType nodeType = getNodeType(graph, parent);
345         if(nodeType == null)
346             return false;        
347         for(ChildContribution contribution : childContributions.get(graph, nodeType))
348             if(contribution.hasChildren(graph, parent))
349                 return true;
350         return false;
351     }
352     
353     private static NodeType getNodeType(ReadGraph graph, NodeContext parent) throws DatabaseException {
354         NodeType nodeType = parent.getConstant(NodeType.TYPE);
355         if(nodeType == null) {            
356             // TODO remove this code when root of model browser is fixed
357             Object input = parent.getConstant(BuiltinKeys.INPUT);
358             if(input instanceof Resource) {
359                 nodeType = EntityNodeType.getNodeTypeFor(graph, (Resource)input);
360             } else if (input instanceof Variable) {
361                 String uri = OntologyVersions.getInstance().currentVersion("http://www.simantics.org/Modeling-0.0/ModelingBrowseContext/Variable");
362                 return new SpecialNodeType(graph.getResource(uri), Variable.class);
363             }
364         }
365         return nodeType;
366     }
367     
368     /**
369      * Finds labels for the given {@link NodeContext} parameter.
370      * 
371      * @param graph
372      * @param parent
373      * @return Map containing all the labels assigned by key indicating the column e.g. "single"
374      * @throws DatabaseException
375      */
376     public Map<String, String> getLabel(ReadGraph graph, NodeContext parent) throws DatabaseException {
377         NodeType nodeType = getNodeType(graph, parent);
378         if(nodeType == null)
379             return Collections.singletonMap(ColumnKeys.SINGLE, "ERROR (no node type)");
380         List<LabelContribution> contributions = labelContributions.get(graph, nodeType); 
381         for(LabelContribution contribution : contributions) { 
382             Map<String, String> label = contribution.getLabel(graph, parent);
383             if(label != null)
384                 return label;
385         }
386         return Collections.singletonMap(ColumnKeys.SINGLE, "(no label rule)");
387     }
388
389     /**
390      * Finds {@link ImageDescriptor}s for the given {@link NodeContext} parameter.
391      * 
392      * @param graph
393      * @param parent
394      * @return Map containing all the {@ImageDescriptor}s or empty
395      * @throws DatabaseException
396      */
397     public Map<String, ImageDescriptor> getImage(ReadGraph graph, NodeContext parent) throws DatabaseException {
398         NodeType nodeType = getNodeType(graph, parent);
399         if(nodeType == null)
400             return Collections.emptyMap();
401         for(ImageContribution contribution : imageContributions.get(graph, nodeType)) { 
402             Map<String, ImageDescriptor> image = contribution.getImage(graph, parent);
403             if(image != null)
404                 return image;
405         }
406         return Collections.emptyMap();
407     }
408
409     /**
410      * Finds if the given {@link NodeContext} is checked or not.
411      * 
412      * @param graph
413      * @param parent
414      * @return
415      * @throws DatabaseException
416      */
417     public CheckedState getCheckedState(ReadGraph graph, NodeContext parent) throws DatabaseException {
418         NodeType nodeType = getNodeType(graph, parent);
419         if(nodeType == null)
420             return CheckedState.NOT_CHECKED;
421         for(CheckedStateContribution contribution : checkedStateContributions.get(graph, nodeType)) { 
422             CheckedState state = contribution.getCheckedState(graph, parent);
423             if(state != null)
424                 return state;
425         }
426         return CheckedState.NOT_CHECKED;
427     }
428
429     /**
430      * Finds {@link LabelDecorator} for the given {@link NodeContext} parameter.
431      * 
432      * @param graph
433      * @param context
434      * @return
435      * @throws DatabaseException
436      */
437     public LabelDecorator getLabelDecorator(ReadGraph graph, NodeContext context) throws DatabaseException {
438         NodeType nodeType = getNodeType(graph, context);
439         if(nodeType == null)
440             return CompositeLabelDecorator.ID;
441         ArrayList<LabelDecorator> decorators = new ArrayList<LabelDecorator>();
442         for(LabelDecorationContribution contribution : labelDecorationContributions.get(graph, nodeType)) {
443             LabelDecorator decorator = contribution.getLabelDecorator(graph, context);
444             if(decorator != null)
445                 decorators.add(decorator);
446         }
447         return CompositeLabelDecorator.create(decorators);
448     }
449
450     /**
451      * Finds {@link ImageDecorator} for the given {@link NodeContext} parameter.
452      * 
453      * @param graph
454      * @param context
455      * @return
456      * @throws DatabaseException
457      */
458     public ImageDecorator getImageDecorator(ReadGraph graph, NodeContext context) throws DatabaseException {
459         NodeType nodeType = getNodeType(graph, context);
460         if(nodeType == null)
461             return CompositeImageDecorator.ID;
462         ArrayList<ImageDecorator> decorators = new ArrayList<ImageDecorator>();
463         for(ImageDecorationContribution contribution : imageDecorationContributions.get(graph, nodeType)) {
464             ImageDecorator decorator = contribution.getImageDecorator(graph, context);
465             if(decorator != null)
466                 decorators.add(decorator);
467         }
468         return CompositeImageDecorator.create(decorators);
469     }
470
471     /**
472      * Finds {@link Modifier} for the given {@link NodeContext} parameter.
473      * 
474      * @param graph
475      * @param context
476      * @param columnKey
477      * @return
478      * @throws DatabaseException
479      */
480     public Modifier getModifier(ReadGraph graph, NodeContext context,
481             String columnKey) throws DatabaseException {
482         NodeType nodeType = getNodeType(graph, context);
483         if(nodeType != null)
484             for(ModifierContribution contribution : modifierContributions.get(graph, nodeType)) { 
485                 Modifier modifier = contribution.getModifier(graph, context, columnKey);
486                 if(modifier == NoModifierRule.NO_MODIFIER)
487                     return null;
488                 if(modifier != null)
489                     return modifier;
490             }
491         return null;
492     }
493     
494     public TooltipContribution shouldCreateToolTip(ReadGraph graph, Event event, NodeContext context) throws DatabaseException {
495         NodeType nodeType = getNodeType(graph, context);
496         if(nodeType != null)
497             for(TooltipContribution contribution : tooltipContributions.get(graph, nodeType)) { 
498                 if (contribution.shouldCreateToolTip(graph, context))
499                     return contribution;
500             }
501         return null;
502     }
503     
504     public Object getTooltip(TooltipContribution contribution, Object event, Object parent, NodeContext context) throws DatabaseException {
505         Object tooltip = contribution.getTooltip(event, parent, context);
506         if (tooltip != null)
507             return tooltip;
508         return null;
509     }
510
511     @Override
512     public int hashCode() {
513         return Arrays.hashCode(uris);
514     }
515
516     @Override
517     public boolean equals(Object obj) {
518         if (this == obj)
519             return true;
520         if (obj == null)
521             return false;
522         if (getClass() != obj.getClass())
523             return false;
524         BrowseContext other = (BrowseContext) obj;
525         return Arrays.equals(uris, other.uris);
526     }
527
528     @Override
529     public String toString() {
530         return getClass().getSimpleName() + Arrays.toString(uris);
531     }
532
533 }