http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java new file mode 100644 index 0000000..f99024a --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java @@ -0,0 +1,164 @@ +/* + * 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.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.TypeVariable; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.valueextraction.ExtractedValue; +import javax.validation.valueextraction.ValueExtractor; +import javax.validation.valueextraction.ValueExtractorDefinitionException; + +import org.apache.bval.util.Lazy; +import org.apache.bval.util.LazyInt; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; + +public class ContainerElementKey implements Comparable<ContainerElementKey> { + + public static ContainerElementKey forValueExtractor(ValueExtractor<?> extractor) { + @SuppressWarnings("rawtypes") + final Class<? extends ValueExtractor> extractorType = extractor.getClass(); + final Lazy<Set<ContainerElementKey>> result = new Lazy<>(HashSet::new); + + Stream.of(extractorType.getAnnotatedInterfaces()).filter(AnnotatedParameterizedType.class::isInstance) + .map(AnnotatedParameterizedType.class::cast) + .filter(apt -> ValueExtractor.class.equals(((ParameterizedType) apt.getType()).getRawType())) + .forEach(decl -> { + final AnnotatedType containerType = decl.getAnnotatedActualTypeArguments()[0]; + + if (containerType.isAnnotationPresent(ExtractedValue.class)) { + result.get().add(new ContainerElementKey(containerType, null)); + } + Optional.of(containerType).filter(AnnotatedParameterizedType.class::isInstance) + .map(AnnotatedParameterizedType.class::cast) + .map(AnnotatedParameterizedType::getAnnotatedActualTypeArguments).ifPresent(args -> { + IntStream.range(0, args.length).forEach(n -> { + if (args[n].isAnnotationPresent(ExtractedValue.class)) { + result.get().add(new ContainerElementKey(containerType, Integer.valueOf(n))); + } + }); + }); + + }); + + return result.optional().filter(s -> s.size() == 1) + .orElseThrow(() -> new ValueExtractorDefinitionException(extractorType.getName())).iterator().next(); + } + + private static Integer validTypeArgumentIndex(Integer typeArgumentIndex, Class<?> containerClass) { + if (typeArgumentIndex != null) { + final int i = typeArgumentIndex.intValue(); + Validate.isTrue(i >= 0 && i < containerClass.getTypeParameters().length, + "type argument index %d is invalid for container type %s", typeArgumentIndex, containerClass); + } + return typeArgumentIndex; + } + + private final Integer typeArgumentIndex; + private final Class<?> containerClass; + private final LazyInt hashCode = new LazyInt(() -> Objects.hash(getContainerClass(), getTypeArgumentIndex())); + private final Lazy<String> toString = new Lazy<>(() -> String.format("%s: %s<[%d]>", + ContainerElementKey.class.getSimpleName(), getContainerClass().getName(), getTypeArgumentIndex())); + private final AnnotatedType annotatedType; + + public ContainerElementKey(AnnotatedType containerType, Integer typeArgumentIndex) { + super(); + Validate.notNull(containerType, "containerType"); + this.containerClass = TypeUtils.getRawType(containerType.getType(), null); + + this.typeArgumentIndex = validTypeArgumentIndex(typeArgumentIndex, containerClass); + + annotatedType = typeArgumentIndex == null ? containerType : ((AnnotatedParameterizedType) containerType) + .getAnnotatedActualTypeArguments()[typeArgumentIndex.intValue()]; + } + + public Class<?> getContainerClass() { + return containerClass; + } + + public Integer getTypeArgumentIndex() { + return typeArgumentIndex; + } + + public AnnotatedType getAnnotatedType() { + return annotatedType; + } + + @Override + public boolean equals(Object obj) { + return obj == this || Optional.ofNullable(obj).filter(ContainerElementKey.class::isInstance) + .map(ContainerElementKey.class::cast) + .filter( + cek -> Objects.equals(containerClass, cek.containerClass) && typeArgumentIndex == cek.typeArgumentIndex) + .isPresent(); + } + + @Override + public int hashCode() { + return hashCode.getAsInt(); + } + + @Override + public String toString() { + return toString.get(); + } + + @Override + public int compareTo(ContainerElementKey o) { + return Comparator.comparing(ContainerElementKey::containerClassName) + .thenComparing(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex)).compare(this, o); + } + + public Set<ContainerElementKey> getAssignableKeys() { + final Lazy<Set<ContainerElementKey>> result = new Lazy<>(LinkedHashSet::new); + + if (typeArgumentIndex != null) { + final TypeVariable<?> var = containerClass.getTypeParameters()[typeArgumentIndex.intValue()]; + + Stream + .concat(Stream.of(containerClass.getAnnotatedSuperclass()), + Stream.of(containerClass.getAnnotatedInterfaces())) + .filter(AnnotatedParameterizedType.class::isInstance).map(AnnotatedParameterizedType.class::cast) + .forEach(t -> { + final AnnotatedType[] args = t.getAnnotatedActualTypeArguments(); + + for (int i = 0; i < args.length; i++) { + if (args[i].getType().equals(var)) { + result.get().add(new ContainerElementKey(t, Integer.valueOf(i))); + } + } + }); + } + return result.optional().map(Collections::unmodifiableSet).orElseGet(Collections::emptySet); + } + + private String containerClassName() { + return getContainerClass().getName(); + } +}
http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/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..778b8f1 --- /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 MetadataBuilder.Level { + + @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/a43c0b0c/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..db05d70 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java @@ -0,0 +1,134 @@ +/* + * 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.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +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.function.Function; +import java.util.function.Predicate; + +import javax.validation.metadata.Scope; + +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 class HierarchyDelegate implements MetadataBuilder.ForBean { + private final Metas<Class<?>> hierarchyType; + private final MetadataBuilder.ForBean delegate; + + HierarchyDelegate(Class<?> hierarchyType, MetadataBuilder.ForBean delegate) { + super(); + this.hierarchyType = new Metas.ForClass(hierarchyType); + this.delegate = delegate; + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return delegate.getClass(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) { + return delegate.getMethods(hierarchyType); + } + } + + 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).orElse(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(t, b)).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/a43c0b0c/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..65683fb --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java @@ -0,0 +1,104 @@ +/* + * 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 Level { + + default AnnotationBehavior getAnnotationBehavior() { + return AnnotationBehavior.ABSTAIN; + } + } + + public interface ForBean extends Level { + 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 Level { + + 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 Level { + + 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/a43c0b0c/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/a43c0b0c/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..fb5e2ef --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.bval.jsr.metadata; + +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 javax.validation.constraintvalidation.ValidationTarget; + +import org.apache.bval.util.Validate; + +/** + * Validation class model. + * + * @param <E> + */ +public abstract class Metas<E extends AnnotatedElement> { + + public static class ForClass extends Metas<Class<?>> { + + public ForClass(Class<?> host) { + super(host, ElementType.TYPE); + } + + public final Class<?> getDeclaringClass() { + return getHost(); + } + + @Override + public Type getType() { + return getHost(); + } + + @Override + public AnnotatedType getAnnotatedType() { + throw new UnsupportedOperationException(); + } + + @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); + } + } + + 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; + } + } + + 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 AnnotatedType getAnnotatedType() { + return getHost().getAnnotatedType(); + } + + public String getName() { + return name; + } + } + + public static class ForContainerElement extends Metas<AnnotatedType> { + + private final ContainerElementKey key; + + public ForContainerElement(ContainerElementKey key) { + super(key.getAnnotatedType(), ElementType.TYPE_USE); + this.key = Validate.notNull(key, "key"); + } + + @Override + public Type getType() { + return getAnnotatedType().getType(); + } + + @Override + public AnnotatedType getAnnotatedType() { + return key.getAnnotatedType(); + } + + public Integer getTypeArgumentIndex() { + return Integer.valueOf(key.getTypeArgumentIndex()); + } + + @Override + public String getName() { + return key.toString(); + } + } + + 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 AnnotatedType getAnnotatedType(); + + public abstract String getName(); + + public ValidationTarget getValidationTarget() { + return ValidationTarget.ANNOTATED_ELEMENT; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ParallelBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ParallelBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ParallelBuilder.java new file mode 100644 index 0000000..77c5bde --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ParallelBuilder.java @@ -0,0 +1,238 @@ +/* + * 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.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 ParallelBuilder { + + private static class Delegator<DELEGATE extends MetadataBuilder.Level> implements MetadataBuilder.Level { + + 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); + + 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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.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 ParallelBuilder.ForContainer<>(this, primaries.get(n), customs.get(n))) + .collect(ToUnmodifiable.list()); + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return new ParallelBuilder.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 ParallelBuilder.ForBean(primaryDelegate, customDelegate); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/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..a9fca40 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ReflectionBuilder.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.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.Predicate; +import java.util.stream.Collectors; +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 { + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return new ReflectionBuilder.ForClass(); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + final Field[] declaredFields = Reflection.getDeclaredFields(meta.getHost()); + if (declaredFields.length == 0) { + return Collections.emptyMap(); + } + // we just read from the passed in meta, so can reuse the same builder instance: + final MetadataBuilder.ForContainer<Field> value = new ReflectionBuilder.ForContainer<>(); + return Stream.of(declaredFields).collect(Collectors.toMap(Field::getName, f -> value)); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + // we just read from the passed in meta, so can reuse the same builder instance: + final MetadataBuilder.ForContainer<Method> value = new ReflectionBuilder.ForContainer<>(); + return Stream.of(Reflection.getDeclaredMethods(meta.getHost())).filter(Methods::isGetter) + .collect(ToUnmodifiable.map(Methods::propertyName, g -> value)); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + final Constructor<?>[] declaredConstructors = Reflection.getDeclaredConstructors(meta.getHost()); + if (declaredConstructors.length == 0) { + return Collections.emptyMap(); + } + // we just read from the passed in meta, so can reuse the same builder instance: + final MetadataBuilder.ForExecutable<Constructor<?>> value = new ReflectionBuilder.ForExecutable<>(); + return Stream.of(declaredConstructors).collect(Collectors.toMap(Signature::of, c -> value)); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + final Method[] declaredMethods = Reflection.getDeclaredMethods(meta.getHost()); + if (declaredMethods.length == 0) { + return Collections.emptyMap(); + } + + // we just read from the passed in meta, so can reuse the same builder instance: + final MetadataBuilder.ForExecutable<Method> value = new ReflectionBuilder.ForExecutable<>(); + return Stream.of(declaredMethods).filter(((Predicate<Method>) Methods::isGetter).negate()) + .collect(Collectors.toMap(Signature::of, m -> value)); + } + } + + private class ForElement<E extends AnnotatedElement> implements MetadataBuilder.ForElement<E> { + @Override + public Annotation[] getDeclaredConstraints(Metas<E> meta) { + return AnnotationsManager.getDeclaredConstraints(meta.getHost()); + } + } + + private class ForClass extends ForElement<Class<?>> implements MetadataBuilder.ForClass { + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + 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> { + + @Override + public Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> meta) { + final AnnotatedType annotatedType = meta.getAnnotatedType(); + if (annotatedType instanceof AnnotatedParameterizedType) { + + final AnnotatedParameterizedType container = (AnnotatedParameterizedType) annotatedType; + + final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> result = new TreeMap<>(); + + final MetadataBuilder.ForContainer<AnnotatedType> value = new ReflectionBuilder.ForContainer<>(); + + final AnnotatedType[] typeArgs = container.getAnnotatedActualTypeArguments(); + for (int i = 0; i < typeArgs.length; i++) { + result.put(new ContainerElementKey(container, i), value); + } + + return result; + } + return Collections.emptyMap(); + } + + @Override + public boolean isCascade(Metas<E> meta) { + return meta.getHost().isAnnotationPresent(Valid.class); + } + + @Override + public Set<GroupConversion> getGroupConversions(Metas<E> meta) { + 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> { + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) { + final int count = meta.getHost().getParameterCount(); + if (count == 0) { + return Collections.emptyList(); + } + // we just read from the passed in meta, so can reuse the same builder instance: + return Collections.nCopies(count, new ReflectionBuilder.ForContainer<>()); + } + + @Override + public ForContainer<E> getReturnValue(Metas<E> meta) { + return new ReflectionBuilder.ForContainer<E>() { + + @Override + public Annotation[] getDeclaredConstraints(Metas<E> meta) { + return getConstraints(meta, ValidationTarget.ANNOTATED_ELEMENT); + } + }; + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return new ReflectionBuilder.ForElement<E>() { + @Override + public Annotation[] getDeclaredConstraints(Metas<E> meta) { + return getConstraints(meta, ValidationTarget.PARAMETERS); + } + }; + } + + private Annotation[] getConstraints(Metas<E> meta, 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> meta) { + final Annotation[] declaredConstraints = AnnotationsManager.getDeclaredConstraints(meta.getHost()); + 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; + private final ReflectionBuilder.ForBean forBean = new ReflectionBuilder.ForBean(); + + public ReflectionBuilder(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + } + + public <T> MetadataBuilder.ForBean forBean(Class<?> beanClass) { + return forBean; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/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..f49b5f6 --- /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.equals(parameterTypes, sig.parameterTypes)) + .isPresent(); + } + + @Override + public int hashCode() { + return hashCode.getAsInt(); + } + + @Override + public String toString() { + return toString.get(); + } +}
