
com.github.victools.jsonschema.generator.MethodScope Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsonschema-generator Show documentation
Show all versions of jsonschema-generator Show documentation
Java JSON Schema Generator – creating a JSON Schema (Draft 6, Draft 7 or Draft 2019-09) from your Java classes
/*
* Copyright 2019 VicTools.
*
* 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
*
* http://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 com.github.victools.jsonschema.generator;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.members.ResolvedMethod;
import com.github.victools.jsonschema.generator.impl.LazyValue;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Representation of a single introspected method.
*/
public class MethodScope extends MemberScope {
private final LazyValue getterField = new LazyValue<>(this::doFindGetterField);
/**
* Constructor.
*
* @param method targeted method
* @param declarationDetails basic details regarding the declaration context
* @param overrideDetails augmenting details (e.g., overridden type, name, or container item index)
* @param context the overall type resolution context
*/
protected MethodScope(ResolvedMethod method, DeclarationDetails declarationDetails, OverrideDetails overrideDetails, TypeContext context) {
super(method, declarationDetails, overrideDetails, context);
}
@Override
public MethodScope withOverriddenType(ResolvedType overriddenType) {
OverrideDetails overrideDetails = new OverrideDetails(overriddenType, this.getOverriddenName(), this.getFakeContainerItemIndex());
return new MethodScope(this.getMember(), this.getDeclarationDetails(), overrideDetails, this.getContext());
}
@Override
public MethodScope withOverriddenName(String overriddenName) {
OverrideDetails overrideDetails = new OverrideDetails(this.getOverriddenType(), overriddenName, this.getFakeContainerItemIndex());
return new MethodScope(this.getMember(), this.getDeclarationDetails(), overrideDetails, this.getContext());
}
@Override
public MethodScope asFakeContainerItemScope() {
return (MethodScope) super.asFakeContainerItemScope();
}
/**
* Indicating whether the method is declared as {@code void}, i.e. has no return value.
*
* @return whether method has no return value
*/
public boolean isVoid() {
return this.getType() == null;
}
/**
* Returns the number of arguments this method has.
*
* @return number of arguments
*/
public int getArgumentCount() {
return this.getMember().getArgumentCount();
}
/**
* Returns the list of types of this method's arguments.
*
* @return argument types
*/
public List getArgumentTypes() {
return IntStream.range(0, this.getArgumentCount())
.mapToObj(this.getMember()::getArgumentType)
.collect(Collectors.toList());
}
/**
* Look-up the field associated with this method if it is deemed to be a getter by convention.
*
* @return associated field
*/
public FieldScope findGetterField() {
return this.getterField.get();
}
/**
* Look-up the field associated with this method if it is deemed to be a getter by convention.
*
* @return associated field
*/
private FieldScope doFindGetterField() {
if (this.getType() == null || this.getArgumentCount() > 0) {
// void methods or those with arguments are not deemed to be getters
return null;
}
if (!this.isPublic()) {
// only public methods are deemed eligible to be getters
return null;
}
String methodName = this.getDeclaredName();
Set 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;
}
// method name matched getter conventions
// check whether a matching field exists
return Stream.of(this.getDeclaringTypeMembers().getMemberFields())
.filter(memberField -> possibleFieldNames.contains(memberField.getName()))
.findFirst()
.map(field -> this.getContext().createFieldScope(field, this.getDeclarationDetails()))
.orElse(null);
}
private static Stream getPossibleFieldNames(String prefix, String methodName) {
if (!methodName.startsWith(prefix) || prefix.length() == methodName.length()) {
return Stream.of();
}
Stream.Builder 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(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 (methodNameWithoutPrefix.length() > 1 && Character.isUpperCase(methodNameWithoutPrefix.charAt(1))) {
possibleFieldNames.add(methodNameWithoutPrefix);
}
return possibleFieldNames.build();
}
/**
* Determine whether the method's name matches the getter naming convention ("getFoo()"/"isFoo()") and a respective field ("foo") exists.
*
* @return whether method name starts with "get"/"is" and rest matches name of field in declaring class
* @see #findGetterField()
*/
public boolean isGetter() {
return this.findGetterField() != null;
}
/**
* Return the annotation of the given type on the method or its return type, if such an annotation is present.
*
* @param type of annotation to look-up
* @param annotationClass annotation class to look up instance on member for
* @return annotation instance (or {@code null} if no annotation of the given type is present
*/
@Override
public A getAnnotation(Class annotationClass, Predicate considerOtherAnnotation) {
A annotation = super.getAnnotation(annotationClass, considerOtherAnnotation);
if (annotation == null) {
List annotationList = Arrays.asList(this.getRawMember().getAnnotatedReturnType().getAnnotations());
annotation = this.getContext().getAnnotationFromList(annotationClass, annotationList, considerOtherAnnotation);
}
return annotation;
}
@Override
public A getContainerItemAnnotation(Class annotationClass, Predicate considerOtherAnnotation) {
AnnotatedType annotatedReturnType = this.getRawMember().getAnnotatedReturnType();
return this.getContext()
.getTypeParameterAnnotation(annotationClass, considerOtherAnnotation, annotatedReturnType, this.getFakeContainerItemIndex());
}
@Override
public A getAnnotationConsideringFieldAndGetter(Class annotationClass, Predicate considerOtherAnnotation) {
A annotation = this.getAnnotation(annotationClass, considerOtherAnnotation);
if (annotation == null) {
MemberScope, ?> associatedField = this.findGetterField();
if (associatedField != null) {
annotation = associatedField.getAnnotation(annotationClass, considerOtherAnnotation);
}
}
return annotation;
}
@Override
public A getContainerItemAnnotationConsideringFieldAndGetter(Class annotationClass,
Predicate considerOtherAnnotation) {
A annotation = this.getContainerItemAnnotation(annotationClass, considerOtherAnnotation);
if (annotation == null) {
MemberScope, ?> associatedField = this.findGetterField();
if (associatedField != null) {
annotation = associatedField.getContainerItemAnnotation(annotationClass, considerOtherAnnotation);
}
}
return annotation;
}
/**
* Returns the name to be used to reference this method in its parent's "properties".
*
* @return the (potentially overridden) name of the method followed by its argument types in parentheses
* @see #getName()
* @see TypeContext#getMethodPropertyArgumentTypeDescription(ResolvedType)
*/
@Override
protected String doGetSchemaPropertyName() {
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 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);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy