ConstraintIntrospector.java
package dev.orne.test.rnd.params;
/*-
* #%L
* Orne Test Generators
* %%
* Copyright (C) 2021 Orne Developments
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.ConstructorDescriptor;
import javax.validation.metadata.MethodDescriptor;
import javax.validation.metadata.ParameterDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import javax.validation.metadata.ReturnValueDescriptor;
import javax.validation.metadata.ElementDescriptor.ConstraintFinder;
import javax.validation.metadata.ExecutableDescriptor;
import org.apache.commons.lang3.Validate;
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
/**
* Utility class for retrieving constraints annotations from generation
* targets.
*
* @author <a href="mailto:wamphiry@orne.dev">(w) Iker Hernaez</a>
* @version 1.1, 2023-11
* @since 0.1
*/
@API(status=Status.EXPERIMENTAL, since="0.1")
public final class ConstraintIntrospector {
/** The default validation groups when no one is provided. */
private static final Class<?>[] DEFAULT_GROUPS = new Class<?>[] {
Default.class
};
/**
* Private constructor.
*/
private ConstraintIntrospector() {
// Utility class
}
/** The default validator. */
private static final Validator DEFAULT_VALIDATOR =
Validation.buildDefaultValidatorFactory().getValidator();
/**
* Retrieves the constraint annotations of the specified property
* for the specified validation groups.
*
* @param type The bean type.
* @param property The property name.
* @param groups The validation groups.
* @return The constraint annotations of the specified property.
*/
public static @NotNull Set<Annotation> findPropertyConstrains(
final @NotNull Class<?> type,
final @NotNull String property,
final @NotNull Class<?>... groups) {
return findPropertyConstrains(DEFAULT_VALIDATOR, type, property, groups);
}
/**
* Retrieves the constraint annotations of the specified property
* for the specified validation groups.
*
* @param validator The validator to use.
* @param type The bean type.
* @param property The property name.
* @param groups The validation groups.
* @return The constraint annotations of the specified property.
*/
public static @NotNull Set<Annotation> findPropertyConstrains(
final @NotNull Validator validator,
final @NotNull Class<?> type,
final @NotNull String property,
final @NotNull Class<?>... groups) {
Validate.notNull(validator);
Validate.notNull(type);
Validate.notNull(property);
final BeanDescriptor beanDesc = validator.getConstraintsForClass(type);
final PropertyDescriptor propDesc = beanDesc.getConstraintsForProperty(property);
if (propDesc == null) {
return Collections.emptySet();
} else {
return extractAnnotations(propDesc.findConstraints(), groups);
}
}
/**
* Retrieves the constraint annotations of the specified parameter
* for the specified validation groups.
*
* @param parameter The target parameter.
* @param groups The validation groups.
* @return The constraint annotations of the specified parameter.
* @since 0.2
*/
public static @NotNull Set<Annotation> findParameterConstrains(
final @NotNull Parameter parameter,
final @NotNull Class<?>... groups) {
return findParameterConstrains(DEFAULT_VALIDATOR, parameter, groups);
}
/**
* Retrieves the constraint annotations of the specified parameter
* for the specified validation groups.
*
* @param validator The validator to use.
* @param parameter The target parameter.
* @param groups The validation groups.
* @return The constraint annotations of the specified parameter.
* @since 0.2
*/
public static @NotNull Set<Annotation> findParameterConstrains(
final @NotNull Validator validator,
final @NotNull Parameter parameter,
final @NotNull Class<?>... groups) {
Validate.notNull(validator);
Validate.notNull(parameter);
final Executable exec = parameter.getDeclaringExecutable();
final BeanDescriptor beanDesc = validator.getConstraintsForClass(
exec.getDeclaringClass());
ExecutableDescriptor exedDesc = null;
if (exec instanceof Constructor) {
exedDesc = beanDesc.getConstraintsForConstructor(
exec.getParameterTypes());
} else {
exedDesc = beanDesc.getConstraintsForMethod(
exec.getName(),
exec.getParameterTypes());
}
if (exedDesc == null) {
return Collections.emptySet();
}
ParameterDescriptor paramDesc = null;
for (final ParameterDescriptor execParamDesc : exedDesc.getParameterDescriptors()) {
if (parameter.getName().equals(execParamDesc.getName())) {
paramDesc = execParamDesc;
break;
}
}
if (paramDesc == null) {
return Collections.emptySet();
}
return extractAnnotations(
paramDesc.findConstraints(),
groups);
}
/**
* Retrieves the constraint annotations of the specified method parameter
* for the specified validation groups.
*
* @param method The target method.
* @param parameterIndex The parameter index.
* @param groups The validation groups.
* @return The constraint annotations of the specified method parameter.
*/
public static @NotNull Set<Annotation> findMethodParameterConstrains(
final @NotNull Method method,
final int parameterIndex,
final @NotNull Class<?>... groups) {
return findMethodParameterConstrains(DEFAULT_VALIDATOR, method, parameterIndex, groups);
}
/**
* Retrieves the constraint annotations of the specified method parameter
* for the specified validation groups.
*
* @param validator The validator to use.
* @param method The target method.
* @param parameterIndex The parameter index.
* @param groups The validation groups.
* @return The constraint annotations of the specified method parameter.
*/
public static @NotNull Set<Annotation> findMethodParameterConstrains(
final @NotNull Validator validator,
final @NotNull Method method,
final int parameterIndex,
final @NotNull Class<?>... groups) {
Validate.notNull(validator);
Validate.notNull(method);
Validate.validIndex(method.getParameterTypes(), parameterIndex);
final BeanDescriptor beanDesc = validator.getConstraintsForClass(
method.getDeclaringClass());
final MethodDescriptor methodDesc = beanDesc.getConstraintsForMethod(
method.getName(),
method.getParameterTypes());
if (methodDesc == null) {
return Collections.emptySet();
}
final ParameterDescriptor paramDesc =
methodDesc.getParameterDescriptors().get(parameterIndex);
return extractAnnotations(
paramDesc.findConstraints(),
groups);
}
/**
* Retrieves the constraint annotations of the specified method result
* for the specified validation groups.
*
* @param method The target method.
* @param groups The validation groups.
* @return The constraint annotations of the specified method result.
*/
public static @NotNull Set<Annotation> findMethodResultConstrains(
final @NotNull Method method,
final Class<?>... groups) {
return findMethodResultConstrains(DEFAULT_VALIDATOR, method, groups);
}
/**
* Retrieves the constraint annotations of the specified method result
* for the specified validation groups.
*
* @param validator The validator to use.
* @param method The target method.
* @param groups The validation groups.
* @return The constraint annotations of the specified method result.
*/
public static @NotNull Set<Annotation> findMethodResultConstrains(
final @NotNull Validator validator,
final @NotNull Method method,
final @NotNull Class<?>... groups) {
Validate.notNull(validator);
Validate.notNull(method);
final BeanDescriptor beanDesc = validator.getConstraintsForClass(
method.getDeclaringClass());
final MethodDescriptor methodDesc = beanDesc.getConstraintsForMethod(
method.getName(),
method.getParameterTypes());
if (methodDesc == null) {
return Collections.emptySet();
}
final ReturnValueDescriptor returnDesc = methodDesc.getReturnValueDescriptor();
return extractAnnotations(
returnDesc.findConstraints(),
groups);
}
/**
* Retrieves the constraint annotations of the specified constructor
* parameter for the specified validation groups.
*
* @param constructor The target constructor.
* @param parameterIndex The parameter index.
* @param groups The validation groups.
* @return The constraint annotations of the specified constructor
* parameter.
*/
public static @NotNull Set<Annotation> findConstructorParameterConstrains(
final @NotNull Constructor<?> constructor,
final int parameterIndex,
final @NotNull Class<?>... groups) {
return findConstructorParameterConstrains(DEFAULT_VALIDATOR, constructor, parameterIndex, groups);
}
/**
* Retrieves the constraint annotations of the specified constructor
* parameter for the specified validation groups.
*
* @param validator The validator to use.
* @param constructor The target constructor.
* @param parameterIndex The parameter index.
* @param groups The validation groups.
* @return The constraint annotations of the specified constructor
* parameter.
*/
public static @NotNull Set<Annotation> findConstructorParameterConstrains(
final @NotNull Validator validator,
final @NotNull Constructor<?> constructor,
final int parameterIndex,
final @NotNull Class<?>... groups) {
Validate.notNull(validator);
Validate.notNull(constructor);
Validate.validIndex(constructor.getParameterTypes(), parameterIndex);
final BeanDescriptor beanDesc = validator.getConstraintsForClass(
constructor.getDeclaringClass());
final ConstructorDescriptor ctrDesc = beanDesc.getConstraintsForConstructor(
constructor.getParameterTypes());
if (ctrDesc == null) {
return Collections.emptySet();
}
final ParameterDescriptor paramDesc =
ctrDesc.getParameterDescriptors().get(parameterIndex);
return extractAnnotations(
paramDesc.findConstraints(),
groups);
}
/**
* Extracts the constraint annotations from the specified constraint finder
* for the specified validation groups.
*
* @param finder The constraint finder.
* @param groups The validation groups.
* @return The constraint annotations found.
*/
private static @NotNull Set<Annotation> extractAnnotations(
final @NotNull ConstraintFinder finder,
@NotNull Class<?>... groups) {
if (groups.length == 0) {
groups = DEFAULT_GROUPS;
}
finder.unorderedAndMatchingGroups(groups);
final Set<Annotation> result = new HashSet<>();
for (final ConstraintDescriptor<?> descriptor : finder.getConstraintDescriptors()) {
extractConstraints(descriptor, result);
}
return result;
}
/**
* Gets the constraint annotations from the specified constraint
* descriptor, including composing constraints annotations, and adds
* to the passed annotation set.
* for the specified validation groups.
*
* @param descriptor The constraint descriptor.
* @param result The set to add the annotations to.
*/
private static void extractConstraints(
final @NotNull ConstraintDescriptor<?> descriptor,
final @NotNull Set<Annotation> result) {
result.add(descriptor.getAnnotation());
for (final ConstraintDescriptor<?> nested : descriptor.getComposingConstraints()) {
extractConstraints(nested, result);
}
}
}