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