/*
* 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.InferenceRule_Runtime;
import jetbrains.mps.lang.typesystem.runtime.IsApplicableStatus;
import jetbrains.mps.logging.Logger;
import jetbrains.mps.newTypesystem.context.typechecking.IncrementalTypechecking;
import jetbrains.mps.newTypesystem.state.State;
import org.apache.log4j.LogManager;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.smodel.*;
import jetbrains.mps.typesystem.inference.TypeChecker;
import jetbrains.mps.util.Pair;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/*
* Non-reenterable.
*/
/*package*/ public class TypeSystemComponent extends IncrementalTypecheckingComponent<State> implements ITypeErrorComponent {
protected static final Logger LOG = Logger.wrap(LogManager.getLogger(TypeSystemComponent.class));
private Map<SNode, Set<SNode>> myNodesToDependentNodes_A;
private Map<SNode, Set<SNode>> myNodesToDependentNodes_B;
private Map<SNode, Set<Pair<String, String>>> myNodesToRules;
private Set<SNode> myNodesDependentOnCaches;
public TypeSystemComponent(TypeChecker typeChecker, State state, IncrementalTypechecking component) {
super(typeChecker, state, component);
myNodesToRules = new THashMap<SNode, Set<Pair<String, String>>>();
myNodesDependentOnCaches = new THashSet<SNode>();
myNodesToDependentNodes_A = new THashMap<SNode, Set<SNode>>();
myNodesToDependentNodes_B = new THashMap<SNode, Set<SNode>>();
}
//returns true if something was invalidated
@Override
protected boolean doInvalidate() {
if (isInvalidationWasPerformed()) {
return isInvalidationResult();
}
boolean result;
Set<SNode> invalidatedNodes_A = new THashSet<SNode>();
Set<SNode> invalidatedNodes_B = new THashSet<SNode>();
Set<SNode> newNodesToInvalidate_A = new THashSet<SNode>();
Set<SNode> currentNodesToInvalidate_A = getCurrentNodesToInvalidate();
Set<SNode> nodesToInvalidate_B = new THashSet<SNode>();
if (isCacheWasRebuilt()) {
currentNodesToInvalidate_A.addAll(myNodesDependentOnCaches);
}
//A means invalidated and type will be recalculated, B means invalidated but type not affected. A => B then.
boolean initial = true;
while (!currentNodesToInvalidate_A.isEmpty()) {
for (SNode nodeToInvalidate : currentNodesToInvalidate_A) {
if (invalidatedNodes_A.contains(nodeToInvalidate)) continue;
boolean recalc = nodeToInvalidate.getModel() != null;
invalidateNodeTypeSystem(nodeToInvalidate, recalc);
invalidatedNodes_A.add(nodeToInvalidate);
Set<SNode> nodes = myNodesToDependentNodes_A.get(nodeToInvalidate);
if (nodes != null) {
newNodesToInvalidate_A.addAll(nodes);
}
// only actually changed nodes affect the to be invalidated "B"
nodes = myNodesToDependentNodes_B.get(nodeToInvalidate);
if (nodes != null) {
nodesToInvalidate_B.addAll(nodes);
}
}
currentNodesToInvalidate_A = newNodesToInvalidate_A;
newNodesToInvalidate_A = new THashSet<SNode>();
}
for (SNode nodeToInvalidate : nodesToInvalidate_B) {
if (invalidatedNodes_A.contains(nodeToInvalidate)) continue;
if (invalidatedNodes_B.contains(nodeToInvalidate)) continue;
invalidateNodeTypeSystem(nodeToInvalidate, false);
invalidatedNodes_B.add(nodeToInvalidate);
}
result = !invalidatedNodes_A.isEmpty() || !invalidatedNodes_B.isEmpty();
clearNodeTypes();
setInvalidationResult(result);
return result;
}
@Override
protected void computeTypes(SNode nodeToCheck, boolean refreshTypes, boolean forceChildrenCheck, Collection<SNode> additionalNodes, boolean finalExpansion, SNode initialNode) {
try {
super.computeTypes(nodeToCheck, refreshTypes, forceChildrenCheck, additionalNodes, finalExpansion, initialNode);
} finally {
setInvalidationWasPerformed(false);
}
}
@Override
protected void invalidateNodeTypeSystem(SNode node, boolean typeWillBeRecalculated) {
super.invalidateNodeTypeSystem(node, typeWillBeRecalculated);
if (typeWillBeRecalculated) {
TypeChecker.getInstance().fireTypeWillBeRecalculatedForTerm(node);
}
myNodesToRules.remove(node);
}
@Override
public Map<SNode, List<IErrorReporter>> getNodesToErrorsMap() {
return getState().getNodeMaps().getNodesToErrors();
}
@Override
public void clear() {
super.clear();
clearCaches();
clearState();
clearNodeTypes();
}
@Override
protected void setTargetNode(SNode initialNode) {
// do nothing
}
public void clearCaches() {
if (myNodesToDependentNodes_A!= null) {
myNodesToDependentNodes_A.clear();
myNodesToDependentNodes_B.clear();
myNodesDependentOnCaches.clear();
myNodesToRules.clear();
}
myFullyCheckedNodes.clear();
myPartlyCheckedNodes.clear();
}
@Override
public SNode computeTypesForNode_special(SNode initialNode, Collection<SNode> givenAdditionalNodes) {
return computeTypesForNode_special_(initialNode, givenAdditionalNodes);
}
public void markNodeAsAffectedByRule(SNode node, String ruleModel, String ruleId) {
Set<Pair<String, String>> rulesWhichAffectNodesType = myNodesToRules.get(node);
if (rulesWhichAffectNodesType == null) {
rulesWhichAffectNodesType = new THashSet<Pair<String, String>>(1);
myNodesToRules.put(node, rulesWhichAffectNodesType);
}
rulesWhichAffectNodesType.add(new Pair<String, String>(ruleModel, ruleId));
}
public Set<Pair<String, String>> getRulesWhichAffectNodeType(SNode node) {
Set<Pair<String, String>> set = myNodesToRules.get(node);
if (set == null) return null;
return new THashSet<Pair<String, String>>(set);
}
@Override
protected MyAccessTracking createAccessTracking() {
return new MyAccessTracking();
}
@Override
protected IncrementalTypechecking getTypechecking() {
return (IncrementalTypechecking) super.getTypechecking();
}
//"type affected" means that *type* of this node depends on this set
// used to decide whether call "type will be recalculated" if node from set invalidated
public void addDependentNodesTypeSystem(@NotNull SNode sNode, Set<SNode> nodesToDependOn, boolean typesAffected) {
Map<SNode, Set<SNode>> dependencies = typesAffected ? myNodesToDependentNodes_A : myNodesToDependentNodes_B;
for (SNode nodeToDependOn : nodesToDependOn) {
if (nodeToDependOn == null) continue;
if (sNode == nodeToDependOn) continue;
Set<SNode> dependentNodes = dependencies.get(nodeToDependOn);
if (dependentNodes == null) {
dependentNodes = new THashSet<SNode>(1);
dependencies.put(nodeToDependOn, dependentNodes);
getTypechecking().track(nodeToDependOn);
}
dependentNodes.add(sNode);
}
}
@Override
public void addError(SNode node, IErrorReporter reporter) {
getState().addError(node, reporter, null);
}
@Override
protected void performActionsAfterChecking() {
getTypechecking().updateGCedNodes();
TypeChecker.getInstance().addTypeRecalculatedListener(getTypechecking().getTypeRecalculatedListener());//method checks if already exists
}
@Override
protected boolean applyRulesToNode(final SNode node) {
final List<Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>>> nodesAndRules = new ArrayList<Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>>>();
if (!collectNodesAndRules(node, nodesAndRules)) return false;
return getTypechecking().runApplyRulesTo(node, new Runnable() {
@Override
public void run() {
for (Pair<SNode, List<Pair<InferenceRule_Runtime, IsApplicableStatus>>> pair : nodesAndRules) {
applyRulesToNode(pair.o1, pair.o2);
}
}
});
}
@Override
public void addNodeToFrontier(SNode node) {
if (node == null || myPartlyCheckedNodes.contains(node)) return;
super.addNodeToFrontier(node);
}
private void addCacheDependentNodesTypesystem(SNode node) {
myNodesDependentOnCaches.add(node);
}
@Override
protected boolean isIncrementalMode() {
return getState().getTypeCheckingContext().isIncrementalMode();
}
private class MyAccessTracking extends AccessTracking {
private MyEventsReadListener nodesReadListener;
public MyAccessTracking() {
this.nodesReadListener = isIncrementalMode() ? new MyEventsReadListener() : null;
}
@Override
protected void installReadListeners() {
if (isIncrementalMode()) {
nodesReadListener.clear();
NodeReadEventsCaster.setNodesReadListener(nodesReadListener);
}
}
@Override
protected void removeReadListeners() {
if (isIncrementalMode()) {
NodeReadEventsCaster.removeNodesReadListener();
}
}
@Override
protected void postProcess(SNode sNode, boolean typeAffected) {
if (isIncrementalMode()) {
nodesReadListener.setAccessReport(true);
Set<SNode> accessedNodes = nodesReadListener.getAccessedNodes();
addDependentNodesTypeSystem(sNode, accessedNodes, typeAffected);
nodesReadListener.setAccessReport(false);
nodesReadListener.clear();
}
}
}
}