/*
* Copyright 2003-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.newTypesystem.context.component;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import jetbrains.mps.errors.IErrorReporter;
import jetbrains.mps.lang.typesystem.runtime.IsApplicableStatus;
import jetbrains.mps.lang.typesystem.runtime.NonTypesystemRule_Runtime;
import jetbrains.mps.newTypesystem.context.typechecking.IncrementalTypechecking;
import jetbrains.mps.languageScope.LanguageScopeExecutor;
import jetbrains.mps.newTypesystem.state.State;
import jetbrains.mps.smodel.NodeReadEventsCaster;
import jetbrains.mps.typesystemEngine.util.TypeSystemUtil;
import jetbrains.mps.util.Cancellable;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.IterableUtil;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.typesystem.inference.TypeChecker;
import jetbrains.mps.typesystem.inference.TypeCheckingContext;
import jetbrains.mps.typesystem.inference.TypesReadListener;
import jetbrains.mps.util.Pair;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
public class NonTypeSystemComponent extends IncrementalTypecheckingComponent<State> implements ITypeErrorComponent {
private ConcurrentLinkedQueue<Pair<SNode, String>> myCurrentPropertiesToInvalidate = new ConcurrentLinkedQueue<>();
private ConcurrentLinkedQueue<SNode> myCurrentTypedTermsToInvalidate = new ConcurrentLinkedQueue<>();
private Set<Pair<SNode, NonTypesystemRule_Runtime>> myCheckedNodes
= new HashSet<Pair<SNode, NonTypesystemRule_Runtime>>(); // nodes which are checked themselves but not children
private Map<SNode, List<IErrorReporter>> myNodesToErrorsMap = new HashMap<SNode, List<IErrorReporter>>();
// nodes to rules which depend on this nodes
private Map<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>> myNodesToDependentNodesWithNTRules =
new THashMap<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>>();
// properties to rules which depend on this nodes' properties
private Map<Pair<SNode, String>, Map<NonTypesystemRule_Runtime, Set<SNode>>> myPropertiesToDependentNodesWithNTRules =
new THashMap<Pair<SNode, String>, Map<NonTypesystemRule_Runtime, Set<SNode>>>();
// typed terms to rules which depend on this nodes
private Map<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>> myTypedTermsToDependentNodesWithNTRules =
new THashMap<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>>();
private Map<SNode, Set<NonTypesystemRule_Runtime>> myNodesDependentOnCachesWithNTRules =
new THashMap<SNode, Set<NonTypesystemRule_Runtime>>();
//checked node & NT rule -> set of errors
private Map<SNode, Map<NonTypesystemRule_Runtime, Set<IErrorReporter>>> myNodesAndNTRulesToErrors =
new THashMap<SNode, Map<NonTypesystemRule_Runtime, Set<IErrorReporter>>>();
private Pair<SNode, NonTypesystemRule_Runtime> myRuleAndNodeBeingChecked = null;
public NonTypeSystemComponent(TypeChecker typeChecker, State state, IncrementalTypechecking nodeTypesComponent) {
super(typeChecker, state, nodeTypesComponent);
}
@Override
public void clear() {
super.clear();
clearCaches();
}
@Override
public void clearNodeTypes() {
super.clearNodeTypes();
clearAllExceptErrors();
myNodesToErrorsMap.clear();
}
private void clearAllExceptErrors() {
super.clearNodeTypes();
myCurrentPropertiesToInvalidate.clear();
myCurrentTypedTermsToInvalidate.clear();
}
void clearCaches() {
myCheckedNodes.clear();
myNodesAndNTRulesToErrors.clear();
myNodesToDependentNodesWithNTRules.clear();
myPropertiesToDependentNodesWithNTRules.clear();
myTypedTermsToDependentNodesWithNTRules.clear();
myNodesDependentOnCachesWithNTRules.clear();
myNodesToErrorsMap.clear();
}
@Override
public Map<SNode, List<IErrorReporter>> getNodesToErrorsMap() {
return Collections.unmodifiableMap(myNodesToErrorsMap);
}
@Override
protected IncrementalTypechecking getTypechecking() {
return (IncrementalTypechecking) super.getTypechecking();
}
private void doInvalidate(Map<NonTypesystemRule_Runtime, Set<SNode>> nodesAndRules, Set<Pair<SNode, NonTypesystemRule_Runtime>> invalidatedNodesAndRules) {
if (nodesAndRules != null) {
for (NonTypesystemRule_Runtime ruleOfNode : nodesAndRules.keySet()) {
Set<SNode> nodes = nodesAndRules.get(ruleOfNode);
if (nodes != null) {
for (SNode depNode : nodes) {
invalidatedNodesAndRules.add(new Pair<SNode, NonTypesystemRule_Runtime>(depNode, ruleOfNode));
}
}
}
}
}
//returns true if something was invalidated
@Override
protected boolean doInvalidate() {
if (isInvalidationWasPerformed()) {
return isInvalidationResult();
}
Set<Pair<SNode, NonTypesystemRule_Runtime>> invalidatedNodesAndRules = new THashSet<Pair<SNode, NonTypesystemRule_Runtime>>(1);
//nodes
for (SNode node : getCurrentNodesToInvalidate()) {
doInvalidate(myNodesToDependentNodesWithNTRules.get(node), invalidatedNodesAndRules);
}
//properties
Pair<SNode, String> nextPair;
while ((nextPair = myCurrentPropertiesToInvalidate.poll()) != null) {
doInvalidate(myPropertiesToDependentNodesWithNTRules.get(nextPair), invalidatedNodesAndRules);
}
//typed terms
SNode nextNode;
while((nextNode = myCurrentTypedTermsToInvalidate.poll()) != null) {
doInvalidate(myTypedTermsToDependentNodesWithNTRules.get(nextNode), invalidatedNodesAndRules);
doInvalidate(myNodesToDependentNodesWithNTRules.get(nextNode), invalidatedNodesAndRules);
}
//cache-dependent
if (isCacheWasRebuilt()) {
for (SNode nodeOfRule : myNodesDependentOnCachesWithNTRules.keySet()) {
Set<NonTypesystemRule_Runtime> rules = myNodesDependentOnCachesWithNTRules.get(nodeOfRule);
if (rules != null) {
for (NonTypesystemRule_Runtime rule : rules) {
invalidatedNodesAndRules.add(new Pair<SNode, NonTypesystemRule_Runtime>(nodeOfRule, rule));
}
}
}
}
boolean result = !invalidatedNodesAndRules.isEmpty();
for (Pair<SNode, NonTypesystemRule_Runtime> nodeAndRule : invalidatedNodesAndRules) {
myCheckedNodes.remove(nodeAndRule);
Map<NonTypesystemRule_Runtime, Set<IErrorReporter>> rulesAndErrors = myNodesAndNTRulesToErrors.get(nodeAndRule.o1);
if (rulesAndErrors != null) {
Set<IErrorReporter> errors = rulesAndErrors.get(nodeAndRule.o2);
if (errors != null) {
for (IErrorReporter errorReporter : new HashSet<IErrorReporter>(errors)) {
List<IErrorReporter> iErrorReporters = myNodesToErrorsMap.get(errorReporter.getSNode());
if (iErrorReporters != null) {
iErrorReporters.remove(errorReporter);
errors.remove(errorReporter);
}
}
}
}
}
clearAllExceptErrors();
setInvalidationResult(result);
return result;
}
public void addPropertyToInvalidate(SNode eventNode, String propertyName) {
myCurrentPropertiesToInvalidate.add(new Pair<SNode, String>(eventNode, propertyName));
setInvalidationWasPerformed(false);
}
public void typeWillBeRecalculatedForTerm(SNode term) {
myCurrentTypedTermsToInvalidate.add(term);
setInvalidationWasPerformed(false);
}
@Override
public void addError(SNode node, IErrorReporter errorReporter) {
Map<SNode, List<IErrorReporter>> errorMap = myNodesToErrorsMap;
List<IErrorReporter> iErrorReporters = errorMap.get(node);
if (iErrorReporters == null) {
iErrorReporters = new ArrayList<IErrorReporter>(1);
errorMap.put(node, iErrorReporters);
}
iErrorReporters.add(errorReporter);
Collections.sort(iErrorReporters, new Comparator<IErrorReporter>() {
@Override
public int compare(IErrorReporter o1, IErrorReporter o2) {
return o1.getMessageStatus().compareTo(o2.getMessageStatus());
}
});
//dependencies
if (myRuleAndNodeBeingChecked != null) {
SNode currentNode = myRuleAndNodeBeingChecked.o1;
NonTypesystemRule_Runtime currentRule = myRuleAndNodeBeingChecked.o2;
Map<NonTypesystemRule_Runtime, Set<IErrorReporter>> rulesToErrorsMap = myNodesAndNTRulesToErrors.get(currentNode);
if (rulesToErrorsMap == null) {
rulesToErrorsMap = new THashMap<NonTypesystemRule_Runtime, Set<IErrorReporter>>(1);
myNodesAndNTRulesToErrors.put(currentNode, rulesToErrorsMap);
}
Set<IErrorReporter> errorsSet = rulesToErrorsMap.get(currentRule);
if (errorsSet == null) {
errorsSet = new THashSet<IErrorReporter>(1);
rulesToErrorsMap.put(currentRule, errorsSet);
}
errorsSet.add(errorReporter);
// make sure the error is cleaned on the node deletion
addDependentNodes(currentNode, currentRule, Collections.singleton(node));
}
}
private void addDependentTypeTerms(SNode sNode, NonTypesystemRule_Runtime rule, Set<SNode> typesToDependOn) {
addDependentNodes(sNode, rule, typesToDependOn, true);
}
private void addDependentProperties(SNode sNode, NonTypesystemRule_Runtime rule, Set<Pair<SNode, String>> propertiesToDependOn) {
Map<Pair<SNode, String>, Map<NonTypesystemRule_Runtime, Set<SNode>>> mapToNodesWithNTRules
= myPropertiesToDependentNodesWithNTRules;
for (Pair<SNode, String> propertyToDependOn : propertiesToDependOn) {
if (propertyToDependOn == null) continue;
Map<NonTypesystemRule_Runtime, Set<SNode>> dependentNodes = mapToNodesWithNTRules.get(propertyToDependOn);
if (dependentNodes == null) {
dependentNodes = new THashMap<NonTypesystemRule_Runtime, Set<SNode>>(1);
mapToNodesWithNTRules.put(propertyToDependOn, dependentNodes);
}
Set<SNode> nodes = dependentNodes.get(rule);
if (nodes == null) {
nodes = new THashSet<SNode>(1);
dependentNodes.put(rule, nodes);
}
nodes.add(sNode);
}
}
private void addDependentNodes(SNode sNode, NonTypesystemRule_Runtime rule, Set<SNode> nodesToDependOn, boolean isTypedTerm) {
Map<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>> mapToNodesWithNTRules =
isTypedTerm ? myTypedTermsToDependentNodesWithNTRules : myNodesToDependentNodesWithNTRules;
for (SNode nodeToDependOn : nodesToDependOn) {
if (nodeToDependOn == null) continue;
Map<NonTypesystemRule_Runtime, Set<SNode>> dependentNodes = mapToNodesWithNTRules.get(nodeToDependOn);
if (dependentNodes == null) {
dependentNodes = new THashMap<NonTypesystemRule_Runtime, Set<SNode>>(1);
mapToNodesWithNTRules.put(nodeToDependOn, dependentNodes);
}
Set<SNode> nodes = dependentNodes.get(rule);
if (nodes == null) {
nodes = new THashSet<SNode>(1);
dependentNodes.put(rule, nodes);
}
nodes.add(sNode);
}
}
private void addCacheDependentNodesNonTypesystem(SNode node, NonTypesystemRule_Runtime rule) {
Map<SNode, Set<NonTypesystemRule_Runtime>> dependentNodes = myNodesDependentOnCachesWithNTRules;
Set<NonTypesystemRule_Runtime> rules = dependentNodes.get(node);
if (rules == null) {
rules = new THashSet<NonTypesystemRule_Runtime>(1);
dependentNodes.put(node, rules);
}
rules.add(rule);
}
private void addDependentNodes(SNode sNode, NonTypesystemRule_Runtime rule, Set<SNode> nodesToDependOn) {
addDependentNodes(sNode, rule, nodesToDependOn, false);
}
// true iff was fully executed (not cancelled)
public boolean applyNonTypeSystemRulesToRoot(final TypeCheckingContext typeCheckingContext, final SNode rootNode, final Cancellable c) {
if (rootNode == null) return false;
return LanguageScopeExecutor.execWithModelScope(rootNode.getModel(), new Computable<Boolean>() {
@Override
public Boolean compute() {
return applyRulesToRoot(typeCheckingContext, rootNode, c);
}
});
}
// true iff fully executed
private boolean applyRulesToRoot(TypeCheckingContext typeCheckingContext, SNode rootNode, Cancellable c) {
doInvalidate();
try {
Queue<SNode> frontier = new LinkedList<SNode>();
frontier.add(rootNode);
while (!(frontier.isEmpty())) {
if (c.isCancelled()) return false;
SNode sNode = frontier.remove();
if (!TypeSystemUtil.shouldApplyTypeSystemRules(sNode)) {
continue;
}
applyNonTypesystemRulesToNode(sNode, typeCheckingContext);
frontier.addAll(IterableUtil.asCollection(sNode.getChildren()));
}
//all error reporters must be simple reporters, no error expansion needed
} finally {
setInvalidationWasPerformed(false);
}
return true;
}
private void applyNonTypesystemRulesToNode(final SNode node, final TypeCheckingContext typeCheckingContext) {
getTypechecking().runApplyRulesTo(node, new Runnable() {
@Override
public void run() {
List<Pair<NonTypesystemRule_Runtime, IsApplicableStatus>> nonTypesystemRules = TypeChecker.getInstance().getRulesManager().getNonTypesystemRules(node);
MyEventsReadListener nodesReadListener = new MyEventsReadListener();
if (nonTypesystemRules == null) return;
boolean incrementalMode = isIncrementalMode();
for (Pair<NonTypesystemRule_Runtime, IsApplicableStatus> rule : nonTypesystemRules) {
Pair<SNode, NonTypesystemRule_Runtime> nodeAndRule = new Pair<SNode, NonTypesystemRule_Runtime>(node, rule.o1);
MyTypesReadListener typesReadListener = new MyTypesReadListener();
if (incrementalMode) {
if (myCheckedNodes.contains(nodeAndRule)) continue;
nodesReadListener.clear();
NodeReadEventsCaster.setNodesReadListener(nodesReadListener);
TypeChecker.getInstance().addTypesReadListener(typesReadListener);
myRuleAndNodeBeingChecked = new Pair<SNode, NonTypesystemRule_Runtime>(node, rule.o1);
}
try {
getTypechecking().applyRuleToNode(node, rule.o1, rule.o2, typeCheckingContext);
} finally {
myRuleAndNodeBeingChecked = null;
if (incrementalMode) {
TypeChecker.getInstance().removeTypesReadListener(typesReadListener);
NodeReadEventsCaster.removeNodesReadListener();
}
}
if (incrementalMode) {
nodesReadListener.setAccessReport(true);
addDependentNodes(node, rule.o1, new THashSet<SNode>(nodesReadListener.getAccessedNodes()));
addDependentNodes(node, rule.o1, Collections.singleton(node));
addDependentProperties(node, rule.o1, new THashSet<Pair<SNode, String>>(nodesReadListener.getAccessedProperties()));
nodesReadListener.setAccessReport(false);
typesReadListener.setAccessReport(true);
addDependentTypeTerms(node, rule.o1, typesReadListener.getAccessedNodes());
typesReadListener.setAccessReport(false);
nodesReadListener.clear();
}
myCheckedNodes.add(nodeAndRule);
}
}
});
}
@Override
protected boolean isIncrementalMode() {
final boolean incrementalMode = getState().getTypeCheckingContext().isIncrementalMode();
return incrementalMode; // alright, alright
}
private static class MyTypesReadListener implements TypesReadListener {
private Set<SNode> myAccessedNodes = new THashSet<SNode>(1);
private boolean myIsSetAccessReport = false;
public void setAccessReport(boolean accessReport) {
myIsSetAccessReport = accessReport;
}
@Override
public void nodeTypeAccessed(SNode term) {
if (myIsSetAccessReport) {
new Throwable().printStackTrace();
}
myAccessedNodes.add(term);
}
public Set<SNode> getAccessedNodes() {
return myAccessedNodes;
}
}
}