/******************************************************************************* * Copyright (c) 2010, 2011 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.browsing.ui.model.browsecontexts; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; import org.simantics.browsing.ui.BuiltinKeys; import org.simantics.browsing.ui.CheckedState; import org.simantics.browsing.ui.NodeContext; import org.simantics.browsing.ui.common.ColumnKeys; import org.simantics.browsing.ui.common.NodeContextBuilder; import org.simantics.browsing.ui.content.CompositeImageDecorator; import org.simantics.browsing.ui.content.CompositeLabelDecorator; import org.simantics.browsing.ui.content.ImageDecorator; import org.simantics.browsing.ui.content.LabelDecorator; import org.simantics.browsing.ui.content.Labeler.Modifier; import org.simantics.browsing.ui.model.InvalidContribution; import org.simantics.browsing.ui.model.actions.ActionBrowseContext; import org.simantics.browsing.ui.model.check.CheckedStateContribution; import org.simantics.browsing.ui.model.children.ChildContribution; import org.simantics.browsing.ui.model.imagedecorators.ImageDecorationContribution; import org.simantics.browsing.ui.model.images.ImageContribution; import org.simantics.browsing.ui.model.labeldecorators.LabelDecorationContribution; import org.simantics.browsing.ui.model.labels.LabelContribution; import org.simantics.browsing.ui.model.modifiers.ModifierContribution; import org.simantics.browsing.ui.model.modifiers.NoModifierRule; import org.simantics.browsing.ui.model.nodetypes.EntityNodeType; import org.simantics.browsing.ui.model.nodetypes.NodeType; import org.simantics.browsing.ui.model.nodetypes.NodeTypeMultiMap; import org.simantics.browsing.ui.model.nodetypes.OrderedNodeTypeMultiMap; import org.simantics.browsing.ui.model.nodetypes.SpecialNodeType; import org.simantics.browsing.ui.model.sorters.AlphanumericSorter; import org.simantics.browsing.ui.model.sorters.Sorter; import org.simantics.browsing.ui.model.sorters.SorterContribution; import org.simantics.browsing.ui.model.tooltips.TooltipContribution; import org.simantics.browsing.ui.model.visuals.FlatNodeContribution; import org.simantics.browsing.ui.model.visuals.VisualsContribution; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ResourceNotFoundException; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.request.Read; import org.simantics.graphviz.Graph; import org.simantics.graphviz.Node; import org.simantics.graphviz.ui.GraphvizComponent; import org.simantics.scl.reflection.OntologyVersions; import org.simantics.viewpoint.ontology.ViewpointResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * BrowseContext holds all contributions related to given set of browse contexts. * * @author Hannu Niemistö */ public class BrowseContext { private static final Logger LOGGER = LoggerFactory.getLogger(BrowseContext.class); public static final boolean DEBUG = false; NodeTypeMultiMap childContributions = new NodeTypeMultiMap(); NodeTypeMultiMap parentContributions = new NodeTypeMultiMap(); OrderedNodeTypeMultiMap labelContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap imageContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap checkedStateContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap labelDecorationContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap imageDecorationContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap modifierContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap sorterContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap flatNodeContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap tooltipContributions = new OrderedNodeTypeMultiMap<>(); private final String[] uris; private BrowseContext(String[] uris) { if (uris == null) throw new NullPointerException("null URIs"); this.uris = uris; } public String[] getURIs() { return uris; } public static BrowseContext get(ReadGraph graph,NodeContext context,BrowseContext defaultContext, boolean useNodeBrowseContexts) throws DatabaseException { if(!useNodeBrowseContexts) return defaultContext; BrowseContext mbc = graph.syncRequest(new ResolveBrowseContext(context)); if(mbc != null) return mbc; BrowseContext parentContext = (BrowseContext)context.getConstant(BuiltinKeys.BROWSE_CONTEXT); if(parentContext != null) return parentContext; return defaultContext; } /** * Creates a new BrowseContext for the given Collection of {@link Resource}s. * * @param g * @param browseContextResources * @return new BrowseContext * @throws DatabaseException * @throws InvalidContribution */ public static BrowseContext create(ReadGraph g, Collection browseContextResources) throws DatabaseException, InvalidContribution { ViewpointResource vr = ViewpointResource.getInstance(g); BrowseContext browseContext = new BrowseContext( BrowseContexts.toSortedURIs(g, browseContextResources) ); for(Resource browseContextResource : findSubcontexts(g, browseContextResources)) { for(Resource childContributionResource : g.getObjects(browseContextResource, vr.BrowseContext_HasChildContribution)) { ChildContribution contribution = ChildContribution.create(g, childContributionResource); browseContext.childContributions.put(contribution.getParentNodeType(), contribution); browseContext.parentContributions.put(contribution.getChildNodeType(), contribution); } for(Resource visualsContributionResource : g.getObjects(browseContextResource, vr.BrowseContext_HasVisualsContribution)) { VisualsContribution.load(g, visualsContributionResource, browseContext.labelContributions, browseContext.imageContributions, browseContext.checkedStateContributions, browseContext.labelDecorationContributions, browseContext.imageDecorationContributions, browseContext.modifierContributions, browseContext.sorterContributions, browseContext.flatNodeContributions, browseContext.tooltipContributions ); } } //browseContext.visualize(); return browseContext; } public static Set getBrowseContextClosure(RequestProcessor processor, final Set browseContexts) throws DatabaseException { return processor.syncRequest(new Read>() { @Override public Set perform(ReadGraph graph) throws DatabaseException { Collection browseContextResources = new ArrayList(browseContexts.size()); for (String browseContext : browseContexts) { try { browseContextResources.add(graph.getResource(browseContext)); } catch (ResourceNotFoundException e) { LOGGER.error("Didn't find " + browseContext + " while loading model browser.", e); } } Collection allBrowseContextResources = BrowseContext.findSubcontexts(graph, browseContextResources); Set result = new HashSet(); for (Resource r : allBrowseContextResources) result.add(graph.getURI(r)); return result; } }); } public static Collection findSubcontexts(ReadGraph g, Collection browseContexts) throws DatabaseException { ViewpointResource vr = ViewpointResource.getInstance(g); HashSet result = new HashSet(browseContexts); ArrayList stack = new ArrayList(browseContexts); while(!stack.isEmpty()) { Resource cur = stack.remove(stack.size()-1); for(Resource sc : g.getObjects(cur, vr.BrowseContext_Includes)) if(result.add(sc)) stack.add(sc); } return result; } /** * Finds the possible children of the given {@link NodeContext} parameter. * * @param graph * @param parent * @return Collection of children or an empty collection in case node has no children * @throws DatabaseException */ public Collection getChildren(ReadGraph graph, NodeContext parent) throws DatabaseException { if(isFlattened(graph, parent)) return Collections.emptyList(); else return getChildrenImpl(graph, parent); } private Collection getChildrenImpl(ReadGraph graph, NodeContext parent) throws DatabaseException { NodeType nodeType = getNodeType(graph, parent); if(nodeType == null) return Collections.emptyList(); ArrayList result = new ArrayList(); Collection contributions = childContributions.get(graph, nodeType); if(contributions.size() > 1) { Map contribs = new HashMap<>(); for(ChildContribution contribution : contributions) { String identifier = contribution.getIdentifier(); ChildContribution current = contribs.get(identifier); if(current != null && current.getPriority() > contribution.getPriority()) continue; contribs.put(identifier, contribution); } contributions = contribs.values(); } for(ChildContribution contribution : contributions) { Collection children = contribution.getChildren(graph, parent); result.addAll(children); if(DEBUG) { LOGGER.info("contribution: " + contribution.getIdentifier()); for(NodeContext ctx : children) LOGGER.info("-" + ctx); } } // Sorting the result if(!result.isEmpty()) { for(SorterContribution contribution : sorterContributions.get(graph, nodeType)) { Sorter sorter = contribution.getSorter(graph, parent); if(sorter != null) { sorter.sort(graph, this, result); return result; } } AlphanumericSorter.INSTANCE.sort(graph, this, result); } result = flatten(graph, result); //result = augment(graph, result); return result; } private ArrayList flatten(ReadGraph graph, ArrayList result) throws DatabaseException { ArrayList flattened = new ArrayList(); for(NodeContext node : result) if(isFlattened(graph, node)) { flattened.add(node); flattened.addAll(getChildrenImpl(graph, node)); } else flattened.add(node); return flattened; } public static ArrayList augment(ReadGraph graph, BrowseContext bc, Collection contexts, boolean resolveABC) throws DatabaseException { ArrayList result = new ArrayList(); for(NodeContext context : contexts) { ActionBrowseContext abc = null; if(resolveABC) { abc = graph.syncRequest(new ResolveActionBrowseContext(context)); if(abc == null) abc = (ActionBrowseContext)context.getConstant(BuiltinKeys.ACTION_BROWSE_CONTEXT); } result.add(NodeContextBuilder.buildWithData(NodeType.KEY_SEQUENCE_EXT, new Object[] { context.getConstant(BuiltinKeys.INPUT), context.getConstant(NodeType.TYPE), context.getConstant(BuiltinKeys.UI_CONTEXT), bc, abc})); } return result; } private boolean isFlattened(ReadGraph graph, NodeContext node) throws DatabaseException { NodeType nodeType = getNodeType(graph, node); return nodeType != null && !flatNodeContributions.get(graph, nodeType).isEmpty(); } /** * Finds the possible parents of the given {@link NodeContext} parameter. * * @param graph * @param child * @return Collection of parents or an empty Collection in case node has no parents. * @throws DatabaseException */ public Collection getParents(ReadGraph graph, NodeContext child) throws DatabaseException { NodeType nodeType = getNodeType(graph, child); if(nodeType == null) return Collections.emptyList(); ArrayList result = new ArrayList(); for(ChildContribution contribution : parentContributions.get(graph, nodeType)) { result.addAll(contribution.getParents(graph, child)); } return result; } public boolean hasChildren(ReadGraph graph, NodeContext parent) throws DatabaseException { NodeType nodeType = getNodeType(graph, parent); if(nodeType == null) return false; for(ChildContribution contribution : childContributions.get(graph, nodeType)) if(contribution.hasChildren(graph, parent)) return true; return false; } private static NodeType getNodeType(ReadGraph graph, NodeContext parent) throws DatabaseException { NodeType nodeType = parent.getConstant(NodeType.TYPE); if(nodeType == null) { // TODO remove this code when root of model browser is fixed Object input = parent.getConstant(BuiltinKeys.INPUT); if(input instanceof Resource) { nodeType = EntityNodeType.getNodeTypeFor(graph, (Resource)input); } else if (input instanceof Variable) { String uri = OntologyVersions.getInstance().currentVersion("http://www.simantics.org/Modeling-0.0/ModelingBrowseContext/Variable"); return new SpecialNodeType(graph.getResource(uri), Variable.class); } } return nodeType; } /** * Finds labels for the given {@link NodeContext} parameter. * * @param graph * @param parent * @return Map containing all the labels assigned by key indicating the column e.g. "single" * @throws DatabaseException */ public Map getLabel(ReadGraph graph, NodeContext parent) throws DatabaseException { NodeType nodeType = getNodeType(graph, parent); if(nodeType == null) return Collections.singletonMap(ColumnKeys.SINGLE, "ERROR (no node type)"); List contributions = labelContributions.get(graph, nodeType); for(LabelContribution contribution : contributions) { Map label = contribution.getLabel(graph, parent); if(label != null) return label; } return Collections.singletonMap(ColumnKeys.SINGLE, "(no label rule)"); } /** * Finds {@link ImageDescriptor}s for the given {@link NodeContext} parameter. * * @param graph * @param parent * @return Map containing all the {@ImageDescriptor}s or empty * @throws DatabaseException */ public Map getImage(ReadGraph graph, NodeContext parent) throws DatabaseException { NodeType nodeType = getNodeType(graph, parent); if(nodeType == null) return Collections.emptyMap(); for(ImageContribution contribution : imageContributions.get(graph, nodeType)) { Map image = contribution.getImage(graph, parent); if(image != null) return image; } return Collections.emptyMap(); } /** * Finds if the given {@link NodeContext} is checked or not. * * @param graph * @param parent * @return * @throws DatabaseException */ public CheckedState getCheckedState(ReadGraph graph, NodeContext parent) throws DatabaseException { NodeType nodeType = getNodeType(graph, parent); if(nodeType == null) return CheckedState.NOT_CHECKED; for(CheckedStateContribution contribution : checkedStateContributions.get(graph, nodeType)) { CheckedState state = contribution.getCheckedState(graph, parent); if(state != null) return state; } return CheckedState.NOT_CHECKED; } /** * Finds {@link LabelDecorator} for the given {@link NodeContext} parameter. * * @param graph * @param context * @return * @throws DatabaseException */ public LabelDecorator getLabelDecorator(ReadGraph graph, NodeContext context) throws DatabaseException { NodeType nodeType = getNodeType(graph, context); if(nodeType == null) return CompositeLabelDecorator.ID; ArrayList decorators = new ArrayList(); for(LabelDecorationContribution contribution : labelDecorationContributions.get(graph, nodeType)) { LabelDecorator decorator = contribution.getLabelDecorator(graph, context); if(decorator != null) decorators.add(decorator); } return CompositeLabelDecorator.create(decorators); } /** * Finds {@link ImageDecorator} for the given {@link NodeContext} parameter. * * @param graph * @param context * @return * @throws DatabaseException */ public ImageDecorator getImageDecorator(ReadGraph graph, NodeContext context) throws DatabaseException { NodeType nodeType = getNodeType(graph, context); if(nodeType == null) return CompositeImageDecorator.ID; ArrayList decorators = new ArrayList(); for(ImageDecorationContribution contribution : imageDecorationContributions.get(graph, nodeType)) { ImageDecorator decorator = contribution.getImageDecorator(graph, context); if(decorator != null) decorators.add(decorator); } return CompositeImageDecorator.create(decorators); } /** * Finds {@link Modifier} for the given {@link NodeContext} parameter. * * @param graph * @param context * @param columnKey * @return * @throws DatabaseException */ public Modifier getModifier(ReadGraph graph, NodeContext context, String columnKey) throws DatabaseException { NodeType nodeType = getNodeType(graph, context); if(nodeType != null) for(ModifierContribution contribution : modifierContributions.get(graph, nodeType)) { Modifier modifier = contribution.getModifier(graph, context, columnKey); if(modifier == NoModifierRule.NO_MODIFIER) return null; if(modifier != null) return modifier; } return null; } public TooltipContribution shouldCreateToolTip(ReadGraph graph, Event event, NodeContext context) throws DatabaseException { NodeType nodeType = getNodeType(graph, context); if(nodeType != null) for(TooltipContribution contribution : tooltipContributions.get(graph, nodeType)) { if (contribution.shouldCreateToolTip(graph, context)) return contribution; } return null; } public Object getTooltip(TooltipContribution contribution, Object event, Object parent, NodeContext context) throws DatabaseException { Object tooltip = contribution.getTooltip(event, parent, context); if (tooltip != null) return tooltip; return null; } private Graph toGraph() { Graph graph = new Graph(); new Node(graph, "Foo"); return graph; } @SuppressWarnings("unused") private void visualize() { final Graph graph = toGraph(); // Show it new Thread() { public void run() { final Display display = new Display(); final Shell shell = new Shell(display); GraphvizComponent comp = new GraphvizComponent(shell, 0); comp.setGraph(graph); comp.setBounds(0, 0, 800, 600); shell.pack(); shell.open (); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } }.start(); } @Override public int hashCode() { return Arrays.hashCode(uris); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BrowseContext other = (BrowseContext) obj; return Arrays.equals(uris, other.uris); } @Override public String toString() { return getClass().getSimpleName() + Arrays.toString(uris); } }