/*******************************************************************************
* Copyright (c) 2000, 2009 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.wst.jsdt.internal.corext.refactoring.structure.constraints;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.wst.jsdt.core.dom.IFunctionBinding;
import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
import org.eclipse.wst.jsdt.core.dom.IVariableBinding;
import org.eclipse.wst.jsdt.core.dom.Name;
import org.eclipse.wst.jsdt.core.dom.Type;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints.CompilationUnitRange;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints.types.TType;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints.types.TypeEnvironment;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.ConstraintVariable2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.ITypeConstraint2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.ImmutableTypeVariable2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.IndependentTypeVariable2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.ParameterTypeVariable2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.ReturnTypeVariable2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.SubTypeConstraint2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.TypeEquivalenceSet;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.TypeVariable2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.VariableVariable2;
import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringASTParser;
/**
* Type constraints model to hold all type constraints to replace type occurrences by a given supertype.
*
*
*/
public final class SuperTypeConstraintsModel {
/** Customized implementation of a hash set */
private static class HashedSet extends AbstractSet implements Set {
/** The backing hash map */
private final Map fImplementation= new HashMap();
/*
* @see java.util.AbstractCollection#add(java.lang.Object)
*/
public final boolean add(final Object object) {
return fImplementation.put(object, object) == null;
}
/**
* Attempts to add the specified object to this set.
*
* @param object the object to add
* @return An already existing object considered equal to the specified one, or the newly added object
*/
public final Object addExisting(final Object object) {
final Object result= fImplementation.get(object);
if (result != null)
return result;
fImplementation.put(object, object);
return object;
}
/*
* @see java.util.AbstractCollection#clear()
*/
public final void clear() {
fImplementation.clear();
}
/*
* @see java.util.AbstractCollection#contains(java.lang.Object)
*/
public final boolean contains(final Object object) {
return fImplementation.containsKey(object);
}
/*
* @see java.util.AbstractCollection#isEmpty()
*/
public final boolean isEmpty() {
return fImplementation.isEmpty();
}
/*
* @see java.util.AbstractCollection#iterator()
*/
public final Iterator iterator() {
return fImplementation.keySet().iterator();
}
/*
* @see java.util.AbstractCollection#remove(java.lang.Object)
*/
public final boolean remove(final Object object) {
return fImplementation.remove(object) == object;
}
/*
* @see java.util.AbstractCollection#size()
*/
public final int size() {
return fImplementation.size();
}
}
/** The usage data */
private static final String DATA_USAGE= "us"; //$NON-NLS-1$
/** Maximal number of TTypes */
private static final int MAX_CACHE= 64;
/**
* Returns the usage of the specified constraint variable.
*
* @param variable the constraint variable
* @return the usage of the constraint variable (element type: <code>ITypeConstraint2</code>)
*/
public static Collection getVariableUsage(final ConstraintVariable2 variable) {
final Object data= variable.getData(DATA_USAGE);
if (data == null)
return Collections.EMPTY_LIST;
else if (data instanceof Collection)
return Collections.unmodifiableCollection((Collection) data);
else
return Collections.singletonList(data);
}
/**
* Is the type represented by the specified binding a constrained type?
*
* @param binding the binding to check, or <code>null</code>
* @return <code>true</code> if it is constrained, <code>false</code> otherwise
*/
public static boolean isConstrainedType(final ITypeBinding binding) {
return binding != null && !binding.isPrimitive();
}
/**
* Sets the usage of the specified constraint variable.
*
* @param variable the constraint variable
* @param constraint the type constraint
*/
public static void setVariableUsage(final ConstraintVariable2 variable, ITypeConstraint2 constraint) {
final Object data= variable.getData(DATA_USAGE);
if (data == null)
variable.setData(DATA_USAGE, constraint);
else if (data instanceof Collection)
((Collection) data).add(constraint);
else {
final Collection usage= new ArrayList(2);
usage.add(data);
usage.add(constraint);
variable.setData(DATA_USAGE, usage);
}
}
/** The cast variables (element type: <code>CastVariable2</code>) */
private final Collection fCastVariables= new ArrayList();
/** The compliance level */
private int fCompliance= 3;
/** The set of constraint variables (element type: <code>ConstraintVariable2</code>) */
private final HashedSet fConstraintVariables= new HashedSet();
/** The covariant type constraints (element type: <code>CovariantTypeConstraint</code>) */
private final Collection fCovariantTypeConstraints= new ArrayList();
/** The type environment to use */
private TypeEnvironment fEnvironment;
/** The subtype to replace */
private final TType fSubType;
/** The supertype as replacement */
private final TType fSuperType;
/** The TType cache (element type: <code><String, ITypeBinding></code>) */
private Map fTTypeCache= new LinkedHashMap(MAX_CACHE, 0.75f, true) {
private static final long serialVersionUID= 1L;
protected final boolean removeEldestEntry(Map.Entry entry) {
return size() > MAX_CACHE;
}
};
/** The set of type constraints (element type: <code>ITypeConstraint2</code>) */
private final Set fTypeConstraints= new HashSet();
/**
* Creates a new super type constraints model.
*
* @param subType the subtype to replace
* @param superType the supertype replacement
*/
public SuperTypeConstraintsModel(final TypeEnvironment environment, TType subType, TType superType) {
fEnvironment= environment;
fSubType= subType;
fSuperType= superType;
}
/**
* Gets called when the creation of the model begins.
*/
public final void beginCreation() {
// Do nothing right now
}
/**
* Creates a conditional type constraint.
*
* @param expressionVariable the expression type constraint variable
* @param thenVariable the then type constraint variable
* @param elseVariable the else type constraint variable
*/
public final void createConditionalTypeConstraint(final ConstraintVariable2 expressionVariable, final ConstraintVariable2 thenVariable, final ConstraintVariable2 elseVariable) {
final ITypeConstraint2 constraint= new ConditionalTypeConstraint(expressionVariable, thenVariable, elseVariable);
if (!fTypeConstraints.contains(constraint)) {
fTypeConstraints.add(constraint);
setVariableUsage(expressionVariable, constraint);
setVariableUsage(thenVariable, constraint);
setVariableUsage(elseVariable, constraint);
}
}
/**
* Creates a subtype constraint.
*
* @param descendant the descendant type constraint variable
* @param ancestor the ancestor type constraint variable
*/
public final void createCovariantTypeConstraint(final ConstraintVariable2 descendant, final ConstraintVariable2 ancestor) {
final ITypeConstraint2 constraint= new CovariantTypeConstraint(descendant, ancestor);
if (!fTypeConstraints.contains(constraint)) {
fTypeConstraints.add(constraint);
fCovariantTypeConstraints.add(constraint);
setVariableUsage(descendant, constraint);
setVariableUsage(ancestor, constraint);
}
}
/**
* Creates a declaring type variable.
* <p>
* A declaring type variable stands for a type where something has been declared.
* </p>
*
* @param type the type binding
* @return the created declaring type variable
*/
public final ConstraintVariable2 createDeclaringTypeVariable(ITypeBinding type) {
if (type.isArray())
type= type.getElementType();
type= type.getTypeDeclaration();
return (ConstraintVariable2) fConstraintVariables.addExisting(new ImmutableTypeVariable2(createTType(type)));
}
/**
* Creates an equality constraint.
*
* @param left the left typeconstraint variable
* @param right the right typeconstraint variable
*/
public final void createEqualityConstraint(final ConstraintVariable2 left, final ConstraintVariable2 right) {
if (left != null && right != null) {
final TypeEquivalenceSet first= left.getTypeEquivalenceSet();
final TypeEquivalenceSet second= right.getTypeEquivalenceSet();
if (first == null) {
if (second == null) {
final TypeEquivalenceSet set= new TypeEquivalenceSet(left, right);
left.setTypeEquivalenceSet(set);
right.setTypeEquivalenceSet(set);
} else {
second.add(left);
left.setTypeEquivalenceSet(second);
}
} else {
if (second == null) {
first.add(right);
right.setTypeEquivalenceSet(first);
} else if (first == second)
return;
else {
final ConstraintVariable2[] variables= second.getContributingVariables();
first.addAll(variables);
for (int index= 0; index < variables.length; index++)
variables[index].setTypeEquivalenceSet(first);
}
}
}
}
/**
* Creates an exception variable.
*
* @param name the name of the thrown exception
* @return the created exception variable
*/
public final ConstraintVariable2 createExceptionVariable(final Name name) {
final ITypeBinding binding= name.resolveTypeBinding();
if (isConstrainedType(binding))
return (ConstraintVariable2) fConstraintVariables.addExisting(new TypeVariable2(createTType(binding), new CompilationUnitRange(RefactoringASTParser.getCompilationUnit(name), name)));
return null;
}
/**
* Creates an immutable type variable.
*
* @param type the type binding
* @return the created plain type variable
*/
public final ConstraintVariable2 createImmutableTypeVariable(ITypeBinding type) {
if (type.isArray())
type= type.getElementType();
if (isConstrainedType(type))
return (ConstraintVariable2) fConstraintVariables.addExisting(new ImmutableTypeVariable2(createTType(type)));
return null;
}
/**
* Creates an independent type variable.
* <p>
* An independant type variable stands for an arbitrary type.
* </p>
*
* @param type the type binding
* @return the created independant type variable
*/
public final ConstraintVariable2 createIndependentTypeVariable(ITypeBinding type) {
if (type.isArray())
type= type.getElementType();
if (isConstrainedType(type))
return (ConstraintVariable2) fConstraintVariables.addExisting(new IndependentTypeVariable2(createTType(type)));
return null;
}
/**
* Creates a new method parameter variable.
*
* @param method the method binding
* @param index the index of the parameter
* @return the created method parameter variable
*/
public final ConstraintVariable2 createMethodParameterVariable(final IFunctionBinding method, final int index) {
final ITypeBinding[] parameters= method.getParameterTypes();
ITypeBinding binding= parameters[Math.min(index, parameters.length - 1)];
if (binding.isArray())
binding= binding.getElementType();
if (isConstrainedType(binding)) {
ConstraintVariable2 variable= null;
final TType type= createTType(binding);
if (method.getDeclaringClass().isFromSource())
variable= new ParameterTypeVariable2(type, index, method.getMethodDeclaration());
else
variable= new ImmutableTypeVariable2(type);
return (ConstraintVariable2) fConstraintVariables.addExisting(variable);
}
return null;
}
/**
* Creates a new return type variable.
*
* @param method the method binding
* @return the created return type variable
*/
public final ConstraintVariable2 createReturnTypeVariable(final IFunctionBinding method) {
if (!method.isConstructor()) {
ITypeBinding binding= method.getReturnType();
if (binding != null && binding.isArray())
binding= binding.getElementType();
if (binding != null && isConstrainedType(binding)) {
ConstraintVariable2 variable= null;
final TType type= createTType(binding);
if (method.getDeclaringClass().isFromSource())
variable= new ReturnTypeVariable2(type, method);
else
variable= new ImmutableTypeVariable2(type);
return (ConstraintVariable2) fConstraintVariables.addExisting(variable);
}
}
return null;
}
/**
* Creates a subtype constraint.
*
* @param descendant the descendant type constraint variable
* @param ancestor the ancestor type constraint variable
*/
public final void createSubtypeConstraint(final ConstraintVariable2 descendant, final ConstraintVariable2 ancestor) {
final ITypeConstraint2 constraint= new SubTypeConstraint2(descendant, ancestor);
if (!fTypeConstraints.contains(constraint)) {
fTypeConstraints.add(constraint);
setVariableUsage(descendant, constraint);
setVariableUsage(ancestor, constraint);
}
}
/**
* Creates a new TType for the corresponding binding.
*
* @param binding The type binding
* @return The corresponding TType
*/
public final TType createTType(final ITypeBinding binding) {
final String key= binding.getKey();
final TType cached= (TType) fTTypeCache.get(key);
if (cached != null)
return cached;
final TType type= fEnvironment.create(binding);
fTTypeCache.put(key, type);
return type;
}
/**
* Creates a type variable.
*
* @param type the type binding
* @param range the compilation unit range
* @return the created type variable
*/
public final ConstraintVariable2 createTypeVariable(ITypeBinding type, final CompilationUnitRange range) {
if (type.isArray())
type= type.getElementType();
if (isConstrainedType(type))
return (ConstraintVariable2) fConstraintVariables.addExisting(new TypeVariable2(createTType(type), range));
return null;
}
/**
* Creates a type variable.
*
* @param type the type
* @return the created type variable
*/
public final ConstraintVariable2 createTypeVariable(final Type type) {
ITypeBinding binding= type.resolveBinding();
if (binding != null) {
if (binding.isArray())
binding= binding.getElementType();
if (isConstrainedType(binding))
return (ConstraintVariable2) fConstraintVariables.addExisting(new TypeVariable2(createTType(binding), new CompilationUnitRange(RefactoringASTParser.getCompilationUnit(type), type)));
}
return null;
}
/**
* Creates a variable type variable.
*
* @param binding the variable binding
* @return the created variable variable
*/
public final ConstraintVariable2 createVariableVariable(final IVariableBinding binding) {
ITypeBinding type= binding.getType();
if (type.isArray())
type= type.getElementType();
if (isConstrainedType(type)) {
ConstraintVariable2 variable= null;
final IVariableBinding declaration= binding.getVariableDeclaration();
if (declaration.isField()) {
final ITypeBinding declaring= declaration.getDeclaringClass();
if (!declaring.isFromSource())
variable= new ImmutableTypeVariable2(createTType(type));
} else {
final IFunctionBinding declaring= declaration.getDeclaringMethod();
if (declaring != null && !declaring.getDeclaringClass().isFromSource())
variable= new ImmutableTypeVariable2(createTType(type));
}
if (variable == null)
variable= new VariableVariable2(createTType(type), declaration);
return (ConstraintVariable2) fConstraintVariables.addExisting(variable);
}
return null;
}
/**
* Gets called when the creation of the model ends.
*/
public final void endCreation() {
fEnvironment= null;
fTTypeCache= null;
}
/**
* Returns the cast variables of this model.
*
* @return the cast variables (element type: <code>CastVariable2</code>)
*/
public final Collection getCastVariables() {
return Collections.unmodifiableCollection(fCastVariables);
}
/**
* Returns the compliance level to use.
*
* @return the compliance level
*/
public final int getCompliance() {
return fCompliance;
}
/**
* Returns the constraint variables of this model.
*
* @return the constraint variables (element type: <code>ConstraintVariable2</code>)
*/
public final Collection getConstraintVariables() {
return Collections.unmodifiableCollection(fConstraintVariables);
}
/**
* Returns the subtype to be replaced.
*
* @return the subtype to be replaced
*/
public final TType getSubType() {
return fSubType;
}
/**
* Returns the supertype as replacement.
*
* @return the supertype as replacement
*/
public final TType getSuperType() {
return fSuperType;
}
/**
* Returns the type constraints of this model.
*
* @return the type constraints (element type: <code>ITypeConstraint2</code>)
*/
public final Collection getTypeConstraints() {
return Collections.unmodifiableCollection(fTypeConstraints);
}
/**
* Sets the compliance level to use.
*
* @param level the compliance level to use. The argument must be one of the <code>AST.JLSx</code> constants.
*/
public final void setCompliance(final int level) {
fCompliance= level;
}
}