package jetbrains.mps.checkers; /*Generated by MPS */ import org.jetbrains.mps.util.DescendantsTreeIterator; import jetbrains.mps.util.containers.MultiMap; import org.jetbrains.mps.openapi.model.SNode; import jetbrains.mps.errors.IErrorReporter; import jetbrains.mps.util.containers.SetBasedMultiMap; import jetbrains.mps.util.containers.ManyToManyMap; import java.util.Set; import java.util.HashSet; import org.jetbrains.mps.openapi.model.SModel; import jetbrains.mps.internal.collections.runtime.SetSequence; import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.model.SNodeUtil; import jetbrains.mps.util.Cancellable; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import org.jetbrains.mps.openapi.event.SNodeAddEvent; import jetbrains.mps.internal.collections.runtime.ListSequence; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.event.SNodeRemoveEvent; import org.jetbrains.mps.openapi.event.SReferenceChangeEvent; import org.jetbrains.mps.openapi.event.SPropertyChangeEvent; import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes; import jetbrains.mps.smodel.AbstractNodesReadListener; import jetbrains.mps.smodel.NodeReadEventsCaster; import org.jetbrains.mps.openapi.model.SNodeChangeListenerAdapter; import org.jetbrains.mps.openapi.model.SModelListenerBase; public class LanguageErrorsComponent extends LanguageErrorsCollector { /** * States: * <ul> * <li>{@code !myFullCheckCompleted && myFullCheckIterator == null}: * state is UNCHECKED (default after creation)</li> * <li>{@code !myFullCheckCompleted && myFullCheckIterator != null}: * state is PARTIALLY CHECKED (a check was interrupted)</li> * <li>{@code myFullCheckCompleted && no invalid nodes}: * state is FULLY CHECKED (a full check was completed and results are up to date)</li> * <li>{@code myFullCheckCompleted && invalid nodes present}: * state is FULLY CHECKED & INVALID (a full check was completed, but results are no longer up to date)</li> * </ul> * (with the caveat that {@link jetbrains.mps.checkers.LanguageErrorsComponent#myInvalidNodes } are valid only after a call to {@link jetbrains.mps.checkers.LanguageErrorsComponent#invalidate() }). */ private DescendantsTreeIterator myFullCheckIterator; private MultiMap<SNode, IErrorReporter> myNodesToErrors = new SetBasedMultiMap<SNode, IErrorReporter>(); private ManyToManyMap<SNode, SNode> myDependenciesToNodesAndViceVersa = new ManyToManyMap<SNode, SNode>(); private Set<SNode> myInvalidNodes = new HashSet<SNode>(); private Set<SNode> myDependenciesToInvalidate = new HashSet<SNode>(); private LanguageErrorsComponent.MyModelChangeListener myChangeListener = new LanguageErrorsComponent.MyModelChangeListener(); private LanguageErrorsComponent.MyModelUnloadListener myUnloadListener = new LanguageErrorsComponent.MyModelUnloadListener(); private Set<SModel> myListenedModels = new HashSet<SModel>(); private boolean myFullCheckCompleted = false; private SNode myCurrentNode = null; private SModel myModel; private boolean myUpdateInspector = false; public LanguageErrorsComponent(SModel model) { myModel = model; } public void dispose() { clear(); } private void removeModelListeners() { for (SModel modelDescriptor : myListenedModels) { removeModelListeners(modelDescriptor); } SetSequence.fromSet(myListenedModels).clear(); } @Override protected void addError(IErrorReporter errorReporter) { myNodesToErrors.putValue(errorReporter.getSNode(), errorReporter); } public Set<IErrorReporter> getErrors() { Iterable<? extends IErrorReporter> values = myNodesToErrors.values(); return SetSequence.fromSetWithValues(new HashSet<IErrorReporter>(), values); } @Override public void addDependency(SNode dependency) { if (myCurrentNode == null) { return; } if (dependency == null) { return; } addDependencyMapping(myCurrentNode, dependency); addModelListener(SNodeOperations.getModel(dependency)); } private void addDependencyMapping(@NotNull SNode node, @NotNull SNode dependency) { myDependenciesToNodesAndViceVersa.addLink(node, dependency); } private Set<SNode> removeDependencyFromMapping(@NotNull SNode dependency) { // removing dependency node from any mappings together with all checked nodes // depending on this dependency node Set<SNode> nodes = SetSequence.fromSetWithValues(new HashSet<SNode>(), myDependenciesToNodesAndViceVersa.getByFirst(dependency)); for (SNode node : SetSequence.fromSet(nodes)) { myDependenciesToNodesAndViceVersa.clearSecond(node); } return nodes; } /*package*/ Set<SNode> getDependenciesToInvalidate(SModel model, SRepository repo) { Set<SNode> result = new HashSet<SNode>(); for (SNode dependency : myDependenciesToNodesAndViceVersa.getFirst()) { if (!(SNodeUtil.isAccessible(dependency, repo)) || SNodeOperations.getModel(dependency) == model) { SetSequence.fromSet(result).addElement(dependency); } } return result; } private void addModelListener(SModel modelDescriptor) { if (modelDescriptor == null) { return; } if (!(SetSequence.fromSet(myListenedModels).contains(modelDescriptor))) { // XX why access to myListenedModels is not synchronized? modelDescriptor.addChangeListener(myChangeListener); modelDescriptor.addModelListener(myUnloadListener); SetSequence.fromSet(myListenedModels).addElement(modelDescriptor); } } private void removeModelListeners(SModel m) { m.removeChangeListener(myChangeListener); m.removeModelListener(myUnloadListener); } private void invalidate() { if (SetSequence.fromSet(myDependenciesToInvalidate).isEmpty()) { return; } for (SNode toInvalidate : myDependenciesToInvalidate) { invalidateDependency(toInvalidate); } SetSequence.fromSet(myDependenciesToInvalidate).clear(); } private void invalidateDependency(SNode dependency) { Set<SNode> checkedNodes = removeDependencyFromMapping(dependency); if (checkedNodes != null) { for (SNode node : checkedNodes) { // avoid searching for _already_removed_ node later in check() if (SNodeOperations.getModel(node) != null) { SetSequence.fromSet(myInvalidNodes).addElement(node); } myNodesToErrors.remove(node); } } } /** * * * @return whether state has changed after the check */ public boolean check(@NotNull SNode root, Set<AbstractNodeChecker> checkers, SRepository repository, Cancellable c) { prepareWorkForCheck(); if (myFullCheckCompleted) { if (SetSequence.fromSet(myInvalidNodes).isEmpty()) { return false; } while (SetSequence.fromSet(myInvalidNodes).isNotEmpty()) { SNode node = SetSequence.fromSet(myInvalidNodes).first(); SetSequence.fromSet(myInvalidNodes).removeElement(node); if ((SNodeOperations.getNodeAncestor(node, MetaAdapterFactory.getInterfaceConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x50ef06e32fec9043L, "jetbrains.mps.lang.core.structure.ISkipConstraintsChecking"), true, false) != null)) { continue; } checkNode(node, checkers, repository); if (c.isCancelled()) { return true; } } } else { assert SetSequence.fromSet(myInvalidNodes).isEmpty(); // Visit and check all nodes, continuing from last position, if available if (myFullCheckIterator == null) { // Never checked since the last reset, start from the beginning myFullCheckIterator = new DescendantsTreeIterator(root); } while (myFullCheckIterator.hasNext()) { SNode node = myFullCheckIterator.next(); if (SNodeOperations.isInstanceOf(node, MetaAdapterFactory.getInterfaceConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x50ef06e32fec9043L, "jetbrains.mps.lang.core.structure.ISkipConstraintsChecking"))) { myFullCheckIterator.skipChildren(); continue; } checkNode(node, checkers, repository); if (c.isCancelled()) { return true; } } myFullCheckIterator = null; myFullCheckCompleted = true; } // traversed the whole root, all invalid nodes should have been removed assert SetSequence.fromSet(myInvalidNodes).isEmpty(); myUpdateInspector = true; return true; } private void prepareWorkForCheck() { invalidate(); if (myFullCheckIterator != null && !(SetSequence.fromSet(myInvalidNodes).isEmpty())) { // Full check interrupted and something invalidated: recheck everything from the beginning clear(); } } private void checkNode(SNode node, Set<AbstractNodeChecker> checkers, SRepository repository) { if (SNodeOperations.getModel(node) == null) { return; } if ((SNodeOperations.getNodeAncestor(node, MetaAdapterFactory.getInterfaceConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x50ef06e32fec9043L, "jetbrains.mps.lang.core.structure.ISkipConstraintsChecking"), true, false) != null)) { return; } try { myCurrentNode = node; addDependency(node); for (AbstractNodeChecker checker : checkers) { checker.checkNode(node, this, repository); } } finally { myCurrentNode = null; } } public boolean checkInspector() { if (myUpdateInspector) { myUpdateInspector = false; return true; } return false; } public void clear() { myFullCheckIterator = null; myFullCheckCompleted = false; SetSequence.fromSet(myDependenciesToInvalidate).clear(); SetSequence.fromSet(myInvalidNodes).clear(); myCurrentNode = null; myDependenciesToNodesAndViceVersa.clear(); myNodesToErrors.clear(); removeModelListeners(); } /*package*/ void processEvent(SNodeAddEvent event) { SetSequence.fromSet(myDependenciesToInvalidate).addElement(event.getParent()); SetSequence.fromSet(myInvalidNodes).addSequence(ListSequence.fromList(SNodeOperations.getNodeDescendants(((SNode) event.getChild()), null, true, new SAbstractConcept[]{}))); } /*package*/ void processEvent(SNodeRemoveEvent event) { if (!(event.isRoot())) { SetSequence.fromSet(myDependenciesToInvalidate).addElement(event.getParent()); } SetSequence.fromSet(myDependenciesToInvalidate).addSequence(ListSequence.fromList(SNodeOperations.getNodeDescendants(((SNode) event.getChild()), null, true, new SAbstractConcept[]{}))); } /*package*/ void processEvent(SReferenceChangeEvent event) { SetSequence.fromSet(myDependenciesToInvalidate).addElement(event.getNode()); } /*package*/ void processEvent(SPropertyChangeEvent event) { SetSequence.fromSet(myDependenciesToInvalidate).addElement(event.getNode()); } @Override public <Result> Result runCheckingAction(_FunctionTypes._return_P0_E0<? extends Result> action) { final Set<SNode> accessedNodes = new HashSet<SNode>(); final Object[] result = new Object[1]; try { AbstractNodesReadListener listener = new AbstractNodesReadListener() { @Override public void nodeUnclassifiedReadAccess(SNode node) { SetSequence.fromSet(accessedNodes).addElement(node); } @Override public void nodePropertyReadAccess(SNode node, String name, String value) { SetSequence.fromSet(accessedNodes).addElement(node); } @Override public void nodeReferentReadAccess(SNode node, String role, SNode referent) { SetSequence.fromSet(accessedNodes).addElement(node); SetSequence.fromSet(accessedNodes).addElement(referent); } @Override public void nodeChildReadAccess(SNode node, String role, SNode child) { SetSequence.fromSet(accessedNodes).addElement(node); SetSequence.fromSet(accessedNodes).addElement(child); } }; NodeReadEventsCaster.setNodesReadListener(listener); result[0] = action.invoke(); } finally { NodeReadEventsCaster.removeNodesReadListener(); } for (SNode accessedNode : accessedNodes) { addDependency(accessedNode); } return (Result) result[0]; } public class MyModelChangeListener extends SNodeChangeListenerAdapter { @Override public void referenceChanged(@NotNull SReferenceChangeEvent event) { processEvent(event); } @Override public void nodeAdded(@NotNull SNodeAddEvent event) { if (event.isRoot()) { return; } processEvent(event); } @Override public void nodeRemoved(@NotNull SNodeRemoveEvent event) { // XXX old listener ignored root change events (listened to childAdded/childRemoved only). // While I can understand why not rootAdded, I don't why rootRemoved was ignored - imo, the root may appear in dependencies and we shall invalidate it here. processEvent(event); } @Override public void propertyChanged(@NotNull SPropertyChangeEvent event) { processEvent(event); } } private class MyModelUnloadListener extends SModelListenerBase { @Override public void modelDetached(SModel model, SRepository repository) { if (myModel != model) { for (SNode dependencyToInvalidate : getDependenciesToInvalidate(model, repository)) { invalidateDependency(dependencyToInvalidate); } } removeModelListeners(model); SetSequence.fromSet(myListenedModels).removeElement(model); } } }