diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java b/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java index ae4e0ad1cb..0ed7756ba6 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java @@ -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; } diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java index 7942c5f25d..cb1ef473ca 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java @@ -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; @@ -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; @@ -65,6 +71,11 @@ public class BeanD extends ElementD, MetadataReader.ForBean> impl private final GroupStrategyMap[]> propertiesByGroups = new GroupStrategyMap<>(); private final GroupStrategyMap plans = new GroupStrategyMap<>(); + /** Descriptors by executable, {@link #NOT_CONSTRAINED} for unconstrained ones. */ + private final ConcurrentMap executableDescriptors = new ConcurrentHashMap<>(); + + private static final Object NOT_CONSTRAINED = new Object(); + private static final GroupStrategy UNCHANGED = GroupStrategy.simple(Collections.emptySet()); BeanD(MetadataReader.ForBean reader) { @@ -134,6 +145,25 @@ public Set 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)); diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java index 30adbdc0f3..208c76c5e5 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java @@ -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; @@ -37,6 +39,7 @@ public abstract class CascadableContainerD

, E extends A private final Set groupConversions; private final Set containerElementTypes; private final boolean cascadedDeep; + private final ContainerElementTypeD[] leafContainerElementTypes; protected CascadableContainerD(MetadataReader.ForContainer reader, P parent) { super(reader, parent); @@ -47,6 +50,21 @@ protected CascadableContainerD(MetadataReader.ForContainer reader, P parent) .filter(DescriptorManager::isConstrained) .collect(ToUnmodifiable.set()); cascadedDeep = cascaded || containerElementTypes.stream().anyMatch(DescriptorManager::isCascaded); + final List 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; } /** diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java index ac5337a090..7c56ffb502 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java @@ -19,9 +19,12 @@ 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; @@ -29,6 +32,8 @@ public class ContainerElementTypeD extends CascadableContainerD, ContainerElementKey> runtimeKeys = new ConcurrentHashMap<>(); ContainerElementTypeD(ContainerElementKey key, MetadataReader.ForContainer reader, CascadableContainerD parent) { @@ -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; + } } diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateExecutable.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateExecutable.java index b222f3ebeb..6b4ca5b998 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateExecutable.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateExecutable.java @@ -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; @@ -44,6 +45,9 @@ public abstract class ValidateExecutable extends Valida protected final E executable; private final Function executableNode; + private PathImpl basePath; + private ExecutableDescriptor executableDescriptor; + private boolean described; @SuppressWarnings("unchecked") public ValidateExecutable(ApacheFactoryContext validatorContext, Class[] groups, Meta meta) { @@ -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(); } \ No newline at end of file diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java index 2f67de4171..517ae37001 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java @@ -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; @@ -63,8 +59,8 @@ public static class ForMethod extends ValidateParameters { @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") @@ -94,8 +90,8 @@ public static class ForConstructor extends ValidateParameters) validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass())) + .getConstraintsForExecutable(executable); } @SuppressWarnings("unchecked") @@ -132,33 +128,34 @@ protected ValidationTarget getValidationTarget() { @Override void process(GroupStrategy groups, Consumer> sink) { Validate.notNull(sink, "sink"); - final Lazy>> 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 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>(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> parameterFrames() { - return executableDescriptor.getParameterDescriptors().stream() - .map(pd -> new SproutFrame>(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> parameterNames = - new Lazy<>(() -> getParameterNames(validatorContext.getParameterNameProvider())); + private List parameterNames; private final Object[] parameterValues; @@ -172,25 +169,24 @@ private Set> 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 getParameterNames(ParameterNameProvider parameterNameProvider); protected abstract T getRootBean(); @@ -205,7 +201,10 @@ ConstraintViolationImpl 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]); } } diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java index e22431ae3a..a2a6ba8dca 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java @@ -26,6 +26,7 @@ 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.ReturnValueD; import org.apache.bval.jsr.metadata.Meta; @@ -60,8 +61,8 @@ protected Class getRootBeanClass() { @Override protected ExecutableDescriptor describe() { - return validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass()) - .getConstraintsForMethod(executable.getName(), executable.getParameterTypes()); + return ((BeanD) validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass())) + .getConstraintsForExecutable(executable); } @Override @@ -97,8 +98,8 @@ protected Class getRootBeanClass() { @Override protected ExecutableDescriptor describe() { - return validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass()) - .getConstraintsForConstructor(executable.getParameterTypes()); + return ((BeanD) validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass())) + .getConstraintsForExecutable(executable); } @Override @@ -127,7 +128,7 @@ Object getBean() { @Override protected Frame computeBaseFrame() { - return createBaseFrame((ReturnValueD) describe().getReturnValueDescriptor(), new GraphContext( + return createBaseFrame((ReturnValueD) getExecutableDescriptor().getReturnValueDescriptor(), new GraphContext( validatorContext, createBasePath().addNode(new NodeImpl.ReturnValueNodeImpl()), returnValue)); } @@ -141,12 +142,10 @@ ConstraintViolationImpl createViolation(String messageTemplate, String messag @Override protected boolean hasWork() { - final ExecutableDescriptor descriptor = describe(); + final ExecutableDescriptor descriptor = getExecutableDescriptor(); return descriptor != null && descriptor.hasConstrainedReturnValue(); } - protected abstract ExecutableDescriptor describe(); - protected abstract T getRootBean(); protected abstract Frame createBaseFrame(ReturnValueD descriptor, GraphContext context); diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java index 84bbcfe4e3..14ad9140b6 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java @@ -21,6 +21,7 @@ import java.lang.reflect.Array; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; @@ -53,6 +54,7 @@ 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.CascadableContainerD; import org.apache.bval.jsr.descriptor.ComposedD; import org.apache.bval.jsr.descriptor.ConstraintD; import org.apache.bval.jsr.descriptor.ContainerElementTypeD; @@ -381,21 +383,17 @@ public SproutFrame(Frame parent, D descriptor, GraphContext context) { void validateDescriptorConstraints(GroupStrategy groups, Consumer> sink) { super.validateDescriptorConstraints(groups, sink); if (context.getValue() != null) { - for (final ContainerElementTypeDescriptor ctd : descriptor.getConstrainedContainerElementTypes()) { - ComposedD.forEachUnwrapped(ctd, ContainerElementTypeD.class, d -> { - if (d.getConstraintsFor(groups).length == 0 - && d.getConstrainedContainerElementTypes().isEmpty()) { - return; - } - final ValueExtractor declaredTypeValueExtractor = - context.getValidatorContext().getValueExtractors().find(d.getKey()); - for (final GraphContext e : ExtractValues.extract(context, d.getKey(), declaredTypeValueExtractor)) { - if (!e.isRecursive()) { - new ContainerElementConstraintsFrame(this, d, e) - .validateDescriptorConstraints(groups, sink); - } + for (final ContainerElementTypeD d : containerElementTypes(descriptor)) { + if (d.getConstraintsFor(groups).length == 0 && d.getConstrainedContainerElementTypes().isEmpty()) { + continue; + } + final ValueExtractor declaredTypeValueExtractor = + context.getValidatorContext().getValueExtractors().find(d.getKey()); + for (final GraphContext e : ExtractValues.extract(context, d.getKey(), declaredTypeValueExtractor)) { + if (!e.isRecursive()) { + new ContainerElementConstraintsFrame(this, d, e).validateDescriptorConstraints(groups, sink); } - }); + } } } } @@ -405,8 +403,18 @@ void recurse(GroupStrategy groups, Consumer> sink) { if (context.getValue() == null || !DescriptorManager.isCascaded(descriptor)) { return; } + final Set groupConversions = descriptor.getGroupConversions(); + if (groupConversions.isEmpty()) { + if (groups instanceof Group) { + // a single group: applyTo would just call back once, and the result is not needed + cascade(groups, sink); + } else { + groups.applyTo(noViolations(gs -> cascade(gs, sink))); + } + return; + } final Map conversions = new HashMap<>(); - for (final GroupConversionDescriptor gc : descriptor.getGroupConversions()) { + for (final GroupConversionDescriptor gc : groupConversions) { conversions.put(Group.of(gc.getFrom()), validatorContext.getGroupsComputer().computeGroups(gc.getTo()).asStrategy()); } @@ -415,19 +423,17 @@ void recurse(GroupStrategy groups, Consumer> sink) { } private void cascade(GroupStrategy groups, Consumer> sink) { - for (final ContainerElementTypeDescriptor ctd : descriptor.getConstrainedContainerElementTypes()) { - ComposedD.forEachUnwrapped(ctd, ContainerElementTypeD.class, d -> { - if (!d.isCascaded() && d.getConstrainedContainerElementTypes().isEmpty()) { - return; - } - final ValueExtractor runtimeTypeValueExtractor = - context.getValidatorContext().getValueExtractors().find(context.runtimeKey(d.getKey())); - for (final GraphContext e : ExtractValues.extract(context, d.getKey(), runtimeTypeValueExtractor)) { - if (!e.isRecursive()) { - new ContainerElementCascadeFrame(this, d, e).recurse(groups, sink); - } + for (final ContainerElementTypeD d : containerElementTypes(descriptor)) { + if (!d.isCascaded() && d.getConstrainedContainerElementTypes().isEmpty()) { + continue; + } + final ValueExtractor runtimeTypeValueExtractor = + context.getValidatorContext().getValueExtractors().find(d.getRuntimeKey(context)); + for (final GraphContext e : ExtractValues.extract(context, d.getKey(), runtimeTypeValueExtractor)) { + if (!e.isRecursive()) { + new ContainerElementCascadeFrame(this, d, e).recurse(groups, sink); } - }); + } } if (!descriptor.isCascaded()) { return; @@ -452,18 +458,20 @@ private void cascade(GroupStrategy groups, Consumer> sink throw new ValidationException(e); } } - multiplexEach(cx -> { - if (cx.getValue() != null && !cx.isRecursive()) { - new BeanFrame<>(this, cx).process(groups, sink); - } - }); + multiplexEach(groups, sink); } protected GraphContext getMultiplexContext() { return context; } - private void multiplexEach(Consumer consumer) { + private void cascadeInto(GraphContext cx, GroupStrategy groups, Consumer> sink) { + if (cx.getValue() != null && !cx.isRecursive()) { + new BeanFrame<>(this, cx).process(groups, sink); + } + } + + private void multiplexEach(GroupStrategy groups, Consumer> sink) { final GraphContext multiplexContext = getMultiplexContext(); final Object value = multiplexContext.getValue(); if (value == null) { @@ -473,37 +481,37 @@ private void multiplexEach(Consumer consumer) { // inconsistent: use Object[] here but specific type for Iterable? RI compatibility final Class arrayType = value instanceof Object[] ? Object[].class : value.getClass(); for (int i = 0, n = Array.getLength(value); i < n; i++) { - consumer.accept( - multiplexContext.child(NodeImpl.atIndex(i).inContainer(arrayType, null), Array.get(value, i))); + cascadeInto(multiplexContext.child(NodeImpl.atIndex(i).inContainer(arrayType, null), + Array.get(value, i)), groups, sink); } return; } if (Map.class.isInstance(value)) { for (final Map.Entry e : ((Map) value).entrySet()) { - consumer.accept(multiplexContext.child( + cascadeInto(multiplexContext.child( setContainerInformation(NodeImpl.atKey(e.getKey()), MAP_VALUE, descriptor.getElementClass()), - e.getValue())); + e.getValue()), groups, sink); } return; } if (List.class.isInstance(value)) { final List l = (List) value; for (int i = 0, n = l.size(); i < n; i++) { - consumer.accept(multiplexContext.child( + cascadeInto(multiplexContext.child( setContainerInformation(NodeImpl.atIndex(i), ITERABLE_ELEMENT, descriptor.getElementClass()), - l.get(i))); + l.get(i)), groups, sink); } return; } if (Iterable.class.isInstance(value)) { for (final Object o : (Iterable) value) { - consumer.accept(multiplexContext.child( + cascadeInto(multiplexContext.child( setContainerInformation(NodeImpl.atIndex(null), ITERABLE_ELEMENT, descriptor.getElementClass()), - o)); + o), groups, sink); } return; } - consumer.accept(multiplexContext); + cascadeInto(multiplexContext, groups, sink); } // RI apparently wants to use e.g. Set for Iterable containers, so use declared type + assigned type @@ -555,32 +563,25 @@ void validateDescriptorConstraints(GroupStrategy groups, Consumer DISCARD = cv -> { }; + private static ContainerElementTypeD[] containerElementTypes(ContainerDescriptor descriptor) { + if (descriptor instanceof CascadableContainerD) { + return ((CascadableContainerD) descriptor).getLeafContainerElementTypes(); + } + final List result = new ArrayList<>(); + for (final ContainerElementTypeDescriptor d : descriptor.getConstrainedContainerElementTypes()) { + ComposedD.forEachUnwrapped(d, ContainerElementTypeD.class, result::add); + } + return result.toArray(new ContainerElementTypeD[0]); + } + protected static final TypeVariable MAP_VALUE = Map.class.getTypeParameters()[1]; protected static final TypeVariable ITERABLE_ELEMENT = Iterable.class.getTypeParameters()[0]; diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java index 353c8b3f51..eedd243783 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/parameter/DefaultParameterNameProvider.java @@ -22,7 +22,10 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -30,8 +33,16 @@ public class DefaultParameterNameProvider implements ParameterNameProvider { - private static List parameterNames(Executable exe) { - return Stream.of(exe.getParameters()).map(Parameter::getName).collect(Collectors.toList()); + private final ConcurrentMap> parameterNames = new ConcurrentHashMap<>(); + + private List parameterNames(Executable exe) { + List result = parameterNames.get(exe); + if (result == null) { + result = Collections.unmodifiableList( + Stream.of(exe.getParameters()).map(Parameter::getName).collect(Collectors.toList())); + parameterNames.putIfAbsent(exe, result); + } + return result; } @Override diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java index 5e87ac85e2..dce0546143 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java @@ -316,6 +316,31 @@ public NodeImpl removeLeafNode() { } } + /** + * Learn whether {@code other} equals this path with its leaf node removed as by {@link #removeLeafNode()}, + * without copying either path. + * + * @param other + * @return {@code boolean} + */ + public boolean isParentPath(PathImpl other) { + final int parentLength = nodeList.size() - 1; + if (parentLength < 1) { + final PathImpl parent = copy(this); + parent.removeLeafNode(); + return parent.equals(other); + } + if (other.nodeList.size() != parentLength) { + return false; + } + for (int i = 0; i < parentLength; i++) { + if (!nodeList.get(i).equals(other.nodeList.get(i))) { + return false; + } + } + return true; + } + /** * {@inheritDoc} */ diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java index 137e68f8bc..fb077032bf 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java @@ -29,7 +29,6 @@ import org.apache.bval.jsr.metadata.ContainerElementKey; import org.apache.bval.jsr.util.NodeImpl; import org.apache.bval.util.Exceptions; -import org.apache.bval.util.Lazy; import org.apache.bval.util.Validate; /** @@ -40,7 +39,7 @@ public final class ExtractValues { private static class Receiver implements ValueExtractor.ValueReceiver { private final GraphContext context; private final ContainerElementKey containerElementKey; - private final Lazy> result = new Lazy<>(ArrayList::new); + private List result; Receiver(GraphContext context, ContainerElementKey containerElementKey) { super(); @@ -77,7 +76,10 @@ public void keyedValue(String nodeName, Object key, Object object) { private void addChild(NodeImpl node, Object value) { // child(NodeImpl, ...) copies the path exactly once; building the path here and passing it to // child(Path, ...) would copy it a second time. - result.get().add(context.child( + if (result == null) { + result = new ArrayList<>(); + } + result.add(context.child( node.inContainer(containerElementKey.getContainerClass(), containerElementKey.getTypeArgumentIndex()), value)); } @@ -102,7 +104,7 @@ public static List extract(GraphContext context, ContainerElementK } catch (Exception e) { throw new ValidationException(e); } - return receiver.result.optional().orElse(Collections.emptyList()); + return receiver.result == null ? Collections. emptyList() : receiver.result; } return Collections.singletonList(context); } diff --git a/bval-jsr/src/main/java/org/apache/bval/util/reflection/Reflection.java b/bval-jsr/src/main/java/org/apache/bval/util/reflection/Reflection.java index 8c10c7d86d..7c84b8c0f8 100644 --- a/bval-jsr/src/main/java/org/apache/bval/util/reflection/Reflection.java +++ b/bval-jsr/src/main/java/org/apache/bval/util/reflection/Reflection.java @@ -149,6 +149,7 @@ public enum Interfaces { * Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. */ private static final Map, Class> PRIMITIVE_WRAPPER_MAP; + private static final Map, Class> WRAPPER_PRIMITIVE_MAP; static { final Map, Class> m = new HashMap<>(); m.put(Boolean.TYPE, Boolean.class); @@ -161,6 +162,9 @@ public enum Interfaces { m.put(Float.TYPE, Float.class); m.put(Void.TYPE, Void.TYPE); PRIMITIVE_WRAPPER_MAP = Collections.unmodifiableMap(m); + final Map, Class> reverse = new HashMap<>(); + m.forEach((primitive, wrapper) -> reverse.put(wrapper, primitive)); + WRAPPER_PRIMITIVE_MAP = Collections.unmodifiableMap(reverse); } /** @@ -184,12 +188,7 @@ public static Class primitiveToWrapper(final Class cls) { } public static Class wrapperToPrimitive(final Class cls) { - for (Map.Entry, Class> primitiveEntry : PRIMITIVE_WRAPPER_MAP.entrySet()) { - if (primitiveEntry.getValue().equals(cls)) { - return primitiveEntry.getKey(); - } - } - return null; + return WRAPPER_PRIMITIVE_MAP.get(cls); } /**