Add metadata support for immutable ConfigurationProperties type
Closes gh-16071
This commit is contained in:
@@ -73,6 +73,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
|
||||
+ "context.properties.DeprecatedConfigurationProperty";
|
||||
|
||||
static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot."
|
||||
+ "context.properties.bind.DefaultValue";
|
||||
|
||||
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate."
|
||||
+ "endpoint.annotation.Endpoint";
|
||||
|
||||
@@ -100,6 +103,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
||||
}
|
||||
|
||||
protected String defaultValueAnnotation() {
|
||||
return DEFAULT_VALUE_ANNOTATION;
|
||||
}
|
||||
|
||||
protected String endpointAnnotation() {
|
||||
return ENDPOINT_ANNOTATION;
|
||||
}
|
||||
@@ -127,8 +134,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
this.metadataEnv = new MetadataGenerationEnvironment(env,
|
||||
configurationPropertiesAnnotation(),
|
||||
nestedConfigurationPropertyAnnotation(),
|
||||
deprecatedConfigurationPropertyAnnotation(), endpointAnnotation(),
|
||||
readOperationAnnotation());
|
||||
deprecatedConfigurationPropertyAnnotation(), defaultValueAnnotation(),
|
||||
endpointAnnotation(), readOperationAnnotation());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.PrimitiveType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.TypeKindVisitor8;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} for a constructor parameter.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||
|
||||
ConstructorParameterPropertyDescriptor(TypeElement ownerElement,
|
||||
ExecutableElement factoryMethod, VariableElement source, String name,
|
||||
TypeMirror type, VariableElement field, ExecutableElement getter,
|
||||
ExecutableElement setter) {
|
||||
super(ownerElement, factoryMethod, source, name, type, field, getter, setter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isProperty(MetadataGenerationEnvironment env) {
|
||||
// If it's a constructor parameter, it doesn't matter as we must be able to bind
|
||||
// it to build the object.
|
||||
return !isNested(env);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||
Object defaultValue = getDefaultValueFromAnnotation(environment, getSource());
|
||||
if (defaultValue != null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
|
||||
}
|
||||
|
||||
private Object getDefaultValueFromAnnotation(
|
||||
MetadataGenerationEnvironment environment, Element element) {
|
||||
AnnotationMirror defaultValueAnnotation = environment
|
||||
.getDefaultValueAnnotation(element);
|
||||
if (defaultValueAnnotation != null) {
|
||||
List<String> defaultValue = (List<String>) environment
|
||||
.getAnnotationElementValues(defaultValueAnnotation).get("value");
|
||||
if (defaultValue != null) {
|
||||
try {
|
||||
TypeMirror specificType = determineSpecificType(environment);
|
||||
if (defaultValue.size() == 1) {
|
||||
return coerceValue(specificType, defaultValue.get(0));
|
||||
}
|
||||
return defaultValue.stream()
|
||||
.map((value) -> coerceValue(specificType, value))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(),
|
||||
element, defaultValueAnnotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypeMirror determineSpecificType(MetadataGenerationEnvironment environment) {
|
||||
TypeMirror candidate = getSource().asType();
|
||||
TypeMirror elementCandidate = environment.getTypeUtils()
|
||||
.extractElementType(candidate);
|
||||
if (elementCandidate != null) {
|
||||
candidate = elementCandidate;
|
||||
}
|
||||
PrimitiveType primitiveType = environment.getTypeUtils()
|
||||
.getPrimitiveType(candidate);
|
||||
return (primitiveType != null) ? primitiveType : candidate;
|
||||
}
|
||||
|
||||
private Object coerceValue(TypeMirror type, String value) {
|
||||
Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE,
|
||||
value);
|
||||
return (coercedValue != null) ? coercedValue : value;
|
||||
}
|
||||
|
||||
private static class DefaultValueCoercionTypeVisitor
|
||||
extends TypeKindVisitor8<Object, String> {
|
||||
|
||||
private static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor();
|
||||
|
||||
private Integer parseInteger(String value) {
|
||||
try {
|
||||
return Integer.valueOf(value);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Invalid number representation '%s'", value));
|
||||
}
|
||||
}
|
||||
|
||||
private Double parseFloatingPoint(String value) {
|
||||
try {
|
||||
return Double.valueOf(value);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(String
|
||||
.format("Invalid floating point representation '%s'", value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsBoolean(PrimitiveType t, String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsByte(PrimitiveType t, String value) {
|
||||
return parseInteger(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsShort(PrimitiveType t, String value) {
|
||||
return parseInteger(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsInt(PrimitiveType t, String value) {
|
||||
return parseInteger(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsLong(PrimitiveType t, String value) {
|
||||
return parseInteger(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsChar(PrimitiveType t, String value) {
|
||||
if (value.length() > 1) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Invalid character representation '%s'", value));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsFloat(PrimitiveType t, String value) {
|
||||
return parseFloatingPoint(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsDouble(PrimitiveType t, String value) {
|
||||
return parseFloatingPoint(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DefaultPrimitiveTypeVisitor
|
||||
extends TypeKindVisitor8<Object, Void> {
|
||||
|
||||
private static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor();
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsBoolean(PrimitiveType t, Void ignore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsByte(PrimitiveType t, Void ignore) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsShort(PrimitiveType t, Void ignore) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsInt(PrimitiveType t, Void ignore) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsLong(PrimitiveType t, Void ignore) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsChar(PrimitiveType t, Void ignore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsFloat(PrimitiveType t, Void ignore) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPrimitiveAsDouble(PrimitiveType t, Void ignore) {
|
||||
return 0D;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,4 +41,9 @@ class JavaBeanPropertyDescriptor extends PropertyDescriptor<ExecutableElement> {
|
||||
&& (getSetter() != null || isCollection);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||
return environment.getFieldDefaultValue(getOwnerElement(), getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||
return !env.isExcluded(getType()) && (hasSetter(env) || isCollection);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||
return environment.getFieldDefaultValue(getOwnerElement(), getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNested(MetadataGenerationEnvironment environment) {
|
||||
if (!hasLombokPublicAccessor(environment, true)) {
|
||||
|
||||
@@ -16,14 +16,18 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
@@ -51,6 +55,8 @@ class MetadataGenerationEnvironment {
|
||||
|
||||
private final Elements elements;
|
||||
|
||||
private final Messager messager;
|
||||
|
||||
private final FieldValuesParser fieldValuesParser;
|
||||
|
||||
private final Map<TypeElement, Map<String, Object>> defaultValues = new HashMap<>();
|
||||
@@ -61,6 +67,8 @@ class MetadataGenerationEnvironment {
|
||||
|
||||
private final String deprecatedConfigurationPropertyAnnotation;
|
||||
|
||||
private final String defaultValueAnnotation;
|
||||
|
||||
private final String endpointAnnotation;
|
||||
|
||||
private final String readOperationAnnotation;
|
||||
@@ -68,15 +76,18 @@ class MetadataGenerationEnvironment {
|
||||
MetadataGenerationEnvironment(ProcessingEnvironment environment,
|
||||
String configurationPropertiesAnnotation,
|
||||
String nestedConfigurationPropertyAnnotation,
|
||||
String deprecatedConfigurationPropertyAnnotation, String endpointAnnotation,
|
||||
String deprecatedConfigurationPropertyAnnotation,
|
||||
String defaultValueAnnotation, String endpointAnnotation,
|
||||
String readOperationAnnotation) {
|
||||
this.typeExcludes = determineTypeExcludes();
|
||||
this.typeUtils = new TypeUtils(environment);
|
||||
this.elements = environment.getElementUtils();
|
||||
this.messager = environment.getMessager();
|
||||
this.fieldValuesParser = resolveFieldValuesParser(environment);
|
||||
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
|
||||
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
|
||||
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
|
||||
this.defaultValueAnnotation = defaultValueAnnotation;
|
||||
this.endpointAnnotation = endpointAnnotation;
|
||||
this.readOperationAnnotation = readOperationAnnotation;
|
||||
}
|
||||
@@ -112,7 +123,18 @@ class MetadataGenerationEnvironment {
|
||||
return this.typeUtils;
|
||||
}
|
||||
|
||||
public Object getDefaultValue(TypeElement type, String name) {
|
||||
public Messager getMessager() {
|
||||
return this.messager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default value of the field with the specified {@code name}.
|
||||
* @param type the type to consider
|
||||
* @param name the name of the field
|
||||
* @return the default value or {@code null} if the field does not exist or no default
|
||||
* value has been detected
|
||||
*/
|
||||
public Object getFieldDefaultValue(TypeElement type, String name) {
|
||||
return this.defaultValues.computeIfAbsent(type, this::resolveFieldValues)
|
||||
.get(name);
|
||||
}
|
||||
@@ -171,10 +193,21 @@ class MetadataGenerationEnvironment {
|
||||
public Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
annotation.getElementValues().forEach((name, value) -> values
|
||||
.put(name.getSimpleName().toString(), value.getValue()));
|
||||
.put(name.getSimpleName().toString(), getAnnotationValue(value)));
|
||||
return values;
|
||||
}
|
||||
|
||||
private Object getAnnotationValue(AnnotationValue annotationValue) {
|
||||
Object value = annotationValue.getValue();
|
||||
if (value instanceof List) {
|
||||
List<Object> values = new ArrayList<>();
|
||||
((List<?>) value)
|
||||
.forEach((v) -> values.add(((AnnotationValue) v).getValue()));
|
||||
return values;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public TypeElement getConfigurationPropertiesAnnotationElement() {
|
||||
return this.elements.getTypeElement(this.configurationPropertiesAnnotation);
|
||||
}
|
||||
@@ -187,6 +220,10 @@ class MetadataGenerationEnvironment {
|
||||
return getAnnotation(element, this.nestedConfigurationPropertyAnnotation);
|
||||
}
|
||||
|
||||
public AnnotationMirror getDefaultValueAnnotation(Element element) {
|
||||
return getAnnotation(element, this.defaultValueAnnotation);
|
||||
}
|
||||
|
||||
public TypeElement getEndpointAnnotationElement() {
|
||||
return this.elements.getTypeElement(this.endpointAnnotation);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
* @param <S> the type of the source element that determines the property
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
abstract class PropertyDescriptor<S> {
|
||||
abstract class PropertyDescriptor<S extends Element> {
|
||||
|
||||
private final TypeElement ownerElement;
|
||||
|
||||
@@ -98,6 +98,9 @@ abstract class PropertyDescriptor<S> {
|
||||
|
||||
protected abstract boolean isProperty(MetadataGenerationEnvironment environment);
|
||||
|
||||
protected abstract Object resolveDefaultValue(
|
||||
MetadataGenerationEnvironment environment);
|
||||
|
||||
protected ItemDeprecation resolveItemDeprecation(
|
||||
MetadataGenerationEnvironment environment) {
|
||||
boolean deprecated = environment.isDeprecated(getGetter())
|
||||
@@ -164,10 +167,6 @@ abstract class PropertyDescriptor<S> {
|
||||
return environment.getTypeUtils().getJavaDoc(getField());
|
||||
}
|
||||
|
||||
private Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||
return environment.getDefaultValue(getOwnerElement(), getName());
|
||||
}
|
||||
|
||||
private boolean isCyclePresent(Element returnType, Element element) {
|
||||
if (!(element.getEnclosingElement() instanceof TypeElement)) {
|
||||
return false;
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
|
||||
/**
|
||||
* Resolve {@link PropertyDescriptor} instances.
|
||||
@@ -49,23 +52,59 @@ class PropertyDescriptorResolver {
|
||||
public Stream<PropertyDescriptor<?>> resolve(TypeElement type,
|
||||
ExecutableElement factoryMethod) {
|
||||
TypeElementMembers members = new TypeElementMembers(this.environment, type);
|
||||
List<PropertyDescriptor<?>> candidates = new ArrayList<>();
|
||||
ExecutableElement constructor = resolveConstructor(type);
|
||||
if (constructor != null) {
|
||||
return resolveConstructorProperties(type, factoryMethod, members,
|
||||
constructor);
|
||||
}
|
||||
else {
|
||||
return resolveJavaBeanProperties(type, factoryMethod, members);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<PropertyDescriptor<?>> resolveConstructorProperties(TypeElement type,
|
||||
ExecutableElement factoryMethod, TypeElementMembers members,
|
||||
ExecutableElement constructor) {
|
||||
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>();
|
||||
constructor.getParameters().forEach((parameter) -> {
|
||||
String name = parameter.getSimpleName().toString();
|
||||
TypeMirror propertyType = parameter.asType();
|
||||
ExecutableElement getter = members.getPublicGetter(name, propertyType);
|
||||
ExecutableElement setter = members.getPublicSetter(name, propertyType);
|
||||
VariableElement field = members.getFields().get(name);
|
||||
register(candidates, new ConstructorParameterPropertyDescriptor(type,
|
||||
factoryMethod, parameter, name, propertyType, field, getter, setter));
|
||||
});
|
||||
return candidates.values().stream();
|
||||
}
|
||||
|
||||
public Stream<PropertyDescriptor<?>> resolveJavaBeanProperties(TypeElement type,
|
||||
ExecutableElement factoryMethod, TypeElementMembers members) {
|
||||
// First check if we have regular java bean properties there
|
||||
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>();
|
||||
members.getPublicGetters().forEach((name, getter) -> {
|
||||
TypeMirror returnType = getter.getReturnType();
|
||||
candidates.add(new JavaBeanPropertyDescriptor(type, factoryMethod, getter,
|
||||
name, returnType, members.getFields().get(name),
|
||||
members.getPublicSetter(name, returnType)));
|
||||
TypeMirror propertyType = getter.getReturnType();
|
||||
register(candidates,
|
||||
new JavaBeanPropertyDescriptor(type, factoryMethod, getter, name,
|
||||
propertyType, members.getFields().get(name),
|
||||
members.getPublicSetter(name, propertyType)));
|
||||
});
|
||||
// Then check for Lombok ones
|
||||
members.getFields().forEach((name, field) -> {
|
||||
TypeMirror returnType = field.asType();
|
||||
ExecutableElement getter = members.getPublicGetter(name, returnType);
|
||||
ExecutableElement setter = members.getPublicSetter(name, returnType);
|
||||
candidates.add(new LombokPropertyDescriptor(type, factoryMethod, field, name,
|
||||
returnType, getter, setter));
|
||||
TypeMirror propertyType = field.asType();
|
||||
ExecutableElement getter = members.getPublicGetter(name, propertyType);
|
||||
ExecutableElement setter = members.getPublicSetter(name, propertyType);
|
||||
register(candidates, new LombokPropertyDescriptor(type, factoryMethod, field,
|
||||
name, propertyType, getter, setter));
|
||||
});
|
||||
return candidates.stream().filter(this::isCandidate);
|
||||
return candidates.values().stream();
|
||||
}
|
||||
|
||||
private void register(Map<String, PropertyDescriptor<?>> candidates,
|
||||
PropertyDescriptor<?> descriptor) {
|
||||
if (!candidates.containsKey(descriptor.getName()) && isCandidate(descriptor)) {
|
||||
candidates.put(descriptor.getName(), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCandidate(PropertyDescriptor<?> descriptor) {
|
||||
@@ -73,4 +112,13 @@ class PropertyDescriptorResolver {
|
||||
|| descriptor.isNested(this.environment);
|
||||
}
|
||||
|
||||
private ExecutableElement resolveConstructor(TypeElement type) {
|
||||
List<ExecutableElement> constructors = ElementFilter
|
||||
.constructorsIn(type.getEnclosedElements());
|
||||
if (constructors.size() == 1 && constructors.get(0).getParameters().size() > 0) {
|
||||
return constructors.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -142,6 +142,42 @@ class TypeUtils {
|
||||
return type.accept(this.typeExtractor, createTypeDescriptor(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the target element type from the specified container type or {@code null}
|
||||
* if no element type was found.
|
||||
* @param type a type, potentially wrapping an element type
|
||||
* @return the element type or {@code null} if no specific type was found
|
||||
*/
|
||||
public TypeMirror extractElementType(TypeMirror type) {
|
||||
if (!this.env.getTypeUtils().isAssignable(type, this.collectionType)) {
|
||||
return null;
|
||||
}
|
||||
return getCollectionElementType(type);
|
||||
}
|
||||
|
||||
private TypeMirror getCollectionElementType(TypeMirror type) {
|
||||
if (((TypeElement) this.types.asElement(type)).getQualifiedName()
|
||||
.contentEquals(Collection.class.getName())) {
|
||||
DeclaredType declaredType = (DeclaredType) type;
|
||||
// raw type, just "Collection"
|
||||
if (declaredType.getTypeArguments().size() == 0) {
|
||||
return this.types.getDeclaredType(this.env.getElementUtils()
|
||||
.getTypeElement(Object.class.getName()));
|
||||
}
|
||||
else { // return type argument to Collection<...>
|
||||
return declaredType.getTypeArguments().get(0);
|
||||
}
|
||||
}
|
||||
|
||||
// recursively walk the supertypes, looking for Collection<...>
|
||||
for (TypeMirror superType : this.env.getTypeUtils().directSupertypes(type)) {
|
||||
if (this.types.isAssignable(superType, this.collectionType)) {
|
||||
return getCollectionElementType(superType);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isCollectionOrMap(TypeMirror type) {
|
||||
return this.env.getTypeUtils().isAssignable(type, this.collectionType)
|
||||
|| this.env.getTypeUtils().isAssignable(type, this.mapType);
|
||||
@@ -156,6 +192,19 @@ class TypeUtils {
|
||||
return "".equals(javadoc) ? null : javadoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link PrimitiveType} of the specified type or {@code null} if the type
|
||||
* does not represent a valid wrapper type.
|
||||
* @param typeMirror a type
|
||||
* @return the primitive type or {@code null} if the type is not a wrapper type
|
||||
*/
|
||||
public PrimitiveType getPrimitiveType(TypeMirror typeMirror) {
|
||||
if (getPrimitiveFor(typeMirror) != null) {
|
||||
return this.types.unboxedType(typeMirror);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TypeMirror getWrapperOrPrimitiveFor(TypeMirror typeMirror) {
|
||||
Class<?> candidate = getWrapperFor(typeMirror);
|
||||
if (candidate != null) {
|
||||
|
||||
Reference in New Issue
Block a user