Skip to content
Open
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
24 changes: 24 additions & 0 deletions bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,30 @@ private PathImpl derivePath() {
return p;
}

/**
* Get the leaf node of this context's path without materializing the path when it is only pending.
*
* @return {@link NodeImpl}
*/
public NodeImpl getLeafNode() {
return path == null && pendingNode != null ? pendingNode : pathReference().getLeafNode();
}

/**
* Learn whether {@code other}'s path equals this context's path with its leaf node removed, as by
* {@link PathImpl#removeLeafNode()}.
*
* @param other
* @return {@code boolean}
*/
public boolean hasParentPath(GraphContext other) {
if (path == null && pendingNode != null && !parent.pathReference().isRootPath()) {
// our path is the parent's with pendingNode appended, so removing the leaf yields the parent's path
return other == parent || other.pathReference().equals(parent.pathReference());
}
return pathReference().isParentPath(other.pathReference());
}

public Object getValue() {
return value;
}
Expand Down
30 changes: 30 additions & 0 deletions bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
package org.apache.bval.jsr.descriptor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -26,9 +29,12 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import jakarta.validation.metadata.BeanDescriptor;
import jakarta.validation.metadata.ConstructorDescriptor;
import jakarta.validation.metadata.ExecutableDescriptor;
import jakarta.validation.metadata.MethodDescriptor;
import jakarta.validation.metadata.MethodType;
import jakarta.validation.metadata.PropertyDescriptor;
Expand Down Expand Up @@ -65,6 +71,11 @@ public class BeanD<T> extends ElementD<Class<T>, MetadataReader.ForBean<T>> impl
private final GroupStrategyMap<PropertyD<?>[]> propertiesByGroups = new GroupStrategyMap<>();
private final GroupStrategyMap<GroupPlan> plans = new GroupStrategyMap<>();

/** Descriptors by executable, {@link #NOT_CONSTRAINED} for unconstrained ones. */
private final ConcurrentMap<Executable, Object> executableDescriptors = new ConcurrentHashMap<>();

private static final Object NOT_CONSTRAINED = new Object();

private static final GroupStrategy UNCHANGED = GroupStrategy.simple(Collections.emptySet());

BeanD(MetadataReader.ForBean<T> reader) {
Expand Down Expand Up @@ -134,6 +145,25 @@ public Set<MethodDescriptor> getConstrainedMethods(MethodType methodType, Method
});
}

/**
* Like {@link #getConstraintsForMethod(String, Class...)} or {@link #getConstraintsForConstructor(Class...)} for
* {@code executable}, cached by executable.
*
* @param executable
* @return {@link ExecutableDescriptor} or {@code null} if {@code executable} is not constrained
*/
public ExecutableDescriptor getConstraintsForExecutable(Executable executable) {
Object result = executableDescriptors.get(executable);
if (result == null) {
final ExecutableDescriptor descriptor = executable instanceof Method
? getConstraintsForMethod(executable.getName(), executable.getParameterTypes())
: getConstraintsForConstructor(((Constructor<?>) executable).getParameterTypes());
result = descriptor == null ? NOT_CONSTRAINED : descriptor;
executableDescriptors.putIfAbsent(executable, result);
}
return result == NOT_CONSTRAINED ? null : (ExecutableDescriptor) result;
}

