Skip to content

Commit

Permalink
feat: extend support for getter names (#366)
Browse files Browse the repository at this point in the history
* feat: support boolean getter with method name matching field name

* feat. consider getter naming according to JavaBeans specification
  • Loading branch information
CarstenWickner authored Jul 17, 2023
1 parent c645b5c commit d8bf965
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 19 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### `jsonschema-generator`
#### Changed
- consider JavaBeans API specification in getter naming convention for field names with the second character being uppercase (e.g., a field `xIndex` has the getter `getxIndex()` according to the specification)
- allow for field names starting with `is` to have getter of the same name (e.g., a field `isBool` may have the getter `isBool()`)

### `jsonschema-module-jackson`
#### Added
- elevate nested properties to the parent type where members are annotated with `@JsonUnwrapped`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

Expand Down Expand Up @@ -112,14 +114,25 @@ public MethodScope findGetter() {
* @return public getter from within the field's declaring class
*/
private MethodScope doFindGetter() {
String capitalisedFieldName = this.getDeclaredName().substring(0, 1).toUpperCase() + this.getDeclaredName().substring(1);
String getterName1 = "get" + capitalisedFieldName;
String getterName2 = "is" + capitalisedFieldName;
String declaredName = this.getDeclaredName();
Set<String> possibleGetterNames = new HashSet<>(5);
// @since 4.32.0 - for a field like "xIndex" also consider "getxIndex()" as getter method (according to JavaBeans specification)
if (declaredName.length() > 1 && Character.isUpperCase(declaredName.charAt(1))) {
possibleGetterNames.add("get" + declaredName);
possibleGetterNames.add("is" + declaredName);
}
// common naming convention: capitalise first character and leave the rest as-is
String capitalisedFieldName = declaredName.substring(0, 1).toUpperCase() + declaredName.substring(1);
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))) {
possibleGetterNames.add(declaredName);
}
ResolvedMethod[] methods = this.getDeclaringTypeMembers().getMemberMethods();
return Stream.of(methods)
.filter(method -> method.getRawMember().getParameterCount() == 0)
.filter(ResolvedMethod::isPublic)
.filter(method -> method.getName().equals(getterName1) || method.getName().equals(getterName2))
.filter(method -> method.isPublic() && method.getRawMember().getParameterCount() == 0)
.filter(method -> possibleGetterNames.contains(method.getName()))
.findFirst()
.map(method -> this.getContext().createMethodScope(method, this.getDeclaringTypeMembers()))
.orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
Expand Down Expand Up @@ -129,24 +131,36 @@ private FieldScope doFindGetterField() {
return null;
}
String methodName = this.getDeclaredName();
String fieldName;
if (methodName.startsWith("get") && methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3))) {
// ensure that the variable starts with a lower-case letter
fieldName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
} else if (methodName.startsWith("is") && methodName.length() > 2 && Character.isUpperCase(methodName.charAt(2))) {
// ensure that the variable starts with a lower-case letter
fieldName = methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
} else {
// method name does not fall into getter conventions
fieldName = null;
Set<String> possibleFieldNames = new HashSet<>(3);
if (methodName.startsWith("get")) {
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));
}
} else if (methodName.startsWith("is")) {
if (methodName.length() > 2 && Character.isUpperCase(methodName.charAt(2))) {
// 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);
}
// @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 (fieldName == null) {
if (possibleFieldNames.isEmpty()) {
// method name does not fall into getter conventions
return null;
}
// method name matched getter conventions
// check whether a matching field exists
return Stream.of(this.getDeclaringTypeMembers().getMemberFields())
.filter(memberField -> memberField.getName().equals(fieldName))
.filter(memberField -> possibleFieldNames.contains(memberField.getName()))
.findFirst()
.map(field -> this.getContext().createFieldScope(field, this.getDeclaringTypeMembers()))
.orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ static Stream<Arguments> parametersForTestFindGetter() {
Arguments.of("fieldWithPrivateGetter", null, null),
Arguments.of("fieldWithPublicGetter", null, "getFieldWithPublicGetter"),
Arguments.of("fieldWithPublicGetter", "fieldWithoutGetter", "getFieldWithPublicGetter"),
Arguments.of("fieldWithPublicBooleanGetter", null, "isFieldWithPublicBooleanGetter")
Arguments.of("fieldWithPublicBooleanGetter", null, "isFieldWithPublicBooleanGetter"),
Arguments.of("isFieldWithMatchingGetter", null, "isFieldWithMatchingGetter"),
Arguments.of("isFieldWithMatchingGetter", "fieldWithPublicGetter", "isFieldWithMatchingGetter"),
Arguments.of("aFieldWithJavaBeansConformingGetter", null, "getaFieldWithJavaBeansConformingGetter"),
Arguments.of("aFieldWithJavaBeansNonConformingGetter", null, "getAFieldWithJavaBeansNonConformingGetter"),
Arguments.of("aFieldWithJavaBeansConformingBooleanGetter", null, "isaFieldWithJavaBeansConformingBooleanGetter"),
Arguments.of("aFieldWithJavaBeansNonConformingBooleanGetter", null, "isAFieldWithJavaBeansNonConformingBooleanGetter")
);
}

