/** * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) * and/or other contributors as indicated by the @authors tag. See the * copyright.txt file in the distribution for a full listing of all * contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mapstruct.ap.internal.model.common; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.NavigableSet; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.JavaStreamConstants; import org.mapstruct.ap.internal.util.RoundContext; import org.mapstruct.ap.internal.util.TypeHierarchyErroneousException; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity; import static org.mapstruct.ap.internal.model.common.ImplementationType.withLoadFactorAdjustment; /** * Factory creating {@link Type} instances. * * @author Gunnar Morling */ public class TypeFactory { private final Elements elementUtils; private final Types typeUtils; private final RoundContext roundContext; private final TypeMirror iterableType; private final TypeMirror collectionType; private final TypeMirror mapType; private final TypeMirror streamType; private final Map<String, ImplementationType> implementationTypes = new HashMap<String, ImplementationType>(); private final Map<String, String> importedQualifiedTypesBySimpleName = new HashMap<String, String>(); public TypeFactory(Elements elementUtils, Types typeUtils, RoundContext roundContext) { this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.roundContext = roundContext; iterableType = typeUtils.erasure( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); collectionType = typeUtils.erasure( elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() ); mapType = typeUtils.erasure( elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); TypeElement streamTypeElement = elementUtils.getTypeElement( JavaStreamConstants.STREAM_FQN ); streamType = streamTypeElement == null ? null : typeUtils.erasure( streamTypeElement.asType() ); implementationTypes.put( Iterable.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); implementationTypes.put( Collection.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); implementationTypes.put( List.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); implementationTypes.put( Set.class.getName(), withLoadFactorAdjustment( getType( HashSet.class ) ) ); implementationTypes.put( SortedSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); implementationTypes.put( NavigableSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); implementationTypes.put( Map.class.getName(), withLoadFactorAdjustment( getType( HashMap.class ) ) ); implementationTypes.put( SortedMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); implementationTypes.put( NavigableMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); implementationTypes.put( ConcurrentMap.class.getName(), withLoadFactorAdjustment( getType( ConcurrentHashMap.class ) ) ); implementationTypes.put( ConcurrentNavigableMap.class.getName(), withDefaultConstructor( getType( ConcurrentSkipListMap.class ) ) ); } public Type getType(Class<?> type) { return type.isPrimitive() ? getType( getPrimitiveType( type ) ) : getType( type.getCanonicalName() ); } public Type getType(String canonicalName) { TypeElement typeElement = elementUtils.getTypeElement( canonicalName ); if ( typeElement == null ) { throw new AnnotationProcessingException( "Couldn't find type " + canonicalName + ". Are you missing a dependency on your classpath?" ); } return getType( typeElement ); } /** * Determines if the type with the given full qualified name is part of the classpath * * @param canonicalName Name of the type to be checked for availability * @return true if the type with the given full qualified name is part of the classpath. */ public boolean isTypeAvailable(String canonicalName) { return null != elementUtils.getTypeElement( canonicalName ); } public Type getWrappedType(Type type ) { Type result = type; if ( type.isPrimitive() ) { PrimitiveType typeMirror = (PrimitiveType) type.getTypeMirror(); result = getType( typeUtils.boxedClass( typeMirror ) ); } return result; } public Type getType(TypeElement typeElement) { return getType( typeElement.asType() ); } public Type getType(TypeMirror mirror) { if ( !canBeProcessed( mirror ) ) { throw new TypeHierarchyErroneousException( mirror ); } ImplementationType implementationType = getImplementationType( mirror ); boolean isIterableType = typeUtils.isSubtype( mirror, iterableType ); boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType ); boolean isMapType = typeUtils.isSubtype( mirror, mapType ); boolean isStreamType = streamType != null && typeUtils.isSubtype( mirror, streamType ); boolean isEnumType; boolean isInterface; String name; String packageName; String qualifiedName; TypeElement typeElement; Type componentType; if ( mirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) mirror; isEnumType = declaredType.asElement().getKind() == ElementKind.ENUM; isInterface = declaredType.asElement().getKind() == ElementKind.INTERFACE; name = declaredType.asElement().getSimpleName().toString(); typeElement = (TypeElement) declaredType.asElement(); if ( typeElement != null ) { packageName = elementUtils.getPackageOf( typeElement ).getQualifiedName().toString(); qualifiedName = typeElement.getQualifiedName().toString(); } else { packageName = null; qualifiedName = name; } componentType = null; } else if ( mirror.getKind() == TypeKind.ARRAY ) { TypeMirror componentTypeMirror = getComponentType( mirror ); if ( componentTypeMirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) componentTypeMirror; TypeElement componentTypeElement = (TypeElement) declaredType.asElement(); name = componentTypeElement.getSimpleName().toString() + "[]"; packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString(); qualifiedName = componentTypeElement.getQualifiedName().toString() + "[]"; } else { name = mirror.toString(); packageName = null; qualifiedName = name; } isEnumType = false; isInterface = false; typeElement = null; componentType = getType( componentTypeMirror ); } else { isEnumType = false; isInterface = false; name = mirror.toString(); packageName = null; qualifiedName = name; typeElement = null; componentType = null; } return new Type( typeUtils, elementUtils, this, mirror, typeElement, getTypeParameters( mirror, false ), implementationType, componentType, packageName, name, qualifiedName, isInterface, isEnumType, isIterableType, isCollectionType, isMapType, isStreamType, isImported( name, qualifiedName ) ); } /** * Returns the Type that represents the declared Class type of the given type. For primitive types, the boxed class * will be used. Examples: * <ul> * <li>If type represents {@code java.lang.Integer}, it will return the type that represents {@code Class<Integer>}. * </li> * <li>If type represents {@code int}, it will return the type that represents {@code Class<Integer>}.</li> * </ul> * * @param type the type to return the declared class type for * @return the type representing {@code Class<type>}. */ public Type classTypeOf(Type type) { TypeMirror typeToUse; if ( type.isVoid() ) { return null; } else if ( type.isPrimitive() ) { typeToUse = typeUtils.boxedClass( (PrimitiveType) type.getTypeMirror() ).asType(); } else { typeToUse = type.getTypeMirror(); } return getType( typeUtils.getDeclaredType( elementUtils.getTypeElement( "java.lang.Class" ), typeToUse ) ); } /** * Get the ExecutableType for given method as part of usedMapper. Possibly parameterized types in method declaration * will be evaluated to concrete types then. * * <b>IMPORTANT:</b> This should only be used from the Processors, as they are operating over executable elements. * The internals should not be using this function and should not be using the {@link ExecutableElement} directly. * * @param includingType the type on which's scope the method type shall be evaluated * @param method the method * @return the ExecutableType representing the method as part of usedMapper */ public ExecutableType getMethodType(DeclaredType includingType, ExecutableElement method) { TypeMirror asMemberOf = typeUtils.asMemberOf( includingType, method ); return (ExecutableType) asMemberOf; } /** * Get the Type for given method as part of usedMapper. Possibly parameterized types in method declaration * will be evaluated to concrete types then. * * @param includingType the type on which's scope the method type shall be evaluated * @param method the method * * @return the ExecutableType representing the method as part of usedMapper */ public TypeMirror getMethodType(DeclaredType includingType, Element method) { return typeUtils.asMemberOf( includingType, method ); } public Parameter getSingleParameter(DeclaredType includingType, Accessor method) { ExecutableElement executable = method.getExecutable(); if ( executable == null ) { return null; } List<? extends VariableElement> parameters = executable.getParameters(); if ( parameters.size() != 1 ) { //TODO: Log error return null; } return Collections.first( getParameters( includingType, method ) ); } public List<Parameter> getParameters(DeclaredType includingType, Accessor accessor) { ExecutableElement method = accessor.getExecutable(); TypeMirror methodType = getMethodType( includingType, accessor.getElement() ); if ( method == null || methodType.getKind() != TypeKind.EXECUTABLE ) { return new ArrayList<Parameter>(); } return getParameters( (ExecutableType) methodType, method ); } public List<Parameter> getParameters(ExecutableType methodType, ExecutableElement method) { List<? extends TypeMirror> parameterTypes = methodType.getParameterTypes(); List<? extends VariableElement> parameters = method.getParameters(); List<Parameter> result = new ArrayList<Parameter>( parameters.size() ); Iterator<? extends VariableElement> varIt = parameters.iterator(); Iterator<? extends TypeMirror> typesIt = parameterTypes.iterator(); for ( ; varIt.hasNext(); ) { VariableElement parameter = varIt.next(); TypeMirror parameterType = typesIt.next(); result.add( Parameter.forElementAndType( parameter, getType( parameterType ) ) ); } return result; } public Type getReturnType(DeclaredType includingType, Accessor accessor) { Type type; TypeMirror accessorType = getMethodType( includingType, accessor.getElement() ); if ( isExecutableType( accessorType ) ) { type = getType( ( (ExecutableType) accessorType ).getReturnType() ); } else { type = getType( accessorType ); } return type; } private boolean isExecutableType(TypeMirror accessorType) { return accessorType.getKind() == TypeKind.EXECUTABLE; } public Type getReturnType(ExecutableType method) { return getType( method.getReturnType() ); } public List<Type> getThrownTypes(ExecutableType method) { List<Type> thrownTypes = new ArrayList<Type>(); for ( TypeMirror exceptionType : method.getThrownTypes() ) { thrownTypes.add( getType( exceptionType ) ); } return thrownTypes; } private List<Type> getTypeParameters(TypeMirror mirror, boolean isImplementationType) { if ( mirror.getKind() != TypeKind.DECLARED ) { return java.util.Collections.emptyList(); } DeclaredType declaredType = (DeclaredType) mirror; List<Type> typeParameters = new ArrayList<Type>( declaredType.getTypeArguments().size() ); for ( TypeMirror typeParameter : declaredType.getTypeArguments() ) { if ( isImplementationType ) { typeParameters.add( getType( typeParameter ).getTypeBound() ); } else { typeParameters.add( getType( typeParameter ) ); } } return typeParameters; } private TypeMirror getPrimitiveType(Class<?> primitiveType) { return primitiveType == byte.class ? typeUtils.getPrimitiveType( TypeKind.BYTE ) : primitiveType == short.class ? typeUtils.getPrimitiveType( TypeKind.SHORT ) : primitiveType == int.class ? typeUtils.getPrimitiveType( TypeKind.INT ) : primitiveType == long.class ? typeUtils.getPrimitiveType( TypeKind.LONG ) : primitiveType == float.class ? typeUtils.getPrimitiveType( TypeKind.FLOAT ) : primitiveType == double.class ? typeUtils.getPrimitiveType( TypeKind.DOUBLE ) : primitiveType == boolean.class ? typeUtils.getPrimitiveType( TypeKind.BOOLEAN ) : primitiveType == char.class ? typeUtils.getPrimitiveType( TypeKind.CHAR ) : typeUtils.getPrimitiveType( TypeKind.VOID ); } private ImplementationType getImplementationType(TypeMirror mirror) { if ( mirror.getKind() != TypeKind.DECLARED ) { return null; } DeclaredType declaredType = (DeclaredType) mirror; ImplementationType implementation = implementationTypes.get( ( (TypeElement) declaredType.asElement() ).getQualifiedName() .toString() ); if ( implementation != null ) { Type implementationType = implementation.getType(); Type replacement = new Type( typeUtils, elementUtils, this, typeUtils.getDeclaredType( implementationType.getTypeElement(), declaredType.getTypeArguments().toArray( new TypeMirror[] { } ) ), implementationType.getTypeElement(), getTypeParameters( mirror, true ), null, null, implementationType.getPackageName(), implementationType.getName(), implementationType.getFullyQualifiedName(), implementationType.isInterface(), implementationType.isEnumType(), implementationType.isIterableType(), implementationType.isCollectionType(), implementationType.isMapType(), implementationType.isStreamType(), isImported( implementationType.getName(), implementationType.getFullyQualifiedName() ) ); return implementation.createNew( replacement ); } return null; } private TypeMirror getComponentType(TypeMirror mirror) { if ( mirror.getKind() != TypeKind.ARRAY ) { return null; } ArrayType arrayType = (ArrayType) mirror; return arrayType.getComponentType(); } private boolean isImported(String name, String qualifiedName) { String trimmedName = TypeFactory.trimSimpleClassName( name ); String trimmedQualifiedName = TypeFactory.trimSimpleClassName( qualifiedName ); String importedType = importedQualifiedTypesBySimpleName.get( trimmedName ); boolean imported = false; if ( importedType != null ) { if ( importedType.equals( trimmedQualifiedName ) ) { imported = true; } } else { importedQualifiedTypesBySimpleName.put( trimmedName, trimmedQualifiedName ); imported = true; } return imported; } /** * Converts any collection type, e.g. {@code List<T>} to {@code Collection<T>} and any map type, e.g. * {@code HashMap<K,V>} to {@code Map<K,V>}. * * @param collectionOrMap any collection or map type * @return the type representing {@code Collection<T>} or {@code Map<K,V>}, if the argument type is a subtype of * {@code Collection<T>} or of {@code Map<K,V>} respectively. */ public Type asCollectionOrMap(Type collectionOrMap) { List<Type> originalParameters = collectionOrMap.getTypeParameters(); TypeMirror[] originalParameterMirrors = new TypeMirror[originalParameters.size()]; int i = 0; for ( Type param : originalParameters ) { originalParameterMirrors[i++] = param.getTypeMirror(); } if ( collectionOrMap.isCollectionType() && !"java.util.Collection".equals( collectionOrMap.getFullyQualifiedName() ) ) { return getType( typeUtils.getDeclaredType( elementUtils.getTypeElement( "java.util.Collection" ), originalParameterMirrors ) ); } else if ( collectionOrMap.isMapType() && !"java.util.Map".equals( collectionOrMap.getFullyQualifiedName() ) ) { return getType( typeUtils.getDeclaredType( elementUtils.getTypeElement( "java.util.Map" ), originalParameterMirrors ) ); } return collectionOrMap; } /** * creates a void return type * * @return void type */ public Type createVoidType() { return getType( typeUtils.getNoType( TypeKind.VOID ) ); } /** * Establishes the type bound: * <ol> * <li>{@code<? extends Number>}, returns Number</li> * <li>{@code<? super Number>}, returns Number</li> * <li>{@code<?>}, returns Object</li> * <li>{@code<T extends Number>, returns Number}</li> * </ol> * * @param typeMirror the type to return the bound for * @return the bound for this parameter */ public TypeMirror getTypeBound(TypeMirror typeMirror) { if ( typeMirror.getKind() == TypeKind.WILDCARD ) { WildcardType wildCardType = (WildcardType) typeMirror; if ( wildCardType.getExtendsBound() != null ) { return wildCardType.getExtendsBound(); } if ( wildCardType.getSuperBound() != null ) { return wildCardType.getSuperBound(); } String wildCardName = wildCardType.toString(); if ( "?".equals( wildCardName ) ) { return elementUtils.getTypeElement( Object.class.getCanonicalName() ).asType(); } } else if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { TypeVariable typeVariableType = (TypeVariable) typeMirror; if ( typeVariableType.getUpperBound() != null ) { return typeVariableType.getUpperBound(); } // Lowerbounds intentionally left out: Type variables otherwise have a lower bound of NullType. } return typeMirror; } /** * It strips the all the {@code []} from the {@code className}. * * E.g. * <pre> * trimSimpleClassName("String[][][]") -> "String" * trimSimpleClassName("String[]") -> "String" * </pre> * * @param className that needs to be trimmed * * @return the trimmed {@code className}, or {@code null} if the {@code className} was {@code null} */ static String trimSimpleClassName(String className) { if ( className == null ) { return null; } String trimmedClassName = className; while ( trimmedClassName.endsWith( "[]" ) ) { trimmedClassName = trimmedClassName.substring( 0, trimmedClassName.length() - 2 ); } return trimmedClassName; } /** * Whether the given type is ready to be processed or not. It can be processed if it is not of kind * {@link TypeKind#ERROR} and all {@link AstModifyingAnnotationProcessor}s (if any) indicated that they've fully * processed the type. */ private boolean canBeProcessed(TypeMirror type) { if ( type.getKind() == TypeKind.ERROR ) { return false; } if ( type.getKind() != TypeKind.DECLARED ) { return true; } if ( roundContext.isReadyForProcessing( type ) ) { return true; } List<AstModifyingAnnotationProcessor> astModifyingAnnotationProcessors = roundContext .getAnnotationProcessorContext() .getAstModifyingAnnotationProcessors(); for ( AstModifyingAnnotationProcessor processor : astModifyingAnnotationProcessors ) { if ( !processor.isTypeComplete( type ) ) { return false; } } roundContext.addTypeReadyForProcessing( type ); return true; } }