Skip to content

Commit

Permalink
Polish 'Generate configuration metadata for records'
Browse files Browse the repository at this point in the history
Restructure `PropertyDescriptor` type hierarchy and polish code.

See gh-
  • Loading branch information
philwebb committed Apr 20, 2024
1 parent 33d7de1 commit adeff2f
Show file tree
Hide file tree
Showing 13 changed files with 580 additions and 383 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,209 +16,47 @@

package org.springframework.boot.configurationprocessor;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.RecordComponentElement;
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
* @author Pavel Anisimov
* @author Phillip Webb
*/
class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<VariableElement> {
class ConstructorParameterPropertyDescriptor extends ParameterPropertyDescriptor {

private final RecordComponentElement recordComponent;
private final ExecutableElement setter;

ConstructorParameterPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod,
VariableElement source, String name, TypeMirror type, VariableElement field,
RecordComponentElement recordComponent, ExecutableElement getter, ExecutableElement setter) {
super(ownerElement, factoryMethod, source, name, type, field, getter, setter);
this.recordComponent = recordComponent;
private final VariableElement field;

ConstructorParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter,
TypeElement declaringElement, ExecutableElement getter, ExecutableElement setter, VariableElement field) {
super(name, type, parameter, declaringElement, getter);
this.setter = setter;
this.field = field;
}

@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);
protected List<Element> getDeprecatableElements() {
return Arrays.asList(getGetter(), this.setter, this.field);
}

@Override
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
Object defaultValue = getDefaultValueFromAnnotation(environment, getSource());
if (defaultValue != null) {
return defaultValue;
}
return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
}

@Override
protected String resolveDescription(MetadataGenerationEnvironment environment) {
// record components descriptions are written using @param tag
if (this.recordComponent != null) {
return environment.getTypeUtils().getJavaDoc(this.recordComponent);
}
return super.resolveDescription(environment);
}

private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) {
AnnotationMirror annotation = environment.getDefaultValueAnnotation(element);
List<String> defaultValue = getDefaultValue(environment, annotation);
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)).toList();
}
catch (IllegalArgumentException ex) {
environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(), element, annotation);
}
}
return null;
}

@SuppressWarnings("unchecked")
private List<String> getDefaultValue(MetadataGenerationEnvironment environment, AnnotationMirror annotation) {
if (annotation == null) {
return null;
}
Map<String, Object> values = environment.getAnnotationElementValues(annotation);
return (List<String>) values.get("value");
}

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 final class DefaultValueCoercionTypeVisitor extends TypeKindVisitor8<Object, String> {

private static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor();

private <T extends Number> T parseNumber(String value, Function<String, T> parser,
PrimitiveType primitiveType) {
try {
return parser.apply(value);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
String.format("Invalid %s representation '%s'", primitiveType, value));
}
}

@Override
public Object visitPrimitiveAsBoolean(PrimitiveType t, String value) {
return Boolean.parseBoolean(value);
}

@Override
public Object visitPrimitiveAsByte(PrimitiveType t, String value) {
return parseNumber(value, Byte::parseByte, t);
}

@Override
public Object visitPrimitiveAsShort(PrimitiveType t, String value) {
return parseNumber(value, Short::parseShort, t);
}

@Override
public Object visitPrimitiveAsInt(PrimitiveType t, String value) {
return parseNumber(value, Integer::parseInt, t);
}

@Override
public Object visitPrimitiveAsLong(PrimitiveType t, String value) {
return parseNumber(value, Long::parseLong, t);
}

@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 parseNumber(value, Float::parseFloat, t);
}

@Override
public Object visitPrimitiveAsDouble(PrimitiveType t, String value) {
return parseNumber(value, Double::parseDouble, t);
}

}

private static final 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 (byte) 0;
}

@Override
public Object visitPrimitiveAsShort(PrimitiveType t, Void ignore) {
return (short) 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 0F;
}

@Override
public Object visitPrimitiveAsDouble(PrimitiveType t, Void ignore) {
return 0D;
}

return environment.getTypeUtils().getJavaDoc(this.field);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2024 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.
Expand All @@ -16,6 +16,10 @@

package org.springframework.boot.configurationprocessor;

import java.util.Arrays;
import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
Expand All @@ -25,23 +29,54 @@
* A {@link PropertyDescriptor} for a standard JavaBean property.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
class JavaBeanPropertyDescriptor extends PropertyDescriptor<ExecutableElement> {
class JavaBeanPropertyDescriptor extends PropertyDescriptor {

private final ExecutableElement setter;

private final VariableElement field;

JavaBeanPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, ExecutableElement getter,
String name, TypeMirror type, VariableElement field, ExecutableElement setter) {
super(ownerElement, factoryMethod, getter, name, type, field, getter, setter);
private final ExecutableElement factoryMethod;

JavaBeanPropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter,
ExecutableElement setter, VariableElement field, ExecutableElement factoryMethod) {
super(name, type, declaringElement, getter);
this.setter = setter;
this.field = field;
this.factoryMethod = factoryMethod;
}

ExecutableElement getSetter() {
return this.setter;
}

@Override
protected boolean isProperty(MetadataGenerationEnvironment env) {
boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType());
return !env.isExcluded(getType()) && getGetter() != null && (getSetter() != null || isCollection);
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
}

@Override
protected String resolveDescription(MetadataGenerationEnvironment environment) {
return environment.getTypeUtils().getJavaDoc(this.field);
}

@Override
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
return environment.getFieldDefaultValue(getOwnerElement(), getName());
return environment.getFieldDefaultValue(getDeclaringElement(), getName());
}

@Override
protected List<Element> getDeprecatableElements() {
return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod);
}

@Override
public boolean isProperty(MetadataGenerationEnvironment env) {
boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType());
boolean hasGetter = getGetter() != null;
boolean hasSetter = getSetter() != null;
return !env.isExcluded(getType()) && hasGetter && (hasSetter || isCollection);
}

}

0 comments on commit adeff2f

Please sign in to comment.