/******************************************************************************* * Copyright (c) 2013 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: * Semantum Oy - initial API and implementation *******************************************************************************/ package org.simantics.annotation.ui; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.ISelection; import org.simantics.Simantics; import org.simantics.annotation.ontology.AnnotationResource; import org.simantics.databoard.Bindings; import org.simantics.databoard.util.URIStringUtils; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.VirtualGraph; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.db.layer0.util.RemoverUtil; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.VariableBuilder; import org.simantics.db.layer0.variable.VariableMap; import org.simantics.db.layer0.variable.VariableMapImpl; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.service.VirtualGraphSupport; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingResources; import org.simantics.modeling.ModelingUtils; import org.simantics.scenegraph.loader.ScenegraphLoaderUtils; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.scl.reflection.annotations.SCLValue; import org.simantics.scl.runtime.function.FunctionImpl1; import org.simantics.scl.runtime.tuple.Tuple; import org.simantics.scl.runtime.tuple.Tuple2; import org.simantics.scl.runtime.tuple.Tuple3; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.strings.AlphanumComparator; import org.simantics.utils.ui.ISelectionUtils; import org.simantics.views.swt.client.base.ISWTViewNode; import org.simantics.views.swt.client.impl.SWTExplorer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.trove.map.hash.THashMap; import gnu.trove.set.hash.THashSet; /** * @author Antti Villberg * @author Tuukka Lehtonen */ public class SCL { private static final Logger LOGGER = LoggerFactory.getLogger(SCL.class); final public static String EMPTY = ""; final public static String MAPPED = "Mapped"; final public static String SELECTED = "Selected"; final public static String NO_ANNOTATIONS = "No annotations"; @SCLValue(type = "ReadGraph -> Resource -> Variable -> [(String, Resource)]") public static List availableSources(ReadGraph graph, Resource resource, Variable context) throws DatabaseException { Variable selection = ScenegraphLoaderUtils.getPossibleVariableSelection(graph, context); if (selection == null) return Collections.emptyList(); List sources = availableSourcesImpl(graph, selection); if (sources.isEmpty()) return Collections.emptyList(); List result = new ArrayList(sources.size()); for(Tuple3 anno : sources) { result.add(new Tuple2(anno.get(0),"")); } return result; } /** * Gathers available annotation sources and always returns them in the same * order. * * @param graph * @param selection * @return * @throws DatabaseException */ private static List availableSourcesImpl(ReadGraph graph, Variable selection) throws DatabaseException { List vars = gatherSourceVariables(graph, selection); if (vars.isEmpty()) return Collections.emptyList(); int size = vars.size(); ArrayList result = new ArrayList(size); result.add(new Tuple3(sourceLabel(graph, vars.get(0)), SELECTED, vars.get(0))); for (int i = 1; i < size; ++i) { Variable v = vars.get(i); result.add(new Tuple3(sourceLabel(graph, v), MAPPED, v)); } return result; } /** * Gathers variables starting from source in a particular order each time. * First configuration components and second diagram elements. * * @param graph * @param source * @return * @throws DatabaseException */ private static List gatherSourceVariables(ReadGraph graph, Variable source) throws DatabaseException { Resource represents = source.getPossibleRepresents(graph); if (represents == null) return Collections.singletonList(source); ModelingResources MOD = ModelingResources.getInstance(graph); ArrayList result = new ArrayList(4); for (Resource r : ModelingUtils.getElementCorrespondendences(graph, represents)) addPossibleVariable(graph, r, result); for(Resource r : graph.getObjects(represents, MOD.DiagramToComposite)) addPossibleVariable(graph, r, result); result.add(source); for(Resource r : graph.getObjects(represents, MOD.ComponentToElement)) addPossibleVariable(graph, r, result); for(Resource r : graph.getObjects(represents, MOD.CompositeToDiagram)) addPossibleVariable(graph, r, result); return result; } private static void addPossibleVariable(ReadGraph graph, Resource r, List result) throws DatabaseException { Variable v = Variables.getPossibleVariable(graph, r); if (v != null) result.add(v); } private static String sourceLabel(ReadGraph graph, Variable variable) throws DatabaseException { Resource represents = variable.getPossibleRepresents(graph); if(represents != null) { DiagramResource DIA = DiagramResource.getInstance(graph); if(graph.isInstanceOf(represents, DIA.Diagram)) return "Diagram"; else if(graph.isInstanceOf(represents, DIA.Flag)) return "Flag Element"; else if(graph.isInstanceOf(represents, DIA.RouteGraphConnection)) return "Connection Element"; else if(graph.isInstanceOf(represents, DIA.Monitor)) return "Monitor Element"; else if(graph.isInstanceOf(represents, DIA.Element)) return "Diagram Element"; else return variable.getName(graph); } else { return variable.getURI(graph); } } @SuppressWarnings("unchecked") private static T selectedSource(ReadGraph graph, Variable selection) throws DatabaseException { String name = selectedSourceName(graph, selection); for(Tuple tuple : availableSourcesImpl(graph, selection)) { if(tuple.get(1).equals(name)) return (T)tuple.get(2); } return null; } @SCLValue(type = "ReadGraph -> Resource -> Variable -> String") public static String selectedSource(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, context); String name = selectedSourceName(graph, selection); for(Tuple tuple : availableSourcesImpl(graph, selection)) { if(tuple.get(1).equals(name)) return (String)tuple.get(0); } return EMPTY; } @SCLValue(type = "ReadGraph -> Resource -> Variable -> a") public static Object selectedSourceModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { return new FunctionImpl1() { @Override public Object apply(Object sourceKey) { try { Session s = Simantics.getSession(); VirtualGraph vg = s.getService(VirtualGraphSupport.class).getWorkspacePersistent("preferences"); s.syncRequest(new SetDefaultAnnotationSourceRequest(vg, context, sourceKey)); } catch (DatabaseException e) { Activator.getDefault().getLog().log( new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to update default annotation source, see exception for details.", e)); } return null; } }; } private static class SetDefaultAnnotationSourceRequest extends WriteRequest { private Variable context; private Object sourceKey; public SetDefaultAnnotationSourceRequest(VirtualGraph vg, Variable context, Object sourceKey) { super(vg); this.context = context; this.sourceKey = sourceKey; } @Override public void perform(WriteGraph graph) throws DatabaseException { AnnotationResource ANNO = AnnotationResource.getInstance(graph); Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, context); Resource model = Variables.getPossibleIndexRoot(graph, selection); List annos = availableSourcesImpl(graph, selection); for(Tuple3 anno : annos) { if(anno.get(0).equals(sourceKey)) { graph.claimLiteral(model, ANNO.DefaultAnnotationSource, (String)anno.get(1), Bindings.STRING); break; } } } } private static final Comparator AVAILABLE_ANNOTATION_SORTER = new Comparator() { @Override public int compare(Tuple3 o1, Tuple3 o2) { String s1 = (String) o1.c2; String s2 = (String) o2.c2; return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(s1, s2); } }; /** * @param graph * @param selection * @return list of (Variable annotation, Resource annotation, String name) tuples * @throws DatabaseException */ private static Collection availableAnnotationsImpl(ReadGraph graph, Variable selection) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); AnnotationResource ANNO = AnnotationResource.getInstance(graph); selection = selectedSource(graph, selection); ArrayList result = new ArrayList(); for (Variable child : selection.getChildren(graph)) { Resource represents = child.getPossibleRepresents(graph); if (represents != null && graph.isInstanceOf(represents, ANNO.Annotation)) { String name = graph.getPossibleRelatedValue(represents, L0.HasName); if (name != null) result.add(new Tuple3(child, represents, name)); } } for (Variable property : selection.getProperties(graph)) { Resource propertyResource = property.getPossibleRepresents(graph); if (propertyResource != null && graph.isInstanceOf(propertyResource, ANNO.Annotation)) { String propertyName = property.getName(graph); result.add(new Tuple3(property, propertyResource, propertyName)); } } // Sort returned annotations by annotation property name to keep the results stable. Collections.sort(result, AVAILABLE_ANNOTATION_SORTER); return result; } private static Pair defaultAnnotationTypeAndName(ReadGraph graph, Variable selection) throws DatabaseException { AnnotationResource ANNO = AnnotationResource.getInstance(graph); Resource model = Variables.getPossibleIndexRoot(graph, selection); Resource type = graph.getPossibleObject(model, ANNO.HasDefaultAnnotationType); String name = graph.getPossibleRelatedValue(model, ANNO.HasDefaultAnnotationName, Bindings.STRING); return Pair.make(type, name); } private static String selectedAnnotationName(ReadGraph graph, Variable selection) throws DatabaseException { Pair typeAndName = defaultAnnotationTypeAndName(graph, selection); Collection available = availableAnnotationsImpl(graph, selection); if (!available.isEmpty()) { if (available.size() == 1 || typeAndName.first == null) return (String) available.iterator().next().c2; String firstTypeMatch = null; for (Tuple3 anno : available) { if (graph.isInstanceOf((Resource) anno.c1, typeAndName.first)) { if (firstTypeMatch == null) firstTypeMatch = (String) anno.c2; if (typeAndName.second != null && typeAndName.second.equals(anno.c2)) { // Ok, it just doesn't match better than this. return (String) anno.c2; } } } if (firstTypeMatch != null) return firstTypeMatch; // Nothing => return the first one from list return (String)available.iterator().next().c2; } return NO_ANNOTATIONS; } private static String selectedSourceName(ReadGraph graph, Variable selection) throws DatabaseException { AnnotationResource ANNO = AnnotationResource.getInstance(graph); Resource model = Variables.getPossibleIndexRoot(graph, selection); if (model == null) return EMPTY; String name = graph.getPossibleRelatedValue(model, ANNO.DefaultAnnotationSource, Bindings.STRING); if(name != null) { for(Tuple tuple : availableSourcesImpl(graph, selection)) { if(tuple.get(1).equals(name)) return name; } } Set available = new THashSet(); for(Tuple tuple : availableSourcesImpl(graph, selection)) available.add((String)tuple.get(1)); if(available.isEmpty()) return EMPTY; if(available.contains(MAPPED)) return MAPPED; else return available.iterator().next(); } @SCLValue(type = "ReadGraph -> Resource -> Variable -> a") public static Object explorerInput(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, context); String selected = selectedAnnotationName(graph, selection); for(Tuple3 anno : availableAnnotationsImpl(graph, selection)) { if(selected.equals(anno.c2)) { return anno.c0; } } return null; } @SCLValue(type = "ReadGraph -> Resource -> Variable -> String") public static String descriptionText(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { String result = ""; Variable sel = getSelectedAnnotationVariable(graph, context); if(sel != null) { Layer0 L0 = Layer0.getInstance(graph); Resource literal = sel.getPossibleRepresents(graph); if(literal != null) { Resource container = graph.getPossibleObject(literal, L0.PartOf); if(container != null) { Resource model = Variables.getPossibleIndexRoot(graph, sel); String modelURI = graph.getURI(model); String path = graph.getURI(literal); if(path.startsWith(modelURI)) path = path.substring(modelURI.length()+1); result += URIStringUtils.unescape(path); } else { result += "The annotation is not attached to a library"; } } } return result; } @SCLValue(type = "ReadGraph -> Resource -> Variable -> a") public static Object explorerInput2(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { return ScenegraphLoaderUtils.getVariableSelection(graph, context); } @SCLValue(type = "ReadGraph -> Resource -> Variable -> [(String, Resource)]") public static List availableAnnotations(ReadGraph graph, Resource resource, Variable context) throws DatabaseException { Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, context); ArrayList result = new ArrayList(); for(Tuple3 anno : availableAnnotationsImpl(graph, selection)) { result.add(new Tuple2(anno.c2, anno.c1)); } if(result.isEmpty()) result.add(new Tuple2(NO_ANNOTATIONS, "")); return result; } @SCLValue(type = "ReadGraph -> Resource -> Variable -> String") public static String selectedAnnotation(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { final Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, context); return selectedAnnotationName(graph, selection); } @SCLValue(type = "ReadGraph -> Resource -> Variable -> a") public static Object selectedAnnotationModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { return new FunctionImpl1() { @Override public Object apply(final Object _key) { Session s = Simantics.getSession(); VirtualGraph vg = s.getService(VirtualGraphSupport.class).getWorkspacePersistent("preferences"); s.async(new WriteRequest(vg) { @Override public void perform(WriteGraph graph) throws DatabaseException { AnnotationResource ANNO = AnnotationResource.getInstance(graph); String key = (String)_key; Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, context); for(Tuple3 anno : availableAnnotationsImpl(graph, selection)) { if(key.equals(anno.c2)) { Resource type = graph.getPossibleType((Resource) anno.c1, ANNO.Annotation); Resource model = Variables.getPossibleIndexRoot(graph, selection); graph.deny(model, ANNO.HasDefaultAnnotationType); graph.claim(model, ANNO.HasDefaultAnnotationType, type); graph.denyValue(model, ANNO.HasDefaultAnnotationName); graph.claimLiteral(model, ANNO.HasDefaultAnnotationName, key, Bindings.STRING); break; } } } }); return null; } }; } private static Resource getSelectionResource(Variable context) throws DatabaseException { return Simantics.getSession().syncRequest(new UnaryRead(context) { @Override public Resource perform(ReadGraph graph) throws DatabaseException { Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, parameter); Variable source = selectedSource(graph, selection); return source.getPossibleRepresents(graph); } }); } static class AddModifier extends FunctionImpl1 { private final Variable context; public AddModifier(Variable context) { this.context = context; } private void doAdd(final Variable variable) throws DatabaseException { if(variable != null) { // We have a selected annotation AnnotationUtils.newAnnotation(variable); } else { // No annotation selected Resource parent = getSelectionResource(context); if(parent != null) AnnotationUtils.newAnnotation(parent); } } @Override public Object apply(Object p0) { ISWTViewNode node = (ISWTViewNode)p0; SWTExplorer properties = (SWTExplorer)NodeUtil.browsePossible(node, "./Properties"); if(properties == null) return null; try { doAdd((Variable)properties.input); } catch (DatabaseException e) { LOGGER.error("newAnnotationModifier failed", e); } return null; } } @SCLValue(type = "ReadGraph -> Resource -> Variable -> a") public static Object newAnnotationModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { return new AddModifier(context); } private static Variable getSelectedAnnotationVariable(ReadGraph graph, Variable context) throws DatabaseException { Variable selection = ScenegraphLoaderUtils.getVariableSelection(graph, context); String selected = selectedAnnotationName(graph, selection); for(Tuple3 anno : availableAnnotationsImpl(graph, selection)) { if(anno.c2.equals(selected)) { return (Variable)anno.c0; } } return null; } @SCLValue(type = "ReadGraph -> Resource -> Variable -> a") public static Object removeAnnotationModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { return new RemoveModifier(); } public static class SaveModifier extends FunctionImpl1 { private boolean doSave(final Variable variable) { if(!AnnotationUtils.isAnnotation(variable)) return false; final Map> map = AnnotationUtils.findLibraries(variable); if(map == null) return false; AnnotationUtils.queryLibrary(map, selected -> { Simantics.getSession().async(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { Resource represents = variable.getPossibleRepresents(graph); if(represents != null && !selected.second.isEmpty()) { saveAnnotation(graph, represents, selected.first, selected.second); } } }); }); return true; } public static Resource saveAnnotation(WriteGraph graph, Resource annotation, Resource library, String name) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); if(graph.hasStatement(annotation, L0.PartOf)) { graph.deny(annotation, L0.PartOf); } graph.claim(library, L0.ConsistsOf, L0.PartOf, annotation); graph.claimLiteral(annotation, L0.HasName, name, Bindings.STRING); return annotation; } @Override public Object apply(Object p0) { ISWTViewNode node = (ISWTViewNode)p0; SWTExplorer properties = (SWTExplorer)NodeUtil.browsePossible(node, "./Properties"); if(properties == null) return null; ISelection selection = properties.lastSelection; if(selection == null || selection.isEmpty()) { doSave((Variable)properties.input); return null; } Collection vars = ISelectionUtils.filterSetSelection(selection, Variable.class); if(vars.size() != 1) return null; Variable selected = vars.iterator().next(); if(!doSave(selected)) doSave((Variable)properties.input); return null; } } static class RemoveModifier extends FunctionImpl1 { private boolean doRemove(final Variable variable) { if(!AnnotationUtils.isAnnotation(variable)) return false; Simantics.getSession().async(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); Resource represents = variable.getPossibleRepresents(graph); if(represents != null) { Layer0 L0 = Layer0.getInstance(graph); AnnotationResource ANNO = AnnotationResource.getInstance(graph); if(graph.isInstanceOf(represents, ANNO.Annotation)) { Resource subject = variable.getParent(graph).getRepresents(graph); Resource predicate = variable.getPossiblePredicateResource(graph); if(predicate != null) { // This is a property annotation (no entry) - unlink graph.deny(subject, predicate); Layer0Utils.addCommentMetadata(graph, "Unlinked a property annotation " + graph.getRelatedValue2(predicate, L0.HasName, Bindings.STRING) + " " + predicate.toString() + " from " + graph.getRelatedValue2(subject, L0.HasName, Bindings.STRING) + " " + subject.toString()); } else { // This should be an entry annotation - remove from container graph.deny(subject, ANNO.Annotation_HasEntry, represents); Layer0Utils.addCommentMetadata(graph, "Removed an entry annotation " + graph.getRelatedValue2(subject, L0.HasName, Bindings.STRING) + " " + subject.toString() + " from its container " + graph.getRelatedValue2(represents, L0.HasName, Bindings.STRING)); } // If the annotation is not in any library remove it if(!graph.hasStatement(represents, L0.PartOf)) RemoverUtil.remove(graph, represents); } } } }); return true; } @Override public Object apply(Object p0) { ISWTViewNode node = (ISWTViewNode)p0; SWTExplorer properties = (SWTExplorer)NodeUtil.browsePossible(node, "./Properties"); if(properties == null) return null; ISelection selection = properties.lastSelection; if(selection == null || selection.isEmpty()) { doRemove((Variable)properties.input); return null; } Collection vars = ISelectionUtils.filterSetSelection(selection, Variable.class); if(vars.size() != 1) return null; Variable selected = vars.iterator().next(); if(!doRemove(selected)) doRemove((Variable)properties.input); return null; } } @SCLValue(type = "ReadGraph -> Resource -> Variable -> a") public static Object saveAnnotationModifier(ReadGraph graph, Resource resource, final Variable context) throws DatabaseException { return new SaveModifier(); } @SCLValue(type = "VariableMap") public static VariableMap domainChildren = new VariableMapImpl() { private Map children(ReadGraph graph, Resource resource) throws DatabaseException { AnnotationResource ANNO = AnnotationResource.getInstance(graph); Layer0 L0 = Layer0.getInstance(graph); Collection objects = graph.getObjects(resource, ANNO.Annotation_HasEntry); THashMap result = new THashMap(objects.size()); for(Resource r : objects) { String name = graph.getPossibleRelatedValue(r, L0.HasName, Bindings.STRING); if(name != null) { if (result.put(name, r) != null) LOGGER.error("The database contains siblings with the same name " + name + " (resource=$" + resource.getResourceId() +")."); } } return result; } @Override public Variable getVariable(ReadGraph graph, Variable context, String name) throws DatabaseException { Map children = children(graph,context.getRepresents(graph)); Resource child = children.get(name); if(child == null) return null; VariableBuilder variableBuilder = graph.adapt(child, VariableBuilder.class); return variableBuilder.buildChild(graph, context, null, child); } @Override public Map getVariables(ReadGraph graph, Variable context, Map map) throws DatabaseException { Map childMap = children(graph,context.getRepresents(graph)); if(childMap.isEmpty()) return map; if(map == null) map = new THashMap(); for(Map.Entry entry : childMap.entrySet()) { String name = entry.getKey(); Resource child = entry.getValue(); VariableBuilder variableBuilder = graph.adapt(child, VariableBuilder.class); Variable var = variableBuilder.buildChild(graph, context, null, child); if(var != null) { map.put(name, var); } else { System.err.println("No adapter for " + child + " in " + context.getURI(graph)); } } return map; } }; }