blob: a58e0561c44933374e10d7035a2809f09613a89e [file] [log] [blame]
/*
* Copyright 2000-2014 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 com.intellij.refactoring.typeCook.deductive.resolver;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.typeCook.Settings;
import com.intellij.refactoring.typeCook.Util;
import com.intellij.refactoring.typeCook.deductive.PsiExtendedTypeVisitor;
import com.intellij.refactoring.typeCook.deductive.builder.Constraint;
import com.intellij.refactoring.typeCook.deductive.builder.ReductionSystem;
import com.intellij.refactoring.typeCook.deductive.builder.Subtype;
import com.intellij.util.containers.EmptyIterator;
import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.graph.Graph;
import gnu.trove.TIntArrayList;
import gnu.trove.TIntProcedure;
import gnu.trove.TObjectIntHashMap;
import java.util.*;
/**
* @author db
*/
public class ResolverTree {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.typeCook.deductive.resolver.ResolverTree");
private ResolverTree[] mySons = new ResolverTree[0];
private final BindingFactory myBindingFactory;
private Binding myCurrentBinding;
private final SolutionHolder mySolutions;
private final Project myProject;
private final TObjectIntHashMap<PsiTypeVariable> myBindingDegree; //How many times this type variable is bound in the system
private final Settings mySettings;
private boolean mySolutionFound = false;
private Set<Constraint> myConstraints;
public ResolverTree(final ReductionSystem system) {
myBindingFactory = new BindingFactory(system);
mySolutions = new SolutionHolder();
myCurrentBinding = myBindingFactory.create();
myConstraints = system.getConstraints();
myProject = system.getProject();
myBindingDegree = calculateDegree();
mySettings = system.getSettings();
reduceCyclicVariables();
}
private ResolverTree(final ResolverTree parent, final Set<Constraint> constraints, final Binding binding) {
myBindingFactory = parent.myBindingFactory;
myCurrentBinding = binding;
mySolutions = parent.mySolutions;
myConstraints = constraints;
myProject = parent.myProject;
myBindingDegree = calculateDegree();
mySettings = parent.mySettings;
}
private static class PsiTypeVarCollector extends PsiExtendedTypeVisitor {
final Set<PsiTypeVariable> mySet = new HashSet<PsiTypeVariable>();
@Override
public Object visitTypeVariable(final PsiTypeVariable var) {
mySet.add(var);
return null;
}
public Set<PsiTypeVariable> getSet(final PsiType type) {
type.accept(this);
return mySet;
}
}
private boolean isBoundElseWhere(final PsiTypeVariable var) {
return myBindingDegree.get(var) != 1;
}
private boolean canBePruned(final Binding b) {
if (mySettings.exhaustive()) return false;
for (final PsiTypeVariable var : b.getBoundVariables()) {
final PsiType type = b.apply(var);
if (!(type instanceof PsiTypeVariable) && isBoundElseWhere(var)) {
return false;
}
}
return true;
}
private TObjectIntHashMap<PsiTypeVariable> calculateDegree() {
final TObjectIntHashMap<PsiTypeVariable> result = new TObjectIntHashMap<PsiTypeVariable>();
for (final Constraint constr : myConstraints) {
final PsiTypeVarCollector collector = new PsiTypeVarCollector();
setDegree(collector.getSet(constr.getRight()), result);
}
return result;
}
private void setDegree(final Set<PsiTypeVariable> set, TObjectIntHashMap<PsiTypeVariable> result) {
for (final PsiTypeVariable var : set) {
result.increment(var);
}
}
private Set<Constraint> apply(final Binding b) {
final Set<Constraint> result = new HashSet<Constraint>();
for (final Constraint constr : myConstraints) {
result.add(constr.apply(b));
}
return result;
}
private Set<Constraint> apply(final Binding b, final Set<Constraint> additional) {
final Set<Constraint> result = new HashSet<Constraint>();
for (final Constraint constr : myConstraints) {
result.add(constr.apply(b));
}
for (final Constraint constr : additional) {
result.add(constr.apply(b));
}
return result;
}
private ResolverTree applyRule(final Binding b) {
final Binding newBinding = b != null ? myCurrentBinding.compose(b) : null;
return newBinding == null ? null : new ResolverTree(this, apply(b), newBinding);
}
private ResolverTree applyRule(final Binding b, final Set<Constraint> additional) {
final Binding newBinding = b != null ? myCurrentBinding.compose(b) : null;
return newBinding == null ? null : new ResolverTree(this, apply(b, additional), newBinding);
}
private void reduceCyclicVariables() {
final Set<PsiTypeVariable> nodes = new HashSet<PsiTypeVariable>();
final Set<Constraint> candidates = new HashSet<Constraint>();
final Map<PsiTypeVariable, Set<PsiTypeVariable>> ins = new HashMap<PsiTypeVariable, Set<PsiTypeVariable>>();
final Map<PsiTypeVariable, Set<PsiTypeVariable>> outs = new HashMap<PsiTypeVariable, Set<PsiTypeVariable>>();
for (final Constraint constraint : myConstraints) {
final PsiType left = constraint.getLeft();
final PsiType right = constraint.getRight();
if (left instanceof PsiTypeVariable && right instanceof PsiTypeVariable) {
final PsiTypeVariable leftVar = (PsiTypeVariable)left;
final PsiTypeVariable rightVar = (PsiTypeVariable)right;
candidates.add(constraint);
nodes.add(leftVar);
nodes.add(rightVar);
Set<PsiTypeVariable> in = ins.get(leftVar);
Set<PsiTypeVariable> out = outs.get(rightVar);
if (in == null) {
final Set<PsiTypeVariable> newIn = new HashSet<PsiTypeVariable>();
newIn.add(rightVar);
ins.put(leftVar, newIn);
}
else {
in.add(rightVar);
}
if (out == null) {
final Set<PsiTypeVariable> newOut = new HashSet<PsiTypeVariable>();
newOut.add(leftVar);
outs.put(rightVar, newOut);
}
else {
out.add(leftVar);
}
}
}
final DFSTBuilder<PsiTypeVariable> dfstBuilder = new DFSTBuilder<PsiTypeVariable>(new Graph<PsiTypeVariable>() {
@Override
public Collection<PsiTypeVariable> getNodes() {
return nodes;
}
@Override
public Iterator<PsiTypeVariable> getIn(final PsiTypeVariable n) {
final Set<PsiTypeVariable> in = ins.get(n);
if (in == null) {
return EmptyIterator.getInstance();
}
return in.iterator();
}
@Override
public Iterator<PsiTypeVariable> getOut(final PsiTypeVariable n) {
final Set<PsiTypeVariable> out = outs.get(n);
if (out == null) {
return EmptyIterator.getInstance();
}
return out.iterator();
}
});
final TIntArrayList sccs = dfstBuilder.getSCCs();
final Map<PsiTypeVariable, Integer> index = new HashMap<PsiTypeVariable, Integer>();
sccs.forEach(new TIntProcedure() {
int myTNumber = 0;
@Override
public boolean execute(int size) {
for (int j = 0; j < size; j++) {
index.put(dfstBuilder.getNodeByTNumber(myTNumber + j), myTNumber);
}
myTNumber += size;
return true;
}
});
for (final Constraint constraint : candidates) {
if (index.get(constraint.getLeft()).equals(index.get(constraint.getRight()))) {
myConstraints.remove(constraint);
}
}
Binding binding = myBindingFactory.create();
for (final PsiTypeVariable fromVar : index.keySet()) {
final PsiTypeVariable toVar = dfstBuilder.getNodeByNNumber(index.get(fromVar).intValue());
if (!fromVar.equals(toVar)) {
binding = binding.compose(myBindingFactory.create(fromVar, toVar));
if (binding == null) {
break;
}
}
}
if (binding != null && binding.nonEmpty()) {
myCurrentBinding = myCurrentBinding.compose(binding);
myConstraints = apply(binding);
}
}
private void reduceTypeType(final Constraint constr) {
final PsiType left = constr.getLeft();
final PsiType right = constr.getRight();
final Set<Constraint> addendumRise = new HashSet<Constraint>();
final Set<Constraint> addendumSink = new HashSet<Constraint>();
final Set<Constraint> addendumWcrd = new HashSet<Constraint>();
int numSons = 0;
Binding riseBinding = myBindingFactory.rise(left, right, addendumRise);
if (riseBinding != null) numSons++;
Binding sinkBinding = myBindingFactory.sink(left, right, addendumSink);
if (sinkBinding != null) numSons++;
Binding wcrdBinding = mySettings.cookToWildcards() ? myBindingFactory.riseWithWildcard(left, right, addendumWcrd) : null;
if (wcrdBinding != null) numSons++;
Binding omitBinding = null;
if (mySettings.exhaustive()) {
final PsiClassType.ClassResolveResult rightResult = Util.resolveType(right);
final PsiClassType.ClassResolveResult leftResult = Util.resolveType(left);
final PsiClass rightClass = rightResult.getElement();
final PsiClass leftClass = leftResult.getElement();
if (rightClass != null && leftClass != null && rightClass.getManager().areElementsEquivalent(rightClass, leftClass)) {
if (PsiUtil.typeParametersIterator(rightClass).hasNext()) {
omitBinding = myBindingFactory.create();
numSons++;
for (PsiType type : rightResult.getSubstitutor().getSubstitutionMap().values()) {
if (! (type instanceof Bottom)) {
numSons--;
omitBinding = null;
break;
}
}
}
}
}
if (numSons == 0) return;
if ((riseBinding != null && sinkBinding != null && riseBinding.equals(sinkBinding)) || canBePruned(riseBinding)) {
numSons--;
sinkBinding = null;
}
if (riseBinding != null && wcrdBinding != null && riseBinding.equals(wcrdBinding)) {
numSons--;
wcrdBinding = null;
}
myConstraints.remove(constr);
mySons = new ResolverTree[numSons];
int n = 0;
if (riseBinding != null) {
mySons[n++] = applyRule(riseBinding, addendumRise);
}
if (wcrdBinding != null) {
mySons[n++] = applyRule(wcrdBinding, addendumWcrd);
}
if (omitBinding != null) {
mySons[n++] = applyRule(omitBinding, addendumWcrd);
}
if (sinkBinding != null) {
mySons[n++] = applyRule(sinkBinding, addendumSink);
}
}
private void fillTypeRange(final PsiType lowerBound,
final PsiType upperBound,
final Set<PsiType> holder) {
if (lowerBound instanceof PsiClassType && upperBound instanceof PsiClassType) {
final PsiClassType.ClassResolveResult resultLower = ((PsiClassType)lowerBound).resolveGenerics();
final PsiClassType.ClassResolveResult resultUpper = ((PsiClassType)upperBound).resolveGenerics();
final PsiClass lowerClass = resultLower.getElement();
final PsiClass upperClass = resultUpper.getElement();
if (lowerClass != null && upperClass != null && !lowerClass.equals(upperClass)) {
final PsiSubstitutor upperSubst = resultUpper.getSubstitutor();
final PsiClass[] parents = upperClass.getSupers();
final PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
for (final PsiClass parent : parents) {
final PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(parent, upperClass, upperSubst);
if (superSubstitutor != null) {
final PsiClassType type = factory.createType(parent, superSubstitutor);
holder.add(type);
fillTypeRange(lowerBound, type, holder);
}
}
}
}
else if (lowerBound instanceof PsiArrayType && upperBound instanceof PsiArrayType) {
fillTypeRange(((PsiArrayType)lowerBound).getComponentType(), ((PsiArrayType)upperBound).getComponentType(), holder);
}
}
private PsiType[] getTypeRange(final PsiType lowerBound, final PsiType upperBound) {
Set<PsiType> range = new HashSet<PsiType>();
range.add(lowerBound);
range.add(upperBound);
fillTypeRange(lowerBound, upperBound, range);
return range.toArray(PsiType.createArray(range.size()));
}
private void reduceInterval(final Constraint left, final Constraint right) {
final PsiType leftType = left.getLeft();
final PsiType rightType = right.getRight();
final PsiTypeVariable var = (PsiTypeVariable)left.getRight();
if (leftType.equals(rightType)) {
final Binding binding = myBindingFactory.create(var, leftType);
myConstraints.remove(left);
myConstraints.remove(right);
mySons = new ResolverTree[]{applyRule(binding)};
return;
}
Binding riseBinding = myBindingFactory.rise(leftType, rightType, null);
Binding sinkBinding = myBindingFactory.sink(leftType, rightType, null);
int indicator = (riseBinding == null ? 0 : 1) + (sinkBinding == null ? 0 : 1);
if (indicator == 0) {
return;
}
else if ((indicator == 2 && riseBinding.equals(sinkBinding)) || canBePruned(riseBinding)) {
indicator = 1;
sinkBinding = null;
}
PsiType[] riseRange = PsiType.EMPTY_ARRAY;
PsiType[] sinkRange = PsiType.EMPTY_ARRAY;
if (riseBinding != null) {
riseRange = getTypeRange(riseBinding.apply(rightType), riseBinding.apply(leftType));
}
if (sinkBinding != null) {
sinkRange = getTypeRange(sinkBinding.apply(rightType), sinkBinding.apply(leftType));
}
if (riseRange.length + sinkRange.length > 0) {
myConstraints.remove(left);
myConstraints.remove(right);
}
mySons = new ResolverTree[riseRange.length + sinkRange.length];
for (int i = 0; i < riseRange.length; i++) {
final PsiType type = riseRange[i];
mySons[i] = applyRule(riseBinding.compose(myBindingFactory.create(var, type)));
}
for (int i = 0; i < sinkRange.length; i++) {
final PsiType type = sinkRange[i];
mySons[i + riseRange.length] = applyRule(sinkBinding.compose(myBindingFactory.create(var, type)));
}
}
private void reduce() {
if (myConstraints.isEmpty()) {
return;
}
if (myCurrentBinding.isCyclic()) {
reduceCyclicVariables();
}
final Map<PsiTypeVariable, Constraint> myTypeVarConstraints = new HashMap<PsiTypeVariable, Constraint>();
final Map<PsiTypeVariable, Constraint> myVarTypeConstraints = new HashMap<PsiTypeVariable, Constraint>();
for (final Constraint constr : myConstraints) {
final PsiType left = constr.getLeft();
final PsiType right = constr.getRight();
switch ((left instanceof PsiTypeVariable ? 0 : 1) + (right instanceof PsiTypeVariable ? 0 : 2)) {
case 0:
continue;
case 1:
{
final Constraint c = myTypeVarConstraints.get(right);
if (c == null) {
final Constraint d = myVarTypeConstraints.get(right);
if (d != null) {
reduceInterval(constr, d);
return;
}
myTypeVarConstraints.put((PsiTypeVariable)right, constr);
}
else {
reduceTypeVar(constr, c);
return;
}
}
break;
case 2:
{
final Constraint c = myVarTypeConstraints.get(left);
if (c == null) {
final Constraint d = myTypeVarConstraints.get(left);
if (d != null) {
reduceInterval(d, constr);
return;
}
myVarTypeConstraints.put((PsiTypeVariable)left, constr);
}
else {
reduceVarType(constr, c);
return;
}
break;
}
case 3:
reduceTypeType(constr);
return;
}
}
//T1 < a < b ... < T2
{
for (final Constraint constr : myConstraints) {
final PsiType left = constr.getLeft();
final PsiType right = constr.getRight();
if (!(left instanceof PsiTypeVariable) && right instanceof PsiTypeVariable) {
Set<PsiTypeVariable> bound = new PsiTypeVarCollector().getSet(left);
if (bound.contains(right)) {
myConstraints.remove(constr);
mySons = new ResolverTree[]{applyRule(myBindingFactory.create(((PsiTypeVariable)right), Bottom.BOTTOM))};
return;
}
final PsiManager manager = PsiManager.getInstance(myProject);
final PsiType leftType = left instanceof PsiWildcardType ? ((PsiWildcardType)left).getBound() : left;
final PsiType[] types = getTypeRange(PsiType.getJavaLangObject(manager, GlobalSearchScope.allScope(myProject)), leftType);
mySons = new ResolverTree[types.length];
if (types.length > 0) {
myConstraints.remove(constr);
}
for (int i = 0; i < types.length; i++) {
final PsiType type = types[i];
mySons[i] = applyRule(myBindingFactory.create(((PsiTypeVariable)right), type));
}
return;
}
}
}
//T1 < a < b < ...
{
Set<PsiTypeVariable> haveLeftBound = new HashSet<PsiTypeVariable>();
Constraint target = null;
Set<PsiTypeVariable> boundVariables = new HashSet<PsiTypeVariable>();
for (final Constraint constr : myConstraints) {
final PsiType leftType = constr.getLeft();
final PsiType rightType = constr.getRight();
if (leftType instanceof PsiTypeVariable) {
boundVariables.add((PsiTypeVariable)leftType);
if (rightType instanceof PsiTypeVariable) {
boundVariables.add((PsiTypeVariable)rightType);
haveLeftBound.add(((PsiTypeVariable)rightType));
}
else if (!Util.bindsTypeVariables(rightType)) {
target = constr;
}
}
}
if (target == null) {
if (mySettings.exhaustive()) {
for (final Constraint constr : myConstraints) {
final PsiType left = constr.getLeft();
final PsiType right = constr.getRight();
PsiType[] range = null;
PsiTypeVariable var = null;
if (left instanceof PsiTypeVariable && !(right instanceof PsiTypeVariable)) {
range = getTypeRange(PsiType.getJavaLangObject(PsiManager.getInstance(myProject), GlobalSearchScope.allScope(myProject)),
right);
var = (PsiTypeVariable)left;
}
if (range == null && right instanceof PsiTypeVariable && !(left instanceof PsiTypeVariable)) {
range = new PsiType[]{right};
var = (PsiTypeVariable)right;
}
if (range != null) {
mySons = new ResolverTree[range.length];
for (int i = 0; i < range.length; i++) {
mySons[i] = applyRule(myBindingFactory.create(var, range[i]));
}
return;
}
}
}
Binding binding = myBindingFactory.create();
for (final PsiTypeVariable var : myBindingFactory.getBoundVariables()) {
if (!myCurrentBinding.binds(var) && !boundVariables.contains(var)) {
binding = binding.compose(myBindingFactory.create(var, Bottom.BOTTOM));
}
}
if (!binding.nonEmpty()) {
myConstraints.clear();
}
mySons = new ResolverTree[]{applyRule(binding)};
}
else {
final PsiType type = target.getRight();
final PsiTypeVariable var = (PsiTypeVariable)target.getLeft();
final Binding binding =
(haveLeftBound.contains(var) || type instanceof PsiWildcardType) || !mySettings.cookToWildcards()
? myBindingFactory.create(var, type)
: myBindingFactory.create(var, PsiWildcardType.createExtends(PsiManager.getInstance(myProject), type));
myConstraints.remove(target);
mySons = new ResolverTree[]{applyRule(binding)};
}
}
}
private void logSolution() {
LOG.debug("Reduced system:");
for (final Constraint constr : myConstraints) {
LOG.debug(constr.toString());
}
LOG.debug("End of Reduced system.");
LOG.debug("Reduced binding:");
LOG.debug(myCurrentBinding.toString());
LOG.debug("End of Reduced binding.");
}
private interface Reducer {
LinkedList<Pair<PsiType, Binding>> unify(PsiType x, PsiType y);
Constraint create(PsiTypeVariable var, PsiType type);
PsiType getType(Constraint c);
PsiTypeVariable getVar(Constraint c);
}
private void reduceTypeVar(final Constraint x, final Constraint y) {
reduceSideVar(x, y, new Reducer() {
@Override
public LinkedList<Pair<PsiType, Binding>> unify(final PsiType x, final PsiType y) {
return myBindingFactory.intersect(x, y);
}
@Override
public Constraint create(final PsiTypeVariable var, final PsiType type) {
return new Subtype(type, var);
}
@Override
public PsiType getType(final Constraint c) {
return c.getLeft();
}
@Override
public PsiTypeVariable getVar(final Constraint c) {
return (PsiTypeVariable)c.getRight();
}
});
}
private void reduceVarType(final Constraint x, final Constraint y) {
reduceSideVar(x, y, new Reducer() {
@Override
public LinkedList<Pair<PsiType, Binding>> unify(final PsiType x, final PsiType y) {
return myBindingFactory.union(x, y);
}
@Override
public Constraint create(final PsiTypeVariable var, final PsiType type) {
return new Subtype(var, type);
}
@Override
public PsiType getType(final Constraint c) {
return c.getRight();
}
@Override
public PsiTypeVariable getVar(final Constraint c) {
return (PsiTypeVariable)c.getLeft();
}
});
}
private void reduceSideVar(final Constraint x, final Constraint y, final Reducer reducer) {
final PsiTypeVariable var = reducer.getVar(x);
final PsiType xType = reducer.getType(x);
final PsiType yType = reducer.getType(y);
final LinkedList<Pair<PsiType, Binding>> union = reducer.unify(xType, yType);
if (union.isEmpty()) {
return;
}
myConstraints.remove(x);
myConstraints.remove(y);
mySons = new ResolverTree[union.size()];
int i = 0;
Constraint prev = null;
for (final Pair<PsiType, Binding> pair : union) {
if (prev != null) {
myConstraints.remove(prev);
}
prev = reducer.create(var, pair.getFirst());
myConstraints.add(prev);
mySons[i++] = applyRule(pair.getSecond());
}
}
public void resolve() {
reduce();
if (mySons.length > 0) {
for (int i = 0; i < mySons.length; i++) {
if (mySons[i] != null) {
mySons[i].resolve();
if (!mySettings.exhaustive() && mySettings.cookToWildcards() && mySons[i].mySolutionFound) break;
mySons[i] = null;
}
}
}
else {
if (myConstraints.isEmpty()) {
logSolution();
mySolutions.putSolution(myCurrentBinding);
mySolutionFound = true;
}
}
}
public Binding getBestSolution() {
return mySolutions.getBestSolution();
}
}