Browse Source
* pr/29403: Polish 'Generate configuration metadata for records' Generate configuration metadata for records Closes gh-29403pull/40477/head
18 changed files with 672 additions and 364 deletions
@ -0,0 +1,216 @@
@@ -0,0 +1,216 @@
|
||||
/* |
||||
* 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. |
||||
* 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.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.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; |
||||
|
||||
/** |
||||
* {@link PropertyDescriptor} created from a constructor or record parameter. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @author Phillip Webb |
||||
*/ |
||||
abstract class ParameterPropertyDescriptor extends PropertyDescriptor { |
||||
|
||||
private final VariableElement parameter; |
||||
|
||||
ParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter, TypeElement declaringElement, |
||||
ExecutableElement getter) { |
||||
super(name, type, declaringElement, getter); |
||||
this.parameter = parameter; |
||||
|
||||
} |
||||
|
||||
final VariableElement getParameter() { |
||||
return this.parameter; |
||||
} |
||||
|
||||
@Override |
||||
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { |
||||
Object defaultValue = getDefaultValueFromAnnotation(environment, getParameter()); |
||||
return (defaultValue != null) ? defaultValue |
||||
: getParameter().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null); |
||||
} |
||||
|
||||
private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) { |
||||
AnnotationMirror annotation = environment.getDefaultValueAnnotation(element); |
||||
List<String> defaultValue = getDefaultValue(environment, annotation); |
||||
if (defaultValue != null) { |
||||
TypeMirror specificType = determineSpecificType(environment); |
||||
try { |
||||
List<Object> coerced = defaultValue.stream().map((value) -> coerceValue(specificType, value)).toList(); |
||||
return (coerced.size() != 1) ? coerced : coerced.get(0); |
||||
} |
||||
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 parameterType = getParameter().asType(); |
||||
TypeMirror elementType = environment.getTypeUtils().extractElementType(parameterType); |
||||
parameterType = (elementType != null) ? elementType : parameterType; |
||||
PrimitiveType primitiveType = environment.getTypeUtils().getPrimitiveType(parameterType); |
||||
return (primitiveType != null) ? primitiveType : parameterType; |
||||
} |
||||
|
||||
private Object coerceValue(TypeMirror type, String value) { |
||||
Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE, value); |
||||
return (coercedValue != null) ? coercedValue : value; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isProperty(MetadataGenerationEnvironment env) { |
||||
return !isNested(env); // We must be able to bind it to build the object.
|
||||
} |
||||
|
||||
/** |
||||
* Visitor that gets the default value for primitives. |
||||
*/ |
||||
private static final class DefaultPrimitiveTypeVisitor extends TypeKindVisitor8<Object, Void> { |
||||
|
||||
static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor(); |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsBoolean(PrimitiveType type, Void parameter) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsByte(PrimitiveType type, Void parameter) { |
||||
return (byte) 0; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsShort(PrimitiveType type, Void parameter) { |
||||
return (short) 0; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsInt(PrimitiveType type, Void parameter) { |
||||
return 0; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsLong(PrimitiveType type, Void parameter) { |
||||
return 0L; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsChar(PrimitiveType type, Void parameter) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsFloat(PrimitiveType type, Void parameter) { |
||||
return 0F; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsDouble(PrimitiveType type, Void parameter) { |
||||
return 0D; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Visitor that gets the default using coercion. |
||||
*/ |
||||
private static final class DefaultValueCoercionTypeVisitor extends TypeKindVisitor8<Object, String> { |
||||
|
||||
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 type, String value) { |
||||
return Boolean.parseBoolean(value); |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsByte(PrimitiveType type, String value) { |
||||
return parseNumber(value, Byte::parseByte, type); |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsShort(PrimitiveType type, String value) { |
||||
return parseNumber(value, Short::parseShort, type); |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsInt(PrimitiveType type, String value) { |
||||
return parseNumber(value, Integer::parseInt, type); |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsLong(PrimitiveType type, String value) { |
||||
return parseNumber(value, Long::parseLong, type); |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsChar(PrimitiveType type, String value) { |
||||
if (value.length() > 1) { |
||||
throw new IllegalArgumentException(String.format("Invalid character representation '%s'", value)); |
||||
} |
||||
return value; |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsFloat(PrimitiveType type, String value) { |
||||
return parseNumber(value, Float::parseFloat, type); |
||||
} |
||||
|
||||
@Override |
||||
public Object visitPrimitiveAsDouble(PrimitiveType type, String value) { |
||||
return parseNumber(value, Double::parseDouble, type); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
/* |
||||
* 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. |
||||
* 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.Arrays; |
||||
import java.util.List; |
||||
|
||||
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.TypeMirror; |
||||
|
||||
/** |
||||
* A {@link PropertyDescriptor} for a record parameter. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @author Pavel Anisimov |
||||
* @author Phillip Webb |
||||
*/ |
||||
class RecordParameterPropertyDescriptor extends ParameterPropertyDescriptor { |
||||
|
||||
private final RecordComponentElement recordComponent; |
||||
|
||||
RecordParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter, |
||||
TypeElement declaringElement, ExecutableElement getter, RecordComponentElement recordComponent) { |
||||
super(name, type, parameter, declaringElement, getter); |
||||
this.recordComponent = recordComponent; |
||||
} |
||||
|
||||
@Override |
||||
protected List<Element> getDeprecatableElements() { |
||||
return Arrays.asList(getGetter()); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
protected String resolveDescription(MetadataGenerationEnvironment environment) { |
||||
return environment.getTypeUtils().getJavaDoc(this.recordComponent); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
/* |
||||
* 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. |
||||
* 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.configurationsample.record; |
||||
|
||||
/** |
||||
* Example Record Javadoc sample |
||||
* |
||||
* @param someString very long description that doesn't fit single line |
||||
* @param someInteger description with @param and @ pitfalls |
||||
* @param someBoolean description with extra spaces |
||||
* @param someLong description without space after asterisk |
||||
* @param someByte last description in Javadoc |
||||
* @since 1.0.0 |
||||
* @author Pavel Anisimov |
||||
*/ |
||||
@org.springframework.boot.configurationsample.ConfigurationProperties("record.descriptions") |
||||
public record ExampleRecord(String someString, Integer someInteger, Boolean someBoolean, Long someLong, Byte someByte) { |
||||
} |
||||
Loading…
Reference in new issue