http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java new file mode 100644 index 0000000..8a416a7 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.descriptor; + +import java.lang.annotation.ElementType; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.ElementDescriptor; +import javax.validation.metadata.ElementDescriptor.ConstraintFinder; +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.groups.GroupsComputer; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Validate; + +class Finder implements ConstraintFinder { + private static final Predicate<ConstraintD<?>> ALWAYS = c -> true; + + private Predicate<ConstraintD<?>> groups = ALWAYS; + private Predicate<ConstraintD<?>> scope = ALWAYS; + private Predicate<ConstraintD<?>> elements = ALWAYS; + + private final ElementDescriptor owner; + private final List<Class<?>> groupSequence; + + Finder(ElementDescriptor owner, List<Class<?>> groupSequence) { + this.owner = Validate.notNull(owner, "owner"); + this.groupSequence = groupSequence; + } + + @Override + public ConstraintFinder unorderedAndMatchingGroups(Class<?>... groups) { + this.groups = c -> new GroupsComputer().computeGroups(groups).getGroups().stream().flatMap(g -> { + if (g.isDefault()) { + final List<Class<?>> seq = groupSequence; + if (seq != null) { + return seq.stream(); + } + } + return Stream.of(g.getGroup()); + }).anyMatch(c.getGroups()::contains); + + return this; + } + + @Override + public ConstraintFinder lookingAt(Scope scope) { + this.scope = scope == Scope.HIERARCHY ? ALWAYS : c -> c.getScope() == scope; + return this; + } + + @Override + public ConstraintFinder declaredOn(ElementType... types) { + this.elements = c -> Stream.of(types).filter(Objects::nonNull) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(ElementType.class))).contains(c.getDeclaredOn()); + + return this; + } + + @Override + public Set<ConstraintDescriptor<?>> getConstraintDescriptors() { + return getConstraints().filter(filter()).collect(ToUnmodifiable.set()); + } + + @Override + public boolean hasConstraints() { + return getConstraints().anyMatch(filter()); + } + + private Stream<ConstraintD<?>> getConstraints() { + return owner.getConstraintDescriptors().stream().<ConstraintD<?>> map(c -> c.unwrap(ConstraintD.class)); + } + + private Predicate<ConstraintD<?>> filter() { + return groups.and(scope).and(elements); + } +}
http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java new file mode 100644 index 0000000..9ef724e --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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 org.apache.bval.jsr.descriptor; + +import java.util.Objects; +import java.util.Optional; + +import javax.validation.metadata.GroupConversionDescriptor; + +import org.apache.bval.util.Lazy; +import org.apache.bval.util.LazyInt; +import org.apache.bval.util.Validate; + +public class GroupConversion implements GroupConversionDescriptor { + public static class Builder { + private final Class<?> from; + + private Builder(Class<?> from) { + this.from = from; + } + + public GroupConversion to(Class<?> to) { + return new GroupConversion(from, to); + } + } + + public static Builder from(Class<?> from) { + return new Builder(from); + } + + private final Class<?> from; + private final Class<?> to; + private final LazyInt hashCode; + private final Lazy<String> toString; + + private GroupConversion(Class<?> from, Class<?> to) { + super(); + this.from = Validate.notNull(from, "from"); + this.to = Validate.notNull(to, "to"); + this.hashCode = new LazyInt(() -> Objects.hash(this.from, this.to)); + this.toString = new Lazy<>( + () -> String.format("%s from %s to %s", GroupConversion.class.getSimpleName(), this.from, this.to)); + } + + @Override + public Class<?> getFrom() { + return from; + } + + @Override + public Class<?> getTo() { + return to; + } + + @Override + public boolean equals(Object obj) { + return obj == this + || Optional.ofNullable(obj).filter(GroupConversion.class::isInstance).map(GroupConversion.class::cast) + .filter(gc -> Objects.equals(from, gc.from) && Objects.equals(to, gc.to)).isPresent(); + } + + @Override + public int hashCode() { + return hashCode.getAsInt(); + } + + @Override + public String toString() { + return toString.get(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java new file mode 100644 index 0000000..8f08674 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java @@ -0,0 +1,266 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.descriptor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.ParameterNameProvider; +import javax.validation.metadata.PropertyDescriptor; +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.metadata.ContainerElementKey; +import org.apache.bval.jsr.metadata.EmptyBuilder; +import org.apache.bval.jsr.metadata.MetadataBuilder; +import org.apache.bval.jsr.metadata.Metas; +import org.apache.bval.jsr.metadata.Signature; +import org.apache.bval.jsr.util.Exceptions; +import org.apache.bval.jsr.util.Methods; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.Reflection; + +class MetadataReader { + + class ForElement<E extends AnnotatedElement, B extends MetadataBuilder.ForElement<E>> { + final Metas<E> meta; + protected final B builder; + + ForElement(Metas<E> meta, B builder) { + super(); + this.meta = Validate.notNull(meta, "meta"); + this.builder = Validate.notNull(builder, "builder"); + } + + Set<ConstraintD<?>> getConstraints() { + return builder.getConstraintsByScope(meta).entrySet().stream() + .flatMap(e -> describe(e.getValue(), e.getKey(), meta)).collect(ToUnmodifiable.set()); + } + + private Stream<ConstraintD<?>> describe(Annotation[] constraints, Scope scope, Metas<?> meta) { + return Stream.of(constraints).map(c -> new ConstraintD<>(c, scope, meta, validatorFactory)); + } + } + + class ForBean extends MetadataReader.ForElement<Class<?>, MetadataBuilder.ForClass> { + private final MetadataBuilder.ForBean beanBuilder; + + ForBean(Metas<Class<?>> meta, MetadataBuilder.ForBean builder) { + super(meta, Validate.notNull(builder, "builder").getClass(meta)); + this.beanBuilder = builder; + } + + Set<PropertyDescriptor> getProperties(BeanD parent) { + final Map<String, List<PropertyD<?>>> properties = new TreeMap<>(); + final Function<? super String, ? extends List<PropertyD<?>>> descriptorList = k -> new ArrayList<>(); + + beanBuilder.getFields(meta).forEach((f, builder) -> { + final Field fld = Reflection.find(meta.getHost(), t -> Reflection.getDeclaredField(t, f)); + properties.computeIfAbsent(f, descriptorList).add(new PropertyD.ForField( + new MetadataReader.ForContainer<>(new Metas.ForField(fld), builder), parent)); + }); + + beanBuilder.getGetters(meta).forEach((g, builder) -> { + final Method getter = Reflection.find(meta.getHost(), t -> { + return Stream.of(Reflection.getDeclaredMethods(t)).filter(Methods::isGetter) + .filter(m -> g.equals(Methods.propertyName(m))).findFirst().orElse(null); + }); + Exceptions.raiseIf(getter == null, IllegalStateException::new, + "Getter method for property %s not found", g); + + properties.computeIfAbsent(g, descriptorList).add(new PropertyD.ForMethod( + new MetadataReader.ForContainer<>(new Metas.ForMethod(getter), builder), parent)); + }); + + if (properties.isEmpty()) { + return Collections.emptySet(); + } + + // merge field/getter into composed PropertyDescriptors: + return properties.values().stream() + .map(delegates -> delegates.size() == 1 ? delegates.get(0) : new ComposedD.ForProperty(delegates)) + .collect(ToUnmodifiable.set()); + } + + Map<Signature, MethodD> getMethods(BeanD parent) { + final Map<Signature, MetadataBuilder.ForExecutable<Method>> methodBuilders = beanBuilder.getMethods(meta); + if (methodBuilders.isEmpty()) { + return Collections.emptyMap(); + } + + final Map<Signature, MethodD> result = new LinkedHashMap<>(); + + methodBuilders.forEach((sig, builder) -> { + final Method m = Reflection.find(meta.getHost(), + t -> Reflection.getDeclaredMethod(t, sig.getName(), sig.getParameterTypes())); + + result.put(sig, new MethodD(new MetadataReader.ForMethod(new Metas.ForMethod(m), builder), parent)); + }); + + return Collections.unmodifiableMap(result); + } + + Map<Signature, ConstructorD> getConstructors(BeanD parent) { + final Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> ctorBuilders = + beanBuilder.getConstructors(meta); + + if (ctorBuilders.isEmpty()) { + return Collections.emptyMap(); + } + + final Map<Signature, ConstructorD> result = new LinkedHashMap<>(); + + ctorBuilders.forEach((sig, builder) -> { + final Constructor<?> c = Reflection.getDeclaredConstructor(meta.getHost(), sig.getParameterTypes()); + result.put(sig, + new ConstructorD(new MetadataReader.ForConstructor(new Metas.ForConstructor(c), builder), parent)); + }); + + return Collections.unmodifiableMap(result); + } + + List<Class<?>> getGroupSequence() { + return builder.getGroupSequence(meta); + } + } + + class ForContainer<E extends AnnotatedElement> extends ForElement<E, MetadataBuilder.ForContainer<E>> { + + ForContainer(Metas<E> meta, MetadataBuilder.ForContainer<E> builder) { + super(meta, builder); + } + + boolean isCascaded() { + return builder.isCascade(meta); + } + + Set<GroupConversion> getGroupConversions() { + return builder.getGroupConversions(meta); + } + + Set<ContainerElementTypeD> getContainerElementTypes(CascadableContainerD<?, ?> parent) { + final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> containerElementTypes = + builder.getContainerElementTypes(meta); + + if (containerElementTypes.isEmpty()) { + return Collections.emptySet(); + } + + final Set<ContainerElementTypeD> result = + new TreeSet<>(Comparator.comparing(ContainerElementTypeD::getKey)); + + containerElementTypes.forEach((k, builder) -> { + result.add(new ContainerElementTypeD(k, + new MetadataReader.ForContainer<>(new Metas.ForContainerElement(k), builder), parent)); + }); + + return Collections.unmodifiableSet(result); + } + } + + abstract class ForExecutable<E extends Executable, SELF extends ForExecutable<E, SELF>> + extends ForElement<E, MetadataBuilder.ForElement<E>> { + private final MetadataBuilder.ForExecutable<E> executableBuilder; + + ForExecutable(Metas<E> meta, MetadataBuilder.ForExecutable<E> executableBuilder) { + super(meta, EmptyBuilder.instance().forElement()); + this.executableBuilder = Validate.notNull(executableBuilder, "executableBuilder"); + } + + <X extends ExecutableD<E, SELF, X>> List<ParameterD<X>> getParameterDescriptors(X parent) { + final Parameter[] parameters = meta.getHost().getParameters(); + + final List<String> parameterNames = + getParameterNames(validatorFactory.getParameterNameProvider(), meta.getHost()); + + final List<MetadataBuilder.ForContainer<Parameter>> builders = executableBuilder.getParameters(meta); + + return IntStream.range(0, parameters.length).mapToObj(i -> { + final Metas.ForParameter param = new Metas.ForParameter(parameters[i], parameterNames.get(i)); + return new ParameterD<X>(param, i, new MetadataReader.ForContainer<>(param, builders.get(i)), parent); + }).collect(ToUnmodifiable.list()); + } + + <X extends ExecutableD<E, SELF, X>> CrossParameterD<X, E> getCrossParameterDescriptor(X parent) { + final Metas.ForCrossParameter<E> cp = new Metas.ForCrossParameter<>(meta); + return new CrossParameterD<>(new MetadataReader.ForElement<>(cp, executableBuilder.getCrossParameter(cp)), + parent); + } + + <X extends ExecutableD<E, SELF, X>> ReturnValueD<X, E> getReturnValueDescriptor(X parent) { + return new ReturnValueD<>(new MetadataReader.ForContainer<>(meta, executableBuilder.getReturnValue(meta)), + parent); + } + + abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider, E host); + } + + class ForMethod extends ForExecutable<Method, ForMethod> { + ForMethod(Metas<Method> meta, MetadataBuilder.ForExecutable<Method> builder) { + super(meta, builder); + } + + @Override + List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Method host) { + return parameterNameProvider.getParameterNames(host); + } + } + + class ForConstructor extends ForExecutable<Constructor<?>, ForConstructor> { + + ForConstructor(Metas<Constructor<?>> meta, MetadataBuilder.ForExecutable<Constructor<?>> builder) { + super(meta, builder); + } + + @Override + List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Constructor<?> host) { + return parameterNameProvider.getParameterNames(host); + } + } + + private final ApacheValidatorFactory validatorFactory; + + MetadataReader(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + } + + MetadataReader.ForBean forBean(Class<?> beanClass, MetadataBuilder.ForBean builder) { + return new MetadataReader.ForBean(new Metas.ForClass(beanClass), builder); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java new file mode 100644 index 0000000..7f718e0 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.descriptor; + +import java.lang.reflect.Method; + +import javax.validation.metadata.MethodDescriptor; +import javax.validation.metadata.MethodType; + +import org.apache.bval.jsr.util.Methods; + +class MethodD extends ExecutableD<Method, MetadataReader.ForMethod, MethodD> implements MethodDescriptor { + private final MethodType methodType; + + MethodD(MetadataReader.ForMethod reader, BeanD parent) { + super(reader, parent); + methodType = Methods.isGetter(reader.meta.getHost()) ? MethodType.GETTER : MethodType.NON_GETTER; + } + + MethodType getMethodType() { + return methodType; + } + + @Override + protected String nameOf(Method e) { + return e.getName(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java new file mode 100644 index 0000000..951eccb --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.descriptor; + +import java.lang.reflect.Parameter; + +import javax.validation.metadata.ParameterDescriptor; + +import org.apache.bval.jsr.metadata.Metas; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; + +public class ParameterD<P extends ExecutableD<?, ?, P>> extends CascadableContainerD<P, Parameter> + implements ParameterDescriptor { + + private final int index; + private final String name; + private final Class<?> type; + + protected ParameterD(Metas.ForParameter meta, int index, MetadataReader.ForContainer<Parameter> reader, P parent) { + super(reader, parent); + + Validate.isTrue(index >= 0 && index < meta.getHost().getDeclaringExecutable().getParameterCount(), + "Invalid parameter index %d", index); + + this.index = index; + + name = reader.meta.getName(); + type = TypeUtils.getRawType(reader.meta.getType(), parent.getElementClass()); + } + + @Override + public Class<?> getElementClass() { + return type; + } + + @Override + public int getIndex() { + return index; + } + + @Override + public String getName() { + return name; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java new file mode 100644 index 0000000..dbd9875 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.bval.jsr.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.metadata.PropertyDescriptor; + +import org.apache.bval.jsr.GraphContext; +import org.apache.bval.jsr.util.Methods; +import org.apache.bval.jsr.util.NodeImpl; +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +public abstract class PropertyD<E extends AnnotatedElement> extends CascadableContainerD<BeanD, E> + implements PropertyDescriptor { + + static class ForField extends PropertyD<Field> { + + ForField(MetadataReader.ForContainer<Field> reader, BeanD parent) { + super(reader, parent); + } + + @Override + public String getPropertyName() { + return host.getName(); + } + + @Override + public Object getValue(Object parent) throws Exception { + final boolean mustUnset = Reflection.setAccessible(host, true); + try { + return host.get(parent); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException(e); + } finally { + if (mustUnset) { + Reflection.setAccessible(host, false); + } + } + } + } + + static class ForMethod extends PropertyD<Method> { + + ForMethod(MetadataReader.ForContainer<Method> reader, BeanD parent) { + super(reader, parent); + } + + @Override + public String getPropertyName() { + return Methods.propertyName(host); + } + + @Override + public Object getValue(Object parent) throws Exception { + final boolean mustUnset = Reflection.setAccessible(host, true); + try { + return host.invoke(parent); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalArgumentException(e); + } finally { + if (mustUnset) { + Reflection.setAccessible(host, false); + } + } + } + } + + protected final E host; + + protected PropertyD(MetadataReader.ForContainer<E> reader, BeanD parent) { + super(reader, parent); + this.host = reader.meta.getHost(); + } + + @Override + protected Stream<GraphContext> readImpl(GraphContext context) throws Exception { + final Supplier<NodeImpl> propertyNode = () -> new NodeImpl.PropertyNodeImpl(getPropertyName()); + final Object value = getValue(context.getValue()); + + if (Map.class.isInstance(value)) { + + return ((Map<?, ?>) value).entrySet().stream().map(e -> { + final NodeImpl node = propertyNode.get(); + node.setKey(e.getKey()); + return context.child(node, e.getValue()); + }); + } + if (value != null && value.getClass().isArray()) { + IntStream.range(0, Array.getLength(value)).mapToObj(i -> { + final NodeImpl node = propertyNode.get(); + node.setIndex(i); + return context.child(node, Array.get(value, i)); + }); + } + if (List.class.isInstance(value)) { + final List<?> l = (List<?>) value; + + return IntStream.range(0, l.size()).mapToObj(i -> { + final NodeImpl node = propertyNode.get(); + node.setIndex(Integer.valueOf(i)); + return context.child(node, l.get(i)); + }); + } + if (Iterable.class.isInstance(value)) { + final NodeImpl node = propertyNode.get(); + node.setInIterable(true); + final Stream.Builder<Object> b = Stream.builder(); + ((Iterable<?>) value).forEach(b); + return b.build().map(o -> context.child(node, o)); + } + return Stream.of(context.child(propertyNode.get(), value)); + } + + public abstract Object getValue(Object parent) throws Exception; +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java new file mode 100644 index 0000000..a2204fc --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.descriptor; + +import java.lang.reflect.Executable; + +import javax.validation.metadata.ReturnValueDescriptor; + +public class ReturnValueD<P extends ExecutableD<?, ?, P>, E extends Executable> extends CascadableContainerD<P, E> + implements ReturnValueDescriptor { + + ReturnValueD(MetadataReader.ForContainer<E> reader, P parent) { + super(reader, parent); + } + + @Override + public Class<?> getElementClass() { + return parent.getElementClass(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java new file mode 100644 index 0000000..5ee9ee1 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.job; + +import java.util.HashSet; +import java.util.Set; + +import javax.validation.ClockProvider; +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintViolation; +import javax.validation.MessageInterpolator; +import javax.validation.Path; +import javax.validation.ValidationException; +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.CrossParameterDescriptor; + +import org.apache.bval.jsr.descriptor.ComposedD; +import org.apache.bval.jsr.descriptor.ConstraintD; +import org.apache.bval.jsr.descriptor.CrossParameterD; +import org.apache.bval.jsr.util.ContainerElementNodeBuilderCustomizableContextImpl; +import org.apache.bval.jsr.util.Exceptions; +import org.apache.bval.jsr.util.LeafNodeBuilderCustomizableContextImpl; +import org.apache.bval.jsr.util.NodeBuilderCustomizableContextImpl; +import org.apache.bval.jsr.util.NodeBuilderDefinedContextImpl; +import org.apache.bval.jsr.util.NodeImpl; +import org.apache.bval.jsr.util.PathImpl; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.Validate; + +public class ConstraintValidatorContextImpl<T> implements ConstraintValidatorContext, MessageInterpolator.Context { + private class ConstraintViolationBuilderImpl implements ConstraintValidatorContext.ConstraintViolationBuilder { + private final String template; + private final PathImpl path; + + ConstraintViolationBuilderImpl(String template, PathImpl path) { + this.template = template; + this.path = path; + } + + /** + * {@inheritDoc} + */ + @Override + public NodeBuilderDefinedContext addNode(String name) { + PathImpl p; + if (path.isRootPath()) { + p = PathImpl.create(); + p.getLeafNode().setName(name); + } else { + p = PathImpl.copy(path); + p.addNode(new NodeImpl(name)); + } + return new NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl.this, template, p); + } + + @Override + public NodeBuilderCustomizableContext addPropertyNode(String name) { + return new NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template, path, name); + } + + @Override + public LeafNodeBuilderCustomizableContext addBeanNode() { + return new LeafNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template, path); + } + + @Override + public NodeBuilderDefinedContext addParameterNode(int index) { + Exceptions.raiseUnless(frame.descriptor instanceof CrossParameterDescriptor, ValidationException::new, + "Cannot add parameter node for %s", frame.descriptor.getClass().getName()); + + final CrossParameterD<?, ?> crossParameter = + ComposedD.unwrap(frame.descriptor, CrossParameterD.class).findFirst().get(); + + final String parameterName = crossParameter.getParent().getParameterDescriptors().get(index).getName(); + + final NodeImpl node = new NodeImpl.ParameterNodeImpl(parameterName, index); + if (!path.isRootPath()) { + path.removeLeafNode(); + } + path.addNode(node); + return new NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl.this, template, path); + } + + /** + * {@inheritDoc} + */ + @Override + public ConstraintValidatorContext addConstraintViolation() { + addError(template, path); + return ConstraintValidatorContextImpl.this; + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, + Class<?> containerType, Integer typeArgumentIndex) { + return new ContainerElementNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template, + path, name, containerType, typeArgumentIndex); + } + } + + private final ValidationJob<T,?>.Frame<?> frame; + private final ConstraintD<?> constraint; + private final Lazy<Set<ConstraintViolation<T>>> violations = new Lazy<>(HashSet::new); + private boolean defaultConstraintViolationDisabled; + + /** + * Temporary for code migration + */ + @Deprecated + protected ConstraintValidatorContextImpl() { + this.frame = null; + this.constraint = null; + } + + ConstraintValidatorContextImpl(ValidationJob<T,?>.Frame<?> frame, ConstraintD<?> constraint) { + super(); + this.frame = Validate.notNull(frame, "frame"); + this.constraint = Validate.notNull(constraint, "constraint"); + } + + @Override + public void disableDefaultConstraintViolation() { + this.defaultConstraintViolationDisabled = true; + } + + @Override + public String getDefaultConstraintMessageTemplate() { + return constraint.getMessageTemplate(); + } + + @Override + public ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate) { + return new ConstraintViolationBuilderImpl(messageTemplate, frame.context.getPath()); + } + + @Override + public ClockProvider getClockProvider() { + return frame.getJob().validatorContext.getClockProvider(); + } + + @Override + public <U> U unwrap(Class<U> type) { + try { + return type.cast(this); + } catch (ClassCastException e) { + throw new ValidationException(e); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void addError(String messageTemplate, Path propertyPath) { + violations.get().add(((ValidationJob) frame.getJob()).createViolation(messageTemplate, this, null)); + } + + ValidationJob<T,?>.Frame<?> getFrame() { + return frame; + } + + Set<ConstraintViolation<T>> getRequiredViolations() { + if (!violations.optional().isPresent()) { + Exceptions.raiseIf(defaultConstraintViolationDisabled, ValidationException::new, + "Expected custom constraint violation(s)"); + + addError(getDefaultConstraintMessageTemplate(), frame.context.getPath()); + } + return violations.get(); + } + + @Override + public ConstraintDescriptor<?> getConstraintDescriptor() { + return constraint; + } + + @Override + public Object getValidatedValue() { + return frame.context.getValue(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java new file mode 100644 index 0000000..74b2efe --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.job; + +import org.apache.bval.jsr.ApacheFactoryContext; +import org.apache.bval.jsr.ConstraintViolationImpl; +import org.apache.bval.jsr.GraphContext; +import org.apache.bval.jsr.descriptor.ConstraintD; +import org.apache.bval.jsr.util.PathImpl; +import org.apache.bval.util.Validate; + +public final class ValidateBean<T> extends ValidationJob<T, ValidateBean<T>> { + + private final T bean; + + ValidateBean(ApacheFactoryContext validatorContext, T bean, Class<?>[] groups) { + super(validatorContext, groups); + this.bean = Validate.notNull(bean, "bean"); + } + + @Override + protected BeanFrame initBaseFrame() { + return new BeanFrame(new GraphContext(validatorContext, PathImpl.create(), bean)); + } + + @SuppressWarnings("unchecked") + @Override + protected Class<T> getRootBeanClass() { + return (Class<T>) bean.getClass(); + } + + @Override + ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context, + Object leafBean) { + final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context); + + return new ConstraintViolationImpl<>(messageTemplate, message, bean, leafBean, + context.getFrame().context.getPath(), context.getFrame().context.getValue(), + context.getConstraintDescriptor(), getRootBeanClass(), + context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), null, null); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..0e02639 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.job; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.List; + +import javax.validation.ParameterNameProvider; +import javax.validation.metadata.CrossParameterDescriptor; +import javax.validation.metadata.ExecutableDescriptor; +import javax.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.ConstraintD; +import org.apache.bval.jsr.util.NodeImpl; +import org.apache.bval.jsr.util.PathImpl; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.Validate; + +public abstract class ValidateParameters<E extends Executable, T, J extends ValidateParameters<E, T, J>> + extends ValidationJob<T, J> { + + public static class ForMethod<T> extends ValidateParameters<Method, T, ForMethod<T>> { + + private final T object; + + ForMethod(ApacheFactoryContext validatorContext, T object, Method executable, Object[] parameterValues, + Class<?>[] groups) { + super(validatorContext, object, executable, parameterValues, groups); + this.object = Validate.notNull(object, "object"); + } + + @Override + protected ExecutableDescriptor describe() { + return validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass()) + .getConstraintsForMethod(executable.getName(), executable.getParameterTypes()); + } + + @SuppressWarnings("unchecked") + @Override + protected Class<T> getRootBeanClass() { + return (Class<T>) object.getClass(); + } + + @Override + protected List<String> getParameterNames(ParameterNameProvider parameterNameProvider) { + return parameterNameProvider.getParameterNames(executable); + } + + @Override + protected T getRootBean() { + return object; + } + } + + public static class ForConstructor<T> extends ValidateParameters<Constructor<? extends T>, T, ForConstructor<T>> { + + ForConstructor(ApacheFactoryContext validatorContext, Constructor<? extends T> executable, + Object[] parameterValues, Class<?>[] groups) { + super(validatorContext, null, executable, parameterValues, groups); + } + + @Override + protected ExecutableDescriptor describe() { + return validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass()) + .getConstraintsForConstructor(executable.getParameterTypes()); + } + + @SuppressWarnings("unchecked") + @Override + protected Class<T> getRootBeanClass() { + return (Class<T>) executable.getDeclaringClass(); + } + + @Override + protected List<String> getParameterNames(ParameterNameProvider parameterNameProvider) { + return parameterNameProvider.getParameterNames(executable); + } + + @Override + protected T getRootBean() { + return null; + } + } + + class ParametersFrame extends Frame<CrossParameterDescriptor> { + private final ExecutableDescriptor executableDescriptor; + + protected ParametersFrame(ExecutableDescriptor executableDescriptor, GraphContext context) { + super(executableDescriptor.getCrossParameterDescriptor(), context); + this.executableDescriptor = executableDescriptor; + } + + @Override + void recurse(Class<?> group) { + executableDescriptor.getParameterDescriptors().stream() + .map(pd -> new SproutFrame<ParameterDescriptor>(pd, parameter(pd.getIndex()))) + .forEach(f -> f.visit(group)); + } + } + + protected final E executable; + protected final Lazy<List<String>> parameterNames = + new Lazy<>(() -> getParameterNames(validatorContext.getParameterNameProvider())); + + private final Object[] parameterValues; + + ValidateParameters(ApacheFactoryContext validatorContext, T object, E executable, Object[] parameterValues, + Class<?>[] groups) { + super(validatorContext, groups); + this.executable = Validate.notNull(executable, "executable"); + this.parameterValues = Validate.notNull(parameterValues, "parameterValues").clone(); + } + + @Override + protected J.Frame<?> initBaseFrame() { + final PathImpl cp = PathImpl.create(); + cp.addNode(new NodeImpl.CrossParameterNodeImpl()); + return new ParametersFrame(describe(), new GraphContext(validatorContext, cp, parameterValues)); + } + + protected abstract ExecutableDescriptor describe(); + + protected abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider); + + protected abstract T getRootBean(); + + @Override + ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context, + Object leafBean) { + + final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context); + + return new ConstraintViolationImpl<T>(messageTemplate, message, getRootBean(), leafBean, + context.getFrame().context.getPath(), context.getFrame().context.getValue(), + context.getConstraintDescriptor(), getRootBeanClass(), + context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), null, parameterValues); + } + + private GraphContext parameter(int i) { + final PathImpl path = PathImpl.create(); + path.addNode(new NodeImpl.ParameterNodeImpl(parameterNames.get().get(i), i)); + return new GraphContext(validatorContext, path, parameterValues[i]); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java new file mode 100644 index 0000000..f8aa79e --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.job; + +import javax.validation.metadata.PropertyDescriptor; + +import org.apache.bval.jsr.ApacheFactoryContext; +import org.apache.bval.jsr.ConstraintViolationImpl; +import org.apache.bval.jsr.GraphContext; +import org.apache.bval.jsr.descriptor.ConstraintD; +import org.apache.bval.jsr.descriptor.PropertyD; +import org.apache.bval.jsr.util.PathImpl; +import org.apache.bval.util.StringUtils; +import org.apache.bval.util.Validate; + +public final class ValidateProperty<T> extends ValidationJob<T, ValidateProperty<T>> { + private final Class<T> rootBeanClass; + private final PropertyD<?> descriptor; + private GraphContext baseContext; + private boolean cascade; + + private ValidateProperty(ApacheFactoryContext validatorContext, Class<T> rootBeanClass, String property, + Class<?>[] groups) { + super(validatorContext, groups); + this.rootBeanClass = Validate.notNull(rootBeanClass, "rootBeanClass"); + Validate.isTrue(StringUtils.isNotBlank(property), "Null/empty/blank property"); + this.descriptor = (PropertyD<?>) validatorContext.getDescriptorManager().getBeanDescriptor(rootBeanClass) + .getConstraintsForProperty(property); + } + + ValidateProperty(ApacheFactoryContext validatorContext, Class<T> rootBeanClass, String property, Object value, + Class<?>[] groups) { + this(validatorContext, rootBeanClass, property, groups); + + baseContext = new GraphContext(validatorContext, PathImpl.createPathFromString(property), value); + } + + @SuppressWarnings("unchecked") + ValidateProperty(ApacheFactoryContext validatorContext, T bean, String property, Class<?>[] groups) + throws Exception { + this(validatorContext, (Class<T>) Validate.notNull(bean, "bean").getClass(), property, groups); + + baseContext = new GraphContext(validatorContext, PathImpl.create(), bean) + .child(PathImpl.createPathFromString(property).getLeafNode(), descriptor.getValue(bean)); + } + + public ValidateProperty<T> cascade(boolean cascade) { + this.cascade = cascade; + return this; + } + + @Override + protected SproutFrame<PropertyDescriptor> initBaseFrame() { + return new SproutFrame<PropertyDescriptor>(descriptor, baseContext) { + @Override + void recurse(Class<?> group) { + if (cascade) { + super.recurse(group); + } + } + }; + } + + @Override + protected Class<T> getRootBeanClass() { + return rootBeanClass; + } + + @Override + ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context, + Object leafBean) { + final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context); + + return new ConstraintViolationImpl<>(messageTemplate, message, null, leafBean, + context.getFrame().context.getPath(), context.getFrame().context.getValue(), + context.getConstraintDescriptor(), rootBeanClass, + context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), null, null); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..d90f932 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java @@ -0,0 +1,104 @@ +package org.apache.bval.jsr.job; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; + +import javax.validation.metadata.ExecutableDescriptor; + +import org.apache.bval.jsr.ApacheFactoryContext; +import org.apache.bval.jsr.ConstraintViolationImpl; +import org.apache.bval.jsr.GraphContext; +import org.apache.bval.jsr.descriptor.ConstraintD; +import org.apache.bval.jsr.util.NodeImpl; +import org.apache.bval.jsr.util.PathImpl; +import org.apache.bval.util.Validate; + +public abstract class ValidateReturnValue<E extends Executable, T, J extends ValidateReturnValue<E, T, J>> + extends ValidationJob<T, J> { + public static class ForMethod<T> extends ValidateReturnValue<Method, T, ForMethod<T>> { + private final T object; + + ForMethod(ApacheFactoryContext validatorContext, T object, Method method, Object returnValue, + Class<?>[] groups) { + super(validatorContext, method, returnValue, groups); + this.object = Validate.notNull(object, "object"); + } + + @Override + protected T getRootBean() { + return object; + } + + @SuppressWarnings("unchecked") + @Override + protected Class<T> getRootBeanClass() { + return (Class<T>) object.getClass(); + } + + @Override + protected ExecutableDescriptor describe() { + return validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass()) + .getConstraintsForMethod(executable.getName(), executable.getParameterTypes()); + } + } + + public static class ForConstructor<T> extends ValidateReturnValue<Constructor<? extends T>, T, ForConstructor<T>> { + + ForConstructor(ApacheFactoryContext validatorContext, Constructor<? extends T> ctor, Object returnValue, + Class<?>[] groups) { + super(validatorContext, ctor, returnValue, groups); + } + + @Override + protected T getRootBean() { + return null; + } + + @SuppressWarnings("unchecked") + @Override + protected Class<T> getRootBeanClass() { + return (Class<T>) executable.getDeclaringClass(); + } + + @Override + protected ExecutableDescriptor describe() { + return validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass()) + .getConstraintsForConstructor(executable.getParameterTypes()); + } + } + + protected final E executable; + private final Object returnValue; + + ValidateReturnValue(ApacheFactoryContext validatorContext, E executable, Object returnValue, Class<?>[] groups) { + super(validatorContext, groups); + this.executable = Validate.notNull(executable, "executable"); + this.returnValue = returnValue; + } + + @Override + protected J.Frame<?> initBaseFrame() { + final PathImpl path = PathImpl.create(); + path.addNode(new NodeImpl.ReturnValueNodeImpl()); + + return new SproutFrame<>(describe().getReturnValueDescriptor(), + new GraphContext(validatorContext, path, returnValue)); + } + + @Override + ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context, + Object leafBean) { + + final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context); + + return new ConstraintViolationImpl<>(messageTemplate, message, getRootBean(), leafBean, + context.getFrame().context.getPath(), context.getFrame().context.getValue(), + context.getConstraintDescriptor(), getRootBeanClass(), + context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), returnValue, null); + } + + protected abstract ExecutableDescriptor describe(); + + protected abstract T getRootBean(); +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..96e1d73 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.job; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintViolation; +import javax.validation.TraversableResolver; +import javax.validation.metadata.BeanDescriptor; +import javax.validation.metadata.CascadableDescriptor; +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.ContainerDescriptor; +import javax.validation.metadata.ElementDescriptor; +import javax.validation.metadata.PropertyDescriptor; + +import org.apache.bval.jsr.ApacheFactoryContext; +import org.apache.bval.jsr.ConstraintViolationImpl; +import org.apache.bval.jsr.GraphContext; +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.PropertyD; +import org.apache.bval.jsr.groups.Group; +import org.apache.bval.jsr.groups.Groups; +import org.apache.bval.jsr.util.NodeImpl; +import org.apache.bval.jsr.util.PathImpl; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.Validate; + +public abstract class ValidationJob<T, J extends ValidationJob<T, J>> { + + public abstract class Frame<D extends ElementDescriptor> { + protected final D descriptor; + protected final GraphContext context; + + protected Frame(D descriptor, GraphContext context) { + super(); + this.descriptor = Validate.notNull(descriptor, "descriptor"); + this.context = Validate.notNull(context, "context"); + } + + @SuppressWarnings("unchecked") + final J getJob() { + return (J) ValidationJob.this; + } + + final void visit(Class<?> group) { + if (skip()) { + return; + } + descriptor.findConstraints().unorderedAndMatchingGroups(group).getConstraintDescriptors().stream() + .map(ConstraintD.class::cast).forEach(this::validate); + + recurse(group); + } + + boolean skip() { + return false; + } + + abstract void recurse(Class<?> group); + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean validate(ConstraintD<?> constraint) { + final Class<? extends ConstraintValidator> constraintValidatorClass = + constraint.getConstraintValidatorClass(); + + final ConstraintValidator constraintValidator = + validatorContext.getConstraintValidatorFactory().getInstance(constraintValidatorClass); + + constraintValidator.initialize(constraint.getAnnotation()); + + final ConstraintValidatorContextImpl<T> constraintValidatorContext = + new ConstraintValidatorContextImpl<>(this, constraint); + + boolean valid = constraintValidator.isValid(context.getValue(), constraintValidatorContext); + if (!valid) { + results.get().addAll(constraintValidatorContext.getRequiredViolations()); + } + + final Iterator<ConstraintDescriptor<?>> components = constraint.getComposingConstraints().iterator(); + + while (valid || !constraint.isReportAsSingleViolation()) { + if (components.hasNext()) { + valid = validate((ConstraintD<?>) components.next()); + continue; + } + break; + } + return valid; + } + } + + public class BeanFrame extends Frame<BeanDescriptor> { + + BeanFrame(GraphContext context) { + super(getBeanDescriptor(context.getValue()), context); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + void recurse(Class<?> group) { + // TODO experiment with parallel streaming, perhaps with an option. In this case would probably need each + // frame to record local violations before adding to the job + + final Stream<PropertyD<?>> properties = + descriptor.getConstrainedProperties().stream().flatMap(d -> ComposedD.unwrap(d, PropertyD.class)); + + final TraversableResolver traversableResolver = validatorContext.getTraversableResolver(); + + final Stream<PropertyD<?>> reachableProperties = + properties.filter(d -> traversableResolver.isReachable(context.getValue(), + new NodeImpl.PropertyNodeImpl(d.getPropertyName()), getRootBeanClass(), context.getPath(), + d.getElementType())); + + reachableProperties.forEach(d -> d.read(context).filter(Objects::nonNull) + .map(child -> new SproutFrame(d, child)).forEach(f -> f.visit(group))); + } + + @Override + boolean skip() { + return seenBeans.put(context.getValue(), Boolean.TRUE) != null; + } + } + + public class SproutFrame<D extends ElementDescriptor & CascadableDescriptor & ContainerDescriptor> + extends Frame<D> { + + public SproutFrame(D descriptor, GraphContext context) { + super(descriptor, context); + } + + @Override + void recurse(Class<?> group) { + final Stream<CascadableContainerD<?, ?>> containerElements = + descriptor.getConstrainedContainerElementTypes().stream() + .flatMap(d -> ComposedD.unwrap(d, CascadableContainerD.class)); + + containerElements.flatMap(d -> d.read(context).map(child -> new SproutFrame<>(d, child))) + .forEach(f -> f.visit(group)); + + // Stream<CascadableContainerD<?, ?>> descriptors = ComposedD.unwrap(descriptor, + // CascadableContainerD.class); + + if (!descriptor.isCascaded()) { + return; + } + if (descriptor instanceof PropertyDescriptor) { + final TraversableResolver traversableResolver = validatorContext.getTraversableResolver(); + + final PathImpl pathToTraversableObject = PathImpl.copy(context.getPath()); + final NodeImpl traversableProperty = pathToTraversableObject.removeLeafNode(); + + if (!traversableResolver.isCascadable(context.getValue(), traversableProperty, getRootBeanClass(), + pathToTraversableObject, ((PropertyD<?>) descriptor).getElementType())) { + return; + } + // descriptors = descriptors.filter(d -> traversableResolver.isCascadable(context.getValue(), + // traversableProperty, getRootBeanClass(), pathToTraversableObject, d.getElementType())); + } + // descriptors.flatMap(d -> d.read(context)).map(BeanFrame::new).forEach(f -> f.visit(group)); + new BeanFrame(context).visit(group); + } + } + + protected final ApacheFactoryContext validatorContext; + + private final Groups groups; + private final Lazy<Set<ConstraintViolation<T>>> results = new Lazy<>(LinkedHashSet::new); + private final IdentityHashMap<Object, Boolean> seenBeans = new IdentityHashMap<>(); + private Frame<?> baseFrame; + + ValidationJob(ApacheFactoryContext validatorContext, Class<?>[] groups) { + super(); + this.validatorContext = Validate.notNull(validatorContext, "validatorContext"); + this.groups = validatorContext.getGroupsComputer().computeGroups(groups); + } + + public Set<ConstraintViolation<T>> getResults() { + Validate.validState(!results.optional().isPresent(), "#getResults() already called"); + + groups.getGroups().stream().map(Group::getGroup).forEach(baseFrame::visit); + + sequences: for (List<Group> seq : groups.getSequences()) { + for (Group g : seq) { + baseFrame.visit(g.getGroup()); + if (violationsFound()) { + break sequences; + } + } + } + return results.optional().orElse(Collections.emptySet()); + } + + BeanDescriptor getBeanDescriptor(Object bean) { + Validate.notNull(bean, "bean"); + return validatorContext.getFactory().getDescriptorManager().getBeanDescriptor(bean.getClass()); + } + + boolean isCascading() { + return true; + } + + @SuppressWarnings("unchecked") + J init() { + this.baseFrame = initBaseFrame(); + Validate.validState(baseFrame != null, "%s calculated null baseFrame", getClass().getName()); + return (J) this; + } + + abstract ConstraintViolationImpl<T> createViolation(String messageTemplate, + ConstraintValidatorContextImpl<T> context, Object leafBean); + + protected abstract Frame<?> initBaseFrame(); + + protected abstract Class<T> getRootBeanClass(); + + private boolean violationsFound() { + return results.optional().filter(s -> !s.isEmpty()).isPresent(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java new file mode 100644 index 0000000..7ccc03e --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.bval.jsr.job; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import javax.validation.ValidationException; +import javax.validation.Validator; +import javax.validation.executable.ExecutableValidator; + +import org.apache.bval.jsr.ApacheFactoryContext; +import org.apache.bval.util.Validate; + +/** + * Creates {@link ValidationJob} instances. + */ +public class ValidationJobFactory { + + private final ApacheFactoryContext validatorContext; + + /** + * Create a new {@link ValidationJobFactory}. + * + * @param validatorContext + */ + public ValidationJobFactory(ApacheFactoryContext validatorContext) { + super(); + this.validatorContext = Validate.notNull(validatorContext, "validatorContext"); + } + + /** + * @see Validator#validate(Object, Class...) + */ + public <T> ValidateBean<T> validateBean(T bean, Class<?>... groups) { + return new ValidateBean<>(validatorContext, bean, groups).init(); + } + + /** + * @see Validator#validateProperty(Object, String, Class...) + */ + public <T> ValidateProperty<T> validateProperty(T bean, String property, Class<?>... groups) { + try { + return new ValidateProperty<>(validatorContext, bean, property, groups).init(); + } catch (Exception e) { + throw new ValidationException(e); + } + } + + /** + * @see Validator#validateValue(Class, String, Object, Class...) + */ + public <T> ValidateProperty<T> validateValue(Class<T> rootBeanClass, String property, Object value, + Class<?>... groups) { + return new ValidateProperty<T>(validatorContext, rootBeanClass, property, value, groups).init(); + } + + /** + * @see ExecutableValidator#validateParameters(Object, Method, Object[], Class...) + */ + public <T> ValidateParameters.ForMethod<T> validateParameters(T object, Method method, Object[] parameterValues, + Class<?>... groups) { + return new ValidateParameters.ForMethod<T>(validatorContext, object, method, parameterValues, groups).init(); + } + + /** + * @see ExecutableValidator#validateReturnValue(Object, Method, Object, Class...) + */ + public <T> ValidateReturnValue.ForMethod<T> validateReturnValue(T object, Method method, Object returnValue, + Class<?>... groups) { + return new ValidateReturnValue.ForMethod<>(validatorContext, object, method, returnValue, groups).init(); + } + + /** + * @see ExecutableValidator#validateConstructorParameters(Constructor, Object[], Class...) + */ + public <T> ValidateParameters.ForConstructor<T> validateConstructorParameters(Constructor<? extends T> constructor, + Object[] parameterValues, Class<?>... groups) { + return new ValidateParameters.ForConstructor<T>(validatorContext, constructor, parameterValues, groups).init(); + } + + /** + * @see ExecutableValidator#validateConstructorReturnValue(Constructor, Object, Class...) + */ + public <T> ValidateReturnValue.ForConstructor<T> validateConstructorReturnValue( + Constructor<? extends T> constructor, T createdObject, Class<?>... groups) { + return new ValidateReturnValue.ForConstructor<T>(validatorContext, constructor, createdObject, groups).init(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java new file mode 100644 index 0000000..887bccd --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.bval.jsr.metadata; + +import org.apache.bval.jsr.metadata.MetadataBuilder; + +/** + * Models the behavior of a {@link MetadataBuilder} with regard to bean validation annotations. + * @see ParallelBuilder + */ +public enum AnnotationBehavior implements AnnotationBehaviorMergeStrategy { + //@formatter:off + INCLUDE, EXCLUDE, ABSTAIN; + //@formatter:on + + @Override + public AnnotationBehavior apply(Iterable<? extends MetadataBuilder.Level> t) { + return this; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java new file mode 100644 index 0000000..837801b --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.bval.jsr.metadata; + +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.bval.util.Validate; + +@FunctionalInterface +public interface AnnotationBehaviorMergeStrategy + extends Function<Iterable<? extends MetadataBuilder.Level>, AnnotationBehavior> { + + public static AnnotationBehaviorMergeStrategy first() { + return coll -> { + final Iterator<? extends MetadataBuilder.Level> iterator = coll.iterator(); + return iterator.hasNext() ? iterator.next().getAnnotationBehavior() : AnnotationBehavior.ABSTAIN; + }; + } + + public static AnnotationBehaviorMergeStrategy consensus() { + return coll -> { + final Stream.Builder<MetadataBuilder.Level> b = Stream.builder(); + coll.forEach(b); + final Set<AnnotationBehavior> annotationBehaviors = + b.build().map(MetadataBuilder.Level::getAnnotationBehavior).filter(Objects::nonNull) + .filter(Predicate.isEqual(AnnotationBehavior.ABSTAIN).negate()) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(AnnotationBehavior.class))); + Validate.validState(annotationBehaviors.size() <= 1, + "Conflicting annotation inclusion behaviors found among %s", coll); + return annotationBehaviors.isEmpty() ? AnnotationBehavior.ABSTAIN : annotationBehaviors.iterator().next(); + }; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java new file mode 100644 index 0000000..1d8fc81 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.bval.jsr.metadata; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Validate; + +public class CompositeBuilder { + + class Delegator<DELEGATE extends MetadataBuilder.Level> implements MetadataBuilder.Level { + + protected final List<DELEGATE> delegates; + + Delegator(List<DELEGATE> delegates) { + this.delegates = Validate.notNull(delegates, "delegates"); + Validate.isTrue(!delegates.isEmpty(), "no delegates specified"); + Validate.isTrue(delegates.stream().noneMatch(Objects::isNull), "One or more supplied delegates was null"); + } + + @Override + public AnnotationBehavior getAnnotationBehavior() { + return annotationBehaviorStrategy.apply(delegates); + } + + <K, D> Map<K, D> merge(Function<DELEGATE, Map<K, D>> toMap, Function<List<D>, D> merge) { + final List<Map<K, D>> maps = delegates.stream().map(toMap).collect(Collectors.toList()); + + final Function<? super K, ? extends D> valueMapper = k -> { + final List<D> mappedDelegates = + maps.stream().map(m -> m.get(k)).filter(Objects::nonNull).collect(Collectors.toList()); + return mappedDelegates.size() == 1 ? mappedDelegates.get(0) : merge.apply(mappedDelegates); + }; + + return maps.stream().map(Map::keySet).flatMap(Collection::stream).distinct() + .collect(Collectors.toMap(Function.identity(), valueMapper)); + } + } + + private class ForBean extends CompositeBuilder.Delegator<MetadataBuilder.ForBean> + implements MetadataBuilder.ForBean { + + ForBean(List<MetadataBuilder.ForBean> delegates) { + super(delegates); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return new CompositeBuilder.ForClass( + delegates.stream().map(d -> d.getClass(meta)).collect(Collectors.toList())); + } + + @SuppressWarnings("unchecked") + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + return merge(b -> b.getFields(meta), CompositeBuilder.ForContainer::new); + } + + @SuppressWarnings("unchecked") + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + return merge(b -> b.getGetters(meta), CompositeBuilder.ForContainer::new); + } + + @SuppressWarnings("unchecked") + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + return merge(b -> b.getConstructors(meta), CompositeBuilder.ForExecutable::new); + } + + @SuppressWarnings("unchecked") + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + return merge(b -> b.getMethods(meta), CompositeBuilder.ForExecutable::new); + } + } + + class ForElement<DELEGATE extends MetadataBuilder.ForElement<E>, E extends AnnotatedElement> + extends Delegator<DELEGATE> implements MetadataBuilder.ForElement<E> { + + ForElement(List<DELEGATE> delegates) { + super(delegates); + } + + @Override + public Map<Scope, Annotation[]> getConstraintsByScope(Metas<E> meta) { + return CompositeBuilder.this.getConstraintsByScope(this, meta); + } + + @Override + public final Annotation[] getDeclaredConstraints(Metas<E> meta) { + return delegates.stream().map(d -> d.getDeclaredConstraints(meta)).flatMap(Stream::of) + .toArray(Annotation[]::new); + } + } + + class ForClass extends ForElement<MetadataBuilder.ForClass, Class<?>> implements MetadataBuilder.ForClass { + + ForClass(List<MetadataBuilder.ForClass> delegates) { + super(delegates); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + return CompositeBuilder.this.getGroupSequence(this, meta); + } + } + + private class ForContainer<DELEGATE extends MetadataBuilder.ForContainer<E>, E extends AnnotatedElement> + extends CompositeBuilder.ForElement<DELEGATE, E> implements MetadataBuilder.ForContainer<E> { + + ForContainer(List<DELEGATE> delegates) { + super(delegates); + } + + @Override + public final boolean isCascade(Metas<E> meta) { + return delegates.stream().anyMatch(d -> d.isCascade(meta)); + } + + @Override + public final Set<GroupConversion> getGroupConversions(Metas<E> meta) { + return delegates.stream().map(d -> d.getGroupConversions(meta)).flatMap(Collection::stream) + .collect(ToUnmodifiable.set()); + } + + @SuppressWarnings("unchecked") + @Override + public final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> meta) { + return merge(b -> b.getContainerElementTypes(meta), CompositeBuilder.ForContainer::new); + } + } + + private class ForExecutable<DELEGATE extends MetadataBuilder.ForExecutable<E>, E extends Executable> + extends Delegator<DELEGATE> implements MetadataBuilder.ForExecutable<E> { + + ForExecutable(List<DELEGATE> delegates) { + super(delegates); + } + + @Override + public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) { + return new CompositeBuilder.ForContainer<>( + delegates.stream().map(d -> d.getReturnValue(meta)).collect(Collectors.toList())); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) { + final List<List<MetadataBuilder.ForContainer<Parameter>>> parameterLists = + delegates.stream().map(d -> d.getParameters(meta)).collect(Collectors.toList()); + + final Set<Integer> parameterCounts = parameterLists.stream().map(List::size).collect(Collectors.toSet()); + Validate.validState(parameterCounts.size() == 1, "Mismatched parameter counts: %s", parameterCounts); + + return IntStream.range(0, parameterCounts.iterator().next().intValue()) + .mapToObj(n -> new CompositeBuilder.ForContainer<>(parameterLists.get(n))) + .collect(ToUnmodifiable.list()); + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return new CompositeBuilder.ForElement<MetadataBuilder.ForElement<E>, E>( + delegates.stream().map(d -> d.getCrossParameter(meta)).collect(Collectors.toList())); + } + } + + public static CompositeBuilder with(AnnotationBehaviorMergeStrategy annotationBehaviorStrategy) { + return new CompositeBuilder(annotationBehaviorStrategy); + } + + private final AnnotationBehaviorMergeStrategy annotationBehaviorStrategy; + + CompositeBuilder(AnnotationBehaviorMergeStrategy annotationBehaviorMergeStrategy) { + super(); + this.annotationBehaviorStrategy = + Validate.notNull(annotationBehaviorMergeStrategy, "annotationBehaviorMergeStrategy"); + } + + public Collector<MetadataBuilder.ForBean, ?, MetadataBuilder.ForBean> compose() { + return Collectors.collectingAndThen(Collectors.toList(), CompositeBuilder.ForBean::new); + } + + protected <E extends AnnotatedElement> Map<Scope, Annotation[]> getConstraintsByScope( + CompositeBuilder.ForElement<? extends MetadataBuilder.ForElement<E>, E> composite, Metas<E> meta) { + return Collections.singletonMap(Scope.LOCAL_ELEMENT, composite.getDeclaredConstraints(meta)); + } + + protected List<Class<?>> getGroupSequence(CompositeBuilder.ForClass composite, Metas<Class<?>> meta) { + final List<List<Class<?>>> groupSequence = + composite.delegates.stream().map(d -> d.getGroupSequence(meta)).collect(Collectors.toList()); + Validate.validState(groupSequence.size() <= 1, + "group sequence returned from multiple composite class metadata builders"); + return groupSequence.isEmpty() ? null : groupSequence.get(0); + } +}
