http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java new file mode 100644 index 0000000..269d953 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java @@ -0,0 +1,243 @@ +/* + * 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.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Validate; + +/** + * Maintains two metadata builds in parallel. The "primary" build is assumed to be the reflection/annotation-based build + * and is subject to the {@link AnnotationBehavior} prescribed by the "custom" build. + */ +public class DualBuilder { + + private static class Delegator<DELEGATE extends HasAnnotationBehavior> implements HasAnnotationBehavior { + + private final Delegator<?> parent; + protected final DELEGATE primaryDelegate; + protected final DELEGATE customDelegate; + + Delegator(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE customDelegate) { + this.parent = parent; + this.primaryDelegate = Validate.notNull(primaryDelegate, "primaryDelegate"); + this.customDelegate = Validate.notNull(customDelegate, "customDelegate"); + } + + AnnotationBehavior getCustomAnnotationBehavior() { + final AnnotationBehavior annotationBehavior = customDelegate.getAnnotationBehavior(); + Validate.validState(annotationBehavior != null, "null %s returned from %s", + AnnotationBehavior.class.getSimpleName(), customDelegate); + if (annotationBehavior == AnnotationBehavior.ABSTAIN && parent != null) { + return parent.getCustomAnnotationBehavior(); + } + return annotationBehavior; + } + + protected Stream<DELEGATE> activeDelegates() { + return getCustomAnnotationBehavior() == AnnotationBehavior.EXCLUDE ? Stream.of(customDelegate) + : Stream.of(primaryDelegate, customDelegate); + } + + <K, D> Map<K, D> merge(Function<DELEGATE, Map<K, D>> toMap, BiFunction<D, D, D> parallel, + Supplier<D> emptyBuilder) { + + final Map<K, D> primaries = toMap.apply(primaryDelegate); + final Map<K, D> customs = toMap.apply(customDelegate); + + if (primaries.isEmpty() && customs.isEmpty()) { + return Collections.emptyMap(); + } + + final Function<? super K, ? extends D> valueMapper = k -> { + final D primary = primaries.get(k); + final D custom = customs.get(k); + + if (custom == null) { + if (primary != null) { + switch (getCustomAnnotationBehavior()) { + case INCLUDE: + case ABSTAIN: + return primary; + default: + break; + } + } + return emptyBuilder.get(); + } + return parallel.apply(primary, custom); + }; + return Stream.of(primaries, customs).map(Map::keySet).flatMap(Collection::stream).distinct() + .collect(Collectors.toMap(Function.identity(), valueMapper)); + } + } + + private static class ForBean extends DualBuilder.Delegator<MetadataBuilder.ForBean> + implements MetadataBuilder.ForBean { + + ForBean(MetadataBuilder.ForBean primaryDelegate, MetadataBuilder.ForBean customDelegate) { + super(null, primaryDelegate, customDelegate); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return new DualBuilder.ForClass(this, primaryDelegate.getClass(meta), customDelegate.getClass(meta)); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + return merge(b -> b.getFields(meta), (t, u) -> new DualBuilder.ForContainer<>(this, t, u), + EmptyBuilder.instance()::forContainer); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + return merge(b -> b.getGetters(meta), (t, u) -> new DualBuilder.ForContainer<>(this, t, u), + EmptyBuilder.instance()::forContainer); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + return merge(b -> b.getConstructors(meta), (t, u) -> new DualBuilder.ForExecutable<>(this, t, u), + EmptyBuilder.instance()::forExecutable); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + return merge(b -> b.getMethods(meta), (t, u) -> new DualBuilder.ForExecutable<>(this, t, u), + EmptyBuilder.instance()::forExecutable); + } + } + + private static class ForElement<DELEGATE extends MetadataBuilder.ForElement<E>, E extends AnnotatedElement> + extends Delegator<DELEGATE> implements MetadataBuilder.ForElement<E> { + + ForElement(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE customDelegate) { + super(parent, primaryDelegate, customDelegate); + } + + @Override + public final Annotation[] getDeclaredConstraints(Metas<E> meta) { + return activeDelegates().map(d -> d.getDeclaredConstraints(meta)).flatMap(Stream::of) + .toArray(Annotation[]::new); + } + } + + private static class ForClass extends ForElement<MetadataBuilder.ForClass, Class<?>> + implements MetadataBuilder.ForClass { + + ForClass(Delegator<?> parent, MetadataBuilder.ForClass primaryDelegate, + MetadataBuilder.ForClass customDelegate) { + super(parent, primaryDelegate, customDelegate); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + final List<Class<?>> customGroupSequence = customDelegate.getGroupSequence(meta); + if (customGroupSequence != null) { + return customGroupSequence; + } + return customDelegate.getAnnotationBehavior() == AnnotationBehavior.EXCLUDE ? null + : primaryDelegate.getGroupSequence(meta); + } + } + + private static class ForContainer<DELEGATE extends MetadataBuilder.ForContainer<E>, E extends AnnotatedElement> + extends DualBuilder.ForElement<DELEGATE, E> implements MetadataBuilder.ForContainer<E> { + + ForContainer(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE customDelegate) { + super(parent, primaryDelegate, customDelegate); + } + + @Override + public final boolean isCascade(Metas<E> meta) { + return activeDelegates().anyMatch(d -> d.isCascade(meta)); + } + + @Override + public final Set<GroupConversion> getGroupConversions(Metas<E> meta) { + return activeDelegates().map(d -> d.getGroupConversions(meta)).flatMap(Collection::stream) + .collect(ToUnmodifiable.set()); + } + + @Override + public final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> meta) { + return merge(b -> b.getContainerElementTypes(meta), (t, u) -> new DualBuilder.ForContainer<>(this, t, u), + EmptyBuilder.instance()::forContainer); + } + } + + private static class ForExecutable<DELEGATE extends MetadataBuilder.ForExecutable<E>, E extends Executable> + extends Delegator<DELEGATE> implements MetadataBuilder.ForExecutable<E> { + + ForExecutable(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE customDelegate) { + super(parent, primaryDelegate, customDelegate); + } + + @Override + public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) { + return new DualBuilder.ForContainer<>(this, primaryDelegate.getReturnValue(meta), + customDelegate.getReturnValue(meta)); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) { + + final List<MetadataBuilder.ForContainer<Parameter>> primaries = primaryDelegate.getParameters(meta); + final List<MetadataBuilder.ForContainer<Parameter>> customs = customDelegate.getParameters(meta); + + Validate.validState(primaries.size() == customs.size(), "Mismatched parameter counts: %d vs. %d", + primaries.size(), customs.size()); + + return IntStream.range(0, primaries.size()) + .mapToObj(n -> new DualBuilder.ForContainer<>(this, primaries.get(n), customs.get(n))) + .collect(ToUnmodifiable.list()); + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return new DualBuilder.ForElement<MetadataBuilder.ForElement<E>, E>(this, + primaryDelegate.getCrossParameter(meta), customDelegate.getCrossParameter(meta)); + } + } + + public static MetadataBuilder.ForBean forBean(MetadataBuilder.ForBean primaryDelegate, + MetadataBuilder.ForBean customDelegate) { + return new DualBuilder.ForBean(primaryDelegate, customDelegate); + } +}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java new file mode 100644 index 0000000..e5b5038 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java @@ -0,0 +1,50 @@ +/* + * 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.util.Arrays; + +import org.apache.bval.util.Validate; + +public class DualValidationMappingProvider extends ValidatorMappingProvider { + private final ValidatorMappingProvider primaryDelegate; + private final ValidatorMappingProvider secondaryDelegate; + + public DualValidationMappingProvider(ValidatorMappingProvider primary, ValidatorMappingProvider secondary) { + super(); + this.primaryDelegate = Validate.notNull(primary, "primary delegate"); + this.secondaryDelegate = Validate.notNull(secondary, "secondary delegate"); + } + + @Override + protected <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType) { + + final ValidatorMapping<A> secondaryMapping = secondaryDelegate.doGetValidatorMapping(constraintType); + if (secondaryMapping == null) { + return primaryDelegate.doGetValidatorMapping(constraintType); + } + final AnnotationBehavior annotationBehavior = secondaryMapping.getAnnotationBehavior(); + + if (annotationBehavior == AnnotationBehavior.EXCLUDE) { + return secondaryMapping; + } + return ValidatorMapping.merge( + Arrays.asList(primaryDelegate.doGetValidatorMapping(constraintType), secondaryMapping), + AnnotationBehaviorMergeStrategy.consensus()); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java new file mode 100644 index 0000000..c95f6d7 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java @@ -0,0 +1,183 @@ +/* + * 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.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.ObjectUtils; +import org.apache.bval.util.Validate; + +public class EmptyBuilder { + private static final Map<AnnotationBehavior, EmptyBuilder> INSTANCES = new EnumMap<>(AnnotationBehavior.class); + + public static EmptyBuilder instance() { + return instance(AnnotationBehavior.ABSTAIN); + } + + public static EmptyBuilder instance(AnnotationBehavior annotationBehavior) { + return INSTANCES.computeIfAbsent(annotationBehavior, EmptyBuilder::new); + } + + private class Level implements HasAnnotationBehavior { + + @Override + public final AnnotationBehavior getAnnotationBehavior() { + return annotationBehavior; + } + } + + private class ForBean extends Level implements MetadataBuilder.ForBean { + private final Lazy<EmptyBuilder.ForClass> forClass = new Lazy<>(EmptyBuilder.ForClass::new); + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return forClass.get(); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + return Collections.emptyMap(); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + return Collections.emptyMap(); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + return Collections.emptyMap(); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + return Collections.emptyMap(); + } + + @Override + public boolean isEmpty() { + return true; + } + } + + private class ForElement<E extends AnnotatedElement> extends Level implements MetadataBuilder.ForElement<E> { + + @Override + public final Annotation[] getDeclaredConstraints(Metas<E> meta) { + return ObjectUtils.EMPTY_ANNOTATION_ARRAY; + } + } + + private class ForClass extends ForElement<Class<?>> implements MetadataBuilder.ForClass { + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + return null; + } + } + + private class ForContainer<E extends AnnotatedElement> extends ForElement<E> + implements MetadataBuilder.ForContainer<E> { + + @Override + public boolean isCascade(Metas<E> meta) { + return false; + } + + @Override + public Set<GroupConversion> getGroupConversions(Metas<E> meta) { + return Collections.emptySet(); + } + + @Override + public Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> meta) { + return Collections.emptyMap(); + } + } + + private class ForExecutable<E extends Executable> extends Level implements MetadataBuilder.ForExecutable<E> { + + @SuppressWarnings("unchecked") + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return forElement.get(); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) { + return Collections.emptyList(); + } + + @SuppressWarnings("unchecked") + @Override + public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) { + return forContainer.get(); + } + } + + private final AnnotationBehavior annotationBehavior; + private final Lazy<EmptyBuilder.ForBean> forBean; + @SuppressWarnings("rawtypes") + private final Lazy<EmptyBuilder.ForContainer> forContainer; + @SuppressWarnings("rawtypes") + private final Lazy<EmptyBuilder.ForExecutable> forExecutable; + @SuppressWarnings("rawtypes") + private final Lazy<EmptyBuilder.ForElement> forElement; + + private EmptyBuilder(AnnotationBehavior annotationBehavior) { + super(); + this.annotationBehavior = Validate.notNull(annotationBehavior, "annotationBehavior"); + forBean = new Lazy<>(EmptyBuilder.ForBean::new); + forContainer = new Lazy<>(EmptyBuilder.ForContainer::new); + forExecutable = new Lazy<>(EmptyBuilder.ForExecutable::new); + forElement = new Lazy<>(EmptyBuilder.ForElement::new); + } + + public MetadataBuilder.ForBean forBean() { + return forBean.get(); + } + + @SuppressWarnings("unchecked") + public <E extends AnnotatedElement> MetadataBuilder.ForContainer<E> forContainer() { + return forContainer.get(); + } + + @SuppressWarnings("unchecked") + public <E extends Executable> MetadataBuilder.ForExecutable<E> forExecutable() { + return forExecutable.get(); + } + + @SuppressWarnings("unchecked") + public <E extends AnnotatedElement> MetadataBuilder.ForElement<E> forElement() { + return forElement.get(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java new file mode 100644 index 0000000..2060954 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java @@ -0,0 +1,24 @@ +/* + * 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; + +public interface HasAnnotationBehavior { + + default AnnotationBehavior getAnnotationBehavior() { + return AnnotationBehavior.ABSTAIN; + } +} \ 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/metadata/HierarchyBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java new file mode 100644 index 0000000..35276ea --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java @@ -0,0 +1,235 @@ +/* + * 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.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.Reflection; +import org.apache.bval.util.reflection.Reflection.Interfaces; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +public class HierarchyBuilder extends CompositeBuilder { + private static abstract class HierarchyDelegate<T> { + final T delegate; + + HierarchyDelegate(T delegate) { + super(); + this.delegate = Validate.notNull(delegate, "delegate"); + } + + static class ForBean extends HierarchyDelegate<MetadataBuilder.ForBean> implements MetadataBuilder.ForBean { + final Metas<Class<?>> hierarchyType; + + ForBean(MetadataBuilder.ForBean delegate, Class<?> hierarchyType) { + super(delegate); + this.hierarchyType = new Metas.ForClass(hierarchyType); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return new HierarchyDelegate.ForClass(delegate.getClass(hierarchyType), hierarchyType); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + return delegate.getFields(hierarchyType); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + return delegate.getGetters(hierarchyType); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + return delegate.getConstructors(hierarchyType); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + final Map<Signature, MetadataBuilder.ForExecutable<Method>> m = delegate.getMethods(hierarchyType); + + return m; + } + } + + static class ForClass extends HierarchyDelegate<MetadataBuilder.ForClass> implements MetadataBuilder.ForClass { + + final Metas<Class<?>> hierarchyType; + + ForClass(MetadataBuilder.ForClass delegate, Metas<Class<?>> hierarchyType) { + super(delegate); + this.hierarchyType = hierarchyType; + } + + @Override + public Annotation[] getDeclaredConstraints(Metas<Class<?>> meta) { + return delegate.getDeclaredConstraints(hierarchyType); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + return delegate.getGroupSequence(hierarchyType); + } + } + + static class ForGetter extends HierarchyDelegate<MetadataBuilder.ForContainer<Method>> + implements MetadataBuilder.ForContainer<Method> { + final Metas.ForMethod meta; + + ForGetter(MetadataBuilder.ForContainer<Method> delegate, + org.apache.bval.jsr.metadata.Metas.ForMethod meta) { + super(delegate); + this.meta = Validate.notNull(meta, "meta"); + } + + @Override + public Annotation[] getDeclaredConstraints(Metas<Method> meta) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isCascade(Metas<Method> meta) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Set<GroupConversion> getGroupConversions(Metas<Method> meta) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map<ContainerElementKey, org.apache.bval.jsr.metadata.MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<Method> meta) { + // TODO Auto-generated method stub + return null; + } + } + + static class ForMethod extends HierarchyDelegate<MetadataBuilder.ForExecutable<Method>> + implements MetadataBuilder.ForExecutable<Method> { + final Metas.ForMethod meta; + + public ForMethod(MetadataBuilder.ForExecutable<Method> delegate, Metas.ForMethod meta) { + super(delegate); + this.meta = Validate.notNull(meta, "meta"); + } + + @Override + public MetadataBuilder.ForContainer<Method> getReturnValue(Metas<Method> meta) { + + // TODO Auto-generated method stub + return null; + } + + @Override + public MetadataBuilder.ForElement<Method> getCrossParameter(Metas<Method> meta) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<Method> meta) { + // TODO Auto-generated method stub + return null; + } + } + } + + private final Function<Class<?>, MetadataBuilder.ForBean> getBeanBuilder; + + public HierarchyBuilder(Function<Class<?>, MetadataBuilder.ForBean> getBeanBuilder) { + super(AnnotationBehaviorMergeStrategy.first()); + this.getBeanBuilder = Validate.notNull(getBeanBuilder, "getBeanBuilder function was null"); + } + + public MetadataBuilder.ForBean forBean(Class<?> beanClass) { + final List<MetadataBuilder.ForBean> delegates = new ArrayList<>(); + + /* + * First add the delegate for the requested bean class, forcing to empty if absent. This is important for the + * same reason that we use the #first() AnnotationBehaviorMergeStrategy: namely, that custom metadata overrides + * only from the immediately available mapping per the BV spec. + */ + delegates.add(Optional.of(beanClass).map(getBeanBuilder).orElseGet(() -> EmptyBuilder.instance().forBean())); + + // iterate the hierarchy, skipping the first (i.e. beanClass handled + // above) + final Iterator<Class<?>> hierarchy = Reflection.hierarchy(beanClass, Interfaces.INCLUDE).iterator(); + hierarchy.next(); + + // skip Object.class; skip null/empty hierarchy builders, mapping others + // to HierarchyDelegate + hierarchy + .forEachRemaining(t -> Optional.of(t).filter(Predicate.isEqual(Object.class).negate()).map(getBeanBuilder) + .filter(b -> !b.isEmpty()).map(b -> new HierarchyDelegate.ForBean(b, t)).ifPresent(delegates::add)); + + // if we have nothing but empty builders (which should only happen for + // absent custom metadata), return empty: + if (delegates.stream().allMatch(MetadataBuilder.ForBean::isEmpty)) { + return EmptyBuilder.instance().forBean(); + } + return delegates.stream().collect(compose()); + } + + @Override + protected <E extends AnnotatedElement> Map<Scope, Annotation[]> getConstraintsByScope( + CompositeBuilder.ForElement<? extends MetadataBuilder.ForElement<E>, E> composite, Metas<E> meta) { + + final Iterator<? extends MetadataBuilder.ForElement<E>> iter = composite.delegates.iterator(); + + final Map<Scope, Annotation[]> result = new EnumMap<>(Scope.class); + result.put(Scope.LOCAL_ELEMENT, iter.next().getDeclaredConstraints(meta)); + + if (iter.hasNext()) { + final List<Annotation> hierarchyConstraints = new ArrayList<>(); + iter.forEachRemaining(d -> Collections.addAll(hierarchyConstraints, d.getDeclaredConstraints(meta))); + result.put(Scope.HIERARCHY, hierarchyConstraints.toArray(new Annotation[hierarchyConstraints.size()])); + } + return result; + } + + @Override + protected List<Class<?>> getGroupSequence(CompositeBuilder.ForClass composite, Metas<Class<?>> meta) { + return composite.delegates.get(0).getGroupSequence(meta); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java new file mode 100644 index 0000000..7dbdcbc --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java @@ -0,0 +1,98 @@ +/* + * 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.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.descriptor.GroupConversion; + +/** + * Common interface for populating the Bean Validation descriptors from various sources. Most implementations should + * concern themselves with a single level of an inheritance hierarchy. + */ +public final class MetadataBuilder { + + public interface ForBean extends HasAnnotationBehavior { + MetadataBuilder.ForClass getClass(Metas<Class<?>> meta); + + Map<String, ForContainer<Field>> getFields(Metas<Class<?>> meta); + + /** + * Returned keys are property names per XML mapping spec. + * + * @param meta + * @return {@link Map} + */ + Map<String, ForContainer<Method>> getGetters(Metas<Class<?>> meta); + + Map<Signature, ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta); + + Map<Signature, ForExecutable<Method>> getMethods(Metas<Class<?>> meta); + + default boolean isEmpty() { + return false; + } + } + + public interface ForElement<E extends AnnotatedElement> extends HasAnnotationBehavior { + + Annotation[] getDeclaredConstraints(Metas<E> meta); + + default Map<Scope, Annotation[]> getConstraintsByScope(Metas<E> meta) { + return Collections.singletonMap(Scope.LOCAL_ELEMENT, getDeclaredConstraints(meta)); + } + } + + public interface ForClass extends ForElement<Class<?>> { + + List<Class<?>> getGroupSequence(Metas<Class<?>> meta); + } + + public interface ForContainer<E extends AnnotatedElement> extends MetadataBuilder.ForElement<E> { + + boolean isCascade(Metas<E> meta); + + Set<GroupConversion> getGroupConversions(Metas<E> meta); + + Map<ContainerElementKey, ForContainer<AnnotatedType>> getContainerElementTypes(Metas<E> meta); + } + + public interface ForExecutable<E extends Executable> extends HasAnnotationBehavior { + + MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta); + + MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta); + + List<ForContainer<Parameter>> getParameters(Metas<E> meta); + } + + private MetadataBuilder() { + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java new file mode 100644 index 0000000..aa301a4 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java @@ -0,0 +1,41 @@ +/* + * 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.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.bval.util.Validate; + +public class MetadataBuilders { + + private final Map<Class<?>, List<MetadataBuilder.ForBean>> beanBuilders = new ConcurrentHashMap<>(); + + public <T> void registerCustomBuilder(Class<?> bean, MetadataBuilder.ForBean builder) { + Validate.notNull(bean, "bean"); + Validate.notNull(builder, "builder"); + beanBuilders.computeIfAbsent(bean, c -> new ArrayList<>()).add(builder); + } + + public List<MetadataBuilder.ForBean> getCustomBuilders(Class<?> bean) { + final List<MetadataBuilder.ForBean> list = beanBuilders.get(bean); + return list == null ? Collections.emptyList() : Collections.unmodifiableList(list); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java new file mode 100644 index 0000000..667c404 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java @@ -0,0 +1,324 @@ +/* + * 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.annotation.ElementType; +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.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.Objects; + +import javax.validation.constraintvalidation.ValidationTarget; + +import org.apache.bval.util.Validate; + +/** + * Validation class model. + * + * @param <E> + */ +// TODO rename to Meta; delete old type of that name +public abstract class Metas<E extends AnnotatedElement> { + + public static class ForClass extends Metas<Class<?>> { + + public ForClass(Class<?> host) { + super(host, ElementType.TYPE); + } + + @Override + public final Class<?> getDeclaringClass() { + return getHost(); + } + + @Override + public Type getType() { + return getHost(); + } + + @Override + public AnnotatedType getAnnotatedType() { + return new AnnotatedType() { + + @Override + public Annotation[] getDeclaredAnnotations() { + return getHost().getDeclaredAnnotations(); + } + + @Override + public Annotation[] getAnnotations() { + return getHost().getAnnotations(); + } + + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { + return getHost().getAnnotation(annotationClass); + } + + @Override + public Type getType() { + return getHost(); + } + }; + } + + @Override + public String getName() { + return getHost().getName(); + } + } + + public static abstract class ForMember<M extends Member & AnnotatedElement> extends Metas<M> { + + protected ForMember(M host, ElementType elementType) { + super(host, elementType); + } + + @Override + public Class<?> getDeclaringClass() { + return getHost().getDeclaringClass(); + } + } + + public static class ForField extends ForMember<Field> { + + public ForField(Field host) { + super(host, ElementType.FIELD); + } + + @Override + public Type getType() { + return getHost().getGenericType(); + } + + @Override + public AnnotatedType getAnnotatedType() { + return getHost().getAnnotatedType(); + } + + @Override + public String getName() { + return getHost().getName(); + } + } + + public static abstract class ForExecutable<E extends Executable> extends ForMember<E> { + + protected ForExecutable(E host, ElementType elementType) { + super(host, elementType); + } + + @Override + public AnnotatedType getAnnotatedType() { + return getHost().getAnnotatedReturnType(); + } + } + + public static class ForConstructor extends ForExecutable<Constructor<?>> { + + public ForConstructor(Constructor<?> host) { + super(host, ElementType.CONSTRUCTOR); + } + + @Override + public Type getType() { + return getHost().getDeclaringClass(); + } + + @Override + public String getName() { + return getHost().getDeclaringClass().getSimpleName(); + } + } + + public static class ForMethod extends ForExecutable<Method> { + + public ForMethod(Method host) { + super(host, ElementType.METHOD); + } + + @Override + public Type getType() { + return getHost().getGenericReturnType(); + } + + @Override + public String getName() { + return getHost().getName(); + } + } + + public static class ForCrossParameter<E extends Executable> extends Metas.ForExecutable<E> { + + public ForCrossParameter(Metas<E> parent) { + super(parent.getHost(), parent.getElementType()); + } + + @Override + public Type getType() { + return Object[].class; + } + + @Override + public String getName() { + return "<cross parameter>"; + } + + @Override + public ValidationTarget getValidationTarget() { + return ValidationTarget.PARAMETERS; + } + + @Override + public String toString() { + return String.format("%s(%s of %s)", getStringPrefix(), getName(), getHost()); + } + } + + public static class ForParameter extends Metas<Parameter> { + + private final String name; + + public ForParameter(Parameter host, String name) { + super(host, ElementType.PARAMETER); + this.name = Validate.notNull(name, "name"); + } + + @Override + public Type getType() { + return getHost().getType(); + } + + @Override + public Class<?> getDeclaringClass() { + return getHost().getDeclaringExecutable().getDeclaringClass(); + } + + @Override + public AnnotatedType getAnnotatedType() { + return getHost().getAnnotatedType(); + } + + public String getName() { + return name; + } + } + + public static class ForContainerElement extends Metas<AnnotatedType> { + + private final Metas<?> parent; + private final ContainerElementKey key; + + public ForContainerElement(Metas<?> parent, ContainerElementKey key) { + super(key.getAnnotatedType(), ElementType.TYPE_USE); + this.parent = Validate.notNull(parent, "parent"); + this.key = Validate.notNull(key, "key"); + } + + @Override + public Type getType() { + return getHost().getType(); + } + + @Override + public Class<?> getDeclaringClass() { + return parent.getDeclaringClass(); + } + + @Override + public AnnotatedType getAnnotatedType() { + return key.getAnnotatedType(); + } + + public Integer getTypeArgumentIndex() { + return Integer.valueOf(key.getTypeArgumentIndex()); + } + + @Override + public String getName() { + return key.toString(); + } + + @Override + public String toString() { + return String.format("%s(%s of %s)", getStringPrefix(), key, getHost()); + } + } + + private final E host; + private final ElementType elementType; + + protected Metas(E host, ElementType elementType) { + super(); + this.host = Validate.notNull(host, "host"); + this.elementType = Validate.notNull(elementType, "elementType"); + } + + public E getHost() { + return host; + } + + public ElementType getElementType() { + return elementType; + } + + public abstract Type getType(); + + public abstract Class<?> getDeclaringClass(); + + public abstract AnnotatedType getAnnotatedType(); + + public abstract String getName(); + + public ValidationTarget getValidationTarget() { + return ValidationTarget.ANNOTATED_ELEMENT; + } + + @Override + public String toString() { + return String.format("%s(%s)", getStringPrefix(), host); + } + + protected String getStringPrefix() { + return Metas.class.getSimpleName() + '.' + getClass().getSimpleName(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!obj.getClass().equals(getClass())) { + return false; + } + return Objects.equals(((Metas<?>) obj).getHost(), getHost()); + } + + @Override + public int hashCode() { + return Objects.hash(getHost()); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java new file mode 100644 index 0000000..fc21ea7 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java @@ -0,0 +1,272 @@ +/* + * 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.AnnotatedParameterizedType; +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.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.GroupSequence; +import javax.validation.Valid; +import javax.validation.constraintvalidation.ValidationTarget; +import javax.validation.groups.ConvertGroup; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.jsr.util.AnnotationsManager; +import org.apache.bval.jsr.util.Methods; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.ObjectUtils; +import org.apache.bval.util.Validate; +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 class ReflectionBuilder { + + private class ForBean implements MetadataBuilder.ForBean { + private final Metas<Class<?>> meta; + + ForBean(Metas<Class<?>> meta) { + super(); + this.meta = Validate.notNull(meta, "meta"); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> ignored) { + return new ReflectionBuilder.ForClass(meta); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> ignored) { + final Field[] declaredFields = Reflection.getDeclaredFields(meta.getHost()); + if (declaredFields.length == 0) { + return Collections.emptyMap(); + } + return Stream.of(declaredFields).collect( + Collectors.toMap(Field::getName, f -> new ReflectionBuilder.ForContainer<>(new Metas.ForField(f)))); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> ignored) { + return Stream.of(Reflection.getDeclaredMethods(meta.getHost())).filter(Methods::isGetter) + .collect(ToUnmodifiable.map(Methods::propertyName, + g -> new ReflectionBuilder.ForContainer<>(new Metas.ForMethod(g)))); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> ignored) { + final Constructor<?>[] declaredConstructors = Reflection.getDeclaredConstructors(meta.getHost()); + if (declaredConstructors.length == 0) { + return Collections.emptyMap(); + } + return Stream.of(declaredConstructors).collect( + Collectors.toMap(Signature::of, c -> new ReflectionBuilder.ForExecutable<>(new Metas.ForConstructor(c), + validatorFactory.getParameterNameProvider()::getParameterNames))); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> ignored) { + final Method[] declaredMethods = Reflection.getDeclaredMethods(meta.getHost()); + if (declaredMethods.length == 0) { + return Collections.emptyMap(); + } + return Stream.of(declaredMethods).filter(((Predicate<Method>) Methods::isGetter).negate()).collect( + Collectors.toMap(Signature::of, m -> new ReflectionBuilder.ForExecutable<>(new Metas.ForMethod(m), + validatorFactory.getParameterNameProvider()::getParameterNames))); + } + } + + private abstract class ForElement<E extends AnnotatedElement> implements MetadataBuilder.ForElement<E> { + final Metas<E> meta; + + ForElement(Metas<E> meta) { + super(); + this.meta = Validate.notNull(meta, "meta"); + } + + @Override + public Annotation[] getDeclaredConstraints(Metas<E> ignored) { + return AnnotationsManager.getDeclaredConstraints(meta); + } + } + + private class ForClass extends ForElement<Class<?>> implements MetadataBuilder.ForClass { + + ForClass(Metas<Class<?>> meta) { + super(meta); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> ignored) { + final GroupSequence groupSequence = meta.getHost().getAnnotation(GroupSequence.class); + return groupSequence == null ? null : Collections.unmodifiableList(Arrays.asList(groupSequence.value())); + } + } + + private class ForContainer<E extends AnnotatedElement> extends ReflectionBuilder.ForElement<E> + implements MetadataBuilder.ForContainer<E> { + + ForContainer(Metas<E> meta) { + super(meta); + } + + @Override + public Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> ignored) { + final AnnotatedType annotatedType = meta.getAnnotatedType(); + if (annotatedType instanceof AnnotatedParameterizedType) { + + final AnnotatedParameterizedType container = (AnnotatedParameterizedType) annotatedType; + + final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> result = new TreeMap<>(); + + final AnnotatedType[] typeArgs = container.getAnnotatedActualTypeArguments(); + for (int i = 0; i < typeArgs.length; i++) { + ContainerElementKey key = new ContainerElementKey(container, i); + result.put(key, new ReflectionBuilder.ForContainer<>(new Metas.ForContainerElement(meta, key))); + } + return result; + } + return Collections.emptyMap(); + } + + @Override + public boolean isCascade(Metas<E> ignored) { + return meta.getHost().isAnnotationPresent(Valid.class); + } + + @Override + public Set<GroupConversion> getGroupConversions(Metas<E> ignored) { + return Stream.of(meta.getHost().getDeclaredAnnotationsByType(ConvertGroup.class)) + .map(cg -> GroupConversion.from(cg.from()).to(cg.to())).collect(ToUnmodifiable.set()); + } + } + + private class ForExecutable<E extends Executable> implements MetadataBuilder.ForExecutable<E> { + + final Metas<E> meta; + final Function<E, List<String>> getParameterNames; + + ForExecutable(Metas<E> meta, Function<E, List<String>> getParameterNames) { + super(); + this.meta = Validate.notNull(meta, "meta"); + this.getParameterNames = Validate.notNull(getParameterNames, "getParameterNames"); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> ignored) { + final Parameter[] parameters = meta.getHost().getParameters(); + if (parameters.length == 0) { + return Collections.emptyList(); + } + final List<String> parameterNames = getParameterNames.apply(meta.getHost()); + + return IntStream.range(0, parameters.length).mapToObj( + n -> new ReflectionBuilder.ForContainer<>(new Metas.ForParameter(parameters[n], parameterNames.get(n)))) + .collect(ToUnmodifiable.list()); + } + + @Override + public ForContainer<E> getReturnValue(Metas<E> ignored) { + return new ReflectionBuilder.ForContainer<E>(meta) { + + @Override + public Annotation[] getDeclaredConstraints(Metas<E> meta) { + return getConstraints(meta, ValidationTarget.ANNOTATED_ELEMENT); + } + }; + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> ignored) { + return new ReflectionBuilder.ForElement<E>(meta) { + @Override + public Annotation[] getDeclaredConstraints(Metas<E> meta) { + return getConstraints(meta, ValidationTarget.PARAMETERS); + } + }; + } + + private Annotation[] getConstraints(Metas<E> ignored, ValidationTarget validationTarget) { + return Optional.of(getConstraintsByTarget(meta)).map(m -> m.get(validationTarget)) + .map(l -> l.toArray(new Annotation[l.size()])).orElse(ObjectUtils.EMPTY_ANNOTATION_ARRAY); + } + + private Map<ValidationTarget, List<Annotation>> getConstraintsByTarget(Metas<E> ignored) { + final Annotation[] declaredConstraints = AnnotationsManager.getDeclaredConstraints(meta); + if (ObjectUtils.isEmpty(declaredConstraints)) { + return Collections.emptyMap(); + } + final Map<ValidationTarget, List<Annotation>> result = new EnumMap<>(ValidationTarget.class); + + for (Annotation constraint : declaredConstraints) { + final Class<? extends Annotation> constraintType = constraint.annotationType(); + final Optional<ValidationTarget> explicitTarget = + Optional.of(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.analyze(constraintType)) + .filter(ConstraintAnnotationAttributes.Worker::isValid).map(w -> w.read(constraint)); + + final ValidationTarget target = explicitTarget.orElseGet(() -> { + final Set<ValidationTarget> supportedTargets = + validatorFactory.getAnnotationsManager().supportedTargets(constraintType); + + Validate.validState(supportedTargets.size() == 1, + "Found %d possible %s types for constraint type %s and no explicit assignment via #%s()", + supportedTargets.size(), ValidationTarget.class.getSimpleName(), constraintType.getName(), + ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName()); + + return supportedTargets.iterator().next(); + }); + result.computeIfAbsent(target, k -> new ArrayList<>()).add(constraint); + } + return result; + } + } + + private final ApacheValidatorFactory validatorFactory; + + public ReflectionBuilder(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + } + + public <T> MetadataBuilder.ForBean forBean(Class<?> beanClass) { + return new ReflectionBuilder.ForBean(new Metas.ForClass(beanClass)); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java new file mode 100644 index 0000000..8def7ae --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Signature.java @@ -0,0 +1,75 @@ +/* + * 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.reflect.Executable; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.bval.util.Lazy; +import org.apache.bval.util.LazyInt; +import org.apache.bval.util.StringUtils; +import org.apache.bval.util.Validate; + +public final class Signature { + public static Signature of(Executable x) { + return new Signature(x.getName(), x.getParameterTypes()); + } + + private final String name; + private final Class<?>[] parameterTypes; + private final LazyInt hashCode; + private final Lazy<String> toString; + + public Signature(String name, Class<?>... parameterTypes) { + super(); + this.name = Validate.notNull(name, "name"); + Validate.isTrue(StringUtils.isNotBlank(name), "name is blank"); + this.parameterTypes = Validate.notNull(parameterTypes, "parameterTypes").clone(); + hashCode = new LazyInt(() -> Arrays.deepHashCode(new Object[] { this.name, this.parameterTypes })); + toString = new Lazy<>(() -> String.format("%s: %s(%s)", getClass().getSimpleName(), this.name, + Stream.of(this.parameterTypes).map(Class::getName).collect(Collectors.joining(", ")))); + } + + public String getName() { + return name; + } + + public Class<?>[] getParameterTypes() { + return parameterTypes.clone(); + } + + @Override + public boolean equals(Object obj) { + return obj == this || Optional.ofNullable(obj).filter(Signature.class::isInstance).map(Signature.class::cast) + .filter(sig -> Objects.equals(name, sig.name) && Objects.deepEquals(parameterTypes, sig.parameterTypes)) + .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/metadata/ValidatorMapping.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMapping.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMapping.java new file mode 100644 index 0000000..bd2ce7f --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMapping.java @@ -0,0 +1,121 @@ +/* + * 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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.validation.ConstraintValidator; + +import org.apache.bval.util.Validate; + +public class ValidatorMapping<A extends Annotation> implements HasAnnotationBehavior { + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static final ValidatorMapping EMPTY = new ValidatorMapping("empty", Collections.emptyList()); + + @SuppressWarnings("unchecked") + public static <A extends Annotation> ValidatorMapping<A> empty() { + return EMPTY; + } + + public static <A extends Annotation> ValidatorMapping<A> merge( + List<? extends ValidatorMapping<A>> validatorMappings, + AnnotationBehaviorMergeStrategy annotationBehaviorMergeStrategy) { + + final AnnotationBehavior behavior = annotationBehaviorMergeStrategy.apply(validatorMappings); + + final List<? extends ValidatorMapping<A>> nonEmpty = + validatorMappings.stream().filter(m -> !m.isEmpty()).collect(Collectors.toList()); + + if (nonEmpty.size() <= 1) { + // avoid creating the composite instance if behavior matches: + final ValidatorMapping<A> simpleResult = nonEmpty.isEmpty() ? empty() : nonEmpty.get(0); + + if (simpleResult.hasBehavior(behavior)) { + return simpleResult; + } + } + final String source = + nonEmpty.stream().map(ValidatorMapping::getSource).collect(Collectors.joining(";", "[", "]")); + + return new ValidatorMapping<>(source, nonEmpty.stream().map(ValidatorMapping::getValidatorTypes) + .flatMap(Collection::stream).distinct().collect(Collectors.toList()), behavior); + } + + private final String source; + private final List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes; + private final AnnotationBehavior annotationBehavior; + + public ValidatorMapping(String source, List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes) { + this(source, validatorTypes, AnnotationBehavior.ABSTAIN); + } + + public ValidatorMapping(String source, List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes, + AnnotationBehavior annotationBehavior) { + this.source = Objects.toString(source, "unspecified"); + this.validatorTypes = Collections.unmodifiableList(Validate.notNull(validatorTypes, "validatorTypes")); + this.annotationBehavior = Validate.notNull(annotationBehavior, "annotationBehavior"); + } + + public List<Class<? extends ConstraintValidator<A, ?>>> getValidatorTypes() { + return validatorTypes; + } + + public AnnotationBehavior getAnnotationBehavior() { + return annotationBehavior; + } + + public boolean isEmpty() { + return validatorTypes.isEmpty(); + } + + public String getSource() { + return source; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!getClass().isInstance(obj)) { + return false; + } + final ValidatorMapping<?> other = (ValidatorMapping<?>) obj; + return getSource().equals(other.getSource()) && getAnnotationBehavior() == other.getAnnotationBehavior() + && getValidatorTypes().equals(other.getValidatorTypes()); + } + + @Override + public int hashCode() { + return Objects.hash(getSource(), getAnnotationBehavior(), getValidatorTypes()); + } + + @Override + public String toString() { + return String.format("%s[source: %s; annotationBehavior: %s; validatorTypes: %s]", + ValidatorMapping.class.getSimpleName(), getSource(), getAnnotationBehavior(), getValidatorTypes()); + } + + public boolean hasBehavior(AnnotationBehavior annotationBehavior) { + return getAnnotationBehavior() == annotationBehavior; + } +} \ 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/metadata/ValidatorMappingProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMappingProvider.java new file mode 100644 index 0000000..8a8cd3f --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ValidatorMappingProvider.java @@ -0,0 +1,51 @@ +/* + * 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.Type; +import java.util.Optional; + +import javax.validation.ConstraintDefinitionException; +import javax.validation.ConstraintValidator; + +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.reflection.TypeUtils; + +public abstract class ValidatorMappingProvider { + + public final <A extends Annotation> ValidatorMapping<A> getValidatorMapping(Class<A> constraintType) { + final Optional<ValidatorMapping<A>> result = + Optional.ofNullable(this.<A> doGetValidatorMapping(constraintType)); + if (result.isPresent()) { + for (Class<? extends ConstraintValidator<A, ?>> t : result.get().getValidatorTypes()) { + final Type constraintParameter = TypeUtils.getTypeArguments(t, ConstraintValidator.class) + .get(ConstraintValidator.class.getTypeParameters()[0]); + + Exceptions.raiseUnless(constraintType.equals(constraintParameter), ConstraintDefinitionException::new, + "%s %s expected first type parameter of %s, %s; source %s", ConstraintValidator.class, t, + constraintType, constraintParameter, result.get().getSource()); + } + return result.get(); + } + return null; + } + + protected abstract <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType); +}
