blob: c52119777e9fac41552ee4c579b6d9a68d3cfe01 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.generics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.ArrayType;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.HierarchyType;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TType;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TypeEnvironment;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.EnumeratedTypeSet;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.SingletonTypeSet;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.TypeSet;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.TypeSetEnvironment;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ArrayElementVariable2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ArrayTypeVariable2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.CastVariable2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.CollectionElementVariable2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ConstraintVariable2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeConstraint2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.IndependentTypeVariable2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.TTypes;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.TypeEquivalenceSet;
public class InferTypeArgumentsConstraintsSolver {
private static class TTypeComparator implements Comparator {
public int compare(Object o1, Object o2) {
return ((TType) o1).getPrettySignature().compareTo(((TType) o2).getPrettySignature());
}
public static TTypeComparator INSTANCE= new TTypeComparator();
}
private final static String CHOSEN_TYPE= "chosenType"; //$NON-NLS-1$
private final InferTypeArgumentsTCModel fTCModel;
private TypeSetEnvironment fTypeSetEnvironment;
/**
* The work-list used by the type constraint solver to hold the set of
* nodes in the constraint graph that remain to be (re-)processed. Entries
* are <code>ConstraintVariable2</code>s.
*/
private LinkedList/*<ConstraintVariable2>*/ fWorkList;
private InferTypeArgumentsUpdate fUpdate;
public InferTypeArgumentsConstraintsSolver(InferTypeArgumentsTCModel typeConstraintFactory) {
fTCModel= typeConstraintFactory;
fWorkList= new LinkedList();
}
public InferTypeArgumentsUpdate solveConstraints(IProgressMonitor pm) {
pm.beginTask("", 2); //$NON-NLS-1$
fUpdate= new InferTypeArgumentsUpdate();
ConstraintVariable2[] allConstraintVariables= fTCModel.getAllConstraintVariables();
if (allConstraintVariables.length == 0)
return fUpdate;
fTypeSetEnvironment= new TypeSetEnvironment(fTCModel.getTypeEnvironment());
ParametricStructureComputer parametricStructureComputer= new ParametricStructureComputer(allConstraintVariables, fTCModel);
Collection/*<CollectionElementVariable2>*/ newVars= parametricStructureComputer.createElemConstraintVariables();
ArrayList newAllConstraintVariables= new ArrayList();
newAllConstraintVariables.addAll(Arrays.asList(allConstraintVariables));
newAllConstraintVariables.addAll(newVars);
allConstraintVariables= (ConstraintVariable2[]) newAllConstraintVariables.toArray(new ConstraintVariable2[newAllConstraintVariables.size()]);
//loop over all TypeEquivalenceSets and unify the elements from the fElemStructureEnv with the existing TypeEquivalenceSets
HashSet allTypeEquivalenceSets= new HashSet();
for (int i= 0; i < allConstraintVariables.length; i++) {
TypeEquivalenceSet typeEquivalenceSet= allConstraintVariables[i].getTypeEquivalenceSet();
if (typeEquivalenceSet != null)
allTypeEquivalenceSets.add(typeEquivalenceSet);
}
for (Iterator iter= allTypeEquivalenceSets.iterator(); iter.hasNext();) {
TypeEquivalenceSet typeEquivalenceSet= (TypeEquivalenceSet) iter.next();
ConstraintVariable2[] contributingVariables= typeEquivalenceSet.getContributingVariables();
for (int i= 0; i < contributingVariables.length; i++) {
for (int j= i + 1; j < contributingVariables.length; j++) {
ConstraintVariable2 first= contributingVariables[i];
ConstraintVariable2 second= contributingVariables[j];
fTCModel.createElementEqualsConstraints(first, second); // recursively
}
}
}
ITypeConstraint2[] allTypeConstraints= fTCModel.getAllTypeConstraints();
for (int i= 0; i < allTypeConstraints.length; i++) {
ITypeConstraint2 typeConstraint= allTypeConstraints[i];
fTCModel.createElementEqualsConstraints(typeConstraint.getLeft(), typeConstraint.getRight());
}
initializeTypeEstimates(allConstraintVariables);
if (pm.isCanceled())
throw new OperationCanceledException();
fWorkList.addAll(Arrays.asList(allConstraintVariables));
runSolver(new SubProgressMonitor(pm, 1));
chooseTypes(allConstraintVariables, new SubProgressMonitor(pm, 1));
findCastsToRemove(fTCModel.getCastVariables());
return fUpdate;
}
private void initializeTypeEstimates(ConstraintVariable2[] allConstraintVariables) {
for (int i= 0; i < allConstraintVariables.length; i++) {
ConstraintVariable2 cv= allConstraintVariables[i];
//TODO: not necessary for types that are not used in a TypeConstraint but only as type in CollectionElementVariable
//TODO: handle nested element variables; see ParametricStructureComputer.createAndInitVars()
TypeEquivalenceSet set= cv.getTypeEquivalenceSet();
if (set == null) {
set= new TypeEquivalenceSet(cv);
set.setTypeEstimate(createInitialEstimate(cv));
cv.setTypeEquivalenceSet(set);
} else {
TypeSet typeEstimate= (TypeSet) cv.getTypeEstimate();
if (typeEstimate == null) {
ConstraintVariable2[] cvs= set.getContributingVariables();
typeEstimate= fTypeSetEnvironment.getUniverseTypeSet();
for (int j= 0; j < cvs.length; j++) //TODO: optimize: just try to find an immutable CV; if not found, use Universe
typeEstimate= typeEstimate.intersectedWith(createInitialEstimate(cvs[j]));
set.setTypeEstimate(typeEstimate);
}
}
}
}
private TypeSet createInitialEstimate(ConstraintVariable2 cv) {
// TODO: check assumption: only immutable CVs have a type
// ParametricStructure parametricStructure= fElemStructureEnv.elemStructure(cv);
// if (parametricStructure != null && parametricStructure != ParametricStructureComputer.ParametricStructure.NONE) {
// return SubTypesOfSingleton.create(parametricStructure.getBase());
// }
TType type= cv.getType();
if (type == null) {
return fTypeSetEnvironment.getUniverseTypeSet();
} else if (cv instanceof IndependentTypeVariable2) {
return fTypeSetEnvironment.getUniverseTypeSet();
//TODO: solve problem with recursive bounds
// TypeVariable tv= (TypeVariable) type;
// TType[] bounds= tv.getBounds();
// TypeSet result= SubTypesOfSingleton.create(bounds[0].getErasure());
// for (int i= 1; i < bounds.length; i++) {
// result= result.intersectedWith(SubTypesOfSingleton.create(bounds[i].getErasure()));
// }
// return result;
} else if (cv instanceof ArrayTypeVariable2) {
return fTypeSetEnvironment.getUniverseTypeSet();
} else if (cv instanceof ArrayElementVariable2) {
if (cv.getType() != null && cv.getType().isTypeVariable()) {
return fTypeSetEnvironment.getUniverseTypeSet();
} else {
return new SingletonTypeSet(type, fTypeSetEnvironment);
}
} else if (type.isVoidType()) {
return fTypeSetEnvironment.getEmptyTypeSet();
} else {
return new SingletonTypeSet(type, fTypeSetEnvironment);
}
}
private void runSolver(SubProgressMonitor pm) {
pm.beginTask("", fWorkList.size() * 3); //$NON-NLS-1$
while (! fWorkList.isEmpty()) {
// Get a variable whose type estimate has changed
ConstraintVariable2 cv= (ConstraintVariable2) fWorkList.removeFirst();
List/*<ITypeConstraint2>*/ usedIn= fTCModel.getUsedIn(cv);
processConstraints(usedIn, cv);
pm.worked(1);
if (pm.isCanceled())
throw new OperationCanceledException();
}
pm.done();
}
/**
* Given a list of <code>ITypeConstraint2</code>s that all refer to a
* given <code>ConstraintVariable2</code> (whose type bound has presumably
* just changed), process each <code>ITypeConstraint</code>, propagating
* the type bound across the constraint as needed.
*
* @param usedIn the <code>List</code> of <code>ITypeConstraint2</code>s
* to process
* @param changedCv the constraint variable whose type bound has changed
*/
private void processConstraints(List/*<ITypeConstraint2>*/ usedIn, ConstraintVariable2 changedCv) {
int i= 0;
for (Iterator iter= usedIn.iterator(); iter.hasNext(); i++) {
ITypeConstraint2 tc= (ITypeConstraint2) iter.next();
maintainSimpleConstraint(changedCv, tc);
//TODO: prune tcs which cannot cause further changes
// Maybe these should be pruned after a special first loop over all ConstraintVariables,
// Since this can only happen once for every CV in the work list.
// if (isConstantConstraint(stc))
// fTypeConstraintFactory.removeUsedIn(stc, changedCv);
}
}
private void maintainSimpleConstraint(ConstraintVariable2 changedCv, ITypeConstraint2 stc) {
ConstraintVariable2 left= stc.getLeft();
ConstraintVariable2 right= stc.getRight();
TypeEquivalenceSet leftSet= left.getTypeEquivalenceSet();
TypeEquivalenceSet rightSet= right.getTypeEquivalenceSet();
TypeSet leftEstimate= (TypeSet) leftSet.getTypeEstimate();
TypeSet rightEstimate= (TypeSet) rightSet.getTypeEstimate();
if (leftEstimate.isUniverse() && rightEstimate.isUniverse())
return; // nothing to do
if (leftEstimate.equals(rightEstimate))
return; // nothing to do
TypeSet lhsSuperTypes= leftEstimate.superTypes();
TypeSet rhsSubTypes= rightEstimate.subTypes();
if (! rhsSubTypes.containsAll(leftEstimate)) {
TypeSet xsection= leftEstimate.intersectedWith(rhsSubTypes);
// if (xsection.isEmpty()) // too bad, but this can happen
// throw new IllegalStateException("Type estimate set is now empty for LHS in " + left + " <= " + right + "; estimates were " + leftEstimate + " <= " + rightEstimate); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
leftSet.setTypeEstimate(xsection);
fWorkList.addAll(Arrays.asList(leftSet.getContributingVariables()));
}
if (! lhsSuperTypes.containsAll(rightEstimate)) {
TypeSet xsection= rightEstimate.intersectedWith(lhsSuperTypes);
// if (xsection.isEmpty())
// throw new IllegalStateException("Type estimate set is now empty for RHS in " + left + " <= " + right + "; estimates were " + leftEstimate + " <= " + rightEstimate); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
rightSet.setTypeEstimate(xsection);
fWorkList.addAll(Arrays.asList(rightSet.getContributingVariables()));
}
}
private void chooseTypes(ConstraintVariable2[] allConstraintVariables, SubProgressMonitor pm) {
pm.beginTask("", allConstraintVariables.length); //$NON-NLS-1$
for (int i= 0; i < allConstraintVariables.length; i++) {
ConstraintVariable2 cv= allConstraintVariables[i];
TypeEquivalenceSet set= cv.getTypeEquivalenceSet();
if (set == null)
continue; //TODO: should not happen iff all unused constraint variables got pruned
//TODO: should calculate only once per EquivalenceRepresentative; can throw away estimate TypeSet afterwards
TType type= chooseSingleType((TypeSet) cv.getTypeEstimate()); //TODO: is null for Universe TypeSet
setChosenType(cv, type);
if (cv instanceof CollectionElementVariable2) {
CollectionElementVariable2 elementCv= (CollectionElementVariable2) cv;
fUpdate.addDeclaration(elementCv);
}
pm.worked(1);
if (pm.isCanceled())
throw new OperationCanceledException();
}
pm.done();
}
private TType chooseSingleType(TypeSet typeEstimate) {
if (typeEstimate.isUniverse() || typeEstimate.isEmpty()) {
return null;
} else if (typeEstimate.hasUniqueLowerBound()) {
return typeEstimate.uniqueLowerBound();
} else {
EnumeratedTypeSet lowerBound= typeEstimate.lowerBound().enumerate();
ArrayList/*<TType>*/ interfaceCandidates= null;
for (Iterator iter= lowerBound.iterator(); iter.hasNext();) {
TType type= (TType) iter.next();
if (! type.isInterface()) {
return type;
} else {
if (interfaceCandidates == null)
interfaceCandidates= new ArrayList(2);
interfaceCandidates.add(type);
}
}
if (interfaceCandidates == null || interfaceCandidates.size() == 0) {
return null;
} else if (interfaceCandidates.size() == 1) {
return (TType) interfaceCandidates.get(0);
} else {
ArrayList nontaggingCandidates= getNonTaggingInterfaces(interfaceCandidates);
if (nontaggingCandidates.size() != 0) {
return (TType) Collections.min(nontaggingCandidates, TTypeComparator.INSTANCE);
} else {
return (TType) Collections.min(interfaceCandidates, TTypeComparator.INSTANCE);
}
}
}
}
private static final int MAX_CACHE= 1024;
private Map/*<TType, Boolean>*/ fInterfaceTaggingCache= new LinkedHashMap(MAX_CACHE, 0.75f, true) {
private static final long serialVersionUID= 1L;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_CACHE;
}
};
private ArrayList getNonTaggingInterfaces(ArrayList interfaceCandidates) {
ArrayList unresolvedTypes= new ArrayList();
ArrayList nonTagging= new ArrayList();
for (int i= 0; i < interfaceCandidates.size(); i++) {
TType interf= (TType) interfaceCandidates.get(i);
Object isTagging= fInterfaceTaggingCache.get(interf);
if (isTagging == null)
unresolvedTypes.add(interf);
else if (isTagging == Boolean.FALSE)
nonTagging.add(interf);
}
if (unresolvedTypes.size() != 0) {
TType[] interfaces= (TType[]) unresolvedTypes.toArray(new TType[unresolvedTypes.size()]);
HierarchyType firstInterface= (HierarchyType) interfaces[0];
IJavaProject javaProject= firstInterface.getJavaElementType().getJavaProject();
ITypeBinding[] interfaceBindings= TypeEnvironment.createTypeBindings(interfaces, javaProject); //expensive...
for (int i= 0; i < interfaceBindings.length; i++) {
if (interfaceBindings[i].getDeclaredMethods().length == 0) {
fInterfaceTaggingCache.put(interfaces[i], Boolean.TRUE);
} else {
fInterfaceTaggingCache.put(interfaces[i], Boolean.FALSE);
nonTagging.add(interfaces[i]);
}
}
}
return nonTagging;
}
private void findCastsToRemove(CastVariable2[] castVariables) {
for (int i= 0; i < castVariables.length; i++) {
CastVariable2 castCv= castVariables[i];
ConstraintVariable2 expressionVariable= castCv.getExpressionVariable();
TType chosenType= InferTypeArgumentsConstraintsSolver.getChosenType(expressionVariable);
TType castType= castCv.getType();
TType expressionType= expressionVariable.getType();
if (chosenType != null && TTypes.canAssignTo(chosenType, castType)) {
if (chosenType.equals(expressionType))
continue; // The type has not changed. Don't remove the cast, since it could be
// there to get access to default-visible members or to
// unify types of conditional expressions.
fUpdate.addCastToRemove(castCv);
} else if (expressionVariable instanceof ArrayTypeVariable2 && castType.isArrayType()) { // bug 97258
ArrayElementVariable2 arrayElementCv= fTCModel.getArrayElementVariable(expressionVariable);
if (arrayElementCv == null)
continue;
TType chosenArrayElementType= InferTypeArgumentsConstraintsSolver.getChosenType(arrayElementCv);
if (chosenArrayElementType != null && TTypes.canAssignTo(chosenArrayElementType, ((ArrayType) castType).getComponentType())) {
if (expressionType instanceof ArrayType && chosenArrayElementType.equals(((ArrayType) expressionType).getComponentType()))
continue; // The type has not changed. Don't remove the cast, since it could be
// there to unify types of conditional expressions.
fUpdate.addCastToRemove(castCv);
}
}
}
}
public static TType getChosenType(ConstraintVariable2 cv) {
TType type= (TType) cv.getData(CHOSEN_TYPE);
if (type != null)
return type;
TypeEquivalenceSet set= cv.getTypeEquivalenceSet();
if (set == null) { //TODO: should not have to set this here. Clean up when caching chosen type
return null;
// // no representative == no restriction
// set= new TypeEquivalenceSet(cv);
// set.setTypeEstimate(TypeUniverseSet.create());
// cv.setTypeEquivalenceSet(set);
}
return cv.getTypeEstimate().chooseSingleType();
}
private static void setChosenType(ConstraintVariable2 cv, TType type) {
cv.setData(CHOSEN_TYPE, type);
}
}