blob: aa9fa00771339705ac00febe6a4e5c429ff04916 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.GenericType;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TType;
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.SubTypeConstraint2;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.TypeEquivalenceSet;
public class ParametricStructureComputer {
public static class ParametricStructure {
public static final ParametricStructure NONE= new ParametricStructure();
private final GenericType fBase;
private final ParametricStructure[] fParameters;
public ParametricStructure(GenericType base) {
if (base == null)
throw new NullPointerException();
fBase= base;
fParameters= new ParametricStructure[base.getTypeParameters().length];
}
private ParametricStructure() {
fBase= null;
fParameters= new ParametricStructure[0];
}
public ParametricStructure[] getParameters() {
return fParameters;
}
public GenericType getBase() {
return fBase;
}
public String toString() {
if (this == NONE)
return "NONE"; //$NON-NLS-1$
else
return "ParamStructure " + fBase.toString() + '<' + Arrays.asList(fParameters) + '>'; //$NON-NLS-1$
}
}
private static final boolean DEBUG_INITIALIZATION= false;
/**
* Maps each ConstraintVariable2 onto an IType that is either an instance
* of AbstractTypeParameter, if the ConstraintVariable2 cannot possibly
* refer to a parametric type, or an instance of ParametricStructure with
* the appropriate sub-structure (if any) if it can.
*/
private final ElementStructureEnvironment fElemStructureEnv= new ElementStructureEnvironment();
private final ConstraintVariable2[] fAllConstraintVariables;
private InferTypeArgumentsTCModel fTCModel;
public ParametricStructureComputer(ConstraintVariable2[] allConstraintVariables, InferTypeArgumentsTCModel tcModel) {
fAllConstraintVariables= allConstraintVariables;
fTCModel= tcModel;
}
public ElementStructureEnvironment getElemStructureEnv() {
return fElemStructureEnv;
}
private void dumpContainerStructure() {
System.out.println("\n*** Container Structure: ***\n"); //$NON-NLS-1$
for (int i= 0; i < fAllConstraintVariables.length; i++) {
ConstraintVariable2 v= fAllConstraintVariables[i];
if (elemStructure(v) != null && !(elemStructure(v) == ParametricStructure.NONE))
System.out.println("elemStructure(" + v.toString() + ") = " + elemStructure(v)); //$NON-NLS-1$ //$NON-NLS-2$
}
System.out.println();
}
private Stack/*<ConstraintVariable2>*/ fWorkList2= new Stack();
private void setStructureAndPush(ConstraintVariable2 v, ParametricStructure structure) {
setElemStructure(v, structure);
fWorkList2.push(v);
}
//TODO hard-wired to collections
private void initializeContainerStructure(){
if (DEBUG_INITIALIZATION)
System.out.println(" *** Seeding container structure ***"); //$NON-NLS-1$
for (int i= 0; i < fAllConstraintVariables.length; i++) {
ConstraintVariable2 v= fAllConstraintVariables[i];
TType varType= declaredTypeOf(v);
if (varType != null) {
// rmf 11/30/2004 - Added isUnmodifiableFieldOrMethod() test to
// avoid unifying element types of container actual arguments
// with formal arguments of binary methods, to permit passing
// List<String> to a binary method taking a raw List.
if (isParametricType(varType) && !isUnmodifiableFieldOrMethod(v)) {
if (DEBUG_INITIALIZATION) System.out.println("Entity has container structure: " + v); //$NON-NLS-1$
setStructureAndPush(v, newParametricType(varType));
} else if (varType != null && !mightBeParametric(varType)) {
// Not a supertype of any container type - can't have container structure
if (DEBUG_INITIALIZATION) System.out.println("Entity DOES NOT have container structure: " + v); //$NON-NLS-1$
setStructureAndPush(v, ParametricStructure.NONE);
}
// else we're not sure yet whether this has container structure
} else {
// TType exprType= v.getType(); // TODO: always null!
//
// if (isArrayAccess(v)) {
// if (DEBUG_INITIALIZATION) System.out.println("Entity DOES NOT have container structure: " + v);
// setStructureAndPush(v, NO_STRUCTURE); // definitely not container structure, Java 1.5 says no generics inside arrays
// } else if (isParametricType(exprType)) {
// if (DEBUG_INITIALIZATION) System.out.println("Entity has container structure: " + v);
// setStructureAndPush(v, newParametricType(exprType));
// } else if (exprType != null && !mightBeParametric(exprType)) {
// // Not a supertype of any container type - can't have container structure
// if (DEBUG_INITIALIZATION) System.out.println("Entity DOES NOT have container structure: " + v);
// setStructureAndPush(v, NO_STRUCTURE);
// }
// TODO Markus: the following just updates the set of child element variables of the parent variable of 'v'.
// You already maintain this information automatically, so the code below is not needed...
// if (v instanceof CollectionElementVariable2) {
// CollectionElementVariable2 ev= (CollectionElementVariable2) v;
// int idx= ev.getDeclarationTypeVariableIndex(); //TODO : INDEX IS -1 IF THE TYPE VARIABLE COMES FROM A SUPERTYPE!!!
//
// Collection/*<ConstraintVariable2>*/ vars= fTCModel.getElementVariables(ev).values();
//
// if (vars == null) vars= new ConstraintVariable2[ev.getNumContainerTypeParams()];
// vars[idx]= ev;
// fVariableElementEnv.setElementVariables(ev.getParentConstraintVariable(), vars);
// }
// else we're not sure yet whether this has container structure
}
}
// Every variable v in fAllVariables is now in one of 3 states:
// - elemStructure(v) == some parametric type: definitely container structure, but we may not know the sub-structure yet
// - elemStructure(v) == some AbstractTypeParameter: definitely not container structure
// - elemStructure(v) == null: we know nothing yet about its structure
}
protected static TType declaredTypeOf(ConstraintVariable2 cv) {
//TODO: record original type of CollectionElementVariable2 iff source already had type parameter
return cv.getType();
// if (v instanceof ContextualExpressionVariable) {
// ContextualExpressionVariable ev= (ContextualExpressionVariable) v;
//
// return ev.getBinding();
// } else if (v instanceof ReturnTypeVariable) {
// ReturnTypeVariable rv= (ReturnTypeVariable) v;
//
// return rv.getBinding();
// } else if (v instanceof RawBindingVariable) {
// RawBindingVariable rv= (RawBindingVariable) v;
//
// return rv.getBinding();
// } else if (v instanceof ParameterTypeVariable) {
// ParameterTypeVariable pv= (ParameterTypeVariable) v;
//
// return pv.getBinding();
// } else
// return null;
}
private boolean mightBeParametric(TType type) {
return isParametricType(type);//TODO check this is the only case?
}
private void computeContainerStructure() {
if (DEBUG_INITIALIZATION)
System.out.println("\n*** Computing Container Structure ***\n"); //$NON-NLS-1$
initializeContainerStructure();
if (DEBUG_INITIALIZATION)
dumpContainerStructure();
while (!fWorkList2.isEmpty()) {
ConstraintVariable2 v= (ConstraintVariable2) fWorkList2.pop();
List/*<ITypeConstraint>*/ usedIn= fTCModel.getUsedIn(v);
for(Iterator/*<ITypeConstraint>*/ iter= usedIn.iterator(); iter.hasNext(); ) {
SubTypeConstraint2 stc= (SubTypeConstraint2) iter.next();
ConstraintVariable2 lhs= stc.getLeft();
ConstraintVariable2 rhs= stc.getRight();
unifyContainerStructure(lhs, rhs);
}
TypeEquivalenceSet typeEquivalenceSet= v.getTypeEquivalenceSet();
if (typeEquivalenceSet != null) {
ConstraintVariable2[] contributingVariables= typeEquivalenceSet.getContributingVariables();
for (int i= 0; i + 1 < contributingVariables.length; i++) {
ConstraintVariable2 first= contributingVariables[i];
ConstraintVariable2 second= contributingVariables[i + 1];
unifyContainerStructure(first, second);
}
}
}
if (DEBUG_INITIALIZATION)
dumpContainerStructure();
}
private void unifyContainerStructure(ConstraintVariable2 lhs, ConstraintVariable2 rhs) {
// RMF 8/19/2004 - exclude propagation through unmodifiable fields/methods
if (isUnmodifiableFieldOrMethod(lhs) || isUnmodifiableFieldOrMethod(rhs))
return;
if (DEBUG_INITIALIZATION)
System.out.println("Examining constraint " + lhs + " {" + elemStructure(lhs) + "} <= " + rhs + " {" + elemStructure(rhs) + "}"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
if (updateStructureOfVar(lhs, elemStructure(rhs), TypeOperator.SubType)) {
if (lhs instanceof CollectionElementVariable2)
updateParentContainerStructureFrom((CollectionElementVariable2) lhs, rhs);
updateElementVarStructureFromParent(lhs);
}
if (updateStructureOfVar(rhs, elemStructure(lhs), TypeOperator.SuperType)) {
if (rhs instanceof CollectionElementVariable2)
updateParentContainerStructureFrom((CollectionElementVariable2) rhs, lhs);
updateElementVarStructureFromParent(rhs);
}
}
private ParametricStructure newParametricType(TType varType) {
//TODO: create CollectionElementVariable2s if necessary?
GenericType genericType= (GenericType) varType.getTypeDeclaration();
return new ParametricStructure(genericType);
}
private boolean isUnmodifiableFieldOrMethod(ConstraintVariable2 v) {
return false; //TODO: find out whether it's declared in a binary type
}
private boolean isParametricType(TType type) {
return type.isParameterizedType() || type.isRawType() || type.isGenericType();
}
/**
* Updates the structure of the i'th type parameter of the given ParametricStructure
* to be consistent with that of 'otherStructure'.
*/
private boolean updateStructureOfIthParamFrom(ParametricStructure structure1, int i, ParametricStructure otherStructure) {
if ((otherStructure == null)) // no structure info to use to update 'structure1'
return false;
Assert.isTrue(structure1 != otherStructure, "updateStructureOfIthParamFrom(): attempt to unify ith param of a parametric type with itself!"); //$NON-NLS-1$
ParametricStructure param1= structure1.getParameters()[i];
boolean param1Unknown= (param1 == null);
if (param1Unknown) {
if (DEBUG_INITIALIZATION)
System.out.println(" setting param " + i + " of " + structure1 + " to " + otherStructure); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
structure1.getParameters()[i]= otherStructure;
return true;
}
boolean paramStructured= !(param1 == ParametricStructure.NONE);
boolean otherStructured= !(otherStructure == ParametricStructure.NONE);
if (paramStructured && otherStructured) { // both parametric
// rmf 12/15/2004 - handle cases where different parametric types (e.g.
// List and Map) flow into the same place. If base types are different,
// conservatively make the type param of structure1 unstructured.
if (param1.getBase().equals(otherStructure.getBase()))
return updateStructureOfType(param1, otherStructure);
else {
structure1.getParameters()[i]= ParametricStructure.NONE;
return true;
}
}
return false;
}
/**
* Updates the structure of any subsidiary element variables (if any) for
* the given ConstraintVariable2 (if it is in fact a container).
*/
private void updateElementVarStructureFromParent(ConstraintVariable2 v) {
// Propagate structure from container variable to any subsidiary element variables
if (elemStructure(v) != ParametricStructure.NONE && fTCModel.getElementVariables(v).size() > 0) {
ParametricStructure t= elemStructure(v);
for(Iterator iterator=fTCModel.getElementVariables(v).values().iterator(); iterator.hasNext(); ) {
CollectionElementVariable2 typeVar= (CollectionElementVariable2) iterator.next();
int declarationTypeVariableIndex= typeVar.getDeclarationTypeVariableIndex();
if (declarationTypeVariableIndex != CollectionElementVariable2.NOT_DECLARED_TYPE_VARIABLE_INDEX)
updateStructureOfVar(typeVar, t.getParameters()[declarationTypeVariableIndex], TypeOperator.Equals);
}
}
}
/**
* Updates the structure of the parent container variable of the given
* CollectionElementVariable2 from the structure of 'v1'.
* @param elemVar
* @param v1
*/
private void updateParentContainerStructureFrom(CollectionElementVariable2 elemVar, ConstraintVariable2 v1) {
ConstraintVariable2 elemContainer= elemVar.getParentConstraintVariable();
// This could be something that appears like it should have container
// structure, but doesn't, e.g., an array access for an array of containers
// (JDK 1.5 disallows arrays of parametric types). So if it doesn't have
// container structure, ignore it.
ParametricStructure elemContainerStructure= elemStructure(elemContainer);
if (elemContainerStructure == ParametricStructure.NONE)
return;
if (false) {
if (v1 instanceof CollectionElementVariable2) {
// if v1's container no longer has structure, remove structure from elemContainer
CollectionElementVariable2 ev1= (CollectionElementVariable2) v1;
ConstraintVariable2 v1Container= ev1.getParentConstraintVariable();
if (elemStructure(v1Container) == ParametricStructure.NONE)
setStructureAndPush(elemContainer, ParametricStructure.NONE);
return;
}
}
if (elemContainerStructure == null) { // handle clone()
elemContainerStructure= newParametricType(elemContainer.getType());
setStructureAndPush(elemContainer, elemContainerStructure);
}
ParametricStructure v1Structure= elemStructure(v1);
int parmIdx= elemVar.getDeclarationTypeVariableIndex(); //TODO: index is NOT_DECLARED_TYPE_VARIABLE_INDEX if the type variable comes from a supertype!!!
if (parmIdx == CollectionElementVariable2.NOT_DECLARED_TYPE_VARIABLE_INDEX)
return; //TODO: ParametricStructure should use type variable keys instead of index
if (elemContainerStructure == v1Structure || containsSubStructure(v1Structure, elemContainerStructure)) { // avoid creating cyclic structure
if (!(elemStructure(elemVar) == ParametricStructure.NONE))
setStructureAndPush(elemVar, ParametricStructure.NONE);
if (elemContainerStructure.getParameters()[parmIdx] == null) {
elemContainerStructure.getParameters()[parmIdx]= ParametricStructure.NONE;
fWorkList2.push(elemContainer);
}
} else if (updateStructureOfIthParamFrom(elemContainerStructure, parmIdx, v1Structure)) {
setStructureAndPush(elemVar, elemContainerStructure.getParameters()[parmIdx]);
fWorkList2.push(elemContainer);
if (DEBUG_INITIALIZATION)
System.out.println(" updated structure of " + elemContainer + " to " + elemContainerStructure); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private boolean containsSubStructure(ParametricStructure containingStructure, ParametricStructure subStructure) {
if (containingStructure == null)
return false;
ParametricStructure[] parameters= containingStructure.getParameters();
for (int i= 0; i < parameters.length; i++) {
ParametricStructure parameter= parameters[i];
if (parameter == subStructure)
return true;
else if (containsSubStructure(parameter, subStructure))
return true;
}
return false;
}
/**
* Make type1's sub-structure at least as detailed as that of type2 by
* copying the structure of type2's parameters (or its parameters' parameters)
* into type1's parameters.
*/
private boolean updateStructureOfType(ParametricStructure type1, ParametricStructure type2) {
if (type1 == null || type2 == null)
return false;
ParametricStructure[] parms1= type1.getParameters();
ParametricStructure[] parms2= type2.getParameters();
boolean someChange= false;
Assert.isTrue(parms1.length == parms2.length);
for(int i=0; i < parms1.length; i++) {
if (type1 == parms2[i]) { // avoid creating cyclic structures!
if (parms1[i] != ParametricStructure.NONE) {
parms1[i]= ParametricStructure.NONE;
someChange= true;
}
} else if (updateStructureOfIthParamFrom(type1, i, parms2[i]))
someChange= true;
}
return someChange;
}
static class TypeOperator {
// This could be a ConstraintOperator, if that had supertype
private final String fOp;
private TypeOperator(String op) {
fOp= op;
}
static public TypeOperator Equals= new TypeOperator("=^="); //$NON-NLS-1$
static public TypeOperator SubType= new TypeOperator("<="); //$NON-NLS-1$
static public TypeOperator SuperType= new TypeOperator("=>"); //$NON-NLS-1$
public String toString() {
return fOp;
}
}
/**
* Updates the structure of the given ConstraintVariable to be consistent
* with the structure of 'type2', in accordance with the given TypeOperator.
* If any changes are made, pushes the variable onto fWorkList2.
*/
private boolean updateStructureOfVar(ConstraintVariable2 v, ParametricStructure type2, TypeOperator op) {
if ((type2 == null)) // no structure info to use to update 'v'
return false ;
ParametricStructure vStructure= elemStructure(v);
boolean vStructureUnknown= (vStructure == null);
boolean type2Structured= type2 != ParametricStructure.NONE;
if (vStructureUnknown) {
if (DEBUG_INITIALIZATION)
System.out.println(" setting structure of " + v + " to " + type2); //$NON-NLS-1$ //$NON-NLS-2$
setStructureAndPush(v, type2);
return true;
}
boolean vStructured= vStructure != ParametricStructure.NONE;
if (vStructured && !type2Structured) {
// If the relation is <=, then it's ok for v to have structure while
// type2 doesn't. On the other hand, if the relation is >= or ==, 'v'
// must be made unstructured, since it cannot be structured and be a
// supertype (or equal to) something unstructured.
if (op == TypeOperator.Equals || op == TypeOperator.SuperType) {
setStructureAndPush(v, type2);
return true;
}
} else if (vStructured && type2Structured) { // both are structured (parametric types)
// rmf 12/15/2004 - handle cases where different parametric types (e.g.
// List and Map) flow into the same place. If base types are different,
// conservatively make v unstructured.
if (!vStructure.getBase().equals(type2.getBase())) { // different parametric types?
if (op == TypeOperator.SuperType) { // if (v >= other), v can't have parametric structure
setStructureAndPush(v, ParametricStructure.NONE);
return true;
}
} else if (updateStructureOfType(vStructure, type2)) {
fWorkList2.push(v);
return true;
}
}
return false;
}
private void setElemStructure(ConstraintVariable2 v, ParametricStructure t) {
fElemStructureEnv.setElemStructure(v, t);
}
private ParametricStructure elemStructure(ConstraintVariable2 v) {
return fElemStructureEnv.elemStructure(v);
}
public Collection/*<CollectionElementVariable2>*/ createElemConstraintVariables() {
Collection newVars= new HashSet();
computeContainerStructure();
if (DEBUG_INITIALIZATION)
System.out.println("\n*** Creating Element Variables: ***\n"); //$NON-NLS-1$
for(int i= 0; i < fAllConstraintVariables.length; i++) {
newVars.addAll(createVariablesFor(fAllConstraintVariables[i]));
}
// // rmf 12/1/2004 - Add all the non-binary created vars to candidates for
// // rewriting; handles the case where a derived class overrides nothing
// // and therefore no constraint explicitly ties that class' type parameters
// // to anything else. Without what follows, the only variables that go into
// // the candidate list are those that appear in constraints.
// for(Iterator iter= newVars.iterator(); iter.hasNext(); ) {
// CollectionElementVariable2 v= (CollectionElementVariable2) iter.next();
//
// if (!isUnmodifiableFieldOrMethod(v.getParentConstraintVariable()))
// fCandidateVariables.add(v);
// }
//
// fAllConstraintVariables.addAll(newVars);
return newVars;
}
private Collection/*ConstraintVariable2*/ createVariablesFor(ConstraintVariable2 v) {
ParametricStructure t= elemStructure(v);
if (t == null || t == ParametricStructure.NONE)
return Collections.EMPTY_LIST;
ParametricStructure parmType= t;
TType base = parmType.getBase();
if (isParametricType(base)) {
return createAndInitVars(v, parmType);
}
throw new IllegalStateException("Attempt to create element variables for parametric variable of unknown type: " + parmType); //$NON-NLS-1$
}
private Collection/*<ConstraintVariable2>*/ getElementVariables(GenericType base, ConstraintVariable2 parent) {
fTCModel.makeElementVariables(parent, base);
return fTCModel.getElementVariables(parent).values();
}
private Collection/*ConstraintVariable2*/ createAndInitVars(ConstraintVariable2 v, ParametricStructure parmType) {
//TODO (->): in InferTypeArgumentsConstraintsSolver#createInitialEstimate(..)
//-> fTypeEstimates.setEstimateOf(v, SubTypesOfSingleton.create(ParametricStructure.getBaseContainerType(parmType.getBase(), sHierarchy)));
Collection/*<ConstraintVariable2>*/ elementVars= getElementVariables(parmType.getBase(), v);
//-> setNewTypeParamEstimateForEach(elementVars);
Collection result= createVars(elementVars, parmType.getParameters());
return result;
}
private Collection/*ConstraintVariable2*/ createVars(Collection/*ConstraintVariable2>*/ cvs, ParametricStructure[] parms) {
if (parms.length > 0) { // happens, e.g., for Properties (non-parametric)
// Assert.isTrue(cvs.size() == parms.length, "cvs.length==" + cvs.size() + " parms.length=" + parms.length); //assumption is wrong in presence of NOT_DECLARED_TYPE_VARIABLE_INDEX
for (Iterator iter= cvs.iterator(); iter.hasNext(); ) {
CollectionElementVariable2 childVar= (CollectionElementVariable2) iter.next();
int declarationTypeVariableIndex= childVar.getDeclarationTypeVariableIndex();
if (declarationTypeVariableIndex != CollectionElementVariable2.NOT_DECLARED_TYPE_VARIABLE_INDEX)
setElemStructure(childVar, parms[declarationTypeVariableIndex]);
}
} else {
for (Iterator iter= cvs.iterator(); iter.hasNext(); ) {
CollectionElementVariable2 childVar= (CollectionElementVariable2) iter.next();
int declarationTypeVariableIndex= childVar.getDeclarationTypeVariableIndex();
if (declarationTypeVariableIndex != CollectionElementVariable2.NOT_DECLARED_TYPE_VARIABLE_INDEX)
setElemStructure(childVar, ParametricStructure.NONE);
}
}
List result= new ArrayList(cvs.size() * 2);//roughly
for (Iterator iter= cvs.iterator(); iter.hasNext(); ) {
CollectionElementVariable2 childVar= (CollectionElementVariable2) iter.next();
int declarationTypeVariableIndex= childVar.getDeclarationTypeVariableIndex();
if (declarationTypeVariableIndex != CollectionElementVariable2.NOT_DECLARED_TYPE_VARIABLE_INDEX) {
result.add(childVar);
result.addAll(createVariablesFor(childVar));
}
}
return result;
}
}