| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| import org.eclipse.jdt.core.BindingKey; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.ITypeParameter; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.dom.ASTParser; |
| import org.eclipse.jdt.core.dom.ASTRequestor; |
| import org.eclipse.jdt.core.dom.IBinding; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| |
| import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; |
| |
| /** |
| * A type environment comprises a set of {@link TType}s that stand for Java {@link ITypeBinding}s. |
| * In contrast to type bindings, TTypes of the same type environment also work across project boundaries and |
| * across compiler environments, i.e. a type environment can handle bindings from multiple {@link ASTParser} sessions. |
| * |
| * @see TType |
| */ |
| public class TypeEnvironment { |
| |
| private static class ProjectKeyPair { |
| private final IJavaProject fProject; |
| private final String fBindingKey; |
| |
| public ProjectKeyPair(IJavaProject project, String bindingKey) { |
| fProject= project; |
| fBindingKey= bindingKey; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) |
| return true; |
| if (! (other instanceof ProjectKeyPair)) |
| return false; |
| ProjectKeyPair otherPair= (ProjectKeyPair) other; |
| return fProject.equals(otherPair.fProject) && fBindingKey.equals(otherPair.fBindingKey); |
| } |
| |
| @Override |
| public int hashCode() { |
| return fProject.hashCode() + fBindingKey.hashCode(); |
| } |
| } |
| |
| /** Type code for the primitive type "int". */ |
| public final PrimitiveType INT= new PrimitiveType(this, PrimitiveType.INT, BindingKey.createTypeBindingKey("int")); //$NON-NLS-1$ |
| /** Type code for the primitive type "char". */ |
| public final PrimitiveType CHAR = new PrimitiveType(this, PrimitiveType.CHAR, BindingKey.createTypeBindingKey("char")); //$NON-NLS-1$ |
| /** Type code for the primitive type "boolean". */ |
| public final PrimitiveType BOOLEAN = new PrimitiveType(this, PrimitiveType.BOOLEAN, BindingKey.createTypeBindingKey("boolean")); //$NON-NLS-1$ |
| /** Type code for the primitive type "short". */ |
| public final PrimitiveType SHORT = new PrimitiveType(this, PrimitiveType.SHORT, BindingKey.createTypeBindingKey("short")); //$NON-NLS-1$ |
| /** Type code for the primitive type "long". */ |
| public final PrimitiveType LONG = new PrimitiveType(this, PrimitiveType.LONG, BindingKey.createTypeBindingKey("long")); //$NON-NLS-1$ |
| /** Type code for the primitive type "float". */ |
| public final PrimitiveType FLOAT = new PrimitiveType(this, PrimitiveType.FLOAT, BindingKey.createTypeBindingKey("float")); //$NON-NLS-1$ |
| /** Type code for the primitive type "double". */ |
| public final PrimitiveType DOUBLE = new PrimitiveType(this, PrimitiveType.DOUBLE, BindingKey.createTypeBindingKey("double")); //$NON-NLS-1$ |
| /** Type code for the primitive type "byte". */ |
| public final PrimitiveType BYTE = new PrimitiveType(this, PrimitiveType.BYTE, BindingKey.createTypeBindingKey("byte")); //$NON-NLS-1$ |
| |
| /** Type code for the primitive type "null". */ |
| public final NullType NULL= new NullType(this); |
| |
| public final VoidType VOID= new VoidType(this); |
| |
| final PrimitiveType[] PRIMITIVE_TYPES= {INT, CHAR, BOOLEAN, SHORT, LONG, FLOAT, DOUBLE, BYTE}; |
| |
| private static final String[] BOXED_PRIMITIVE_NAMES= new String[] { |
| "java.lang.Integer", //$NON-NLS-1$ |
| "java.lang.Character", //$NON-NLS-1$ |
| "java.lang.Boolean", //$NON-NLS-1$ |
| "java.lang.Short", //$NON-NLS-1$ |
| "java.lang.Long", //$NON-NLS-1$ |
| "java.lang.Float", //$NON-NLS-1$ |
| "java.lang.Double", //$NON-NLS-1$ |
| "java.lang.Byte"}; //$NON-NLS-1$ |
| |
| private TType OBJECT_TYPE= null; |
| |
| private List<Map<TType, ArrayType>> fArrayTypes= new ArrayList<>(); |
| private Map<IJavaElement, StandardType> fStandardTypes= new HashMap<>(); |
| private Map<IJavaElement, GenericType> fGenericTypes= new HashMap<>(); |
| private Map<ProjectKeyPair, ParameterizedType> fParameterizedTypes= new HashMap<>(); |
| private Map<IJavaElement, RawType> fRawTypes= new HashMap<>(); |
| private Map<IJavaElement, TypeVariable> fTypeVariables= new HashMap<>(); |
| private Map<ProjectKeyPair, CaptureType> fCaptureTypes= new HashMap<>(); |
| private Map<TType, ExtendsWildcardType> fExtendsWildcardTypes= new HashMap<>(); |
| private Map<TType, SuperWildcardType> fSuperWildcardTypes= new HashMap<>(); |
| private UnboundWildcardType fUnboundWildcardType= null; |
| |
| private static final int MAX_ENTRIES= 1024; |
| private Map<TypeTuple, Boolean> fSubTypeCache= new LinkedHashMap<TypeTuple, Boolean>(50, 0.75f, true) { |
| private static final long serialVersionUID= 1L; |
| @Override |
| protected boolean removeEldestEntry(Map.Entry<TypeTuple, Boolean> eldest) { |
| return size() > MAX_ENTRIES; |
| } |
| }; |
| |
| /** |
| * Map from TType to its known subtypes, or <code>null</code> iff subtype |
| * information was not requested in the constructor. |
| */ |
| private Map<TType, ArrayList<TType>> fSubTypes; |
| /** |
| * If <code>true</code>, replace all capture types by their wildcard type. |
| * @since 3.7 |
| */ |
| private final boolean fRemoveCapures; |
| |
| public static ITypeBinding[] createTypeBindings(TType[] types, IJavaProject project) { |
| final Map<String, Object> mapping= new HashMap<>(); |
| List<String> keys= new ArrayList<>(); |
| for (int i= 0; i < types.length; i++) { |
| TType type= types[i]; |
| String bindingKey= type.getBindingKey(); |
| mapping.put(bindingKey, type); |
| keys.add(bindingKey); |
| } |
| ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); |
| parser.setProject(project); |
| parser.setResolveBindings(true); |
| parser.createASTs(new ICompilationUnit[0], keys.toArray(new String[keys.size()]), |
| new ASTRequestor() { |
| @Override |
| public void acceptBinding(String bindingKey, IBinding binding) { |
| mapping.put(bindingKey, binding); |
| } |
| }, null); |
| ITypeBinding[] result= new ITypeBinding[types.length]; |
| for (int i= 0; i < types.length; i++) { |
| TType type= types[i]; |
| String bindingKey= type.getBindingKey(); |
| Object value= mapping.get(bindingKey); |
| if (value instanceof ITypeBinding) { |
| result[i]= (ITypeBinding)value; |
| } |
| } |
| return result; |
| } |
| |
| public TypeEnvironment() { |
| this(false); |
| } |
| |
| public TypeEnvironment(boolean rememberSubtypes) { |
| this(rememberSubtypes, false); |
| } |
| |
| public TypeEnvironment(boolean rememberSubtypes, boolean removeCapures) { |
| if (rememberSubtypes) { |
| fSubTypes= new HashMap<>(); |
| } |
| fRemoveCapures= removeCapures; |
| } |
| |
| Map<TypeTuple, Boolean> getSubTypeCache() { |
| return fSubTypeCache; |
| } |
| |
| public TType create(ITypeBinding binding) { |
| if (binding.isPrimitive()) { |
| return createPrimitiveType(binding); |
| } else if (binding.isArray()) { |
| return createArrayType(binding); |
| } else if (binding.isRawType()) { |
| return createRawType(binding); |
| } else if (binding.isGenericType()) { |
| return createGenericType(binding); |
| } else if (binding.isParameterizedType()) { |
| return createParameterizedType(binding); |
| } else if (binding.isTypeVariable()) { |
| return createTypeVariable(binding); |
| } else if (binding.isWildcardType()) { |
| if (binding.getBound() == null) { |
| return createUnboundWildcardType(binding); |
| } else if (binding.isUpperbound()) { |
| return createExtendsWildCardType(binding); |
| } else { |
| return createSuperWildCardType(binding); |
| } |
| } else if (binding.isCapture()) { |
| if (fRemoveCapures) { |
| return create(binding.getWildcard()); |
| } else { |
| return createCaptureType(binding); |
| } |
| } |
| if ("null".equals(binding.getName())) //$NON-NLS-1$ |
| return NULL; |
| return createStandardType(binding); |
| } |
| |
| public TType[] create(ITypeBinding[] bindings) { |
| TType[] result= new TType[bindings.length]; |
| for (int i= 0; i < bindings.length; i++) { |
| result[i]= create(bindings[i]); |
| } |
| return result; |
| } |
| |
| /** |
| * Returns the TType for java.lang.Object. |
| * <p> |
| * Warning: currently returns <code>null</code> unless this type environment |
| * has already created its first hierarchy type or it has been initialized explicitly. |
| * |
| * @return the TType for java.lang.Object |
| * |
| * @see #initializeJavaLangObject(IJavaProject) |
| */ |
| public TType getJavaLangObject() { |
| return OBJECT_TYPE; |
| } |
| |
| public void initializeJavaLangObject(IJavaProject project) { |
| if (OBJECT_TYPE != null) |
| return; |
| |
| TType objectType= createStandardType("java.lang.Object", project); //$NON-NLS-1$ |
| Assert.isTrue(objectType.isJavaLangObject()); |
| } |
| |
| void initializeJavaLangObject(ITypeBinding object) { |
| if (OBJECT_TYPE != null) |
| return; |
| |
| TType objectType= createStandardType(object); |
| Assert.isTrue(objectType.isJavaLangObject()); |
| } |
| |
| PrimitiveType createUnBoxed(StandardType type) { |
| String name= type.getPlainPrettySignature(); |
| for (int i= 0; i < BOXED_PRIMITIVE_NAMES.length; i++) { |
| if (BOXED_PRIMITIVE_NAMES[i].equals(name)) |
| return PRIMITIVE_TYPES[i]; |
| } |
| return null; |
| } |
| |
| StandardType createBoxed(PrimitiveType type, IJavaProject focus) { |
| String fullyQualifiedName= BOXED_PRIMITIVE_NAMES[type.getId()]; |
| return createStandardType(fullyQualifiedName, focus); |
| } |
| |
| private StandardType createStandardType(String fullyQualifiedName, IJavaProject focus) { |
| try { |
| IType javaElementType= focus.findType(fullyQualifiedName); |
| StandardType result= fStandardTypes.get(javaElementType); |
| if (result != null) |
| return result; |
| ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); |
| parser.setProject(focus); |
| IBinding[] bindings= parser.createBindings(new IJavaElement[] {javaElementType} , null); |
| return createStandardType((ITypeBinding)bindings[0]); |
| } catch (JavaModelException e) { |
| // fall through |
| } |
| return null; |
| } |
| |
| Map<TType, ArrayList<TType>> getSubTypes() { |
| return fSubTypes; |
| } |
| |
| private void cacheSubType(TType supertype, TType result) { |
| if (fSubTypes == null) |
| return; |
| if (supertype == null) |
| supertype= OBJECT_TYPE; |
| |
| ArrayList<TType> subtypes= fSubTypes.get(supertype); |
| if (subtypes == null) { |
| subtypes= new ArrayList<>(5); |
| fSubTypes.put(supertype, subtypes); |
| } else { |
| Assert.isTrue(! subtypes.contains(result)); |
| } |
| subtypes.add(result); |
| } |
| |
| private void cacheSubTypes(TType[] interfaces, TType result) { |
| for (int i= 0; i < interfaces.length; i++) { |
| cacheSubType(interfaces[i], result); |
| } |
| } |
| |
| private TType createPrimitiveType(ITypeBinding binding) { |
| String name= binding.getName(); |
| String[] names= PrimitiveType.NAMES; |
| for (int i= 0; i < names.length; i++) { |
| if (name.equals(names[i])) { |
| return PRIMITIVE_TYPES[i]; |
| } |
| } |
| Assert.isTrue(false, "Primitive type " + name + "unkown"); //$NON-NLS-1$//$NON-NLS-2$ |
| return null; |
| } |
| |
| private ArrayType createArrayType(ITypeBinding binding) { |
| int index= binding.getDimensions() - 1; |
| TType elementType= create(binding.getElementType()); |
| Map<TType, ArrayType> arrayTypes= getArrayTypesMap(index); |
| ArrayType result= arrayTypes.get(elementType); |
| if (result != null) |
| return result; |
| result= new ArrayType(this); |
| arrayTypes.put(elementType, result); |
| result.initialize(binding, elementType); |
| return result; |
| } |
| |
| public ArrayType createArrayType(TType elementType, int dimensions) { |
| Assert.isTrue(! elementType.isArrayType()); |
| Assert.isTrue(! elementType.isAnonymous()); |
| Assert.isTrue(dimensions > 0); |
| |
| int index= dimensions - 1; |
| Map<TType, ArrayType> arrayTypes= getArrayTypesMap(index); |
| ArrayType result= arrayTypes.get(elementType); |
| if (result != null) |
| return result; |
| result= new ArrayType(this, BindingKey.createArrayTypeBindingKey(elementType.getBindingKey(), dimensions)); |
| arrayTypes.put(elementType, result); |
| result.initialize(elementType, dimensions); |
| return result; |
| } |
| |
| private Map<TType, ArrayType> getArrayTypesMap(int index) { |
| int oldLength= fArrayTypes.size(); |
| if (index >= oldLength) { |
| fArrayTypes.addAll(Collections.<Map<TType,ArrayType>>nCopies(index + 1 - oldLength, null)); |
| } |
| Map<TType, ArrayType> arrayTypes= fArrayTypes.get(index); |
| if (arrayTypes == null) { |
| arrayTypes= new HashMap<>(); |
| fArrayTypes.set(index, arrayTypes); |
| } |
| return arrayTypes; |
| } |
| |
| private StandardType createStandardType(ITypeBinding binding) { |
| IJavaElement javaElement= binding.getJavaElement(); |
| StandardType result= fStandardTypes.get(javaElement); |
| if (result != null) |
| return result; |
| result= new StandardType(this); |
| fStandardTypes.put(javaElement, result); |
| result.initialize(binding, (IType)javaElement); |
| if (OBJECT_TYPE == null && result.isJavaLangObject()) |
| OBJECT_TYPE= result; |
| return result; |
| } |
| |
| private GenericType createGenericType(ITypeBinding binding) { |
| IJavaElement javaElement= binding.getJavaElement(); |
| GenericType result= fGenericTypes.get(javaElement); |
| if (result != null) |
| return result; |
| result= new GenericType(this); |
| fGenericTypes.put(javaElement, result); |
| result.initialize(binding, (IType)javaElement); |
| cacheSubType(result.getSuperclass(), result); |
| cacheSubTypes(result.getInterfaces(), result); |
| return result; |
| } |
| |
| private ParameterizedType createParameterizedType(ITypeBinding binding) { |
| IJavaProject javaProject= binding.getJavaElement().getJavaProject(); |
| String bindingKey= binding.getKey(); |
| ProjectKeyPair pair= new ProjectKeyPair(javaProject, bindingKey); |
| ParameterizedType result= fParameterizedTypes.get(pair); |
| if (result != null) |
| return result; |
| result= new ParameterizedType(this); |
| fParameterizedTypes.put(pair, result); |
| result.initialize(binding, (IType)binding.getJavaElement()); |
| cacheSubType(result.getSuperclass(), result); |
| cacheSubTypes(result.getInterfaces(), result); |
| return result; |
| } |
| |
| private RawType createRawType(ITypeBinding binding) { |
| IJavaElement javaElement= binding.getJavaElement(); |
| RawType result= fRawTypes.get(javaElement); |
| if (result != null) |
| return result; |
| result= new RawType(this); |
| fRawTypes.put(javaElement, result); |
| result.initialize(binding, (IType)javaElement); |
| cacheSubType(result.getSuperclass(), result); |
| cacheSubTypes(result.getInterfaces(), result); |
| return result; |
| } |
| |
| private TType createUnboundWildcardType(ITypeBinding binding) { |
| if (fUnboundWildcardType == null) { |
| fUnboundWildcardType= new UnboundWildcardType(this); |
| fUnboundWildcardType.initialize(binding); |
| } |
| return fUnboundWildcardType; |
| } |
| |
| private TType createExtendsWildCardType(ITypeBinding binding) { |
| TType bound= create(binding.getBound()); |
| ExtendsWildcardType result= fExtendsWildcardTypes.get(bound); |
| if (result != null) |
| return result; |
| result= new ExtendsWildcardType(this); |
| fExtendsWildcardTypes.put(bound, result); |
| result.initialize(binding); |
| return result; |
| } |
| |
| private TType createSuperWildCardType(ITypeBinding binding) { |
| TType bound= create(binding.getBound()); |
| SuperWildcardType result= fSuperWildcardTypes.get(bound); |
| if (result != null) |
| return result; |
| result= new SuperWildcardType(this); |
| fSuperWildcardTypes.put(bound, result); |
| result.initialize(binding); |
| return result; |
| } |
| |
| private TypeVariable createTypeVariable(ITypeBinding binding) { |
| IJavaElement javaElement= binding.getJavaElement(); |
| TypeVariable result= fTypeVariables.get(javaElement); |
| if (result != null) |
| return result; |
| result= new TypeVariable(this); |
| fTypeVariables.put(javaElement, result); |
| result.initialize(binding, (ITypeParameter)javaElement); |
| return result; |
| } |
| |
| private CaptureType createCaptureType(ITypeBinding binding) { |
| IJavaProject javaProject= binding.getDeclaringClass().getJavaElement().getJavaProject(); |
| String bindingKey= binding.getKey(); |
| ProjectKeyPair pair= new ProjectKeyPair(javaProject, bindingKey); |
| CaptureType result= fCaptureTypes.get(pair); |
| if (result != null) |
| return result; |
| result= new CaptureType(this); |
| fCaptureTypes.put(pair, result); |
| result.initialize(binding, javaProject); |
| return result; |
| } |
| } |