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