Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: further code health refactoring #414

Merged
merged 4 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
#### Changed
- include new `Option.STANDARD_FORMATS` in `OptionPreset.PLAIN_JSON` by default

#### Fixed
- when using `Option.FIELDS_DERIVED_FROM_ARGUMENTFREE_METHODS` on a method where the second character of the derived field name is in uppercase, don't capitalise the first character

## [4.32.0] - 2023-10-27
### `jsonschema-generator`
#### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ private MethodScope doFindGetter() {
possibleGetterNames.add("get" + capitalisedFieldName);
possibleGetterNames.add("is" + capitalisedFieldName);
// @since 4.32.0 - for a field like "isBool" also consider "isBool()" as potential getter method
if (declaredName.startsWith("is") && declaredName.length() > 2 && Character.isUpperCase(declaredName.charAt(2))) {
CarstenWickner marked this conversation as resolved.
Show resolved Hide resolved
boolean fieldNameStartsWithIs = declaredName.startsWith("is") && declaredName.length() > 2 && Character.isUpperCase(declaredName.charAt(2));
if (fieldNameStartsWithIs) {
possibleGetterNames.add(declaredName);
}
ResolvedMethod[] methods = this.getDeclaringTypeMembers().getMemberMethods();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,11 @@ public <A extends Annotation> A getContainerItemAnnotationConsideringFieldAndGet
if (this.isFakeContainerItemScope()) {
return this.getContainerItemAnnotationConsideringFieldAndGetter(annotationClass, considerOtherAnnotation);
}
if (this.getOverriddenType() != null
&& this.getDeclaredType().getErasedType() == Optional.class
&& this.getOverriddenType().getErasedType() == this.getDeclaredType().getTypeParameters().get(0).getErasedType()) {
CarstenWickner marked this conversation as resolved.
Show resolved Hide resolved
// in addition to an explicitly marked "faked container item scope", also support a type wrapped in an Optional
if (this.getOverriddenType() == null || this.getDeclaredType().getErasedType() != Optional.class) {
return null;
}
if (this.getOverriddenType().getErasedType() == this.getDeclaredType().getTypeParameters().get(0).getErasedType()) {
return this.getContainerItemAnnotationConsideringFieldAndGetter(annotationClass, considerOtherAnnotation);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
CarstenWickner marked this conversation as resolved.
Show resolved Hide resolved
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -126,17 +126,18 @@ public FieldScope findGetterField() {
* @return associated field
*/
private FieldScope doFindGetterField() {
if (this.getType() == null || !this.isPublic() || this.getArgumentCount() > 0) {
CarstenWickner marked this conversation as resolved.
Show resolved Hide resolved
// void and non-public methods or those with arguments are not deemed to be getters
if (this.getType() == null || this.getArgumentCount() > 0) {
// void methods or those with arguments are not deemed to be getters
return null;
}
String methodName = this.getDeclaredName();
Set<String> possibleFieldNames = new HashSet<>(3);
if (methodName.startsWith("get")) {
getPossibleFieldNamesStartingWithGet(methodName, possibleFieldNames);
} else if (methodName.startsWith("is")) {
getPossibleFieldNamesStartingWithIs(methodName, possibleFieldNames);
if (!this.isPublic()) {
// only public methods are deemed eligible to be getters
return null;
}
String methodName = this.getDeclaredName();
Set<String> possibleFieldNames = Stream.of("get", "is")
.flatMap(prefix -> getPossibleFieldNames(prefix, methodName))
.collect(Collectors.toSet());
if (possibleFieldNames.isEmpty()) {
// method name does not fall into getter conventions
return null;
Expand All @@ -150,28 +151,25 @@ private FieldScope doFindGetterField() {
.orElse(null);
}

private static void getPossibleFieldNamesStartingWithGet(String methodName, Set<String> possibleFieldNames) {
if (methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3))) {
// ensure that the variable starts with a lower-case letter
possibleFieldNames.add(methodName.substring(3, 4).toLowerCase() + methodName.substring(4));
}
// @since 4.32.0 - conforming with JavaBeans API specification edge case when second character in field name is in uppercase
if (methodName.length() > 4 && Character.isUpperCase(methodName.charAt(4))) {
possibleFieldNames.add(methodName.substring(3));
private static Stream<String> getPossibleFieldNames(String prefix, String methodName) {
if (!methodName.startsWith(prefix) || prefix.length() == methodName.length()) {
return Stream.of();
}
}

private static void getPossibleFieldNamesStartingWithIs(String methodName, Set<String> possibleFieldNames) {
if (methodName.length() > 2 && Character.isUpperCase(methodName.charAt(2))) {
Stream.Builder<String> possibleFieldNames = Stream.builder();
String methodNameWithoutPrefix = methodName.substring(prefix.length());
if (Character.isUpperCase(methodNameWithoutPrefix.charAt(0))) {
// ensure that the variable starts with a lower-case letter
possibleFieldNames.add(methodName.substring(2, 3).toLowerCase() + methodName.substring(3));
// since 4.32.0: a method "isBool()" is considered a possible getter for a field "isBool" as well as for "bool"
possibleFieldNames.add(methodName);
possibleFieldNames.add(methodNameWithoutPrefix.substring(0, 1).toLowerCase() + methodNameWithoutPrefix.substring(1));
if ("is".equals(prefix)) {
// since 4.32.0: a method "isBool()" is considered a possible getter for a field "isBool" as well as for "bool"
possibleFieldNames.add(methodName);
}
}
// @since 4.32.0 - conforming with JavaBeans API specification edge case when second character in field name is in uppercase
if (methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3))) {
possibleFieldNames.add(methodName.substring(2));
if (methodNameWithoutPrefix.length() > 1 && Character.isUpperCase(methodNameWithoutPrefix.charAt(1))) {
possibleFieldNames.add(methodNameWithoutPrefix);
}
return possibleFieldNames.build();
}

/**
Expand Down Expand Up @@ -213,7 +211,9 @@ public <A extends Annotation> A getAnnotationConsideringFieldAndGetter(Class<A>
A annotation = this.getAnnotation(annotationClass, considerOtherAnnotation);
if (annotation == null) {
MemberScope<?, ?> associatedField = this.findGetterField();
annotation = associatedField == null ? null : associatedField.getAnnotation(annotationClass, considerOtherAnnotation);
if (associatedField != null) {
annotation = associatedField.getAnnotation(annotationClass, considerOtherAnnotation);
}
}
return annotation;
}
Expand All @@ -224,7 +224,9 @@ public <A extends Annotation> A getContainerItemAnnotationConsideringFieldAndGet
A annotation = this.getContainerItemAnnotation(annotationClass, considerOtherAnnotation);
if (annotation == null) {
MemberScope<?, ?> associatedField = this.findGetterField();
annotation = associatedField == null ? null : associatedField.getContainerItemAnnotation(annotationClass, considerOtherAnnotation);
if (associatedField != null) {
annotation = associatedField.getContainerItemAnnotation(annotationClass, considerOtherAnnotation);
}
}
return annotation;
}
Expand All @@ -238,23 +240,36 @@ public <A extends Annotation> A getContainerItemAnnotationConsideringFieldAndGet
*/
@Override
protected String doGetSchemaPropertyName() {
String result = this.getName();
if (this.getContext().isDerivingFieldsFromArgumentFreeMethods() && this.getArgumentCount() == 0) {
if (this.getOverriddenName() == null) {
// remove the "get"/"is" prefix from non-overridden method names
if (result.startsWith("get") && result.length() > 3) {
result = Character.toLowerCase(result.charAt(3)) + result.substring(4);
} else if (result.startsWith("is") && result.length() > 2) {
result = Character.toLowerCase(result.charAt(2)) + result.substring(3);
} else {
result += "()";
}
}
} else {
result += this.getArgumentTypes().stream()
String result;
if (!this.getContext().isDerivingFieldsFromArgumentFreeMethods() || this.getArgumentCount() > 0) {
result = this.getName() + this.getArgumentTypes().stream()
.map(this.getContext()::getMethodPropertyArgumentTypeDescription)
.collect(Collectors.joining(", ", "(", ")"));
} else if (this.getOverriddenName() == null) {
result = this.deriveFieldName();
} else {
result = this.getName();
}
return result;
}

private String deriveFieldName() {
String methodName = this.getDeclaredName();
// remove the "get"/"is" prefix from non-overridden method names
Optional<String> possibleFieldName = Stream.of("get", "is")
.filter(methodName::startsWith)
.map(prefix -> methodName.substring(prefix.length()))
.filter(name -> !name.isEmpty())
.findFirst();
if (!possibleFieldName.isPresent()) {
return methodName + "()";
}
String methodNameWithoutPrefix = possibleFieldName.get();
if (Character.isUpperCase(methodNameWithoutPrefix.charAt(1))) {
// since 4.33.0 according to the Java Beans specification, if the second character is uppercase the first should remain as-is
// e.g., getxIndex() corresponds to "xIndex" and isURL() to "URL"
return methodNameWithoutPrefix;
}
return Character.toLowerCase(methodNameWithoutPrefix.charAt(0)) + methodNameWithoutPrefix.substring(1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.List;
import java.util.Optional;
import java.util.WeakHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -188,10 +189,15 @@ public TypeScope createTypeScope(ResolvedType type) {
*/
public ResolvedType getTypeParameterFor(ResolvedType type, Class<?> erasedSuperType, int parameterIndex) {
List<ResolvedType> typeParameters = type.typeParametersFor(erasedSuperType);
if (typeParameters == null
|| (!typeParameters.isEmpty() && typeParameters.size() <= parameterIndex)
|| (typeParameters.isEmpty() && erasedSuperType.getTypeParameters().length <= parameterIndex)) {
CarstenWickner marked this conversation as resolved.
Show resolved Hide resolved
// given type is not a super type of the type in scope or given index is out of bounds
if (typeParameters == null) {
// given type is not a super type of the type in scope
return null;
}
if (!typeParameters.isEmpty() && typeParameters.size() <= parameterIndex) {
// given index is out of bounds for the specific type
}
if (typeParameters.isEmpty() && erasedSuperType.getTypeParameters().length <= parameterIndex) {
// given index is out of bounds for the designated super type
return null;
}
if (typeParameters.isEmpty()) {
Expand Down Expand Up @@ -441,4 +447,27 @@ private String getTypeDescription(ResolvedType type, boolean simpleClassNames) {
public String getMethodPropertyArgumentTypeDescription(ResolvedType type) {
return this.getSimpleTypeDescription(type);
}

/**
* Helper function to write generic code that targets either a {@link FieldScope} or {@link MethodScope}.
*
* @param <R> type of expected return value
* @param member field or method being targeted
* @param fieldAction action to perform in case the given member is a {@link FieldScope}
* @param methodAction action to perform in case the given member is a {@link MethodScope}
* @return value returned by the performed action
* @throws IllegalStateException if given member is neither a {@link FieldScope} or {@link MethodScope}
*/
public <R> R performActionOnMember(MemberScope<?, ?> member, Function<FieldScope, R> fieldAction,
Function<MethodScope, R> methodAction) {
R result;
if (member instanceof FieldScope) {
result = fieldAction.apply((FieldScope) member);
} else if (member instanceof MethodScope) {
result = methodAction.apply((MethodScope) member);
} else {
throw new IllegalStateException("Unsupported member scope of type: " + member.getClass());
}
return result;
}
}
Loading
Loading