@Override
public ConstructorDescriptor getConstraintsForConstructor(Class<?>... parameterTypes) {
return constructors.get(new Signature(beanClass.getName(), parameterTypes));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package org.apache.bval.jsr.descriptor;

import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import jakarta.validation.metadata.CascadableDescriptor;
Expand All @@ -37,6 +39,7 @@ public abstract class CascadableContainerD<P extends ElementD<?, ?>, E extends A
private final Set<GroupConversion> groupConversions;
private final Set<ContainerElementTypeDescriptor> containerElementTypes;
private final boolean cascadedDeep;
private final ContainerElementTypeD[] leafContainerElementTypes;

protected CascadableContainerD(MetadataReader.ForContainer<E> reader, P parent) {
super(reader, parent);
Expand All @@ -47,6 +50,21 @@ protected CascadableContainerD(MetadataReader.ForContainer<E> reader, P parent)
.filter(DescriptorManager::isConstrained)
.collect(ToUnmodifiable.set());
cascadedDeep = cascaded || containerElementTypes.stream().anyMatch(DescriptorManager::isCascaded);
final List<ContainerElementTypeD> leaves = new ArrayList<>();
for (ContainerElementTypeDescriptor d : containerElementTypes) {
ComposedD.forEachUnwrapped(d, ContainerElementTypeD.class, leaves::add);
}
leafContainerElementTypes = leaves.toArray(new ContainerElementTypeD[0]);
}

/**
* Get the leaf (non-composed) delegates of {@link #getConstrainedContainerElementTypes()}. The result must not
* be modified.
*
* @return {@link ContainerElementTypeD} array, possibly empty
*/
public ContainerElementTypeD[] getLeafContainerElementTypes() {
return leafContainerElementTypes;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,21 @@
package org.apache.bval.jsr.descriptor;

import java.lang.reflect.AnnotatedType;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import jakarta.validation.metadata.ContainerElementTypeDescriptor;

import org.apache.bval.jsr.GraphContext;
import org.apache.bval.jsr.metadata.ContainerElementKey;
import org.apache.bval.util.Validate;

public class ContainerElementTypeD extends CascadableContainerD<CascadableContainerD<?, ?>, AnnotatedType>
implements ContainerElementTypeDescriptor {

private final ContainerElementKey key;
/** {@link GraphContext#runtimeKey(ContainerElementKey)} of {@link #key} by runtime container class. */
private final ConcurrentMap<Class<?>, ContainerElementKey> runtimeKeys = new ConcurrentHashMap<>();

ContainerElementTypeD(ContainerElementKey key, MetadataReader.ForContainer<AnnotatedType> reader,
CascadableContainerD<?, ?> parent) {
Expand All @@ -49,4 +54,24 @@ public Integer getTypeArgumentIndex() {
public ContainerElementKey getKey() {
return key;
}

/**
* Get {@link #getKey()} in terms of the runtime type of the container held by {@code context}.
*
* @param context
* @return {@link ContainerElementKey}
* @see GraphContext#runtimeKey(ContainerElementKey)
*/
public ContainerElementKey getRuntimeKey(GraphContext context) {
final Object value = context.getValue();
if (value == null || value.getClass() == key.getContainerClass()) {
return key;
}
ContainerElementKey result = runtimeKeys.get(value.getClass());
if (result == null) {
result = context.runtimeKey(key);
runtimeKeys.putIfAbsent(value.getClass(), result);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import jakarta.validation.Path;
import jakarta.validation.Path.Node;
import jakarta.validation.metadata.ExecutableDescriptor;

import org.apache.bval.jsr.ApacheFactoryContext;
import org.apache.bval.jsr.metadata.Meta;
Expand All @@ -44,6 +45,9 @@ public abstract class ValidateExecutable<E extends Executable, T> extends Valida
protected final E executable;

private final Function<E, Path.Node> executableNode;
private PathImpl basePath;
private ExecutableDescriptor executableDescriptor;
private boolean described;

@SuppressWarnings("unchecked")
public ValidateExecutable(ApacheFactoryContext validatorContext, Class<?>[] groups, Meta<E> meta) {
Expand All @@ -64,8 +68,25 @@ public ValidateExecutable(ApacheFactoryContext validatorContext, Class<?>[] grou
}

protected PathImpl createBasePath() {
final PathImpl path = PathImpl.create();
path.addNode(executableNode.apply(executable));
return path;
if (basePath == null) {
basePath = PathImpl.create();
basePath.addNode(executableNode.apply(executable));
}
return PathImpl.copy(basePath);
}

/**
* Get the descriptor of {@link #executable}, computed once per job.
*
* @return {@link ExecutableDescriptor} or {@code null} if the executable is not constrained
*/
protected final ExecutableDescriptor getExecutableDescriptor() {
if (!described) {
executableDescriptor = describe();
described = true;
}
return executableDescriptor;
}

protected abstract ExecutableDescriptor describe();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,27 @@
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ParameterNameProvider;
import jakarta.validation.constraintvalidation.ValidationTarget;
import jakarta.validation.metadata.ExecutableDescriptor;
import jakarta.validation.metadata.ParameterDescriptor;

import org.apache.bval.jsr.ApacheFactoryContext;
import org.apache.bval.jsr.ConstraintViolationImpl;
import org.apache.bval.jsr.GraphContext;
import org.apache.bval.jsr.descriptor.BeanD;
import org.apache.bval.jsr.descriptor.ConstraintD;
import org.apache.bval.jsr.descriptor.CrossParameterD;
import org.apache.bval.jsr.descriptor.ParameterD;
import org.apache.bval.jsr.groups.Group;
import org.apache.bval.jsr.groups.GroupStrategy;
import org.apache.bval.jsr.metadata.Meta;
import org.apache.bval.jsr.util.NodeImpl;
import org.apache.bval.jsr.util.PathImpl;
import org.apache.bval.util.Exceptions;
import org.apache.bval.util.Lazy;
import org.apache.bval.util.Validate;
import org.apache.bval.util.reflection.TypeUtils;

Expand All @@ -63,8 +59,8 @@ public static class ForMethod<T> extends ValidateParameters<Method, T> {

@Override
protected ExecutableDescriptor describe() {
return validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass())
.getConstraintsForMethod(executable.getName(), executable.getParameterTypes());
return ((BeanD<?>) validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass()))
.getConstraintsForExecutable(executable);
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -94,8 +90,8 @@ public static class ForConstructor<T> extends ValidateParameters<Constructor<? e

@Override
protected ExecutableDescriptor describe() {
return validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass())
.getConstraintsForConstructor(executable.getParameterTypes());
return ((BeanD<?>) validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass()))
.getConstraintsForExecutable(executable);
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -132,33 +128,34 @@ protected ValidationTarget getValidationTarget() {
@Override
void process(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
Validate.notNull(sink, "sink");
final Lazy<Set<Frame<?>>> parameterFrames = new Lazy<>(this::parameterFrames);

GroupStrategy.redefining(groups, Collections.singletonMap(Group.DEFAULT, descriptor.getGroupStrategy()))
.applyTo(noViolations(gs -> {
validateDescriptorConstraints(gs, sink);
parameterFrames.get().forEach(p -> p.validateDescriptorConstraints(gs, sink));
}));
parameterFrames.get().forEach(p -> p.recurse(groups, sink));
final List<ParameterDescriptor> parameters = executableDescriptor.getParameterDescriptors();
@SuppressWarnings("unchecked")
final Frame<?>[] parameterFrames = new Frame[parameters.size()];
for (int i = 0; i < parameterFrames.length; i++) {
final ParameterD<?> pd = (ParameterD<?>) parameters.get(i);
parameterFrames[i] = new SproutFrame<ParameterD<?>>(this, pd, parameter(pd.getIndex()));
}
descriptor.getLocalGroupStrategy(groups).applyTo(noViolations(gs -> {
validateDescriptorConstraints(gs, sink);
for (final Frame<?> p : parameterFrames) {
p.validateDescriptorConstraints(gs, sink);
}
}));
for (final Frame<?> p : parameterFrames) {
p.recurse(groups, sink);
}
}

@Override
Object getBean() {
return object;
}

private Set<Frame<?>> parameterFrames() {
return executableDescriptor.getParameterDescriptors().stream()
.map(pd -> new SproutFrame<ParameterD<?>>(this, (ParameterD<?>) pd, parameter(pd.getIndex())))
.collect(Collectors.toSet());
}
}

private static final String PARAMETERS_DO_NOT_MATCH = "Parameters do not match";

protected final T object;
protected final Lazy<List<String>> parameterNames =
new Lazy<>(() -> getParameterNames(validatorContext.getParameterNameProvider()));
private List<String> parameterNames;

private final Object[] parameterValues;

Expand All @@ -172,25 +169,24 @@ private Set<Frame<?>> parameterFrames() {
final Type[] genericParameterTypes = executable.getGenericParameterTypes();
Exceptions.raiseUnless(parameterValues.length == genericParameterTypes.length, IllegalArgumentException::new,
PARAMETERS_DO_NOT_MATCH);
IntStream.range(0, genericParameterTypes.length)
.forEach(n -> Exceptions.raiseUnless(TypeUtils.isInstance(parameterValues[n], genericParameterTypes[n]),
IllegalArgumentException::new, PARAMETERS_DO_NOT_MATCH));
for (int n = 0; n < genericParameterTypes.length; n++) {
Exceptions.raiseUnless(TypeUtils.isInstance(parameterValues[n], genericParameterTypes[n]),
IllegalArgumentException::new, PARAMETERS_DO_NOT_MATCH);
}
}

@Override
protected Frame<?> computeBaseFrame() {
return new ParametersFrame(describe(), new GraphContext(validatorContext,
return new ParametersFrame(getExecutableDescriptor(), new GraphContext(validatorContext,
createBasePath().addNode(new NodeImpl.CrossParameterNodeImpl()), parameterValues));
}

@Override
protected boolean hasWork() {
final ExecutableDescriptor descriptor = describe();
final ExecutableDescriptor descriptor = getExecutableDescriptor();
return descriptor != null && descriptor.hasConstrainedParameters();
}

protected abstract ExecutableDescriptor describe();

protected abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider);

protected abstract T getRootBean();
Expand All @@ -205,7 +201,10 @@ ConstraintViolationImpl<T> createViolation(String messageTemplate, String messag

private GraphContext parameter(int i) {
final PathImpl path = createBasePath();
path.addNode(new NodeImpl.ParameterNodeImpl(parameterNames.get().get(i), i));
if (parameterNames == null) {
parameterNames = getParameterNames(validatorContext.getParameterNameProvider());
}
path.addNode(new NodeImpl.ParameterNodeImpl(parameterNames.get(i), i));
return new GraphContext(validatorContext, path, parameterValues[i]);
}
}
Loading
Loading