http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java new file mode 100644 index 0000000..21816d7 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java @@ -0,0 +1,74 @@ +/* + * 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.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.validation.metadata.BeanDescriptor; +import javax.validation.metadata.CascadableDescriptor; +import javax.validation.metadata.ElementDescriptor; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.metadata.AnnotationBehaviorMergeStrategy; +import org.apache.bval.jsr.metadata.CompositeBuilder; +import org.apache.bval.jsr.metadata.HierarchyBuilder; +import org.apache.bval.jsr.metadata.MetadataBuilder; +import org.apache.bval.jsr.metadata.DualBuilder; +import org.apache.bval.jsr.metadata.ReflectionBuilder; +import org.apache.bval.util.Validate; + +public class DescriptorManager { + public static <D extends ElementDescriptor & CascadableDescriptor> boolean isConstrained(D descriptor) { + return descriptor.hasConstraints() || descriptor.isCascaded(); + } + + private final ApacheValidatorFactory validatorFactory; + private final ConcurrentMap<Class<?>, BeanD> beanDescriptors = new ConcurrentHashMap<>(); + private final ReflectionBuilder reflectionBuilder; + private final MetadataReader metadataReader; + + public DescriptorManager(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + this.reflectionBuilder = new ReflectionBuilder(validatorFactory); + this.metadataReader = new MetadataReader(validatorFactory); + } + + public BeanDescriptor getBeanDescriptor(Class<?> beanClass) { + Validate.notNull(beanClass, IllegalArgumentException::new, "beanClass"); + return beanDescriptors.computeIfAbsent(beanClass, k -> new BeanD(metadataReader.forBean(k, builder(k)))); + } + + private MetadataBuilder.ForBean builder(Class<?> beanClass) { + final MetadataBuilder.ForBean primaryBuilder = + new HierarchyBuilder(reflectionBuilder::forBean).forBean(beanClass); + + final MetadataBuilder.ForBean customBuilder = new HierarchyBuilder(this::customBuilder).forBean(beanClass); + + return customBuilder.isEmpty() ? primaryBuilder : DualBuilder.forBean(primaryBuilder, customBuilder); + } + + private MetadataBuilder.ForBean customBuilder(Class<?> beanClass) { + final List<MetadataBuilder.ForBean> customBuilders = + validatorFactory.getMetadataBuilders().getCustomBuilders(beanClass); + + return customBuilders.isEmpty() ? null : customBuilders.stream() + .collect(CompositeBuilder.with(AnnotationBehaviorMergeStrategy.consensus()).compose()); + } +}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java new file mode 100644 index 0000000..c139773 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java @@ -0,0 +1,122 @@ +/* + * 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.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.ElementDescriptor; + +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; + +public abstract class ElementD<E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>> + implements ElementDescriptor { + + public static abstract class NonRoot<P extends ElementD<?, ?>, E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>> + extends ElementD<E, R> { + + protected final P parent; + + protected NonRoot(R reader, P parent) { + super(reader); + this.parent = Validate.notNull(parent, "parent"); + } + + public P getParent() { + return parent; + } + + @Override + public final Type getGenericType() { + if (TypeUtils.containsTypeVariables(genericType)) { + final Map<TypeVariable<?>, Type> args = + TypeUtils.getTypeArguments(parent.getGenericType(), Object.class); + return TypeUtils.unrollVariables(args, genericType); + } + return genericType; + } + + @Override + final protected BeanD getBean() { + return parent.getBean(); + } + + @Override + public final List<Class<?>> getGroupSequence() { + return getBean().getGroupSequence(); + } + } + + protected final Type genericType; + + private final E target; + private final ElementType elementType; + private final Set<ConstraintD<?>> constraints; + + protected ElementD(R reader) { + super(); + Validate.notNull(reader, "reader"); + this.genericType = reader.meta.getType(); + this.target = reader.meta.getHost(); + this.elementType = reader.meta.getElementType(); + this.constraints = reader.getConstraints(); + } + + @Override + public final boolean hasConstraints() { + return !constraints.isEmpty(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public final Set<ConstraintDescriptor<?>> getConstraintDescriptors() { + return (Set) constraints; + } + + @Override + public final ConstraintFinder findConstraints() { + return new Finder(this); + } + + public final ElementType getElementType() { + return elementType; + } + + public final E getTarget() { + return target; + } + + public abstract Type getGenericType(); + + public abstract List<Class<?>> getGroupSequence(); + + protected abstract BeanD getBean(); + + @Override + public String toString() { + return String.format("%s: %s", getClass().getSimpleName(), target); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java new file mode 100644 index 0000000..db3df6c --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java @@ -0,0 +1,84 @@ +/* + * 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 java.util.List; + +import javax.validation.metadata.CrossParameterDescriptor; +import javax.validation.metadata.ExecutableDescriptor; +import javax.validation.metadata.ParameterDescriptor; +import javax.validation.metadata.ReturnValueDescriptor; + +public abstract class ExecutableD<E extends Executable, R extends MetadataReader.ForExecutable<E, R>, SELF extends ExecutableD<E, R, SELF>> + extends ElementD.NonRoot<BeanD, E, R> implements ExecutableDescriptor { + + private final String name; + private final ReturnValueD<SELF, E> returnValue; + private final List<ParameterD<SELF>> parameters; + private final CrossParameterD<SELF, E> crossParameter; + + @SuppressWarnings("unchecked") + protected ExecutableD(R reader, BeanD parent) { + super(reader, parent); + + name = reader.meta.getName(); + + returnValue = reader.getReturnValueDescriptor((SELF) this); + parameters = reader.getParameterDescriptors((SELF) this); + crossParameter = reader.getCrossParameterDescriptor((SELF) this); + } + + @Override + public final String getName() { + return name; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public final List<ParameterDescriptor> getParameterDescriptors() { + return (List) parameters; + } + + @Override + public final CrossParameterDescriptor getCrossParameterDescriptor() { + return crossParameter; + } + + @Override + public final ReturnValueDescriptor getReturnValueDescriptor() { + return returnValue; + } + + @Override + public final boolean hasConstrainedParameters() { + return parameters.stream().anyMatch(this::isConstrained); + } + + @Override + public final boolean hasConstrainedReturnValue() { + return isConstrained(returnValue); + } + + protected abstract String nameOf(E e); + + private boolean isConstrained(CascadableContainerD<?, ?> child) { + return child.isCascaded() || child.hasConstraints(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/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..c01e069 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java @@ -0,0 +1,103 @@ +/* + * 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.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.groups.Default; +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.util.ToUnmodifiable; +import org.apache.bval.util.Validate; + +class Finder implements ConstraintFinder, Supplier<Stream<ConstraintD<?>>> { + private Predicate<ConstraintD<?>> groups = c -> true; + private Predicate<ConstraintD<?>> scope; + private Predicate<ConstraintD<?>> elements; + + private final ElementDescriptor owner; + + Finder(ElementDescriptor owner) { + this.owner = Validate.notNull(owner, "owner"); + } + + @Override + public ConstraintFinder unorderedAndMatchingGroups(Class<?>... groups) { + this.groups = c -> Stream.of(groups).anyMatch(t -> { + final Set<Class<?>> constraintGroups = c.getGroups(); + return constraintGroups.contains(t) + || constraintGroups.contains(Default.class) && c.getDeclaringClass().isAssignableFrom(t); + }); + return this; + } + + @Override + public ConstraintFinder lookingAt(Scope scope) { + this.scope = scope == Scope.HIERARCHY ? null : 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 get().collect(ToUnmodifiable.set()); + } + + @Override + public Stream<ConstraintD<?>> get() { + return getConstraints().filter(filter()); + } + + @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() { + Predicate<ConstraintD<?>> result = groups; + if (scope != null) { + result = result.and(scope); + } + if (elements != null) { + result = result.and(elements); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/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/3f287a7a/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..c2f9f0c --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java @@ -0,0 +1,291 @@ +/* + * 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.TreeSet; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.GroupDefinitionException; +import javax.validation.GroupSequence; +import javax.validation.ParameterNameProvider; +import javax.validation.groups.Default; +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.Methods; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Exceptions; +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; + } + + Map<String, PropertyDescriptor> getProperties(BeanD parent) { + final Map<String, List<PropertyD<?>>> properties = new LinkedHashMap<>(); + 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)); + }); + return properties.entrySet().stream().collect(ToUnmodifiable.map(Map.Entry::getKey, e -> { + final List<PropertyD<?>> delegates = e.getValue(); + + if (delegates.size() == 1) { + return delegates.get(0); + } + final Set<PropertyD<?>> constrained = + delegates.stream().filter(DescriptorManager::isConstrained).collect(Collectors.toSet()); + if (constrained.isEmpty()) { + return delegates.get(0); + } + if (constrained.size() == 1) { + return constrained.iterator().next(); + } + return new ComposedD.ForProperty(delegates); + })); + } + + 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() { + List<Class<?>> result = builder.getGroupSequence(meta); + if (result == null) { + // resolve group sequence/Default redefinition up class hierarchy: + final Class<?> superclass = meta.getHost().getSuperclass(); + if (superclass != null) { + // attempt to mock parent sequence intent by appending this type immediately after supertype: + result = ((ElementD<?, ?>) validatorFactory.getDescriptorManager().getBeanDescriptor(superclass)) + .getGroupSequence(); + if (result != null) { + result = new ArrayList<>(result); + result.add(result.indexOf(superclass) + 1, meta.getHost()); + } + } + } + if (result != null) { + Exceptions.raiseUnless(result.contains(meta.getHost()), GroupDefinitionException::new, + "@%s for %s must contain %<s", GroupSequence.class.getSimpleName(), meta.getHost()); + Exceptions.raiseIf(result.contains(Default.class), GroupDefinitionException::new, + "@%s for %s must not contain %s", GroupSequence.class.getSimpleName(), meta.getHost(), + Default.class.getName()); + } + return result; + } + } + + 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(meta, 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<>(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/3f287a7a/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..647569d --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java @@ -0,0 +1,49 @@ +/* + * 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; + } + + @Override + public Class<?> getElementClass() { + return getTarget().getReturnType(); + } + + MethodType getMethodType() { + return methodType; + } + + @Override + protected String nameOf(Method e) { + return e.getName(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/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/3f287a7a/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..818e7e0 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java @@ -0,0 +1,106 @@ +/* + * 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.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Supplier; +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()); + 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/3f287a7a/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/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java index 4f9d10a..6a211ed 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java @@ -18,6 +18,8 @@ */ package org.apache.bval.jsr.groups; +import java.util.Objects; + import javax.validation.groups.Default; /** @@ -52,7 +54,7 @@ public final class Group { */ @Override public String toString() { - return "Group{" + "group=" + group + '}'; + return String.format("%s{group=%s}", Group.class.getSimpleName(), group); } /** @@ -71,13 +73,10 @@ public final class Group { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !getClass().equals(o.getClass())) { return false; } - - Group group1 = (Group) o; - - return group != null ? group.equals(group1.group) : group1.group == null; + return Objects.equals(group, ((Group) o).group); } /** @@ -85,6 +84,6 @@ public final class Group { */ @Override public int hashCode() { - return (group != null ? group.hashCode() : 0); + return Objects.hashCode(group); } } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java index ba3a617..6d45ced 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java @@ -26,10 +26,9 @@ public class GroupConversionDescriptorImpl implements GroupConversionDescriptor public GroupConversionDescriptorImpl(final Group from, final Group to) { this.from = from.getGroup(); - if (this.from.getAnnotation(GroupSequence.class) != null) { + if (this.from.isAnnotationPresent(GroupSequence.class)) { throw new ConstraintDeclarationException("from() can't get a group sequence"); } - this.to = to.getGroup(); } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java index 162bb66..3e7f008 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java @@ -18,25 +18,29 @@ */ package org.apache.bval.jsr.groups; -import javax.validation.GroupDefinitionException; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; +import javax.validation.GroupDefinitionException; + +import org.apache.bval.util.Exceptions; + /** - * Defines the order to validate groups during validation. - * with some inspiration from reference implementation + * Defines the order to validate groups during validation. with some inspiration + * from reference implementation * * @author Roman Stumm */ public class Groups { - /** The list of single groups. */ - final List<Group> groups = new LinkedList<Group>(); - /** The list of sequences. */ - final List<List<Group>> sequences = new LinkedList<List<Group>>(); + private final List<List<Group>> sequences = new ArrayList<>(); + + /** The list of single groups. */ + final List<Group> groups = new ArrayList<>(); /** * Get the Groups. + * * @return {@link List} of {@link Group}. */ public List<Group> getGroups() { @@ -45,6 +49,7 @@ public class Groups { /** * Get the Group sequences. + * * @return {@link List} of {@link List} of {@link Group} */ public List<List<Group>> getSequences() { @@ -53,7 +58,9 @@ public class Groups { /** * Insert a {@link Group}. - * @param group to insert + * + * @param group + * to insert */ void insertGroup(Group group) { if (!groups.contains(group)) { @@ -63,52 +70,52 @@ public class Groups { /** * Insert a sequence. - * @param groups {@link List} of {@link Group} to insert + * + * @param groups + * {@link List} of {@link Group} to insert */ void insertSequence(List<Group> groups) { - if (groups == null || groups.isEmpty()) { - return; - } - - if (!sequences.contains(groups)) { + if (!(groups == null || groups.isEmpty() || sequences.contains(groups))) { sequences.add(groups); } } /** - * Assert that the default group can be expanded to <code>defaultGroups</code>. + * Assert that the default group can be expanded to + * <code>defaultGroups</code>. + * * @param defaultGroups */ public void assertDefaultGroupSequenceIsExpandable(List<Group> defaultGroups) { for (List<Group> groupList : sequences) { - int idx = groupList.indexOf(Group.DEFAULT); - if (idx != -1) { + final int idx = groupList.indexOf(Group.DEFAULT); + if (idx >= 0) { ensureExpandable(groupList, defaultGroups, idx); } } } private void ensureExpandable(List<Group> groupList, List<Group> defaultGroupList, int defaultGroupIndex) { - for (int i = 0; i < defaultGroupList.size(); i++) { - Group group = defaultGroupList.get(i); + for (int i = 0, sz = defaultGroupList.size(); i < sz; i++) { + final Group group = defaultGroupList.get(i); if (group.isDefault()) { continue; // the default group is the one we want to replace } - int index = groupList.indexOf(group); // sequence contains group of default group sequence - if (index == -1) { - continue; // if group is not in the sequence + // sequence contains group of default group sequence + final int index = groupList.indexOf(group); + if (index < 0) { + // group is not in the sequence + continue; } - if ((i == 0 && index == defaultGroupIndex - 1) || (i == defaultGroupList.size() - 1 && index == defaultGroupIndex + 1)) { - // if we are at the beginning or end of he defaultGroupSequence and the - // matches are either directly before or after we can continue, - // since we basically have two groups + // if we are at the beginning or end of he defaultGroupSequence + // and the matches are either directly before or after we can + // continue, since we basically have two groups continue; } - throw new GroupDefinitionException( - "Unable to expand default group list" + defaultGroupList + " into sequence " + groupList); + Exceptions.raise(GroupDefinitionException::new, "Unable to expand default group list %s into sequence %s", + defaultGroupList, groupList); } } - } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java index 398d6c3..ae6f629 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java @@ -18,126 +18,115 @@ */ package org.apache.bval.jsr.groups; -import javax.validation.GroupDefinitionException; -import javax.validation.GroupSequence; -import javax.validation.ValidationException; -import javax.validation.groups.Default; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.validation.GroupDefinitionException; +import javax.validation.GroupSequence; +import javax.validation.ValidationException; +import javax.validation.groups.Default; + +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Validate; + /** * Description: compute group order, based on the RI behavior as to guarantee * compatibility with interpretations of the spec.<br/> * Implementation is thread-safe. */ public class GroupsComputer { - public static final Class<?>[] DEFAULT_GROUP = new Class<?>[] { Default.class }; + public static final Class<?>[] DEFAULT_GROUP = { Default.class }; - /** The default group array used in case any of the validate methods is called without a group. */ + /** + * The default group array used in case any of the validate methods is + * called without a group. + */ private static final Groups DEFAULT_GROUPS; static { - DEFAULT_GROUPS = new GroupsComputer().computeGroups(Arrays.asList(DEFAULT_GROUP)); + DEFAULT_GROUPS = new Groups(); + for (Class<?> g : DEFAULT_GROUP) { + DEFAULT_GROUPS.insertGroup(new Group(g)); + } } /** caching resolved groups in a thread-safe map. */ - private final Map<Class<?>, List<Group>> resolvedSequences = new ConcurrentHashMap<Class<?>, List<Group>>(); + private final Map<Class<?>, List<Group>> resolvedSequences = new ConcurrentHashMap<>(); /** * Compute groups from an array of group classes. + * * @param groups * @return {@link Groups} */ - public Groups computeGroups(Class<?>[] groups) { - if (groups == null) { - throw new IllegalArgumentException("null passed as group"); - } - - // if no groups is specified use the default - if (groups.length == 0) { - return DEFAULT_GROUPS; - } - + @SafeVarargs + public final Groups computeGroups(Class<?>... groups) { + Exceptions.raiseIf(groups == null, IllegalArgumentException::new, "null validation groups specified"); return computeGroups(Arrays.asList(groups)); } /** * Main compute implementation. + * * @param groups * @return {@link Groups} */ protected Groups computeGroups(Collection<Class<?>> groups) { - if (groups == null || groups.size() == 0) { - throw new IllegalArgumentException("At least one group has to be specified."); + Validate.notNull(groups, "groups"); + + if (groups.isEmpty() || Arrays.asList(DEFAULT_GROUP).equals(new ArrayList<>(groups))) { + return DEFAULT_GROUPS; } + Exceptions.raiseIf(groups.stream().anyMatch(Objects::isNull), IllegalArgumentException::new, + "Null group specified"); for (final Class<?> clazz : groups) { - if (clazz == null) { - throw new IllegalArgumentException("At least one group has to be specified."); - } - - if (!clazz.isInterface()) { - throw new ValidationException("A group has to be an interface. " + clazz.getName() + " is not."); - } + Exceptions.raiseUnless(clazz.isInterface(), ValidationException::new, + "A group must be an interface. %s is not.", clazz); } - - Groups chain = new Groups(); + final Groups chain = new Groups(); for (Class<?> clazz : groups) { - GroupSequence anno = clazz.getAnnotation(GroupSequence.class); + final GroupSequence anno = clazz.getAnnotation(GroupSequence.class); if (anno == null) { - Group group = new Group(clazz); - chain.insertGroup(group); + chain.insertGroup(new Group(clazz)); insertInheritedGroups(clazz, chain); - } else { - insertSequence(clazz, anno, chain); + continue; } + chain.insertSequence( + resolvedSequences.computeIfAbsent(clazz, g -> resolveSequence(g, anno, new HashSet<>()))); } - return chain; } private void insertInheritedGroups(Class<?> clazz, Groups chain) { for (Class<?> extendedInterface : clazz.getInterfaces()) { - Group group = new Group(extendedInterface); - chain.insertGroup(group); + chain.insertGroup(new Group(extendedInterface)); insertInheritedGroups(extendedInterface, chain); } } - private void insertSequence(Class<?> clazz, GroupSequence anno, Groups chain) { - List<Group> sequence; - if (resolvedSequences.containsKey(clazz)) { - sequence = resolvedSequences.get(clazz); - } else { - sequence = resolveSequence(clazz, anno, new HashSet<Class<?>>()); - } - chain.insertSequence(sequence); - } - private List<Group> resolveSequence(Class<?> group, GroupSequence sequenceAnnotation, Set<Class<?>> processedSequences) { - if (processedSequences.contains(group)) { - throw new GroupDefinitionException("Cyclic dependency in groups definition"); - } else { - processedSequences.add(group); - } - List<Group> resolvedGroupSequence = new LinkedList<Group>(); - Class<?>[] sequenceArray = sequenceAnnotation.value(); - for (Class<?> clazz : sequenceArray) { - GroupSequence anno = clazz.getAnnotation(GroupSequence.class); + Exceptions.raiseUnless(processedSequences.add(group), GroupDefinitionException::new, + "Cyclic dependency in groups definition"); + + final List<Group> resolvedGroupSequence = new ArrayList<>(); + for (Class<?> clazz : sequenceAnnotation.value()) { + final GroupSequence anno = clazz.getAnnotation(GroupSequence.class); if (anno == null) { - resolvedGroupSequence.add(new Group(clazz)); // group part of sequence + // group part of sequence + resolvedGroupSequence.add(new Group(clazz)); } else { - List<Group> tmpSequence = resolveSequence(clazz, anno, processedSequences); // recursion! - resolvedGroupSequence.addAll(tmpSequence); + // recursion! + resolvedGroupSequence.addAll(resolveSequence(clazz, anno, processedSequences)); } } - resolvedSequences.put(group, resolvedGroupSequence); return resolvedGroupSequence; } } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/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..c3cc3a7 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java @@ -0,0 +1,195 @@ +/* + * 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.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.Exceptions; +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 + */ + // TODO delete + @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, propertyPath)); + } + + 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/3f287a7a/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..dc3fab5 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java @@ -0,0 +1,60 @@ +/* + * 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.Path; + +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.util.PathImpl; +import org.apache.bval.util.Validate; + +public final class ValidateBean<T> extends ValidationJob<T> { + + private final T bean; + + ValidateBean(ApacheFactoryContext validatorContext, T bean, Class<?>[] groups) { + super(validatorContext, groups); + this.bean = Validate.notNull(bean, IllegalArgumentException::new, "bean"); + } + + @Override + protected Frame<BeanD> computeBaseFrame() { + 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, + Path propertyPath) { + final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context); + + return new ConstraintViolationImpl<>(messageTemplate, message, bean, context.getFrame().getBean(), propertyPath, + 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/3f287a7a/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..c0d866b --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java @@ -0,0 +1,188 @@ +/* + * 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.lang.reflect.Type; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import javax.validation.ConstraintViolation; +import javax.validation.ParameterNameProvider; +import javax.validation.Path; +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.descriptor.CrossParameterD; +import org.apache.bval.jsr.descriptor.ParameterD; +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; + +public abstract class ValidateParameters<E extends Executable, T> extends ValidationJob<T> { + + public static class ForMethod<T> extends ValidateParameters<Method, 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, IllegalArgumentException::new, "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(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<CrossParameterD<?, ?>> { + private final ExecutableDescriptor executableDescriptor; + + protected ParametersFrame(ExecutableDescriptor executableDescriptor, GraphContext context) { + super(null, (CrossParameterD<?, ?>) Validate.notNull(executableDescriptor, "executableDescriptor") + .getCrossParameterDescriptor(), context); + this.executableDescriptor = executableDescriptor; + } + + @Override + void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) { + executableDescriptor.getParameterDescriptors().stream() + .map(pd -> new SproutFrame<ParameterD<?>>(this, (ParameterD<?>) pd, parameter(pd.getIndex()))) + .forEach(f -> f.process(group, sink)); + } + + @Override + Object getBean() { + return object; + } + } + + private static final String PARAMETERS_DO_NOT_MATCH = "Parameters do not match"; + + protected final T object; + 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.object = object; + this.executable = Validate.notNull(executable, IllegalArgumentException::new, "executable"); + this.parameterValues = + Validate.notNull(parameterValues, IllegalArgumentException::new, "parameterValues").clone(); + + 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)); + } + + @Override + protected Frame<?> computeBaseFrame() { + 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, + Path propertyPath) { + + final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context); + + return new ConstraintViolationImpl<T>(messageTemplate, message, getRootBean(), context.getFrame().getBean(), + propertyPath, 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]); + } +}
