/*******************************************************************************
* Copyright (c) 2007, 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.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.simantics.Simantics;
import org.simantics.databoard.Bindings;
import org.simantics.db.ReadGraph;
import org.simantics.db.Resource;
import org.simantics.db.Statement;
import org.simantics.db.WriteGraph;
import org.simantics.db.common.CommentMetadata;
import org.simantics.db.common.NamedResource;
import org.simantics.db.common.primitiverequest.Adapter;
import org.simantics.db.common.procedure.adapter.TransientCacheListener;
import org.simantics.db.common.request.ObjectsWithType;
import org.simantics.db.common.request.PossibleIndexRoot;
import org.simantics.db.common.request.WriteRequest;
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.layer0.adapter.Instances;
import org.simantics.db.layer0.request.ActiveModels;
import org.simantics.db.layer0.util.RemoverUtil;
import org.simantics.diagram.content.ConnectionUtil;
import org.simantics.diagram.handler.CopyPasteStrategy;
import org.simantics.diagram.handler.ElementObjectAssortment;
import org.simantics.diagram.handler.PasteOperation;
import org.simantics.diagram.handler.Paster;
import org.simantics.diagram.handler.Paster.RouteLine;
import org.simantics.diagram.stubs.DiagramResource;
import org.simantics.diagram.synchronization.CollectingModificationQueue;
import org.simantics.diagram.synchronization.CopyAdvisor;
import org.simantics.diagram.synchronization.SynchronizationHints;
import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
import org.simantics.diagram.ui.DiagramModelHints;
import org.simantics.document.DocumentResource;
import org.simantics.g2d.canvas.ICanvasContext;
import org.simantics.g2d.diagram.DiagramClass;
import org.simantics.g2d.diagram.IDiagram;
import org.simantics.g2d.diagram.impl.Diagram;
import org.simantics.layer0.Layer0;
import org.simantics.modeling.ModelingResources;
import org.simantics.modeling.ModelingUtils;
import org.simantics.modeling.mapping.ModelingSynchronizationHints;
import org.simantics.modeling.typicals.rules.AuxKeys;
import org.simantics.modeling.typicals.rules.FlagRule;
import org.simantics.modeling.typicals.rules.InstanceOfRule;
import org.simantics.modeling.typicals.rules.LabelRule;
import org.simantics.modeling.typicals.rules.MonitorRule;
import org.simantics.modeling.typicals.rules.NameRule;
import org.simantics.modeling.typicals.rules.ProfileMonitorRule;
import org.simantics.modeling.typicals.rules.SVGElementRule;
import org.simantics.modeling.typicals.rules.TransformRule;
import org.simantics.scenegraph.g2d.events.command.Commands;
import org.simantics.scl.runtime.function.Function4;
import org.simantics.structural.stubs.StructuralResource2;
import org.simantics.utils.datastructures.MapSet;
import org.simantics.utils.strings.AlphanumComparator;
import org.simantics.utils.strings.EString;
import org.simantics.utils.ui.ErrorLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.trove.map.hash.THashMap;
import gnu.trove.set.hash.THashSet;
/**
* A write request that synchronizes typical master templates and their
* instances as specified.
*
*
* Use {@link #SyncTypicalTemplatesToInstances(Resource[], MapSet)} to
* synchronize all instances of specified templates. Use
* {@link #syncSingleInstance(Resource)} to synchronize a single typical
* instance with its master template.
*
* @author Tuukka Lehtonen
*
* @see ReadTypicalInfo
* @see TypicalInfo
* @see TypicalSynchronizationMetadata
*/
public class SyncTypicalTemplatesToInstances extends WriteRequest {
private static final Logger LOGGER = LoggerFactory.getLogger(SyncTypicalTemplatesToInstances.class);
/**
* A constant used as the second argument to
* {@link #SyncTemplates(Resource[], MapSet)} for stating that all specified
* templates should be fully synchronized to their instances.
*
* This is useful for forcing complete synchronization and unit testing.
*/
public static final EmptyMapSet ALL = EmptyMapSet.INSTANCE;
public static class EmptyMapSet extends MapSet {
public static final EmptyMapSet INSTANCE = new EmptyMapSet();
public EmptyMapSet() {
this.sets = Collections.emptyMap();
}
@Override
protected Set getOrCreateSet(Resource key) {
throw new UnsupportedOperationException("immutable constant instance");
}
};
protected static final boolean DEBUG = false;
// Input
final private IProgressMonitor monitor;
/**
* Typical diagram rules to apply
*/
protected Set selectedRules;
/**
* Typical diagram templates to synchronize with their instances.
*/
protected Resource[] templates;
/**
* Typical diagram instances to synchronize with their templates.
*/
protected Resource[] instances;
/**
* For each template diagram in {@link #templates}, shall contain a set of
* elements that have changed and should be synchronized into the instance
* diagrams. Provided as an argument by the client. Allows optimizing
* real-time synchronization by not processing everything all the time.
*
* If the value is {@link #ALL}, all elements of the template shall be fully
* synchronized.
*/
protected MapSet changedElementsByDiagram;
// Temporary data
protected Layer0 L0;
protected StructuralResource2 STR;
protected DiagramResource DIA;
protected ModelingResources MOD;
/**
* Needed for using {@link Paster} in
* {@link #addMissingElements(WriteGraph, TypicalInfo, Resource, Resource, Set)}
*/
protected GraphSynchronizationContext syncCtx;
/**
* For collecting commit metadata during the processing of this request.
*/
protected TypicalSynchronizationMetadata metadata;
/**
* Necessary for using {@link CopyPasteStrategy} and {@link PasteOperation}
* for now. Will be removed in the future once IDiagram is removed from
* PasteOperation.
*/
protected IDiagram temporaryDiagram;
protected ConnectionUtil cu;
/**
* Maps source -> target connection route nodes, i.e. connectors and
* interior route nodes (route lines). Inverse mapping of {@link #t2s}.
*/
protected Map s2t;
/**
* Maps target -> source connection route nodes, i.e. connectors and
* interior route nodes (route lines). Inverse mapping of {@link #s2t}.
*/
protected Map t2s;
/**
* An auxiliary resource map for extracting the correspondences between
* originals and copied resource when diagram contents are copied from
* template to instance.
*/
protected Map copyMap;
final private Map> messageLogs = new HashMap<>();
public List logs = new ArrayList<>();
private boolean writeLog;
/**
* For SCL API.
*
* @param graph
* @param selectedRules
* @param templates
* @param instances
* @throws DatabaseException
*/
public static void syncTypicals(WriteGraph graph, boolean log, List templates, List instances) throws DatabaseException {
graph.syncRequest(
new SyncTypicalTemplatesToInstances(
null,
templates.toArray(Resource.NONE),
instances.toArray(Resource.NONE),
ALL,
null)
.logging(log));
}
/**
* @param templates typical diagram templates to completely synchronize with
* their instances
*/
public SyncTypicalTemplatesToInstances(Set selectedRules, Resource... templates) {
this(selectedRules, templates, null, ALL, null);
}
/**
* @param templates typical diagram templates to partially synchronize with
* their instances
* @param changedElementsByDiagram see {@link #changedElementsByDiagram}
*/
public SyncTypicalTemplatesToInstances(Set selectedRules, Resource[] templates, MapSet changedElementsByDiagram) {
this(selectedRules, templates, null, changedElementsByDiagram, null);
}
/**
* Return a write request that completely synchronizes the specified
* instance diagram with its template.
*
* @param instance
* @return
*/
public static SyncTypicalTemplatesToInstances syncSingleInstance(Set selectedRules, Resource instance) {
return new SyncTypicalTemplatesToInstances(selectedRules, null, new Resource[] { instance }, ALL, null);
}
/**
* @param templates typical diagram templates to synchronize with their instances
* @param instances typical diagram instances to synchronize with their templates
* @param changedElementsByDiagram see {@link #changedElementsByDiagram}
*/
private SyncTypicalTemplatesToInstances(Set selectedRules, Resource[] templates, Resource[] instances, MapSet changedElementsByDiagram, IProgressMonitor monitor) {
this.selectedRules = selectedRules;
this.templates = templates;
this.instances = instances;
this.changedElementsByDiagram = changedElementsByDiagram;
this.monitor = monitor;
}
public SyncTypicalTemplatesToInstances logging(boolean writeLog) {
this.writeLog = writeLog;
return this;
}
private Resource getDiagramNameResource(ReadGraph graph, Resource diagram) throws DatabaseException {
Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
if(composite != null) return composite;
else return diagram;
}
private Resource getElementNameResource(ReadGraph graph, Resource element) throws DatabaseException {
Resource corr = ModelingUtils.getPossibleElementCorrespondendence(graph, element);
if(corr != null) return corr;
else return element;
}
private List getLog(ReadGraph graph, Resource diagram) throws DatabaseException {
Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(diagram));
if(indexRoot == null) throw new DatabaseException("FATAL: Diagram is not under any index root.");
List log = messageLogs.get(indexRoot);
if(log == null) {
log = new ArrayList<>();
messageLogs.put(indexRoot, log);
}
return log;
}
private String elementName(ReadGraph graph, Resource element) throws DatabaseException {
StringBuilder b = new StringBuilder();
b.append(safeNameAndType(graph, element));
int spaces = 60-b.length();
for(int i=0;i();
this.temporaryDiagram = Diagram.spawnNew(DiagramClass.DEFAULT);
this.temporaryDiagram.setHint(SynchronizationHints.CONTEXT, syncCtx);
this.cu = new ConnectionUtil(graph);
if (templates != null) {
// Look for typical template instances from the currently active models only.
Collection activeModels = graph.syncRequest(new ActiveModels(Simantics.getProjectResource()));
if (!activeModels.isEmpty()) {
for (Resource template : templates) {
syncTemplate(graph, template, activeModels);
}
}
}
if (instances != null) {
for (Resource instance : instances) {
syncInstance(graph, instance);
}
}
if (writeLog) {
for(Map.Entry> entry : messageLogs.entrySet()) {
Resource indexRoot = entry.getKey();
List messageLog = entry.getValue();
Layer0 L0 = Layer0.getInstance(graph);
DocumentResource DOC = DocumentResource.getInstance(graph);
Collection libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary));
if(libs.isEmpty()) continue;
List nrs = new ArrayList<>();
for(Resource lib : libs) nrs.add(new NamedResource(NameUtils.getSafeName(graph, lib), lib));
Collections.sort(nrs, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR);
Resource library = nrs.iterator().next().getResource();
CommonDBUtils.selectClusterSet(graph, library);
String text = "--- Created: " + new Date().toString() + " ---\n";
text += EString.implode(messageLog);
Resource log = graph.newResource();
graph.claim(log, L0.InstanceOf, null, DOC.PlainTextDocument);
graph.claimLiteral(log, L0.HasName, L0.String, "Typical Sync " + new Date().toString());
graph.claim(library, L0.ConsistsOf, L0.PartOf, log);
graph.claimLiteral(log, DOC.PlainTextDocument_text, L0.String, text);
logs.add(log);
}
}
if (!metadata.getTypicals().isEmpty()) {
graph.addMetadata(metadata);
// Add comment to change set.
CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
graph.addMetadata( cm.add("Synchronized " + metadata.getTypicals().size() + " typical diagram instances (" + metadata.getTypicals() + ") with their templates.") );
}
temporaryDiagram = null;
syncCtx = null;
}
private Collection findInstances(ReadGraph graph, Resource ofType, Collection indexRoots) throws DatabaseException {
Instances index = graph.adapt(ofType, Instances.class);
Set instances = new HashSet<>();
for (Resource indexRoot : indexRoots)
instances.addAll( index.find(graph, indexRoot) );
return instances;
}
private void syncTemplate(WriteGraph graph, Resource template, Collection indexRoots) throws DatabaseException {
Resource templateType = graph.getPossibleType(template, DIA.Diagram);
if (templateType == null)
return;
Collection instances = findInstances(graph, templateType, indexRoots);
// Do not include the template itself as it is also an instance of templateType
instances.remove(template);
if (instances.isEmpty())
return;
Set templateElements = new THashSet<>( graph.syncRequest(
new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );
try {
for (Resource instance : instances) {
this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);
syncInstance(graph, template, instance, templateElements);
}
} catch (Exception e) {
LOGGER.error("Template synchronization failed.", e);
} finally {
this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
}
}
private void syncInstance(WriteGraph graph, Resource instance) throws DatabaseException {
Resource template = graph.getPossibleObject(instance, MOD.HasDiagramSource);
if (template == null)
return;
Set templateElements = new THashSet<>( graph.syncRequest(
new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );
try {
this.temporaryDiagram.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, instance);
syncInstance(graph, template, instance, templateElements);
} finally {
this.temporaryDiagram.removeHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
}
}
private Resource findInstanceCounterpart(ReadGraph graph, Resource instanceDiagram, Resource templateElement) throws DatabaseException {
Map children = graph.syncRequest(new UnescapedChildMapOfResource(instanceDiagram));
for(Resource child : children.values()) {
if(graph.hasStatement(child, MOD.HasElementSource, templateElement)) return child;
}
return null;
}
private boolean isSynchronizedConnector(ReadGraph graph, Resource templateConnection, Resource instanceConnector) throws DatabaseException {
DiagramResource DIA = DiagramResource.getInstance(graph);
Resource instanceConnection = graph.getPossibleObject(instanceConnector, DIA.IsConnectorOf);
if (instanceConnection == null)
return false;
return graph.hasStatement(instanceConnection, MOD.HasElementSource, templateConnection)
// If the master connection has been removed, this is all that's left
// to identify a connection that at least was originally synchronized
// from the typical master to this instance.
|| graph.hasStatement(instanceConnection, MOD.IsTemplatized);
}
/**
* Perform the following synchronization steps for the instance diagram:
*
* remove such templatized elements from the instance diagram whose
* template counterpart no longer exists
* add elements to the instance diagram that are only in the template
* synchronize elements of the instance diagram that have been deemed
* changed
*
*
* @param graph database write access
* @param template the synchronization source diagram
* @param instance the synchronization target diagram
* @param currentTemplateElements the set of all elements currently in the
* template diagram
* @throws DatabaseException if anything goes wrong
*/
private void syncInstance(WriteGraph graph, Resource template, Resource instance, Set currentTemplateElements) throws DatabaseException {
List messageLog = getLog(graph, instance);
messageLog.add("Synchronization of changed typical template: " + SyncTypicalTemplatesToInstances.safeNameAndType(graph, getDiagramNameResource(graph, template)));
messageLog.add("----\n\ttypical instance: " + safeNameAndType(graph, getDiagramNameResource(graph, instance)));
CommonDBUtils.selectClusterSet(graph, instance);
// Form instance element <-> template element bijection
TypicalInfoBean typicalInfoBean = graph.syncRequest(
new ReadTypicalInfo(instance),
TransientCacheListener. instance());
// Must be able to modify the typicalInfo structure,
// therefore clone the query result.
typicalInfoBean = (TypicalInfoBean) typicalInfoBean.clone();
typicalInfoBean.templateElements = currentTemplateElements;
typicalInfoBean.auxiliary = new HashMap<>(1);
TypicalInfo info = new TypicalInfo();
info.monitor = monitor;
info.messageLog = messageLog;
info.bean = typicalInfoBean;
// Resolve naming function for this typical instance.
Resource compositeInstance = graph.getPossibleObject(instance, MOD.DiagramToComposite);
if (compositeInstance != null) {
Function4 namingFunction = TypicalUtil.getTypicalNamingFunction(graph, compositeInstance);
if (namingFunction != null)
typicalInfoBean.auxiliary.put(AuxKeys.KEY_TYPICAL_NAMING_FUNCTION, namingFunction);
}
int dSizeAbs = Math.abs(typicalInfoBean.instanceElements.size() - currentTemplateElements.size());
if(DEBUG)
System.out.println("typical <-> template mapping: " + typicalInfoBean.instanceToTemplate);
// Find elements to be removed from instance by looking for all
// instance elements that do not have a MOD.HasElementSource
// relation but have a MOD.IsTemplatized tag.
Set instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate(
graph, info, new THashSet<>(dSizeAbs));
// Find elements in template that do not yet exist in the instance
Set templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance(
graph, currentTemplateElements, info,
new THashSet<>(dSizeAbs));
Set changedTemplateElements = changedElementsByDiagram.removeValues(template);
if(DEBUG)
System.out.println("ADDED: " + templateElementsAddedToTemplate.size() + ", REMOVED: " + instanceElementsRemovedFromTemplate.size() + ", CHANGED: " + changedTemplateElements.size());
// Validate
for(Resource templateElement : graph.getObjects(template, L0.ConsistsOf)) {
if(graph.isInstanceOf(templateElement, DIA.RouteGraphConnection)) {
for(Resource connector : graph.getObjects(templateElement, DIA.HasConnector)) {
for(Statement elementStm : graph.getStatements(connector, STR.Connects)) {
Resource otherElement = elementStm.getObject();
if(!otherElement.equals(templateElement)) {
Resource counterPartElement = findInstanceCounterpart(graph, instance, otherElement);
if(counterPartElement != null) {
Resource diagramConnectionPoint = graph.getInverse(elementStm.getPredicate());
Resource connectionPoint = graph.getPossibleObject(diagramConnectionPoint, MOD.DiagramConnectionRelationToConnectionRelation);
if(connectionPoint != null) {
Statement stm = graph.getPossibleStatement(counterPartElement, diagramConnectionPoint);
if(stm != null) {
if(graph.isInstanceOf(connectionPoint, L0.FunctionalRelation)) {
if(!isSynchronizedConnector(graph, templateElement, stm.getObject())) {
messageLog.add("\t\tWARNING: skipping addition of template connection " + NameUtils.getSafeName(graph, templateElement, true) + " into instance.");
messageLog.add("\t\t\ttried to connect to an already connected terminal " + NameUtils.getSafeName(graph, counterPartElement, true) + " " + NameUtils.getSafeName(graph, connectionPoint));
templateElementsAddedToTemplate.remove(templateElement);
}
}
}
}
}
}
}
}
}
}
// Perform changes
boolean changed = false;
changed |= synchronizeDiagramChanges(graph, info, template, instance);
changed |= removeElements(graph, info, instanceElementsRemovedFromTemplate);
changed |= addMissingElements(graph, info, template, instance, templateElementsAddedToTemplate);
changed |= synchronizeChangedElements(graph, info, template, instance, changedTemplateElements, templateElementsAddedToTemplate, changedElementsByDiagram == ALL);
if (changed)
metadata.addTypical(instance);
}
/**
* Synchronize any configurable aspects of the typical diagram instance itself.
* Every rule executed here comes from the ontology, nothing is fixed.
*
* @param graph
* @param typicalInfo
* @param template
* @param instance
* @return if any changes were made.
* @throws DatabaseException
*/
private boolean synchronizeDiagramChanges(
WriteGraph graph,
TypicalInfo typicalInfo,
Resource template,
Resource instance)
throws DatabaseException
{
boolean changed = false;
for (Resource rule : graph.getObjects(template, MOD.HasTypicalSynchronizationRule)) {
if (selectedRules != null && !selectedRules.contains(rule))
continue;
ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);
if (r != null)
changed |= r.synchronize(graph, template, instance, typicalInfo);
}
return changed;
}
/**
* Add elements from template that do not yet exist in the instance.
*
* @param graph
* @param template
* @param instance
* @param elementsAddedToTemplate
* @return true
if changes were made to the instance
* @throws DatabaseException
*/
private boolean addMissingElements(WriteGraph graph, TypicalInfo typicalInfo, Resource template,
Resource instance, Set elementsAddedToTemplate)
throws DatabaseException {
if (elementsAddedToTemplate.isEmpty())
return false;
CopyAdvisor copyAdvisor = graph.syncRequest(new Adapter(instance, CopyAdvisor.class));
this.temporaryDiagram.setHint(SynchronizationHints.COPY_ADVISOR, copyAdvisor);
ElementObjectAssortment assortment = new ElementObjectAssortment(graph, elementsAddedToTemplate);
if (copyMap == null)
copyMap = new THashMap<>();
else
copyMap.clear();
if (DEBUG)
System.out.println("ADD MISSING ELEMENTS: " + assortment);
// initialCopyMap argument is needed for copying just connections
// when their end-points are not copied at the same time.
PasteOperation pasteOp = new PasteOperation(Commands.COPY,
(ICanvasContext) null, template, instance, temporaryDiagram,
assortment, false, new Point2D.Double(0, 0),
typicalInfo.bean.templateToInstance, copyMap)
.options(PasteOperation.ForceCopyReferences.INSTANCE);
new Paster(graph.getSession(), pasteOp).perform(graph);
boolean changed = false;
if(!elementsAddedToTemplate.isEmpty())
typicalInfo.messageLog.add("\tadded elements");
for (Resource addedElement : elementsAddedToTemplate) {
Resource copyElement = (Resource) copyMap.get(addedElement);
if (copyElement != null) {
graph.claim(copyElement, MOD.IsTemplatized, MOD.IsTemplatized, copyElement);
graph.claim(copyElement, MOD.HasElementSource, MOD.ElementHasInstance, addedElement);
typicalInfo.bean.instanceElements.add(copyElement);
typicalInfo.bean.instanceToTemplate.put(copyElement, addedElement);
typicalInfo.bean.templateToInstance.put(addedElement, copyElement);
typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, copyElement));
changed = true;
}
}
ModelingResources MOD = ModelingResources.getInstance(graph);
Resource instanceComposite = graph.getPossibleObject(instance, MOD.DiagramToComposite);
List instanceComponents = new ArrayList<>(elementsAddedToTemplate.size());
// Post-process added elements after typicalInfo has been updated and
// template mapping statements are in place.
for (Resource addedElement : elementsAddedToTemplate) {
Resource copyElement = (Resource) copyMap.get(addedElement);
if (copyElement != null) {
postProcessAddedElement(graph, addedElement, copyElement, typicalInfo);
if (instanceComponents != null) {
// Gather all instance typical components for applying naming
// strategy on them.
Resource component = graph.getPossibleObject(copyElement, MOD.ElementToComponent);
if (component != null)
instanceComponents.add(component);
}
}
}
if (instanceComposite != null)
TypicalUtil.applySelectedModuleNames(graph, instanceComposite, instanceComponents);
return changed;
}
private void postProcessAddedElement(WriteGraph graph,
Resource addedTemplateElement, Resource addedInstanceElement,
TypicalInfo typicalInfo) throws DatabaseException {
if (graph.isInstanceOf(addedInstanceElement, DIA.Monitor)) {
postProcessAddedMonitor(graph, addedTemplateElement, addedInstanceElement, typicalInfo);
}
}
private void postProcessAddedMonitor(WriteGraph graph,
Resource addedTemplateMonitor, Resource addedInstanceMonitor,
TypicalInfo typicalInfo) throws DatabaseException {
Resource monitor = addedInstanceMonitor;
Resource monitoredComponent = graph.getPossibleObject(monitor, DIA.HasMonitorComponent);
if (monitoredComponent != null) {
Resource monitoredTemplateElement = graph.getPossibleObject(monitoredComponent, MOD.ComponentToElement);
if (monitoredTemplateElement != null) {
Resource monitoredInstanceElement = typicalInfo.bean.templateToInstance.get(monitoredTemplateElement);
if (monitoredInstanceElement != null) {
Resource monitoredInstanceComponent = graph.getPossibleObject(monitoredInstanceElement, MOD.ElementToComponent);
if (monitoredInstanceComponent != null) {
// Ok, the monitor refers to a component within the
// template composite. Change it to refer to the
// instance composite.
graph.deny(monitor, DIA.HasMonitorComponent);
graph.claim(monitor, DIA.HasMonitorComponent, monitoredInstanceComponent);
}
}
}
}
}
private boolean removeElements(WriteGraph graph, TypicalInfo typicalInfo, Set elementsRemovedFromTemplate) throws DatabaseException {
if (elementsRemovedFromTemplate.isEmpty())
return false;
// Remove mapped elements from instance that are removed from the template.
boolean changed = false;
if(!elementsRemovedFromTemplate.isEmpty())
typicalInfo.messageLog.add("\tremoved elements");
for (Resource removedElement : elementsRemovedFromTemplate) {
typicalInfo.messageLog.add("\t\t" + safeNameAndType(graph, removedElement));
RemoverUtil.remove(graph, removedElement);
typicalInfo.bean.instanceElements.remove(removedElement);
Resource template = typicalInfo.bean.instanceToTemplate.remove(removedElement);
if (template != null)
typicalInfo.bean.templateToInstance.remove(template);
changed = true;
}
return changed;
}
private Set findTemplateElementsMissingFromInstance(
WriteGraph graph, Collection currentTemplateElements,
TypicalInfo typicalInfo, THashSet result)
throws DatabaseException {
for (Resource templateElement : currentTemplateElements) {
Resource instanceElement = typicalInfo.bean.templateToInstance.get(templateElement);
if (instanceElement == null) {
if(DEBUG)
System.out.println("No instance correspondence for template element " + NameUtils.getSafeName(graph, templateElement, true) + " => add");
result.add(templateElement);
}
}
return result;
}
public Set findInstanceElementsRemovedFromTemplate(
ReadGraph graph, TypicalInfo typicalInfo,
THashSet result) throws DatabaseException {
for (Resource instanceElement : typicalInfo.bean.instanceElements) {
if (!typicalInfo.bean.instanceToTemplate.containsKey(instanceElement)) {
if (typicalInfo.bean.isTemplatized.contains(instanceElement)) {
if(DEBUG)
System.out.println("Templatized typical instance element " + NameUtils.getSafeName(graph, instanceElement, true) + " has no correspondence in template => remove");
result.add(instanceElement);
}
}
}
return result;
}
/**
* Synchronize basic visual aspects of changed elements. For all elements,
* transform and label are synchronized. Otherwise synchronization is
* type-specific for connections, flags, monitors and svg elements.
*
* @param graph
* @param typicalInfo
* @param template
* @param instance
* @param changedTemplateElements
* @param addedElements
* elements that have been added and thus need not be
* synchronized
* @param synchronizeAllElements
* @return
* @throws DatabaseException
*/
private boolean synchronizeChangedElements(WriteGraph graph,
TypicalInfo typicalInfo, Resource template, Resource instance,
Collection changedTemplateElements,
Set addedElements,
boolean synchronizeAllElements) throws DatabaseException {
if (synchronizeAllElements) {
// For unit testing purposes.
changedTemplateElements = graph.syncRequest(new ObjectsWithType(template, L0.ConsistsOf, DIA.Element));
}
if (changedTemplateElements.isEmpty())
return false;
boolean changed = false;
typicalInfo.messageLog.add("\telement change analysis");
int analysisLogPosition = typicalInfo.messageLog.size();
for (Resource changedTemplateElement : changedTemplateElements) {
// Skip synchronization of elements that were just added and are
// thus already synchronized.
if (addedElements.contains(changedTemplateElement))
continue;
Resource instanceElement = typicalInfo.bean.templateToInstance.get(changedTemplateElement);
if (instanceElement == null) {
// There's an earlier problem in the sync process if this happens.
typicalInfo.messageLog.add("\t\tSKIPPING SYNC OF CHANGED TEMPLATE ELEMENT DUE TO MISSING INSTANCE: " + safeNameAndType(graph, getElementNameResource(graph, changedTemplateElement)));
continue;
}
typicalInfo.messageLog.add("\t\t" + elementName(graph, changedTemplateElement));
int currentLogSize = typicalInfo.messageLog.size();
changed |= InstanceOfRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
changed |= NameRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
changed |= TransformRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
changed |= LabelRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
Collection types = graph.getTypes(changedTemplateElement);
if (types.contains(DIA.RouteGraphConnection)) {
changed |= synchronizeConnection(graph, changedTemplateElement, instanceElement, typicalInfo);
} else if (types.contains(DIA.Flag)) {
changed |= FlagRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
} else if (types.contains(DIA.Monitor)) {
changed |= MonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
} else if (types.contains(DIA.SVGElement)) {
changed |= SVGElementRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
}
changed |= ProfileMonitorRule.INSTANCE.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
for (Resource rule : graph.getObjects(changedTemplateElement, MOD.HasTypicalSynchronizationRule)) {
if(selectedRules != null && !selectedRules.contains(rule)) continue;
ITypicalSynchronizationRule r = graph.getPossibleAdapter(rule, ITypicalSynchronizationRule.class);
if (r != null)
changed |= r.synchronize(graph, changedTemplateElement, instanceElement, typicalInfo);
}
// Show element only if something has happened
if(currentLogSize == typicalInfo.messageLog.size())
typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);
}
if (s2t != null)
s2t.clear();
if (t2s != null)
t2s.clear();
// Show analysis header only if something has happened
if(analysisLogPosition == typicalInfo.messageLog.size())
typicalInfo.messageLog.remove(typicalInfo.messageLog.size()-1);
return changed;
}
private static class Connector {
public final Resource attachmentRelation;
public final Resource connector;
public RouteLine attachedTo;
public Connector(Resource attachmentRelation, Resource connector) {
this.attachmentRelation = attachmentRelation;
this.connector = connector;
}
}
/**
* Synchronizes two route graph connection topologies if and only if the
* destination connection is not attached to any node elements besides
* the ones that exist in the source. This means that connections that
* have instance-specific connections to non-template nodes are ignored
* here.
*
* @param graph
* @param sourceConnection
* @param targetConnection
* @param typicalInfo
* @return true
if changes were made
* @throws DatabaseException
*/
private boolean synchronizeConnection(WriteGraph graph, Resource sourceConnection, Resource targetConnection, TypicalInfo typicalInfo)
throws DatabaseException {
if(DEBUG)
System.out.println("connection " + NameUtils.getSafeName(graph, sourceConnection, true) + " to target connection " + NameUtils.getSafeName(graph, targetConnection, true));
boolean changed = false;
// Initialize utilities and data maps
s2t = newOrClear(s2t);
t2s = newOrClear(t2s);
if (cu == null)
cu = new ConnectionUtil(graph);
// 0.1. find mappings between source and target connection connectors
Collection toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector);
Map targetConnectors = new THashMap<>(toTargetConnectors.size());
for (Statement toTargetConnector : toTargetConnectors) {
Resource targetConnector = toTargetConnector.getObject();
targetConnectors.put(targetConnector, new Connector(toTargetConnector.getPredicate(), targetConnector));
Statement toNode = cu.getConnectedComponentStatement(targetConnection, targetConnector);
if (toNode == null) {
// Corrupted target connection!
ErrorLogger.defaultLogError("Encountered corrupted typical template connection "
+ NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "
+ NameUtils.getSafeName(graph, targetConnector, true) + " that is not attached to any element.",
new Exception("trace"));
return false;
}
if (!graph.hasStatement(targetConnector, DIA.AreConnected)) {
// Corrupted target connection!
ErrorLogger.defaultLogError("Encountered corrupted typical template connection "
+ NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "
+ NameUtils.getSafeName(graph, targetConnector, true) + " that is not connected to any other route node.",
new Exception("trace"));
return false;
}
//Resource templateNode = typicalInfo.instanceToTemplate.get(toNode.getObject());
Resource templateNode = graph.getPossibleObject(toNode.getObject(), MOD.HasElementSource);
if (templateNode != null) {
Resource isConnectedTo = graph.getPossibleInverse(toNode.getPredicate());
if (isConnectedTo != null) {
Resource templateConnector = graph.getPossibleObject(templateNode, isConnectedTo);
if (templateConnector != null) {
Resource connectionOfTemplateConnector = ConnectionUtil.tryGetConnection(graph, templateConnector);
if (sourceConnection.equals(connectionOfTemplateConnector)) {
s2t.put(templateConnector, targetConnector);
t2s.put(targetConnector, templateConnector);
if (DEBUG)
debug(typicalInfo, "Mapping connector "
+ NameUtils.getSafeName(graph, templateConnector, true)
+ " to " + NameUtils.getSafeName(graph, targetConnector, true));
}
}
}
}
}
// 0.2. find mapping between source and target route lines
Collection sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode);
Collection targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode);
Map sourceToRouteLine = new THashMap<>();
Map targetToRouteLine = new THashMap<>();
for (Resource source : sourceInteriorRouteNodes)
sourceToRouteLine.put(source, Paster.readRouteLine(graph, source));
for (Resource target : targetInteriorRouteNodes)
targetToRouteLine.put(target, Paster.readRouteLine(graph, target));
Map originalSourceToRouteLine = new THashMap<>(sourceToRouteLine);
Map originalTargetToRouteLine = new THashMap<>(targetToRouteLine);
nextSourceLine:
for (Iterator> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) {
Map.Entry sourceEntry = sourceIt.next();
Paster.RouteLine sourceLine = sourceEntry.getValue();
for (Iterator> targetIt = targetToRouteLine.entrySet().iterator(); targetIt.hasNext();) {
Map.Entry targetEntry = targetIt.next();
if (sourceLine.equals(targetEntry.getValue())) {
s2t.put(sourceEntry.getKey(), targetEntry.getKey());
t2s.put(targetEntry.getKey(), sourceEntry.getKey());
sourceIt.remove();
targetIt.remove();
if (DEBUG)
debug(typicalInfo, "Mapping routeline "
+ NameUtils.getSafeName(graph, sourceEntry.getKey(), true)
+ " - " + sourceEntry.getValue()
+ " to " + NameUtils.getSafeName(graph, targetEntry.getKey(), true)
+ " - " + targetEntry.getValue());
continue nextSourceLine;
}
}
}
if (DEBUG) {
debug(typicalInfo, "Take 1: Source to target route nodes map : " + s2t);
debug(typicalInfo, "Take 1: Target to source route nodes map : " + t2s);
}
// 1.1. Temporarily disconnect instance-specific connectors from the the connection .
// They will be added back to the connection after the templatized parts of the
// connection have been synchronized.
// Stores diagram connectors that are customizations in the synchronized instance.
List instanceOnlyConnectors = null;
for (Connector connector : targetConnectors.values()) {
if (!t2s.containsKey(connector.connector)) {
typicalInfo.messageLog.add("\t\tencountered instance-specific diagram connector in target connection: " + NameUtils.getSafeName(graph, connector.connector));
// Find the RouteLine this connectors is connected to.
for (Resource rl : graph.getObjects(connector.connector, DIA.AreConnected)) {
connector.attachedTo = originalTargetToRouteLine.get(rl);
if (connector.attachedTo != null)
break;
}
// Disconnect connector from connection
graph.deny(targetConnection, connector.attachmentRelation, connector.connector);
graph.deny(connector.connector, DIA.AreConnected);
// Keep track of the disconnected connector
if (instanceOnlyConnectors == null)
instanceOnlyConnectors = new ArrayList<>(targetConnectors.size());
instanceOnlyConnectors.add(connector);
}
}
// 1.2. add missing connectors to target
Collection sourceConnectors = graph.getObjects(sourceConnection, DIA.HasConnector);
for (Resource sourceConnector : sourceConnectors) {
if (!s2t.containsKey(sourceConnector)) {
Statement sourceIsConnectorOf = graph.getSingleStatement(sourceConnector, DIA.IsConnectorOf);
Statement connects = cu.getConnectedComponentStatement(sourceConnection, sourceConnector);
if (connects == null) {
// TODO: serious error!
throw new DatabaseException("ERROR: connector is astray, i.e. not connected to a node element: " + safeNameAndType(graph, sourceConnector));
}
Resource connectsInstanceElement = typicalInfo.bean.templateToInstance.get(connects.getObject());
if (connectsInstanceElement == null) {
// TODO: serious error!
throw new DatabaseException("ERROR: could not find instance element to which template element " + safeNameAndType(graph, connects.getObject()) + " is connected to");
}
Resource hasConnector = graph.getInverse(sourceIsConnectorOf.getPredicate());
Resource newTargetConnector = cu.newConnector(targetConnection, hasConnector);
graph.claim(newTargetConnector, connects.getPredicate(), connectsInstanceElement);
changed = true;
s2t.put(sourceConnector, newTargetConnector);
t2s.put(newTargetConnector, sourceConnector);
typicalInfo.messageLog.add("\t\t\tadd new connector to target connection: " + NameUtils.getSafeName(graph, newTargetConnector) + " to map to source connector " + NameUtils.getSafeName(graph, sourceConnector));
}
}
// 2. sync route lines and their connectivity:
// 2.1. assign correspondences in target for each source route line
// by reusing excess route lines in target and by creating new
// route lines.
Resource[] targetRouteLines = targetToRouteLine.keySet().toArray(Resource.NONE);
int targetRouteLine = targetRouteLines.length - 1;
for (Iterator> sourceIt = sourceToRouteLine.entrySet().iterator(); sourceIt.hasNext();) {
Map.Entry sourceEntry = sourceIt.next();
Resource source = sourceEntry.getKey();
Paster.RouteLine sourceLine = sourceEntry.getValue();
typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline counterpart for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine);
// Assign target route line for source
Resource target = null;
if (targetRouteLine < 0) {
// by creating new route lines
target = cu.newRouteLine(targetConnection, sourceLine.getPosition(), sourceLine.isHorizontal());
typicalInfo.messageLog.add("\t\t\tcreate new route line " + NameUtils.getSafeName(graph, target));
changed = true;
} else {
// by reusing existing route line
target = targetRouteLines[targetRouteLine--];
copyRouteLine(graph, source, target);
cu.disconnectFromAllRouteNodes(target);
typicalInfo.messageLog.add("\t\t\treused existing route line " + NameUtils.getSafeName(graph, target));
changed = true;
}
s2t.put(source, target);
t2s.put(target, source);
typicalInfo.messageLog.add("\t\t\tmapped source route line " + NameUtils.getSafeName(graph, source) + " to target route line " + NameUtils.getSafeName(graph, target));
}
if (targetRouteLine >= 0) {
typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection");
for (; targetRouteLine >= 0; targetRouteLine--) {
typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true));
cu.removeConnectionPart(targetRouteLines[targetRouteLine]);
}
}
if (DEBUG) {
debug(typicalInfo, "Take 2: Source to target route nodes map : " + s2t);
debug(typicalInfo, "Take 2: Target to source route nodes map : " + t2s);
}
// 2.2. Synchronize target connection topology (DIA.AreConnected)
changed |= connectRouteNodes(graph, typicalInfo, sourceInteriorRouteNodes);
changed |= connectRouteNodes(graph, typicalInfo, sourceConnectors);
// 3. remove excess routelines & connectors from target connection
changed |= cu.removeExtraInteriorRouteNodes(targetConnection) > 0;
changed |= cu.removeUnusedConnectors(targetConnection) > 0;
// 3.1. Ensure that all mapped route nodes in the target connection
// are tagged with MOD.IsTemplatized. Future synchronization
// can then take advantage of this information to more easily
// decide which parts of the connection are originated from
// the template and which are not.
changed |= markMappedRouteNodesTemplatized(graph, s2t.values());
// 4. Add temporarily disconnected instance-specific connectors
// back to the synchronized connection. The route line to attach
// to is based on a simple heuristic.
if (instanceOnlyConnectors != null) {
if (originalSourceToRouteLine.isEmpty()) {
// If there are 0 route lines in the template connection,
// then one must be added to the instance connection.
// This can only happen if the template connection is
// simple, i.e. just between two terminals without any
// custom routing.
// Attach all target connection connectors to the newly created route line
Resource rl = cu.newRouteLine(targetConnection, null, null);
for (Resource sourceConnector : sourceConnectors) {
Resource targetConnector = s2t.get(sourceConnector);
graph.deny(targetConnector, DIA.AreConnected);
graph.claim(targetConnector, DIA.AreConnected, DIA.AreConnected, rl);
}
// Copy orientation and position for new route line from original target route lines.
// This is a simplification that will attach any amount of route lines in the original
// target connection into just one route line. There is room for improvement here
// but it will require a more elaborate algorithm to find and cut the non-templatized
// route lines as well as connectors out of the connection before synchronizing it.
//
// TODO: This implementation chooses the added route line position at random if
// there are multiple route lines in the target connection.
if (!originalTargetToRouteLine.isEmpty()) {
RouteLine originalRl = originalTargetToRouteLine.values().iterator().next();
setRouteLine(graph, rl, originalRl);
}
// Attach the instance specific connectors also to the only route line
for (Connector connector : instanceOnlyConnectors) {
graph.claim(targetConnection, connector.attachmentRelation, connector.connector);
graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, rl);
}
changed = true;
} else {
for (Connector connector : instanceOnlyConnectors) {
// Find the route line that most closely matches the original
// route line that the connector was connected to.
Resource closestMatch = null;
double closestDistance = Double.MAX_VALUE;
if (connector.attachedTo != null) {
for (Map.Entry sourceLine : originalSourceToRouteLine.entrySet()) {
double dist = distance(sourceLine.getValue(), connector.attachedTo);
if (dist < closestDistance) {
closestMatch = s2t.get(sourceLine.getKey());
closestDistance = dist;
}
}
} else {
closestMatch = originalSourceToRouteLine.keySet().iterator().next();
}
graph.claim(targetConnection, connector.attachmentRelation, connector.connector);
graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, closestMatch);
if (closestDistance > 0)
changed = true;
typicalInfo.messageLog.add("\t\t\treattached instance-specific connector "
+ NameUtils.getSafeName(graph, connector.connector) + " to nearest existing route line "
+ NameUtils.getSafeName(graph, closestMatch) + " with distance " + closestDistance);
}
}
}
return changed;
}
private boolean markMappedRouteNodesTemplatized(WriteGraph graph, Iterable routeNodes) throws DatabaseException {
boolean changed = false;
for (Resource rn : routeNodes) {
if (!graph.hasStatement(rn, MOD.IsTemplatized)) {
graph.claim(rn, MOD.IsTemplatized, MOD.IsTemplatized, rn);
changed = true;
}
}
return changed;
}
private static double distance(RouteLine l1, RouteLine l2) {
double dist = Math.abs(l2.getPosition() - l1.getPosition());
dist *= l2.isHorizontal() == l1.isHorizontal() ? 1 : 1000;
return dist;
}
private boolean connectRouteNodes(WriteGraph graph, TypicalInfo typicalInfo, Collection sourceRouteNodes) throws DatabaseException {
boolean changed = false;
for (Resource src : sourceRouteNodes) {
Resource dst = s2t.get(src);
if (dst == null) {
throw new DatabaseException("TARGET ROUTE NODE == NULL FOR SRC: " + NameUtils.getSafeName(graph, src));
}
Collection connectedToSrcs = graph.getObjects(src, DIA.AreConnected);
Collection connectedToDsts = graph.getObjects(dst, DIA.AreConnected);
// Remove excess statements
for (Resource connectedToDst : connectedToDsts) {
Resource connectedToSrc = t2s.get(connectedToDst);
if (connectedToSrc == null) {
throw new DatabaseException("CONNECTED TO SRC == NULL FOR DST: " + NameUtils.getSafeName(graph, connectedToDst));
}
if (connectedToSrc == null || !graph.hasStatement(src, DIA.AreConnected, connectedToSrc)) {
graph.deny(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
changed = true;
typicalInfo.messageLog.add("\t\t\tdisconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");
}
}
// Add necessary statements
for (Resource connectedToSrc : connectedToSrcs) {
Resource connectedToDst = s2t.get(connectedToSrc);
if (connectedToDst == null) {
throw new DatabaseException("CONNECTED TO DST == NULL FOR SRC: " + NameUtils.getSafeName(graph, connectedToSrc));
}
if (!graph.hasStatement(dst, DIA.AreConnected, connectedToDst)) {
graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
changed = true;
typicalInfo.messageLog.add("\t\t\tconnected route nodes (" + NameUtils.getSafeName(graph, dst) + ", " + NameUtils.getSafeName(graph, connectedToDst) + ")");
}
}
}
return changed;
}
private void setRouteLine(WriteGraph graph, Resource line, double position, boolean horizontal) throws DatabaseException {
graph.claimLiteral(line, DIA.HasPosition, L0.Double, position, Bindings.DOUBLE);
graph.claimLiteral(line, DIA.IsHorizontal, L0.Boolean, horizontal, Bindings.BOOLEAN);
}
private void setRouteLine(WriteGraph graph, Resource line, RouteLine rl) throws DatabaseException {
setRouteLine(graph, line, rl.getPosition(), rl.isHorizontal());
}
private void copyRouteLine(WriteGraph graph, Resource src, Resource tgt) throws DatabaseException {
Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);
Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);
if (pos == null)
pos = 0.0;
if (hor == null)
hor = Boolean.TRUE;
graph.claimLiteral(tgt, DIA.HasPosition, L0.Double, pos, Bindings.DOUBLE);
graph.claimLiteral(tgt, DIA.IsHorizontal, L0.Boolean, hor, Bindings.BOOLEAN);
}
private static String safeNameAndType(ReadGraph graph, Resource r) throws DatabaseException {
StringBuilder sb = new StringBuilder();
sb.append(NameUtils.getSafeName(graph, r, true));
sb.append(" : [");
boolean first = true;
for (Resource type : graph.getPrincipalTypes(r)) {
if (!first)
sb.append(",");
first = false;
sb.append(NameUtils.getSafeName(graph, type, true));
}
sb.append("]");
return sb.toString();
}
private static Map newOrClear(Map current) {
if (current == null)
return new THashMap<>();
current.clear();
return current;
}
private void debug(TypicalInfo typicalInfo, String message) {
if (DEBUG) {
System.out.println(message);
typicalInfo.messageLog.add(message);
}
}
}