/**
* 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.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.Filters;
import org.mapstruct.ap.internal.util.Nouns;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor;
/**
* Represents (a reference to) the type of a bean property, parameter etc. Types are managed per generated source file.
* Each type corresponds to a {@link TypeMirror}, i.e. there are different instances for e.g. {@code Set<String>} and
* {@code Set<Integer>}.
* <p>
* Allows for a unified handling of declared and primitive types and usage within templates. Instances are obtained
* through {@link TypeFactory}.
*
* @author Gunnar Morling
*/
public class Type extends ModelElement implements Comparable<Type> {
private final Types typeUtils;
private final Elements elementUtils;
private final TypeFactory typeFactory;
private final TypeMirror typeMirror;
private final TypeElement typeElement;
private final List<Type> typeParameters;
private final ImplementationType implementationType;
private final Type componentType;
private final String packageName;
private final String name;
private final String qualifiedName;
private final boolean isInterface;
private final boolean isEnumType;
private final boolean isIterableType;
private final boolean isCollectionType;
private final boolean isMapType;
private final boolean isImported;
private final boolean isVoid;
private final boolean isStream;
private final List<String> enumConstants;
private Map<String, Accessor> readAccessors = null;
private Map<String, ExecutableElementAccessor> presenceCheckers = null;
private List<Accessor> allAccessors = null;
private List<Accessor> setters = null;
private List<Accessor> adders = null;
private List<Accessor> alternativeTargetAccessors = null;
private Type boundingBase = null;
private Boolean hasEmptyAccessibleContructor;
//CHECKSTYLE:OFF
public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory,
TypeMirror typeMirror, TypeElement typeElement,
List<Type> typeParameters, ImplementationType implementationType, Type componentType,
String packageName, String name, String qualifiedName,
boolean isInterface, boolean isEnumType, boolean isIterableType,
boolean isCollectionType, boolean isMapType, boolean isStreamType, boolean isImported) {
this.typeUtils = typeUtils;
this.elementUtils = elementUtils;
this.typeFactory = typeFactory;
this.typeMirror = typeMirror;
this.typeElement = typeElement;
this.typeParameters = typeParameters;
this.componentType = componentType;
this.implementationType = implementationType;
this.packageName = packageName;
this.name = name;
this.qualifiedName = qualifiedName;
this.isInterface = isInterface;
this.isEnumType = isEnumType;
this.isIterableType = isIterableType;
this.isCollectionType = isCollectionType;
this.isMapType = isMapType;
this.isStream = isStreamType;
this.isImported = isImported;
this.isVoid = typeMirror.getKind() == TypeKind.VOID;
if ( isEnumType ) {
enumConstants = new ArrayList<String>();
for ( Element element : typeElement.getEnclosedElements() ) {
// #162: The check for visibility shouldn't be required, but the Eclipse compiler implementation
// exposes non-enum members otherwise
if ( element.getKind() == ElementKind.ENUM_CONSTANT &&
element.getModifiers().contains( Modifier.PUBLIC ) ) {
enumConstants.add( element.getSimpleName().toString() );
}
}
}
else {
enumConstants = Collections.emptyList();
}
}
//CHECKSTYLE:ON
public TypeMirror getTypeMirror() {
return typeMirror;
}
public TypeElement getTypeElement() {
return typeElement;
}
public String getPackageName() {
return packageName;
}
public String getName() {
return name;
}
public List<Type> getTypeParameters() {
return typeParameters;
}
public Type getComponentType() {
return componentType;
}
public boolean isPrimitive() {
return typeMirror.getKind().isPrimitive();
}
public boolean isInterface() {
return isInterface;
}
public boolean isEnumType() {
return isEnumType;
}
public boolean isVoid() {
return isVoid;
}
public boolean isAbstract() {
return typeElement != null && typeElement.getModifiers().contains( Modifier.ABSTRACT );
}
/**
* @return this type's enum constants in case it is an enum, an empty list otherwise.
*/
public List<String> getEnumConstants() {
return enumConstants;
}
/**
* Returns the implementation type to be instantiated in case this type is an interface iterable, collection or map
* type. The type will have the correct type arguments, so if this type e.g. represents {@code Set<String>}, the
* implementation type is {@code HashSet<String>}.
*
* @return The implementation type to be instantiated in case this type is an interface iterable, collection or map
* type, {@code null} otherwise.
*/
public Type getImplementationType() {
return implementationType != null ? implementationType.getType() : null;
}
public ImplementationType getImplementation() {
return implementationType;
}
/**
* Whether this type is a sub-type of {@link Iterable} or an array type.
*
* @return {@code true} if this type is a sub-type of {@link Iterable} or an array type, {@code false} otherwise.
*/
public boolean isIterableType() {
return isIterableType || isArrayType();
}
/**
* Whether this type is a sub-type of{@link Iterable}, {@link java.util.stream.Stream} or an array type
*
* @return {@code true} if this type is a sub-type of{@link Iterable}, {@link java.util.stream.Stream} or
* an array type, {@code false} otherwise
*/
public boolean isIterableOrStreamType() {
return isIterableType() || isStreamType();
}
public boolean isCollectionType() {
return isCollectionType;
}
public boolean isMapType() {
return isMapType;
}
public boolean isCollectionOrMapType() {
return isCollectionType || isMapType;
}
public boolean isArrayType() {
return componentType != null;
}
public boolean isTypeVar() {
return (typeMirror.getKind() == TypeKind.TYPEVAR);
}
/**
* Whether this type is a sub-type of {@link java.util.stream.Stream}.
*
* @return {@code true} it this type is a sub-type of {@link java.util.stream.Stream}, {@code false otherwise}
*/
public boolean isStreamType() {
return isStream;
}
public boolean isWildCardSuperBound() {
boolean result = false;
if ( typeMirror.getKind() == TypeKind.WILDCARD ) {
WildcardType wildcardType = (WildcardType) typeMirror;
result = wildcardType.getSuperBound() != null;
}
return result;
}
public boolean isWildCardExtendsBound() {
boolean result = false;
if ( typeMirror.getKind() == TypeKind.WILDCARD ) {
WildcardType wildcardType = (WildcardType) typeMirror;
result = wildcardType.getExtendsBound() != null;
}
return result;
}
public String getFullyQualifiedName() {
return qualifiedName;
}
/**
* @return The name of this type as to be used within import statements.
*/
public String getImportName() {
return isArrayType() ? qualifiedName.substring( 0, qualifiedName.length() - 2 ) : qualifiedName;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> result = new HashSet<Type>();
if ( getTypeMirror().getKind() == TypeKind.DECLARED ) {
result.add( this );
}
if ( componentType != null ) {
result.addAll( componentType.getImportTypes() );
}
for ( Type parameter : typeParameters ) {
result.addAll( parameter.getImportTypes() );
}
if ( boundingBase != null ) {
result.addAll( boundingBase.getImportTypes() );
}
return result;
}
/**
* Whether this type is imported by means of an import statement in the currently generated source file (meaning it
* can be referenced in the generated source using its simple name) or not (meaning it has to be referenced using
* the fully-qualified name).
*
* @return {@code true} if the type is imported, {@code false} otherwise.
*/
public boolean isImported() {
return isImported;
}
/**
* @param annotationTypeName the fully qualified name of the annotation type
*
* @return true, if the type is annotated with an annotation of the specified type (super-types are not inspected)
*/
public boolean isAnnotatedWith(String annotationTypeName) {
List<? extends AnnotationMirror> annotationMirrors = typeElement.getAnnotationMirrors();
for ( AnnotationMirror mirror : annotationMirrors ) {
Name mirrorAnnotationName = ( (TypeElement) mirror.getAnnotationType().asElement() ).getQualifiedName();
if ( mirrorAnnotationName.contentEquals( annotationTypeName ) ) {
return true;
}
}
return false;
}
public Type erasure() {
return new Type(
typeUtils,
elementUtils,
typeFactory,
typeUtils.erasure( typeMirror ),
typeElement,
typeParameters,
implementationType,
componentType,
packageName,
name,
qualifiedName,
isInterface,
isEnumType,
isIterableType,
isCollectionType,
isMapType,
isStream,
isImported
);
}
/**
* Whether this type is assignable to the given other type.
*
* @param other The other type.
*
* @return {@code true} if and only if this type is assignable to the given other type.
*/
// TODO This doesn't yet take wild card types into account; e.g. ? extends Integer wouldn't be assignable to Number
// atm.
public boolean isAssignableTo(Type other) {
if ( equals( other ) ) {
return true;
}
return typeUtils.isAssignable( typeMirror, other.typeMirror );
}
/**
* getPropertyReadAccessors
*
* @return an unmodifiable map of all read accessors (including 'is' for booleans), indexed by property name
*/
public Map<String, Accessor> getPropertyReadAccessors() {
if ( readAccessors == null ) {
List<Accessor> getterList = Filters.getterMethodsIn( getAllAccessors() );
Map<String, Accessor> modifiableGetters = new LinkedHashMap<String, Accessor>();
for ( Accessor getter : getterList ) {
String propertyName = Executables.getPropertyName( getter );
if ( modifiableGetters.containsKey( propertyName ) ) {
// In the DefaultAccessorNamingStrategy, this can only be the case for Booleans: isFoo() and
// getFoo(); The latter is preferred.
if ( !getter.getSimpleName().toString().startsWith( "is" ) ) {
modifiableGetters.put( Executables.getPropertyName( getter ), getter );
}
}
else {
modifiableGetters.put( Executables.getPropertyName( getter ), getter );
}
}
List<Accessor> fieldsList = Filters.fieldsIn( getAllAccessors() );
for ( Accessor field : fieldsList ) {
String propertyName = Executables.getPropertyName( field );
if ( !modifiableGetters.containsKey( propertyName ) ) {
// If there was no getter or is method for booleans, then resort to the field.
// If a field was already added do not add it again.
modifiableGetters.put( propertyName, field );
}
}
readAccessors = Collections.unmodifiableMap( modifiableGetters );
}
return readAccessors;
}
/**
* getPropertyPresenceCheckers
*
* @return an unmodifiable map of all presence checkers, indexed by property name
*/
public Map<String, ExecutableElementAccessor> getPropertyPresenceCheckers() {
if ( presenceCheckers == null ) {
List<ExecutableElementAccessor> checkerList = Filters.presenceCheckMethodsIn( getAllAccessors() );
Map<String, ExecutableElementAccessor> modifiableCheckers = new LinkedHashMap<String,
ExecutableElementAccessor>();
for ( ExecutableElementAccessor checker : checkerList ) {
modifiableCheckers.put( Executables.getPropertyName( checker ), checker );
}
presenceCheckers = Collections.unmodifiableMap( modifiableCheckers );
}
return presenceCheckers;
}
/**
* getPropertyWriteAccessors returns a map of the write accessors according to the CollectionMappingStrategy. These
* accessors include:
* <ul>
* <li>setters, the obvious candidate :-), {@link #getSetters() }</li>
* <li>readAccessors, for collections that do not have a setter, e.g. for JAXB generated collection attributes
* {@link #getPropertyReadAccessors() }</li>
* <li>adders, typically for from table generated entities, {@link #getAdders() }</li>
* </ul>
*
* @param cmStrategy collection mapping strategy
* @return an unmodifiable map of all write accessors indexed by property name
*/
public Map<String, Accessor> getPropertyWriteAccessors( CollectionMappingStrategyPrism cmStrategy ) {
// collect all candidate target accessors
List<Accessor> candidates = new ArrayList<Accessor>( getSetters() );
candidates.addAll( getAlternativeTargetAccessors() );
Map<String, Accessor> result = new LinkedHashMap<String, Accessor>();
for ( Accessor candidate : candidates ) {
String targetPropertyName = Executables.getPropertyName( candidate );
Accessor readAccessor = getPropertyReadAccessors().get( targetPropertyName );
Type preferredType = determinePreferredType( readAccessor );
Type targetType = determineTargetType( candidate );
// A target access is in general a setter method on the target object. However, in case of collections,
// the current target accessor can also be a getter method.
// The following if block, checks if the target accessor should be overruled by an add method.
if ( cmStrategy == CollectionMappingStrategyPrism.SETTER_PREFERRED
|| cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED
|| cmStrategy == CollectionMappingStrategyPrism.TARGET_IMMUTABLE ) {
// first check if there's a setter method.
Accessor adderMethod = null;
if ( Executables.isSetterMethod( candidate )
// ok, the current accessor is a setter. So now the strategy determines what to use
&& cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) {
adderMethod = getAdderForType( targetType, targetPropertyName );
}
else if ( Executables.isGetterMethod( candidate ) ) {
// the current accessor is a getter (no setter available). But still, an add method is according
// to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter.
adderMethod = getAdderForType( targetType, targetPropertyName );
}
if ( adderMethod != null ) {
// an adder has been found (according strategy) so overrule current choice.
candidate = adderMethod;
}
}
else if ( Executables.isFieldAccessor( candidate ) && ( Executables.isFinal( candidate ) ||
result.containsKey( targetPropertyName ) ) ) {
// if the candidate is a field and a mapping already exists, then use that one, skip it.
continue;
}
Accessor previousCandidate = result.get( targetPropertyName );
if ( previousCandidate == null || preferredType == null || ( targetType != null
&& typeUtils.isAssignable( preferredType.getTypeMirror(), targetType.getTypeMirror() ) ) ) {
result.put( targetPropertyName, candidate );
}
}
return result;
}
private Type determinePreferredType(Accessor readAccessor) {
if ( readAccessor != null ) {
return typeFactory.getReturnType( (DeclaredType) typeMirror, readAccessor );
}
return null;
}
private Type determineTargetType(Accessor candidate) {
Parameter parameter = typeFactory.getSingleParameter( (DeclaredType) typeMirror, candidate );
if ( parameter != null ) {
return parameter.getType();
}
else if ( Executables.isGetterMethod( candidate ) || Executables.isFieldAccessor( candidate ) ) {
return typeFactory.getReturnType( (DeclaredType) typeMirror, candidate );
}
return null;
}
private List<Accessor> getAllAccessors() {
if ( allAccessors == null ) {
allAccessors = Executables.getAllEnclosedAccessors( elementUtils, typeElement );
}
return allAccessors;
}
/**
* Tries to find an addMethod in this type for given collection property in this type.
*
* Matching occurs on:
* <ol>
* <li>The generic type parameter type of the collection should match the adder method argument</li>
* <li>When there are more candidates, property name is made singular (as good as is possible). This routine
* looks for a matching add method name.</li>
* <li>The singularization rules of Dali are used to make a property name singular. This routine
* looks for a matching add method name.</li>
* </ol>
*
* @param collectionProperty property type (assumed collection) to find the adder method for
* @param pluralPropertyName the property name (assumed plural)
*
* @return corresponding adder method for getter when present
*/
private Accessor getAdderForType(Type collectionProperty, String pluralPropertyName) {
List<Accessor> candidates = new ArrayList<Accessor>();
if ( collectionProperty.isCollectionType ) {
// this is a collection, so this can be done always
if ( !collectionProperty.getTypeParameters().isEmpty() ) {
// there's only one type arg to a collection
TypeMirror typeArg = collectionProperty.getTypeParameters().get( 0 ).getTypeMirror();
// now, look for a method that
// 1) starts with add,
// 2) and has typeArg as one and only arg
List<Accessor> adderList = getAdders();
for ( Accessor adder : adderList ) {
ExecutableElement executable = adder.getExecutable();
if ( executable == null ) {
// it should not be null, but to be safe
continue;
}
VariableElement arg = executable.getParameters().get( 0 );
if ( arg.asType().equals( typeArg ) ) {
candidates.add( adder );
}
}
}
}
if ( candidates.isEmpty() ) {
return null;
}
else if ( candidates.size() == 1 ) {
return candidates.get( 0 );
}
else {
for ( Accessor candidate : candidates ) {
String elementName = Executables.getElementNameForAdder( candidate );
if ( elementName != null && elementName.equals( Nouns.singularize( pluralPropertyName ) ) ) {
return candidate;
}
}
}
return null;
}
/**
* getSetters
*
* @return an unmodifiable list of all setters
*/
private List<Accessor> getSetters() {
if ( setters == null ) {
setters = Collections.unmodifiableList( Filters.setterMethodsIn( getAllAccessors() ) );
}
return setters;
}
/**
* Alternative accessors could be a getter for a collection / map. By means of the
* {@link Collection#addAll(Collection) } or {@link Map#putAll(Map)} this getter can still be used as
* targetAccessor. JAXB XJC tool generates such constructs. This method can be extended when new cases come along.
* getAdders
*
* @return an unmodifiable list of all adders
*/
private List<Accessor> getAdders() {
if ( adders == null ) {
adders = Collections.unmodifiableList( Filters.adderMethodsIn( getAllAccessors() ) );
}
return adders;
}
/**
* Alternative accessors could be a getter for a collection. By means of the
* {@link java.util.Collection#addAll(java.util.Collection) } this getter can still
* be used as targetAccessor. JAXB XJC tool generates such constructs.
*
* This method can be extended when new cases come along.
*
* @return an unmodifiable list of alternative target accessors.
*/
private List<Accessor> getAlternativeTargetAccessors() {
if ( alternativeTargetAccessors == null ) {
List<Accessor> result = new ArrayList<Accessor>();
List<Accessor> setterMethods = getSetters();
List<Accessor> readAccessors =
new ArrayList<Accessor>( getPropertyReadAccessors().values() );
// All the fields are also alternative accessors
readAccessors.addAll( Filters.fieldsIn( getAllAccessors() ) );
// there could be a read accessor (field or method) for a list/map that is not present as setter.
// an accessor could substitute the setter in that case and act as setter.
// (assuming it is initialized)
for ( Accessor readAccessor : readAccessors ) {
if ( isCollectionOrMap( readAccessor ) &&
!correspondingSetterMethodExists( readAccessor, setterMethods ) ) {
result.add( readAccessor );
}
else if ( Executables.isFieldAccessor( readAccessor ) &&
!correspondingSetterMethodExists( readAccessor, setterMethods ) ) {
result.add( readAccessor );
}
}
alternativeTargetAccessors = Collections.unmodifiableList( result );
}
return alternativeTargetAccessors;
}
private boolean correspondingSetterMethodExists(Accessor getterMethod,
List<Accessor> setterMethods) {
String getterPropertyName = Executables.getPropertyName( getterMethod );
for ( Accessor setterMethod : setterMethods ) {
String setterPropertyName = Executables.getPropertyName( setterMethod );
if ( getterPropertyName.equals( setterPropertyName ) ) {
return true;
}
}
return false;
}
private boolean isCollectionOrMap(Accessor getterMethod) {
return isCollection( getterMethod.getAccessedType() ) || isMap( getterMethod.getAccessedType() );
}
private boolean isCollection(TypeMirror candidate) {
return isSubType( candidate, Collection.class );
}
private boolean isMap(TypeMirror candidate) {
return isSubType( candidate, Map.class );
}
private boolean isSubType(TypeMirror candidate, Class<?> clazz) {
String className = clazz.getCanonicalName();
TypeMirror classType = typeUtils.erasure( elementUtils.getTypeElement( className ).asType() );
return typeUtils.isSubtype( candidate, classType );
}
/**
* Returns the length of the shortest path in the type hierarchy between this type and the specified other type.
* Returns {@code -1} if this type is not assignable to the other type. Returns {@code 0} if this type is equal to
* the other type. Returns {@code 1}, if the other type is a direct super type of this type, and so on.
*
* @param assignableOther the other type
*
* @return the length of the shortest path in the type hierarchy between this type and the specified other type
*/
public int distanceTo(Type assignableOther) {
return distanceTo( typeMirror, assignableOther.typeMirror );
}
private int distanceTo(TypeMirror base, TypeMirror targetType) {
if ( typeUtils.isSameType( base, targetType ) ) {
return 0;
}
if ( !typeUtils.isAssignable( base, targetType ) ) {
return -1;
}
List<? extends TypeMirror> directSupertypes = typeUtils.directSupertypes( base );
int minDistanceOfSuperToTargetType = Integer.MAX_VALUE;
for ( TypeMirror type : directSupertypes ) {
int distanceToTargetType = distanceTo( type, targetType );
if ( distanceToTargetType >= 0 ) {
minDistanceOfSuperToTargetType = Math.min( minDistanceOfSuperToTargetType, distanceToTargetType );
}
}
return 1 + minDistanceOfSuperToTargetType;
}
/**
* @param type the type declaring the method
* @param method the method to check
* @return Whether this type can access the given method declared on the given type.
*/
public boolean canAccess(Type type, ExecutableElement method) {
if ( method.getModifiers().contains( Modifier.PRIVATE ) ) {
return false;
}
else if ( method.getModifiers().contains( Modifier.PROTECTED ) ) {
return isAssignableTo( type ) || getPackageName().equals( type.getPackageName() );
}
else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) {
// default
return getPackageName().equals( type.getPackageName() );
}
// public
return true;
}
/**
* @return A valid Java expression most suitable for representing null - useful for dealing with primitives from
* FTL.
*/
public String getNull() {
if ( !isPrimitive() || isArrayType() ) {
return "null";
}
if ( "boolean".equals( getName() ) ) {
return "false";
}
if ( "byte".equals( getName() ) ) {
return "0";
}
if ( "char".equals( getName() ) ) {
//"'\u0000'" would have been better, but depends on platform encoding
return "0";
}
if ( "double".equals( getName() ) ) {
return "0.0d";
}
if ( "float".equals( getName() ) ) {
return "0.0f";
}
if ( "int".equals( getName() ) ) {
return "0";
}
if ( "long".equals( getName() ) ) {
return "0L";
}
if ( "short".equals( getName() ) ) {
return "0";
}
throw new UnsupportedOperationException( getName() );
}
@Override
public int hashCode() {
// javadoc typemirror: "Types should be compared using the utility methods in Types. There is no guarantee
// that any particular type will always be represented by the same object." This is true when the objects
// are in another jar than the mapper. So the qualfiedName is a better candidate.
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
Type other = (Type) obj;
return typeUtils.isSameType( typeMirror, other.typeMirror );
}
@Override
public int compareTo(Type o) {
return getFullyQualifiedName().compareTo( o.getFullyQualifiedName() );
}
@Override
public String toString() {
return typeMirror.toString();
}
/**
*
* @return an identification that can be used as part in a forged method name.
*/
public String getIdentification() {
if ( isArrayType() ) {
return componentType.getName() + "Array";
}
else {
return getTypeBound().getName();
}
}
/**
* 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>
* @return the bound for this parameter
*/
public Type getTypeBound() {
if ( boundingBase != null ) {
return boundingBase;
}
boundingBase = typeFactory.getType( typeFactory.getTypeBound( getTypeMirror() ) );
return boundingBase;
}
public boolean hasEmptyAccessibleContructor() {
if ( this.hasEmptyAccessibleContructor == null ) {
hasEmptyAccessibleContructor = false;
List<ExecutableElement> constructors = ElementFilter.constructorsIn( typeElement.getEnclosedElements() );
for ( ExecutableElement constructor : constructors ) {
if ( (constructor.getModifiers().contains( Modifier.PUBLIC )
|| constructor.getModifiers().contains( Modifier.PROTECTED ) )
&& constructor.getParameters().isEmpty() ) {
hasEmptyAccessibleContructor = true;
break;
}
}
}
return hasEmptyAccessibleContructor;
}
/**
* Searches for the given superclass and collects all type arguments for the given class
*
* @param superclass the superclass or interface the generic type arguments are searched for
* @return a list of type arguments or null, if superclass was not found
*/
public List<Type> determineTypeArguments(Class<?> superclass) {
if ( qualifiedName.equals( superclass.getName() ) ) {
return getTypeParameters();
}
List<? extends TypeMirror> directSupertypes = typeUtils.directSupertypes( typeMirror );
for ( TypeMirror supertypemirror : directSupertypes ) {
Type supertype = typeFactory.getType( supertypemirror );
List<Type> supertypeTypeArguments = supertype.determineTypeArguments( superclass );
if ( supertypeTypeArguments != null ) {
return supertypeTypeArguments;
}
}
return null;
}
}