/******************************************************************************* * Copyright (c) 2012 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.modeling.typicals; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.eclipse.core.runtime.NullProgressMonitor; import org.simantics.NameLabelMode; import org.simantics.NameLabelUtil; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.WriteOnlyGraph; import org.simantics.db.common.CommentMetadata; import org.simantics.db.common.NamedResource; import org.simantics.db.common.request.FreshName; import org.simantics.db.common.request.ObjectsWithType; import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.common.uri.UnescapedChildMapOfResource; import org.simantics.db.common.utils.CommonDBUtils; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.RuntimeDatabaseException; import org.simantics.db.layer0.adapter.CopyHandler; import org.simantics.db.layer0.adapter.Instances; import org.simantics.db.layer0.request.Configuration; import org.simantics.db.layer0.request.PossibleModel; import org.simantics.db.layer0.util.ClipboardUtils; import org.simantics.db.layer0.util.SimanticsClipboard.Representation; import org.simantics.db.layer0.util.SimanticsClipboardImpl; import org.simantics.db.layer0.util.SimanticsKeys; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.procedure.Procedure; import org.simantics.db.request.WriteResult; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.graph.db.IImportAdvisor; import org.simantics.graph.db.TransferableGraphs; import org.simantics.graph.representation.Root; import org.simantics.graph.representation.TransferableGraph1; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingResources; import org.simantics.modeling.ModelingUtils.CompositeInfo; import org.simantics.modeling.ModelingUtils.DiagramComponentInfo; import org.simantics.modeling.services.ComponentNamingUtil; import org.simantics.modeling.services.NamingException; import org.simantics.operation.Layer0X; import org.simantics.scl.runtime.function.Function2; import org.simantics.scl.runtime.function.Function4; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.structural2.utils.StructuralUtils; import org.simantics.ui.SimanticsUI; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.ui.dialogs.ShowMessage; /** * @author Tuukka Lehtonen */ public class TypicalUtil { private static final boolean DEBUG = false; private static class TypicalNamingFunction implements Function2 { private NameLabelMode mode; @Override public String apply(ReadGraph graph, Resource r) { try { if (mode == null) mode = NameLabelUtil.getNameLabelMode(graph); Variable v = Variables.getPossibleVariable(graph, r); if (v != null) { Resource root = Variables.getPossibleIndexRoot(graph, v); if (root != null) { Variable rootV = Variables.getVariable(graph, root); List path = Variables.getPath(graph, rootV, v); path.add(0, rootV); return typicalLabel(graph, v, path); } } return TypicalUtil.getName(graph, r); } catch (DatabaseException e) { throw new RuntimeDatabaseException(e); } } protected String typicalLabel(ReadGraph graph, Variable v, List path) throws DatabaseException { StringBuilder sb = new StringBuilder(); labelVariable(graph, v, sb); if (path.size() > 0) { sb.append(" ("); for (Variable vv : path) { sb.append("/"); labelVariable(graph, vv, sb); } sb.append(")"); } return sb.toString(); } protected StringBuilder labelVariable(ReadGraph graph, Variable v, StringBuilder result) throws DatabaseException { Resource r = v.getPossibleRepresents(graph); if (r != null) { result.append(NameLabelUtil.modalName(graph, r, mode)); } else { result.append(NameLabelUtil.modalName(graph, v, mode)); } return result; } }; public static List toNamedResources(RequestProcessor processor, final Collection rs) throws DatabaseException { return toNamedResources(processor, rs, new TypicalNamingFunction()); } public static List toNamedResources(RequestProcessor processor, final Collection rs, final Function2 namingFunction) throws DatabaseException { return processor.syncRequest(new UniqueRead>() { @Override public List perform(ReadGraph graph) throws DatabaseException { return toNamedResources(graph, rs, namingFunction); } }); } public static List toNamedResources(ReadGraph graph, Collection rs, final Function2 namingFunction) throws DatabaseException { List result = new ArrayList<>(rs.size()); for (Resource r : rs) result.add(new NamedResource(namingFunction.apply(graph, r), r)); return result; } public static String getName(ReadGraph graph, Resource r) throws DatabaseException { String s = graph.getPossibleAdapter(r, String.class); if (s == null) s = NameUtils.getSafeLabel(graph, r); return s; } public static WriteResult instantiateTemplate( Resource target, NamedResource template, Consumer> successContinuation) { return new WriteResultRequest() { @Override public Resource perform(WriteGraph graph) throws DatabaseException { // Custom instantiation by copying the original and mapping the original to the copy CommonDBUtils.selectClusterSet(graph, target); SimanticsClipboardImpl clipboard = new SimanticsClipboardImpl(); CopyHandler ch = new TypicalCompositeCopyHandler(template.getResource()); ch.copyToClipboard(graph, clipboard, new NullProgressMonitor()); Map hints = Collections.singletonMap(ClipboardUtils.HINT_TARGET_RESOURCE, target); for (Set object : clipboard.getContents()) { TransferableGraph1 tg = ClipboardUtils.accept(graph, object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH, hints); if (tg != null) { DiagramPasteImportAdvisor advisor = new DiagramPasteImportAdvisor(graph, target, template.getName()); TransferableGraphs.importGraph1(graph, tg, advisor); Resource copy = advisor.getRoot(); configureCopyType(graph, copy, template.getResource()); associateCopyToTemplate(graph, copy, template.getResource()); if (successContinuation!= null) successContinuation.accept(Pair.make(graph, copy)); return copy; } } throw new DatabaseException("Failed to instantiate typical template through clipboard"); } }; } public static void configureCopyType(WriteGraph graph, Resource copy, Resource template) throws DatabaseException { // Remove master template instance tag type(s) Layer0 L0 = Layer0.getInstance(graph); DiagramResource DIA = DiagramResource.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); for (Resource type : graph.getObjects(template, L0.InstanceOf)) { if (graph.isInheritedFrom(type, MOD.MasterTypicalCompositeType)) graph.deny(copy, L0.InstanceOf, type); else graph.claim(copy, L0.InstanceOf, null, type); } for (Resource templateDiagram : graph.getObjects(template, MOD.CompositeToDiagram)) { Resource templateDiagramType = graph.getPossibleType(templateDiagram, DIA.Diagram); if (templateDiagramType != null) { for (Resource copiedDiagram : graph.getObjects(copy, MOD.CompositeToDiagram)) { graph.claim(copiedDiagram, L0.InstanceOf, null, templateDiagramType); } } } } public static void associateCopyToTemplate(WriteGraph graph, Resource copy, Resource template) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); Resource templateDiagram = graph.getSingleObject(template, MOD.CompositeToDiagram); Resource copyDiagram = graph.getSingleObject(copy, MOD.CompositeToDiagram); Map templateDiagramElements = graph.syncRequest(new UnescapedChildMapOfResource(templateDiagram)); Map copyDiagramElements = graph.syncRequest(new UnescapedChildMapOfResource(copyDiagram)); // Associations are intentionally bi-directional graph.claim(copyDiagram, MOD.HasDiagramSource, MOD.DiagramHasInstance, templateDiagram); for (String element : templateDiagramElements.keySet()) { Resource templateElement = templateDiagramElements.get(element); if (!graph.isInstanceOf(templateElement, DIA.Element)) continue; Resource copyElement = copyDiagramElements.get(element); graph.claim(copyElement, MOD.HasElementSource, MOD.ElementHasInstance, templateElement); graph.claim(copyElement, MOD.IsTemplatized, MOD.IsTemplatized, copyElement); } Long modCount = graph.getPossibleRelatedValue(copyDiagram, DIA.HasModCount); if (modCount == null) modCount = 0L; modCount += 1L << 32; graph.claimLiteral(copyDiagram, DiagramResource.getInstance(graph).HasModCount, modCount, Bindings.LONG); } /** * @param graph * @param typicalCompositeInstance * @param excludedComponents the set of components in the specified * composite that are not freshly renamed or null to * freshly name all components * @throws DatabaseException */ public static void generateFreshModuleNames(WriteGraph graph, Resource typicalCompositeInstance, Set excludedComponents) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); Resource configurationRoot = graph.sync(new Configuration(typicalCompositeInstance)); for(Map.Entry entry : graph.syncRequest(new UnescapedChildMapOfResource(typicalCompositeInstance)).entrySet()) { Resource component = entry.getValue(); if (!graph.isInstanceOf(component, STR.Component)) continue; if (excludedComponents != null && excludedComponents.contains(component)) continue; try { String renamed = ComponentNamingUtil.findFreshInstanceName(graph, Simantics.getProject(), configurationRoot, typicalCompositeInstance, component); if (DEBUG) System.out.println("Typicals: renamed " + entry.getKey() + " -> " + renamed); graph.claimLiteral(entry.getValue(), L0.HasName, L0.NameOf, renamed, Bindings.STRING); } catch (NamingException e) { throw new DatabaseException(e); } } } public static class DiagramPasteImportAdvisor implements IImportAdvisor { protected final Resource library; protected final Resource model; protected Resource diagram; protected final String diagramName; public DiagramPasteImportAdvisor(ReadGraph graph, Resource library, String originalName) throws DatabaseException { this.library = library; this.diagram = null; this.diagramName = graph.syncRequest(new FreshName(library, originalName)); this.model = graph.syncRequest(new PossibleModel(library)); } public void analyzeType(ReadGraph graph, Root root) throws DatabaseException { } @Override public Resource analyzeRoot(ReadGraph graph, Root root) throws DatabaseException { if("%model".equals(root.name)) return model; return null; } @Override public Resource createRoot(WriteOnlyGraph graph, Root root) throws DatabaseException { Layer0 l0 = graph.getService(Layer0.class); if(CompositeInfo.isComposite(root.name)) { // Use existing if available if(diagram == null) diagram = graph.newResource(); graph.claim(library, l0.ConsistsOf, l0.PartOf, diagram); graph.newClusterSet(diagram); graph.setClusterSet4NewResource(diagram); graph.addLiteral(diagram, l0.HasName, l0.NameOf, l0.String, diagramName, Bindings.STRING); return diagram; } else if (DiagramComponentInfo.isDiagramComponent(root.name)) { DiagramComponentInfo info = DiagramComponentInfo.parse(root.name); Resource child = graph.newResource(); graph.addLiteral(child, l0.HasName, l0.NameOf, l0.String, info.getUnescapedComponentName(), Bindings.STRING); return child; } else { throw new DatabaseException("Unclassified root " + root.name); } } public Resource getRoot() { return diagram; } } /** * @param graph * @param typicalInstanceComposite * @param renamedComponentsOutput a set that can be provided to get the set * of components that was renamed as output from this method or * null to not collect renamed components * @throws DatabaseException */ public static void applyTypicalModuleNames(WriteGraph graph, Resource typicalInstanceComposite, Set renamedComponentsOutput) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); Function4 nameEvaluator = getTypicalNamingFunction(graph, typicalInstanceComposite); if (nameEvaluator == null) return; Collection components = graph.syncRequest(new ObjectsWithType(typicalInstanceComposite, L0.ConsistsOf, STR.Component)); for (Resource component : components) { applyTypicalModuleName(graph, component, nameEvaluator, renamedComponentsOutput); } } public static boolean applyTypicalModuleName(WriteGraph graph, Resource instanceComponent, Function4 nameEvaluator, Set renamedComponentsOutput) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); Resource componentType = graph.getPossibleType(instanceComponent, STR.Component); if (componentType == null) return false; Resource instanceElement = graph.getPossibleObject(instanceComponent, MOD.ComponentToElement); if (instanceElement == null) return false; Resource templateElement = graph.getPossibleObject(instanceElement, MOD.HasElementSource); if (templateElement == null) return false; Resource templateComponent = graph.getPossibleObject(templateElement, MOD.ElementToComponent); if (templateComponent == null) return false; // TODO: Use variables and EXPRESSION property instead ? String nameExpression = graph.getPossibleRelatedValue(templateComponent, L0.HasName, Bindings.STRING); if (nameExpression == null) return false; Resource instanceComposite = graph.getPossibleObject(instanceComponent, L0.PartOf); if (instanceComposite == null) return false; // This evaluator replaces % with assigned primary position name String evaluatedInstanceName = (String) nameEvaluator.apply(graph, instanceComposite, instanceComponent, nameExpression); if(evaluatedInstanceName == null) return false; String instanceName = graph.getPossibleRelatedValue(instanceComponent, L0.HasName, Bindings.STRING); if(instanceName == null) return false; if(!evaluatedInstanceName.equals(instanceName)) { graph.claimLiteral(instanceComponent, L0.HasName, evaluatedInstanceName, Bindings.STRING); if (renamedComponentsOutput != null) renamedComponentsOutput.add(instanceComponent); if (DEBUG) System.out.println("TypicalUtil.applyTypicalModuleName: applied name expression " + nameExpression + " -> " + instanceName + " -> " + evaluatedInstanceName); return true; } return false; } /** * @param graph * @param typicalComposite * @param componentsToCheck the set of components to check for required * naming * @throws DatabaseException */ public static void applySelectedModuleNames(WriteGraph graph, Resource typicalComposite, List componentsToCheck) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); Function4 nameEvaluator = getTypicalNamingFunction(graph, typicalComposite); if (nameEvaluator == null) return; for (Resource component : componentsToCheck) { Resource componentType = graph.getPossibleType(component, STR.Component); if (componentType == null) continue; Resource element = graph.getPossibleObject(component, MOD.ComponentToElement); if (element == null) continue; Resource templateElement = graph.getPossibleObject(element, MOD.HasElementSource); if (templateElement == null) continue; Resource templateComponent = graph.getPossibleObject(templateElement, MOD.ElementToComponent); if (templateComponent == null) continue; String nameExpression = graph.getPossibleRelatedValue(templateComponent, L0.HasName, Bindings.STRING); if (nameExpression == null) continue; // NOTE: This assumes that nameEvaluator also makes sure that the // evaluated names do not collide with any existing names in the // model. String evaluatedInstanceName = (String) nameEvaluator.apply(graph, typicalComposite, component, nameExpression); if (evaluatedInstanceName != null && !evaluatedInstanceName.equals(nameExpression)) { if (DEBUG) System.out.println("TypicalUtil.applySelectionModuleNames: applied name expression " + nameExpression + " -> " + evaluatedInstanceName); graph.claimLiteral(component, L0.HasName, evaluatedInstanceName, Bindings.STRING); } } } /** * @param graph * @param typicalComposite * @return f :: ReadGraph -> Resource composite -> Resource component -> String expression -> String name * @throws DatabaseException */ public static Function4 getTypicalNamingFunction(ReadGraph graph, Resource typicalComposite) throws DatabaseException { ModelingResources MOD = ModelingResources.getInstance(graph); Function4 nameEvaluator = graph.getPossibleRelatedValue2(typicalComposite, MOD.TypicalComposite_typicalNamingFunction); return nameEvaluator; } /** * @param processor * @param model * @return * @throws DatabaseException */ public static Collection findModelTypicals(RequestProcessor processor, final Resource model) throws DatabaseException { return processor.syncRequest(new UniqueRead>() { @Override public Collection perform(ReadGraph graph) throws DatabaseException { ModelingResources MOD = ModelingResources.getInstance(graph); Resource typicalMasterType = graph.getSingleObject(model, MOD.StructuralModel_HasMasterTypicalCompositeType); Instances query = graph.adapt(typicalMasterType, Instances.class); return query.find(graph, model); } }); } /** * A utility for synchronous execution of asynchronous procedures. * @param runnable the callback that contains asynchronous execution * @return the result received from the specified callback * @throws DatabaseException */ public static T syncExec(Consumer> runnable) throws DatabaseException { final AtomicReference ref = new AtomicReference(); final AtomicReference exc = new AtomicReference(); final Semaphore sem = new Semaphore(0); runnable.accept(new Procedure() { @Override public void execute(T result) { if (ref.compareAndSet(null, result)) sem.release(); } @Override public void exception(Throwable t) { if (exc.compareAndSet(null, t)) sem.release(); } }); try { sem.acquire(); Throwable t = exc.get(); if (t != null) { if (t instanceof DatabaseException) throw (DatabaseException) t; throw new DatabaseException(t); } return (T) ref.get(); } catch (InterruptedException ex) { throw new DatabaseException(ex); } } /* * SCL API */ public static void syncTypicalInstance(WriteGraph graph, Resource instance) throws DatabaseException { SyncTypicalTemplatesToInstances sync = SyncTypicalTemplatesToInstances.syncSingleInstance(null, instance); sync.perform(graph); } /** * Creates a new master typical diagram and its corresponding composites. * *

* The created typical composite and diagram type are specified by the model * using {@link ModelingResources#StructuralModel_HasTypicalCompositeBaseType} * and {@link ModelingResources#StructuralModel_HasTypicalDiagramBaseType}. * *

* Marks the created master composite with the model-specified master composite * type to support searching. The master type is specified by the model using * {@link ModelingResources#StructuralModel_HasMasterTypicalCompositeType}. * *

* Clones symbol contributions from the sources specified by the model through * {@link ModelingResources#StructuralModel_CloneTypicalDiagramSymbolContributionsFrom} * . * * @author Tuukka Lehtonen */ public static Resource newMasterTypical(final Resource target) throws DatabaseException { return Simantics.getSession().syncRequest(new WriteResultRequest() { @Override public Resource perform(WriteGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); Layer0X L0X = Layer0X.getInstance(graph); DiagramResource DIA = DiagramResource.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); Resource indexRoot = graph.sync(new PossibleIndexRoot(target)); if (indexRoot == null) { ShowMessage.showInformation("No Model or Shared Library", "Cannot find a containing model or shared library from the input selection. Typical master diagram creation not possible."); return null; } Resource compositeBaseType = graph.getPossibleObject(indexRoot, MOD.StructuralModel_HasTypicalCompositeBaseType); Resource diagramBaseType = graph.getPossibleObject(indexRoot, MOD.StructuralModel_HasTypicalDiagramBaseType); Resource masterCompositeType = graph.getPossibleObject(indexRoot, MOD.StructuralModel_HasMasterTypicalCompositeType); Collection cloneSymbolContributionsFrom = graph.getObjects(indexRoot, MOD.StructuralModel_CloneTypicalDiagramSymbolContributionsFrom); if (compositeBaseType == null || diagramBaseType == null || masterCompositeType == null) { ShowMessage.showInformation("No Typical Support", "Creation of typical diagrams is not supported for this container."); return null; } Resource compositeType = graph.newResource(); graph.claim(compositeType, L0.Inherits, compositeBaseType); String compositeTypeName = NameUtils.findFreshName(graph, "TypicalCompositeType", target); graph.claimLiteral(compositeType, L0.HasName, compositeTypeName); Resource diagramType = graph.newResource(); graph.claim(diagramType, L0.Inherits, diagramBaseType); graph.claimLiteral(diagramType, L0.HasName, "Type"); String name = NameUtils.findFreshName(graph, "Typical", target, L0.ConsistsOf, "%s%d"); Resource composite = StructuralUtils.newComponent(graph, target, name + "@1", compositeType); graph.claim(composite, L0.InstanceOf, null, masterCompositeType); Resource diagram = graph.newResource(); graph.claim(diagram, L0.InstanceOf, null, diagramType); graph.claimLiteral(diagram, L0.HasName, "__DIAGRAM__", Bindings.STRING); graph.claim(diagram, L0.SubrelationOf, null, L0.HasNext); graph.claim(diagram, MOD.DiagramToComposite, composite); Resource diagramInv = graph.newResource(); graph.claim(diagramInv, L0.InverseOf, diagram); graph.claim(diagramInv, L0.SubrelationOf, null, L0.HasPrevious); graph.claimLiteral(diagramInv, L0.HasName, "Inverse", Bindings.STRING); graph.claim(diagram, L0.ConsistsOf, diagramInv); graph.claim(diagram, diagram, diagramInv, diagram); Resource mapping = graph.newResource(); graph.claim(diagram, L0X.HasTrigger, mapping); graph.claim(mapping, L0.InstanceOf, null, MOD.DiagramToCompositeMapping); // Make diagram part of a dummy container entity attached to the parent // composite if it's not already part of something. Resource container = graph.newResource(); graph.claim(container, L0.InstanceOf, null, DIA.DiagramContainer); graph.addLiteral(container, L0.HasName, L0.NameOf, L0.String, "__CONTAINER__", Bindings.STRING); // Compose all created resources into the following hierarchy: // Typical Composite : TypicalCompositeType // Typical Composite Type : STR.CompositeType // __CONTAINER__ : DIA.DiagramContainer // "__DIAGRAM__" : "Type" // "Type"