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);
}
}
}