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

Ignore JavaBean method prefixes when method name matches field name #1928

Merged
merged 1 commit into from
Jul 30, 2024
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
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"
}
}
}
}
}
}