Skip to content

Commit

Permalink
Ignore JavaBean method prefixes when method name matches field name (#…
Browse files Browse the repository at this point in the history
…1928)

Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar authored Jul 30, 2024
1 parent dde0fb1 commit ce468d5
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

Expand Down Expand Up @@ -54,9 +55,10 @@ public enum Visibility {
UNSET
}

public Visibility isIgnore(AnnotationTarget annotationTarget, AnnotationTarget reference) {
public Visibility isIgnore(Map<String, TypeResolver> properties,
AnnotationTarget annotationTarget, AnnotationTarget reference) {
for (IgnoreAnnotationHandler handler : ignoreHandlers) {
Visibility v = handler.shouldIgnore(annotationTarget, reference);
Visibility v = handler.shouldIgnore(properties, annotationTarget, reference);

if (v != Visibility.UNSET) {
return v;
Expand All @@ -82,7 +84,8 @@ public Visibility getDescendantVisibility(String propertyName, List<ClassInfo> d
*/
private final class SchemaHiddenHandler implements IgnoreAnnotationHandler {
@Override
public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) {
public Visibility shouldIgnore(Map<String, TypeResolver> properties, AnnotationTarget target,
AnnotationTarget reference) {
AnnotationInstance annotationInstance = context.annotations().getAnnotation(target, getNames());
if (annotationInstance != null) {
Boolean hidden = context.annotations().value(annotationInstance, SchemaConstant.PROP_HIDDEN);
Expand All @@ -105,7 +108,8 @@ public List<DotName> getNames() {
*/
private final class JsonbTransientHandler implements IgnoreAnnotationHandler {
@Override
public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) {
public Visibility shouldIgnore(Map<String, TypeResolver> properties, AnnotationTarget target,
AnnotationTarget reference) {
return context.annotations().hasAnnotation(target, getNames()) ? Visibility.IGNORED : Visibility.UNSET;
}

Expand All @@ -121,14 +125,15 @@ public List<DotName> getNames() {
private final class JsonIgnorePropertiesHandler implements IgnoreAnnotationHandler {

@Override
public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) {
Visibility visibility = declaringClassIgnore(target);
public Visibility shouldIgnore(Map<String, TypeResolver> properties, AnnotationTarget target,
AnnotationTarget reference) {
Visibility visibility = declaringClassIgnore(properties, target);

if (visibility != Visibility.UNSET) {
return visibility;
}

return nestingPropertyIgnore(reference, propertyName(target));
return nestingPropertyIgnore(reference, propertyName(properties, target));
}

/**
Expand All @@ -146,10 +151,10 @@ public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget referen
* @param target
* @return
*/
private Visibility declaringClassIgnore(AnnotationTarget target) {
private Visibility declaringClassIgnore(Map<String, TypeResolver> properties, AnnotationTarget target) {
AnnotationInstance declaringClassJIP = context.annotations().getAnnotation(TypeUtil.getDeclaringClass(target),
getNames());
return shouldIgnoreTarget(declaringClassJIP, propertyName(target));
return shouldIgnoreTarget(declaringClassJIP, propertyName(properties, target));
}

/**
Expand Down Expand Up @@ -181,12 +186,12 @@ private Visibility nestingPropertyIgnore(AnnotationTarget nesting, String proper
return shouldIgnoreTarget(nestedTypeJIP, propertyName);
}

private String propertyName(AnnotationTarget target) {
private String propertyName(Map<String, TypeResolver> properties, AnnotationTarget target) {
if (target.kind() == Kind.FIELD) {
return target.asField().name();
}
// Assuming this is a getter or setter
return TypeResolver.propertyName(target.asMethod());
return TypeResolver.propertyName(properties, target.asMethod());
}

private Visibility shouldIgnoreTarget(AnnotationInstance jipAnnotation, String targetName) {
Expand Down Expand Up @@ -227,7 +232,8 @@ public Visibility getDescendantVisibility(String propertyName, List<ClassInfo> d
private final class JsonIgnoreHandler implements IgnoreAnnotationHandler {

@Override
public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) {
public Visibility shouldIgnore(Map<String, TypeResolver> properties, AnnotationTarget target,
AnnotationTarget reference) {
AnnotationInstance annotationInstance = context.annotations().getAnnotation(target, getNames());
if (annotationInstance != null && valueAsBooleanOrTrue(annotationInstance)) {
return Visibility.IGNORED;
Expand All @@ -248,7 +254,8 @@ private final class JsonIgnoreTypeHandler implements IgnoreAnnotationHandler {
private final Set<DotName> ignoredTypes = new LinkedHashSet<>();

@Override
public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) {
public Visibility shouldIgnore(Map<String, TypeResolver> properties, AnnotationTarget target,
AnnotationTarget reference) {
Type classType;

switch (target.kind()) {
Expand Down Expand Up @@ -277,7 +284,7 @@ public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget referen
// Primitive and non-indexed types will result in a null
if (classType.kind() == Type.Kind.PRIMITIVE ||
classType.kind() == Type.Kind.VOID ||
(classType.kind() == Type.Kind.ARRAY && classType.asArrayType().component().kind() == Type.Kind.PRIMITIVE)
(classType.kind() == Type.Kind.ARRAY && classType.asArrayType().constituent().kind() == Type.Kind.PRIMITIVE)
||
!index.containsClass(classType)) {
return Visibility.UNSET;
Expand Down Expand Up @@ -309,7 +316,8 @@ public List<DotName> getNames() {

private final class TransientIgnoreHandler implements IgnoreAnnotationHandler {
@Override
public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) {
public Visibility shouldIgnore(Map<String, TypeResolver> properties, AnnotationTarget target,
AnnotationTarget reference) {
if (target.kind() == AnnotationTarget.Kind.FIELD) {
FieldInfo field = target.asField();
// If field has transient modifier, e.g. `transient String foo;`, then hide it.
Expand All @@ -336,7 +344,8 @@ public List<DotName> getNames() {

private final class JaxbAccessibilityHandler implements IgnoreAnnotationHandler {
@Override
public Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference) {
public Visibility shouldIgnore(Map<String, TypeResolver> properties, AnnotationTarget target,
AnnotationTarget reference) {
if (hasXmlTransient(target)) {
return Visibility.IGNORED;
}
Expand Down Expand Up @@ -411,7 +420,9 @@ private boolean valueAsBooleanOrTrue(AnnotationInstance annotation) {
}

private interface IgnoreAnnotationHandler {
Visibility shouldIgnore(AnnotationTarget target, AnnotationTarget reference);
Visibility shouldIgnore(Map<String, TypeResolver> properties,
AnnotationTarget target,
AnnotationTarget reference);

List<DotName> getNames();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ private static List<MethodInfo> methods(AnnotationScannerContext context, ClassI
}

private static boolean acceptMethod(MethodInfo method) {
if (Modifier.isStatic(method.flags())) {
if (Modifier.isStatic(method.flags()) || method.isSynthetic()) {
return false;
}
String name = method.name();
Expand Down Expand Up @@ -603,11 +603,11 @@ private static boolean isViewable(AnnotationScannerContext context, AnnotationTa
* @param target the field or method to be checked for ignoring or exposure in the API
* @param reference an annotated member (field or method) that referenced the type of target's declaring class
* @param descendants list of classes that descend from the class containing target
* @param ignoreResolver resolver to determine if the field is ignored
* @param properties map of other known properties that are peers of the target
*/
private void processVisibility(AnnotationScannerContext context, AnnotationTarget target, AnnotationTarget reference,
List<ClassInfo> descendants,
IgnoreResolver ignoreResolver) {
Map<String, TypeResolver> properties) {
if (this.exposed || this.ignored) {
// @Schema with hidden = false OR ignored somehow by a member lower in the class hierarchy
return;
Expand All @@ -619,7 +619,7 @@ private void processVisibility(AnnotationScannerContext context, AnnotationTarge
return;
}

switch (getVisibility(context, target, reference, descendants, ignoreResolver)) {
switch (getVisibility(context, target, reference, descendants, properties)) {
case EXPOSED:
this.exposed = true;
break;
Expand Down Expand Up @@ -674,22 +674,23 @@ private void processAccess(AnnotationTarget target) {
* @param target the field or method to be checked for ignoring or exposure in the API
* @param reference an annotated member (field or method) that referenced the type of target's declaring class
* @param descendants list of classes that descend from the class containing target
* @param ignoreResolver resolver to determine if the field is ignored
* @param properties map of other known properties that are peers of the target
*/
private IgnoreResolver.Visibility getVisibility(AnnotationScannerContext context, AnnotationTarget target,
AnnotationTarget reference,
List<ClassInfo> descendants,
IgnoreResolver ignoreResolver) {
Map<String, TypeResolver> properties) {

if (!isViewable(context, target)) {
return IgnoreResolver.Visibility.IGNORED;
}

IgnoreResolver ignoreResolver = context.getIgnoreResolver();
// First check if a descendant class has hidden/exposed the property
IgnoreResolver.Visibility visibility = ignoreResolver.getDescendantVisibility(propertyName, descendants);

if (visibility == IgnoreResolver.Visibility.UNSET) {
visibility = ignoreResolver.isIgnore(target, reference);
visibility = ignoreResolver.isIgnore(properties, target, reference);
}

return visibility;
Expand Down Expand Up @@ -773,7 +774,7 @@ private static void scanField(AnnotationScannerContext context, Map<String, Type
// Ignored for getters/setters
resolver.ignored = true;
} else {
resolver.processVisibility(context, field, reference, descendants, context.getIgnoreResolver());
resolver.processVisibility(context, field, reference, descendants, properties);
resolver.processAccess(field);
}
}
Expand Down Expand Up @@ -841,7 +842,7 @@ private static void scanMethod(AnnotationScannerContext context, Map<String, Typ
if (propertyType != null) {
TypeResolver resolver = updateTypeResolvers(context, properties, stack, method, propertyType);
if (resolver != null) {
resolver.processVisibility(context, method, reference, descendants, context.getIgnoreResolver());
resolver.processVisibility(context, method, reference, descendants, properties);
resolver.processAccess(method);
}
}
Expand All @@ -865,7 +866,7 @@ private static TypeResolver updateTypeResolvers(AnnotationScannerContext context
MethodInfo method,
Type propertyType) {

final String propertyName = propertyName(method);
final String propertyName = propertyName(properties, method);

if (propertyName == null) {
return null;
Expand Down Expand Up @@ -913,7 +914,7 @@ private static TypeResolver updateTypeResolvers(AnnotationScannerContext context
* @param method either an accessor (getter) or mutator (setter) for the property
* @return the property name
*/
static String propertyName(MethodInfo method) {
static String propertyName(Map<String, TypeResolver> properties, MethodInfo method) {
final String methodName = method.name();
final ClassInfo declaringClass = method.declaringClass();

Expand All @@ -923,6 +924,11 @@ static String propertyName(MethodInfo method) {
return methodName;
}

if (Optional.ofNullable(properties.get(methodName)).map(p -> p.field).isPresent()) {
// The method name matches the field name. Do not modify further.
return methodName;
}

final int nameStart = methodNamePrefix(method).length();
final String propertyName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -789,4 +789,35 @@ class Bean {

assertJsonEquals("components.schemas.exceptional-examples.json", Bean.class);
}

@Test
void testPropertyWithJavaBeanPrefixes() throws IOException, JSONException {
@Schema(name = "Bean")
@SuppressWarnings("unused")
class Bean {
@Schema(name = "getProperty1")
public String getProperty1;

@Schema(name = "propertyTwo")
public String getProperty2;

public String getProperty1() {
return getProperty1;
}

public void getProperty1(String getProperty1) {
this.getProperty1 = getProperty1;
}

public String getProperty2() {
return getProperty2;
}

public void getProperty2(String getProperty2) {
this.getProperty2 = getProperty2;
}
}

assertJsonEquals("components.schemas.javabean-property-prefix.json", Bean.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"openapi" : "3.0.3",
"components" : {
"schemas" : {
"Bean" : {
"type" : "object",
"properties" : {
"getProperty1" : {
"type" : "string"
},
"propertyTwo" : {
"type" : "string"
}
}
}
}
}
}

0 comments on commit ce468d5

Please sign in to comment.