gt: 		if (type == null)
context: /*******************************************************************************
   * Copyright (c) 2000, 2013 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.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.Platform;
  
  import org.eclipse.jdt.core.ICompilationUnit;
  import org.eclipse.jdt.core.dom.CastExpression;
  import org.eclipse.jdt.core.dom.Expression;
  import org.eclipse.jdt.core.dom.IMethodBinding;
  import org.eclipse.jdt.core.dom.ITypeBinding;
  import org.eclipse.jdt.core.dom.IVariableBinding;
  import org.eclipse.jdt.core.dom.Modifier;
  import org.eclipse.jdt.core.dom.Type;
  
  import org.eclipse.jdt.internal.corext.dom.Bindings;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.CompilationUnitRange;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.ArrayType;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.GenericType;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.ParameterizedType;
  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.types.TypeVariable;
  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.ImmutableTypeVariable2;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.IndependentTypeVariable2;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ParameterTypeVariable2;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ParameterizedTypeVariable2;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ReturnTypeVariable2;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.SubTypeConstraint2;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.TypeEquivalenceSet;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.TypeVariable2;
  import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.VariableVariable2;
  import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
  
  
  public class InferTypeArgumentsTCModel {
  
  	protected static final boolean DEBUG= Boolean.valueOf(Platform.getDebugOption("org.eclipse.jdt.ui/debug/TypeConstraints")).booleanValue(); //$NON-NLS-1$
  
  	private static final String INDEXED_COLLECTION_ELEMENTS= "IndexedCollectionElements"; //$NON-NLS-1$
  	private static final String ARRAY_ELEMENT= "ArrayElement"; //$NON-NLS-1$
  	private static final String USED_IN= "UsedIn"; //$NON-NLS-1$
  	private static final String METHOD_RECEIVER= "MethodReceiver"; //$NON-NLS-1$
  	private static final Map<String, CollectionElementVariable2> EMPTY_COLLECTION_ELEMENT_VARIABLES_MAP= Collections.emptyMap();
  
  	protected static boolean fStoreToString= DEBUG;
  
  	/**
  	 * Map from a {@link ConstraintVariable2} to itself.
  	 */
  	private HashMap<ConstraintVariable2, ConstraintVariable2> fConstraintVariables;
  	/**
  	 * Map from a {@link ITypeConstraint2} to itself.
  	 */
  	private HashMap<ITypeConstraint2, ITypeConstraint2> fTypeConstraints;
  	private Collection<CastVariable2> fCastVariables;
  
  	private HashSet<ConstraintVariable2> fCuScopedConstraintVariables;
  
  	private TypeEnvironment fTypeEnvironment;
  
  	private static final int MAX_TTYPE_CACHE= 1024;
  	private Map<String, TType> fTTypeCache= new LinkedHashMap<String, TType>(MAX_TTYPE_CACHE, 0.75f, true) {
  		private static final long serialVersionUID= 1L;
  		@Override
  		protected boolean removeEldestEntry(Map.Entry<String, TType> eldest) {
  			return size() > MAX_TTYPE_CACHE;
  		}
  	};
  
  
  	public InferTypeArgumentsTCModel() {
  		fTypeConstraints= new HashMap<ITypeConstraint2, ITypeConstraint2>();
  		fConstraintVariables= new LinkedHashMap<ConstraintVariable2, ConstraintVariable2>(); // make iteration independent of hashCode() implementation
  		fCastVariables= new ArrayList<CastVariable2>();
  
  		fCuScopedConstraintVariables= new HashSet<ConstraintVariable2>();
  
  		fTypeEnvironment= new TypeEnvironment(true);
  	}
  
  	/**
  	 * Allows for avoiding the creation of SimpleTypeConstraints based on properties of
  	 * their constituent ConstraintVariables and ConstraintOperators. Can be used to e.g.
  	 * avoid creation of constraints for assignments between built-in types.
  	 *
  	 * @param cv1
  	 * @param cv2
  	 * @return <code>true</code> iff the type constraint should really be created
  	 */
  	protected boolean keep(ConstraintVariable2 cv1, ConstraintVariable2 cv2) {
  		if ((cv1 == null || cv2 == null))
  			return false;
  
  		if (cv1.equals(cv2)) {
  			if (cv1 == cv2)
  				return false;
  			else
  				Assert.isTrue(false);
  		}
  
  		if (cv1 instanceof CollectionElementVariable2 || cv2 instanceof CollectionElementVariable2)
  			return true;
  
  		if (cv1 instanceof IndependentTypeVariable2 || cv2 instanceof IndependentTypeVariable2)
  			return true;
  
  		if (isAGenericType(cv1.getType()))
  			return true;
  
  		if (isAGenericType(cv2.getType()))
  			return true;
  
  		return false;
  	}
  
  	/**
  	 * @param cv
  	 * @return a List of ITypeConstraint2s where cv is used
  	 */
  	@SuppressWarnings("unchecked")
  	public List<ITypeConstraint2> getUsedIn(ConstraintVariable2 cv) {
  		Object usedIn= cv.getData(USED_IN);
  		if (usedIn == null)
  			return Collections.emptyList();
  		else if (usedIn instanceof ArrayList)
  			return Collections.unmodifiableList((ArrayList<ITypeConstraint2>) usedIn);
  		else
  			return Collections.singletonList((ITypeConstraint2) usedIn);
  	}
  
  	public void newCu() {
  		pruneUnusedCuScopedCvs();
  		fCuScopedConstraintVariables.clear();
  		fTTypeCache.clear();
  	}
  
  	private void pruneUnusedCuScopedCvs() {
  		for (Iterator<ConstraintVariable2> iter= fCuScopedConstraintVariables.iterator(); iter.hasNext();) {
  			ConstraintVariable2 cv= iter.next();
  			pruneCvIfUnused(cv);
  		}
  	}
  
  	private boolean pruneCvIfUnused(ConstraintVariable2 cv) {
  		if (getUsedIn(cv).size() != 0)
  			return false;
  
  		if (cv.getTypeEquivalenceSet() != null) {
  			if (cv.getTypeEquivalenceSet().getContributingVariables().length > 0)
  				return false;
  		}
  
  		ArrayElementVariable2 arrayElementVariable= getArrayElementVariable(cv);
  		if (arrayElementVariable != null && ! pruneCvIfUnused(arrayElementVariable))
  			return false;
  
  		Map<String, CollectionElementVariable2> elementVariables= getElementVariables(cv);
  		for (Iterator<CollectionElementVariable2> iter= elementVariables.values().iterator(); iter.hasNext();) {
  			CollectionElementVariable2 elementVariable= iter.next();
  			if (! pruneCvIfUnused(elementVariable))
  				return false;
  		}
  
  		fConstraintVariables.remove(cv);
  		return true;
  	}
  
  	public ConstraintVariable2[] getAllConstraintVariables() {
  		ConstraintVariable2[] result= new ConstraintVariable2[fConstraintVariables.size()];
  		int i= 0;
  		for (Iterator<ConstraintVariable2> iter= fConstraintVariables.keySet().iterator(); iter.hasNext(); i++)
  			result[i]= iter.next();
  		return result;
  	}
  
  	public ITypeConstraint2[] getAllTypeConstraints() {
  		Set<ITypeConstraint2> typeConstraints= fTypeConstraints.keySet();
  		return typeConstraints.toArray(new ITypeConstraint2[typeConstraints.size()]);
  	}
  
  	public CastVariable2[] getCastVariables() {
  		return fCastVariables.toArray(new CastVariable2[fCastVariables.size()]);
  	}
  
  	/**
  	 * Controls calculation and storage of information for more readable toString() messages.
  	 * <p><em>Warning: This method is for testing purposes only and should not be called except from unit tests.</em></p>
  	 *
  	 * @param store <code>true</code> iff information for toString() should be stored
  	 */
  	public static void setStoreToString(boolean store) {
  		fStoreToString= store;
  	}
  
  	public void createSubtypeConstraint(ConstraintVariable2 cv1, ConstraintVariable2 cv2) {
  		if (! keep(cv1, cv2))
  			return;
  
  		ConstraintVariable2 storedCv1= storedCv(cv1);
  		ConstraintVariable2 storedCv2= storedCv(cv2);
  		ITypeConstraint2 typeConstraint= new SubTypeConstraint2(storedCv1, storedCv2);
  
  		Object storedTc= fTypeConstraints.get(typeConstraint);
  		if (storedTc == null) {
  			fTypeConstraints.put(typeConstraint, typeConstraint);
  		} else {
  			typeConstraint= (ITypeConstraint2) storedTc;
  		}
  
  		registerCvWithTc(storedCv1, typeConstraint);
  		registerCvWithTc(storedCv2, typeConstraint);
  	}
  
  	private ConstraintVariable2 storedCv(ConstraintVariable2 cv) {
  		Object stored= fConstraintVariables.get(cv);
  		if (stored == null) {
  			fConstraintVariables.put(cv, cv);
  			return cv;
  		} else {
  			return (ConstraintVariable2) stored;
  		}
  	}
  
  	private void registerCvWithTc(ConstraintVariable2 storedCv, ITypeConstraint2 typeConstraint) {
  		Object usedIn= storedCv.getData(USED_IN);
  		if (usedIn == null) {
  			storedCv.setData(USED_IN, typeConstraint);
  		} else if (usedIn instanceof ArrayList) {
  			@SuppressWarnings("unchecked")
  			ArrayList<ITypeConstraint2> usedInList= (ArrayList<ITypeConstraint2>) usedIn;
  			usedInList.add(typeConstraint);
  		} else {
  			ArrayList<ITypeConstraint2> usedInList= new ArrayList<ITypeConstraint2>(2);
  			usedInList.add((ITypeConstraint2) usedIn);
  			usedInList.add(typeConstraint);
  			storedCv.setData(USED_IN, usedInList);
  		}
  	}
  
  	public void createEqualsConstraint(ConstraintVariable2 leftElement, ConstraintVariable2 rightElement) {
  		if (leftElement == null || rightElement == null)
  			return;
  
  		TypeEquivalenceSet leftSet= leftElement.getTypeEquivalenceSet();
  		TypeEquivalenceSet rightSet= rightElement.getTypeEquivalenceSet();
  		if (leftSet == null) {
  			if (rightSet == null) {
  				TypeEquivalenceSet set= new TypeEquivalenceSet(leftElement, rightElement);
  				leftElement.setTypeEquivalenceSet(set);
  				rightElement.setTypeEquivalenceSet(set);
  			} else {
  				rightSet.add(leftElement);
  				leftElement.setTypeEquivalenceSet(rightSet);
  			}
  		} else {
  			if (rightSet == null) {
  				leftSet.add(rightElement);
  				rightElement.setTypeEquivalenceSet(leftSet);
  			} else if (leftSet == rightSet) {
  				return;
  			} else {
  				ConstraintVariable2[] cvs= rightSet.getContributingVariables();
  				leftSet.addAll(cvs);
  				for (int i= 0; i < cvs.length; i++)
  					cvs[i].setTypeEquivalenceSet(leftSet);
  			}
  		}
  	}
  
  	public TType createTType(ITypeBinding typeBinding) {
  		String key= typeBinding.getKey();
  		TType cached= fTTypeCache.get(key);
  		if (cached != null)
  			return cached;
  		TType type= fTypeEnvironment.create(typeBinding);
  		fTTypeCache.put(key, type);
  		return type;
  	}
  
  	private TType getBoxedType(ITypeBinding typeBinding, Expression expression) {
  		if (typeBinding == null)
  			return null;
  
  		if (! typeBinding.isPrimitive())
  			return createTType(typeBinding);
  
  		if (expression == null || ! expression.resolveBoxing())
  			return null;
  
  		ITypeBinding boxed= Bindings.getBoxedTypeBinding(typeBinding, expression.getAST());
  		return createTType(boxed);
  	}
  
  	public VariableVariable2 makeVariableVariable(IVariableBinding variableBinding) {
  		if (variableBinding == null)
  			return null;
  		TType type= getBoxedType(variableBinding.getType(), /*no boxing*/null);
  