/*
* Copyright 2003-2016 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;
import gnu.trove.THashSet;
import jetbrains.mps.lang.pattern.IMatchingPattern;
import jetbrains.mps.smodel.NodeReadAccessCasterInEditor;
import jetbrains.mps.typesystem.inference.TypeChecker;
import jetbrains.mps.typesystem.inference.TypeCheckingContext;
import jetbrains.mps.typesystem.inference.util.StructuralNodeSet;
import jetbrains.mps.typesystem.inference.util.SubtypingCache;
import jetbrains.mps.typesystemEngine.util.CoerceUtil;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.util.Pair;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
public class CoercionManager {
private final TypeChecker myTypeChecker;
// TODO: why this dependency?
private final SubTypingManagerNew mySubTyping;
public CoercionManager(TypeChecker typeChecker, SubTypingManagerNew subTyping) {
myTypeChecker = typeChecker;
mySubTyping = subTyping;
}
public SNode coerceSubTypingNew(final SNode subtype, final IMatchingPattern pattern, final boolean isWeak, final TypeCheckingContext context) {
if (subtype == null) {
return null;
}
if (pattern.match(subtype)) {
return subtype;
}
if (!CoerceUtil.canBeCoerced(subtype, pattern.getConcept())) {
return null;
}
if (CoerceUtil.concept_MeetType.equals(subtype.getConcept())) {
List<SNode> children = new ArrayList<SNode>(IterableUtil.asCollection(subtype.getChildren(CoerceUtil.link_MeetType_argument)));
for (SNode child : children) {
SNode result = coerceSubTypingNew(child, pattern, isWeak, context);
if (result != null) {
return result;
}
}
return null;
}
if (CoerceUtil.concept_JoinType.equals(subtype.getConcept())) {
List<SNode> children = new ArrayList<SNode>(IterableUtil.asCollection(subtype.getChildren(CoerceUtil.link_JoinType_argument)));
SNode lcs = SubtypingUtil.createLeastCommonSupertype(children, context);
return coerceSubTypingNew(lcs, pattern, isWeak, context);
}
//asking the cache
return NodeReadAccessCasterInEditor.runReadTransparentAction(new Computable<SNode>() {
@Override
public SNode compute() {
Pair<Boolean, SNode> answer = getCoerceCacheAnswer(subtype, pattern, isWeak);
if (answer != null && answer.o1) {
return answer.o2;
}
CoercionMatcher coercionMatcher = new CoercionMatcher(pattern);
SNode result = searchInSuperTypes(subtype, coercionMatcher, isWeak);
//writing to the cache
SubtypingCache subtypingCache = myTypeChecker.getSubtypingCache();
if (subtypingCache != null) {
subtypingCache.cacheCoerce(subtype, pattern, result, isWeak);
}
return result;
}
});
}
@Nullable
Pair<Boolean, SNode> getCoerceCacheAnswer(SNode subtype, IMatchingPattern pattern, boolean isWeak) {
SubtypingCache cache = myTypeChecker.getSubtypingCache();
if (cache != null) {
Pair<Boolean, SNode> coerced = cache.getCoerced(subtype, pattern, isWeak);
if (coerced != null) {
return coerced;
}
}
return null;
}
private SNode searchInSuperTypes(SNode subType, CoercionMatcher superType, boolean isWeak) {
StructuralNodeSet<?> frontier = new StructuralNodeSet();
StructuralNodeSet<?> newFrontier = new StructuralNodeSet();
StructuralNodeSet<?> yetPassed = new StructuralNodeSet();
frontier.add(subType);
while (!frontier.isEmpty()) {
Set<SNode> yetPassedRaw = new THashSet<SNode>();
//collecting a set of frontier's ancestors
StructuralNodeSet<?> ancestors = new StructuralNodeSet();
for (SNode node : frontier) {
mySubTyping.collectImmediateSuperTypes(node, isWeak, ancestors, null);
yetPassedRaw.add(node);
}
ArrayList<SNode> ancestorsSorted;
ancestorsSorted = new ArrayList<SNode>(ancestors);
Collections.sort(ancestorsSorted, new Comparator<SNode>() {
@Override
public int compare(SNode o1, SNode o2) {
return TypesUtil.depth(o2) - TypesUtil.depth(o1);
}
});
List<SNode> results = new ArrayList<SNode>();
for (SNode ancestor : ancestorsSorted) {
if (superType.matchesWith(ancestor)) {
results.add(ancestor);
}
}
if (!results.isEmpty()) {
if (results.size() > 1) {
results = SubtypingUtil.eliminateSuperTypes(results);
}
if (!results.isEmpty()) {
return results.get(0);
}
}
for (SNode passedNodeRaw : yetPassedRaw) {
yetPassed.add(passedNodeRaw);
}
for (SNode passedNode : yetPassed) {
ancestors.removeStructurally(passedNode);
}
for (SNode ancestor : ancestors) {
Pair<Boolean, SNode> answer = getCoerceCacheAnswer(ancestor, superType.getMatchingPattern(), isWeak);
if (answer != null) {
if (answer.o1 && answer.o2 == null) {
// System.out.println("coerce optimized");
continue;
}
}
newFrontier.addStructurally(ancestor);
yetPassed.addStructurally(ancestor);
}
frontier = newFrontier;
newFrontier = new StructuralNodeSet();
}
return null;
}
static class CoercionMatcher {
private final IMatchingPattern myPattern;
public CoercionMatcher(IMatchingPattern pattern) {
myPattern = pattern;
}
public boolean matchesWith(SNode nodeToMatch) {
return myPattern.match(nodeToMatch);
}
public IMatchingPattern getMatchingPattern() {
return myPattern;
}
}
}