package org.simantics.issues.common; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import org.simantics.Simantics; import org.simantics.db.MetadataI; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.Indexing; import org.simantics.db.common.changeset.GenericChangeListener; import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.common.request.PossibleTypedParent; import org.simantics.db.common.utils.Functions; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.event.ChangeListener; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.genericrelation.DependenciesRelation.DependencyChangesRequest; import org.simantics.db.layer0.genericrelation.DependencyChanges; import org.simantics.db.service.GraphChangeListenerSupport; import org.simantics.issues.ontology.IssueResource; import org.simantics.layer0.Layer0; import org.simantics.scl.runtime.function.Function2; import org.simantics.simulation.ontology.SimulationResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DependencyIssueSource2 implements IssueSource { private static final Logger LOGGER = LoggerFactory.getLogger(DependencyIssueSource2.class); public static final boolean DEBUG = false; private Resource source; private Resource model; private Resource type; private Set searchTypes; private Resource base; private Resource extensionFunction; private CopyOnWriteArraySet, Boolean>> listeners = new CopyOnWriteArraySet<>(); private ConcurrentMap, Boolean>, ChangeListener> listenerMap = new ConcurrentHashMap<>(); public DependencyIssueSource2(ReadGraph graph, Resource source) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); IssueResource IR = IssueResource.getInstance(graph); this.source = source; this.model = graph.syncRequest(new PossibleTypedParent(source, SimulationResource.getInstance(graph).Model)); this.extensionFunction = graph.getPossibleObject(source, IR.Sources_DependencyTracker_HasExtension); this.type = graph.getSingleObject(source, IR.Sources_DependencyTracker_HasType); HashSet _searchTypes = new HashSet<>(); _searchTypes.addAll(graph.getObjects(source, IR.Sources_DependencyTracker_HasSearchType)); _searchTypes.add(type); this.searchTypes = new HashSet<>(_searchTypes); Resource baseFunction = graph.getSingleObject(source, IR.Sources_DependencyTracker_HasBaseFunction); this.base = Functions.exec(graph, baseFunction, graph, graph.getSingleObject(source, L0.PartOf)); } private List resourcesToCheck(ReadGraph graph, DependencyChanges event) throws DatabaseException { HashSet depSet = new HashSet<>(); if(DEBUG) { LOGGER.info("resourcesToCheck[" + NameUtils.getSafeName(graph, source) + "] - component changes for type " + NameUtils.getSafeName(graph, searchTypes)); } depSet.addAll(IssueSourceUtils.getChangedDependencies(graph, model, base, searchTypes, event)); depSet.addAll(IssueSourceUtils.getChangedDependencies(graph, source, model, base, searchTypes)); List deps = new ArrayList<>(depSet); if(DEBUG) { LOGGER.info("resourcesToCheck[" + NameUtils.getSafeName(graph, source) + "] " + deps); for(Resource r : deps) { LOGGER.info("dep " + NameUtils.getSafeName(graph, r)); } } if(extensionFunction != null) { try { deps = Functions.exec(graph, extensionFunction, graph, deps); } catch (Throwable t) { t.printStackTrace(); } } if(DEBUG) { for(Resource r : deps) { LOGGER.info("dep extension " + NameUtils.getSafeName(graph, r)); } } ArrayList result = new ArrayList<>(); for(Resource dep : deps) { // TODO: still not complete - e.g. someone can replace the InstanceOf Set types = graph.getTypes(dep); if(types.isEmpty() || types.contains(type)) result.add(dep); } return result; } class DependencyChangeListener extends GenericChangeListener { private boolean isValid(ReadGraph graph, List toCheck) throws DatabaseException { for(Resource resource : toCheck) { if(!graph.syncRequest(new DependencyIssueValidator2(resource, model, source), TransientCacheListener.instance())) { return false; } } return true; } @Override public boolean preEventRequest() { return !Indexing.isDependenciesIndexingDisabled(); } @Override public void onEvent(ReadGraph graph, MetadataI metadata, DependencyChanges event) throws DatabaseException { List toCheck = resourcesToCheck(graph, event); if(!isValid(graph, toCheck)) { for(Function2, Boolean> r : listeners) { r.apply(graph, toCheck); } } if(graph instanceof WriteGraph) { IssueSourceUtils.update((WriteGraph)graph, source); } } } @Override public void addDirtyListener(Function2, Boolean> runnable) { boolean added = listeners.add(runnable); if (added) { GraphChangeListenerSupport changeSupport = Simantics.getSession().getService(GraphChangeListenerSupport.class); ChangeListener metadataListener = new DependencyChangeListener(); listenerMap.put(runnable, metadataListener); changeSupport.addMetadataListener(metadataListener); } } @Override public void removeDirtyListener(Function2, Boolean> runnable) { boolean removed = listeners.remove(runnable); ChangeListener metadataListener = listenerMap.remove(runnable); if (removed && metadataListener != null) { GraphChangeListenerSupport changeSupport = Simantics.getSession().getService(GraphChangeListenerSupport.class); changeSupport.removeMetadataListener(metadataListener); } } @Override public void update(WriteGraph graph, List resources) throws DatabaseException { for(Resource resource : resources) { if(!graph.syncRequest(new DependencyIssueValidator2(resource, model, source), TransientCacheListener.instance())) { new DependencyIssueSynchronizer2(resource, source).perform(graph); } } IssueSourceUtils.update(graph, source); } @Override public boolean needUpdate(ReadGraph graph, List resources) throws DatabaseException { for(Resource resource : resources) { if(!graph.syncRequest(new DependencyIssueValidator2(resource, model, source), TransientCacheListener.instance())) return true; } return false; } }