Expand Down Expand Up @@ -107,6 +113,11 @@ private static class TestClass {
private int fieldWithPrivateGetter;
private long fieldWithPublicGetter;
private boolean fieldWithPublicBooleanGetter;
private boolean isFieldWithMatchingGetter;
private int aFieldWithJavaBeansConformingGetter;
private long aFieldWithJavaBeansNonConformingGetter;
private boolean aFieldWithJavaBeansConformingBooleanGetter;
private boolean aFieldWithJavaBeansNonConformingBooleanGetter;

private int getFieldWithPrivateGetter() {
return this.fieldWithPrivateGetter;
Expand All @@ -120,6 +131,26 @@ public long getFieldWithPublicGetter() {
public boolean isFieldWithPublicBooleanGetter() {
return this.fieldWithPublicBooleanGetter;
}

public boolean isFieldWithMatchingGetter() {
return this.isFieldWithMatchingGetter;
}

public int getaFieldWithJavaBeansConformingGetter() {
return this.aFieldWithJavaBeansConformingGetter;
}

public long getAFieldWithJavaBeansNonConformingGetter() {
return this.aFieldWithJavaBeansNonConformingGetter;
}

public boolean isaFieldWithJavaBeansConformingBooleanGetter() {
return this.aFieldWithJavaBeansConformingBooleanGetter;
}

public boolean isAFieldWithJavaBeansNonConformingBooleanGetter() {
return this.aFieldWithJavaBeansNonConformingBooleanGetter;
}
}

@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ static Stream<Arguments> parametersForTestFindGetterField() {
Arguments.of("getFieldWithPublicGetter", "getFieldWithPrivateGetter", "fieldWithPublicGetter"),
Arguments.of("isFieldWithPublicBooleanGetter", null, "fieldWithPublicBooleanGetter"),
Arguments.of("isFieldWithPublicBooleanGetter", "isBehavingSomehow", "fieldWithPublicBooleanGetter"),
Arguments.of("isFieldWithMatchingPublicGetter", null, "isFieldWithMatchingPublicGetter"),
Arguments.of("isFieldWithMatchingPublicGetter", "isFieldWithPublicBooleanGetter", "isFieldWithMatchingPublicGetter"),
Arguments.of("getaFieldWithJavaBeansConformingGetter", null, "aFieldWithJavaBeansConformingGetter"),
Arguments.of("getAFieldWithJavaBeansNonConformingGetter", null, "aFieldWithJavaBeansNonConformingGetter"),
Arguments.of("isaFieldWithJavaBeansConformingBooleanGetter", null, "aFieldWithJavaBeansConformingBooleanGetter"),
Arguments.of("isAFieldWithJavaBeansNonConformingBooleanGetter", null, "aFieldWithJavaBeansNonConformingBooleanGetter"),
Arguments.of("getCalculatedValue", null, null),
Arguments.of("isBehavingSomehow", null, null),
Arguments.of("isBehavingSomehow", "isFieldWithPublicBooleanGetter", null),
Expand Down Expand Up @@ -116,6 +122,11 @@ private static class TestClass {
private long fieldWithPublicGetter;
@TestAnnotation
private boolean fieldWithPublicBooleanGetter;
private boolean isFieldWithMatchingPublicGetter;
private int aFieldWithJavaBeansConformingGetter;
private long aFieldWithJavaBeansNonConformingGetter;
private boolean aFieldWithJavaBeansConformingBooleanGetter;
private boolean aFieldWithJavaBeansNonConformingBooleanGetter;

@TestAnnotation
private int getFieldWithPrivateGetter() {
Expand All @@ -130,6 +141,26 @@ public boolean isFieldWithPublicBooleanGetter() {
return this.fieldWithPublicBooleanGetter;
}

public boolean isFieldWithMatchingPublicGetter() {
return this.isFieldWithMatchingPublicGetter;
}

public int getaFieldWithJavaBeansConformingGetter() {
return this.aFieldWithJavaBeansConformingGetter;
}

public long getAFieldWithJavaBeansNonConformingGetter() {
return this.aFieldWithJavaBeansNonConformingGetter;
}

public boolean isaFieldWithJavaBeansConformingBooleanGetter() {
return this.aFieldWithJavaBeansConformingBooleanGetter;
}

public boolean isAFieldWithJavaBeansNonConformingBooleanGetter() {
return this.aFieldWithJavaBeansNonConformingBooleanGetter;
}

public double getCalculatedValue() {
return 42.;
}
Expand Down

0 comments on commit d8bf965

Please sign in to comment.