BV2: new metadata model
Project: http://git-wip-us.apache.org/repos/asf/bval/repo Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/59bd964b Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/59bd964b Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/59bd964b Branch: refs/heads/bv2 Commit: 59bd964bb20dc465c42123315c8d0f6e760bb728 Parents: 40fc20f Author: Matt Benson <[email protected]> Authored: Wed Feb 21 14:50:31 2018 -0600 Committer: Matt Benson <[email protected]> Committed: Wed Feb 21 14:59:57 2018 -0600 ---------------------------------------------------------------------- .../bval/jsr/metadata/AnnotationBehavior.java | 35 + .../AnnotationBehaviorMergeStrategy.java | 54 ++ ...otationDeclaredValidatorMappingProvider.java | 42 ++ .../ClassLoadingValidatorMappingProvider.java | 48 ++ .../bval/jsr/metadata/CompositeBuilder.java | 227 ++++++ .../CompositeValidatorMappingProvider.java | 42 ++ .../bval/jsr/metadata/ContainerElementKey.java | 175 +++++ .../apache/bval/jsr/metadata/DualBuilder.java | 243 +++++++ .../metadata/DualValidationMappingProvider.java | 50 ++ .../apache/bval/jsr/metadata/EmptyBuilder.java | 183 +++++ .../jsr/metadata/HasAnnotationBehavior.java | 24 + .../bval/jsr/metadata/HierarchyBuilder.java | 235 +++++++ .../bval/jsr/metadata/MetadataBuilder.java | 98 +++ .../bval/jsr/metadata/MetadataBuilders.java | 41 ++ .../org/apache/bval/jsr/metadata/Metas.java | 324 +++++++++ .../bval/jsr/metadata/ReflectionBuilder.java | 272 ++++++++ .../org/apache/bval/jsr/metadata/Signature.java | 75 ++ .../bval/jsr/metadata/ValidatorMapping.java | 121 ++++ .../jsr/metadata/ValidatorMappingProvider.java | 51 ++ .../apache/bval/jsr/metadata/XmlBuilder.java | 694 +++++++++++++++++++ .../metadata/XmlValidationMappingProvider.java | 64 ++ 21 files changed, 3098 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java new file mode 100644 index 0000000..56ed4f0 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.metadata; + +import org.apache.bval.jsr.metadata.MetadataBuilder; + +/** + * Models the behavior of a {@link MetadataBuilder} with regard to bean validation annotations. + * + * @see DualBuilder + */ +public enum AnnotationBehavior implements AnnotationBehaviorMergeStrategy { + //@formatter:off + INCLUDE, EXCLUDE, ABSTAIN; + //@formatter:on + + @Override + public AnnotationBehavior apply(Iterable<? extends HasAnnotationBehavior> t) { + return this; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java new file mode 100644 index 0000000..bfd16c5 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.metadata; + +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.bval.util.Validate; + +@FunctionalInterface +public interface AnnotationBehaviorMergeStrategy + extends Function<Iterable<? extends HasAnnotationBehavior>, AnnotationBehavior> { + + public static AnnotationBehaviorMergeStrategy first() { + return coll -> { + final Iterator<? extends HasAnnotationBehavior> iterator = coll.iterator(); + return iterator.hasNext() ? iterator.next().getAnnotationBehavior() : AnnotationBehavior.ABSTAIN; + }; + } + + public static AnnotationBehaviorMergeStrategy consensus() { + return coll -> { + final Stream.Builder<HasAnnotationBehavior> b = Stream.builder(); + coll.forEach(b); + final Set<AnnotationBehavior> annotationBehaviors = + b.build().map(HasAnnotationBehavior::getAnnotationBehavior).filter(Objects::nonNull) + .filter(Predicate.isEqual(AnnotationBehavior.ABSTAIN).negate()) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(AnnotationBehavior.class))); + Validate.validState(annotationBehaviors.size() <= 1, + "Conflicting annotation inclusion behaviors found among %s", coll); + return annotationBehaviors.isEmpty() ? AnnotationBehavior.ABSTAIN : annotationBehaviors.iterator().next(); + }; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.java new file mode 100644 index 0000000..b2126ac --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.java @@ -0,0 +1,42 @@ +/* + * 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 java.util.List; + +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; + +import org.apache.bval.util.Validate; + +public class AnnotationDeclaredValidatorMappingProvider extends ValidatorMappingProvider { + public static final AnnotationDeclaredValidatorMappingProvider INSTANCE = + new AnnotationDeclaredValidatorMappingProvider(); + + @Override + protected <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType) { + Validate.notNull(constraintType); + Validate.isTrue(constraintType.isAnnotationPresent(Constraint.class), + "%s does not represent a validation constraint", constraintType); + @SuppressWarnings({ "unchecked", "rawtypes" }) + final List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes = + (List) Arrays.asList(constraintType.getAnnotation(Constraint.class).validatedBy()); + return new ValidatorMapping<>("@Constraint.validatedBy()", validatorTypes); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java new file mode 100644 index 0000000..e636a8a --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java @@ -0,0 +1,48 @@ +/* + * 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.Objects; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +public abstract class ClassLoadingValidatorMappingProvider extends ValidatorMappingProvider { + + protected final <T> Stream<Class<? extends T>> load(Stream<String> classNames, Class<T> assignableTo, + Consumer<? super ClassNotFoundException> handleException) { + return classNames.map(className -> { + try { + return Reflection.toClass(className, getClassLoader()); + } catch (ClassNotFoundException e) { + handleException.accept(e); + return (Class<?>) null; + } + }).filter(Objects::nonNull).map(c -> (Class<? extends T>) c.asSubclass(assignableTo)); + } + + protected ClassLoader getClassLoader() { + final ClassLoader classloader = Thread.currentThread().getContextClassLoader(); + return classloader == null ? getClass().getClassLoader() : classloader; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java new file mode 100644 index 0000000..52a7407 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.metadata; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.descriptor.GroupConversion; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Validate; + +public class CompositeBuilder { + + class Delegator<DELEGATE extends HasAnnotationBehavior> implements HasAnnotationBehavior { + + protected final List<DELEGATE> delegates; + + Delegator(List<DELEGATE> delegates) { + this.delegates = Validate.notNull(delegates, "delegates"); + Validate.isTrue(!delegates.isEmpty(), "no delegates specified"); + Validate.isTrue(delegates.stream().noneMatch(Objects::isNull), "One or more supplied delegates was null"); + } + + @Override + public AnnotationBehavior getAnnotationBehavior() { + return annotationBehaviorStrategy.apply(delegates); + } + + <K, D> Map<K, D> merge(Function<DELEGATE, Map<K, D>> toMap, Function<List<D>, D> merge) { + final List<Map<K, D>> maps = delegates.stream().map(toMap).collect(Collectors.toList()); + + final Function<? super K, ? extends D> valueMapper = k -> { + final List<D> mappedDelegates = + maps.stream().map(m -> m.get(k)).filter(Objects::nonNull).collect(Collectors.toList()); + return mappedDelegates.size() == 1 ? mappedDelegates.get(0) : merge.apply(mappedDelegates); + }; + + return maps.stream().map(Map::keySet).flatMap(Collection::stream).distinct() + .collect(Collectors.toMap(Function.identity(), valueMapper)); + } + } + + private class ForBean extends CompositeBuilder.Delegator<MetadataBuilder.ForBean> + implements MetadataBuilder.ForBean { + + ForBean(List<MetadataBuilder.ForBean> delegates) { + super(delegates); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return new CompositeBuilder.ForClass( + delegates.stream().map(d -> d.getClass(meta)).collect(Collectors.toList())); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + return merge(b -> b.getFields(meta), CompositeBuilder.ForContainer::new); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + return merge(b -> b.getGetters(meta), CompositeBuilder.ForContainer::new); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + return merge(b -> b.getConstructors(meta), CompositeBuilder.ForExecutable::new); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + return merge(b -> b.getMethods(meta), CompositeBuilder.ForExecutable::new); + } + } + + class ForElement<DELEGATE extends MetadataBuilder.ForElement<E>, E extends AnnotatedElement> + extends Delegator<DELEGATE> implements MetadataBuilder.ForElement<E> { + + ForElement(List<DELEGATE> delegates) { + super(delegates); + } + + @Override + public Map<Scope, Annotation[]> getConstraintsByScope(Metas<E> meta) { + return CompositeBuilder.this.getConstraintsByScope(this, meta); + } + + @Override + public final Annotation[] getDeclaredConstraints(Metas<E> meta) { + return delegates.stream().map(d -> d.getDeclaredConstraints(meta)).flatMap(Stream::of) + .toArray(Annotation[]::new); + } + } + + class ForClass extends ForElement<MetadataBuilder.ForClass, Class<?>> implements MetadataBuilder.ForClass { + + ForClass(List<MetadataBuilder.ForClass> delegates) { + super(delegates); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + return CompositeBuilder.this.getGroupSequence(this, meta); + } + } + + private class ForContainer<DELEGATE extends MetadataBuilder.ForContainer<E>, E extends AnnotatedElement> + extends CompositeBuilder.ForElement<DELEGATE, E> implements MetadataBuilder.ForContainer<E> { + + ForContainer(List<DELEGATE> delegates) { + super(delegates); + } + + @Override + public final boolean isCascade(Metas<E> meta) { + return delegates.stream().anyMatch(d -> d.isCascade(meta)); + } + + @Override + public final Set<GroupConversion> getGroupConversions(Metas<E> meta) { + return delegates.stream().map(d -> d.getGroupConversions(meta)).flatMap(Collection::stream) + .collect(ToUnmodifiable.set()); + } + + @Override + public final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> meta) { + return merge(b -> b.getContainerElementTypes(meta), CompositeBuilder.ForContainer::new); + } + } + + private class ForExecutable<DELEGATE extends MetadataBuilder.ForExecutable<E>, E extends Executable> + extends Delegator<DELEGATE> implements MetadataBuilder.ForExecutable<E> { + + ForExecutable(List<DELEGATE> delegates) { + super(delegates); + } + + @Override + public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) { + return new CompositeBuilder.ForContainer<>( + delegates.stream().map(d -> d.getReturnValue(meta)).collect(Collectors.toList())); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) { + final List<List<MetadataBuilder.ForContainer<Parameter>>> parameterLists = + delegates.stream().map(d -> d.getParameters(meta)).collect(Collectors.toList()); + + final Set<Integer> parameterCounts = parameterLists.stream().map(List::size).collect(Collectors.toSet()); + Validate.validState(parameterCounts.size() == 1, "Mismatched parameter counts: %s", parameterCounts); + + return IntStream.range(0, parameterCounts.iterator().next().intValue()) + .mapToObj(n -> new CompositeBuilder.ForContainer<>(parameterLists.get(n))) + .collect(ToUnmodifiable.list()); + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return new CompositeBuilder.ForElement<MetadataBuilder.ForElement<E>, E>( + delegates.stream().map(d -> d.getCrossParameter(meta)).collect(Collectors.toList())); + } + } + + public static CompositeBuilder with(AnnotationBehaviorMergeStrategy annotationBehaviorStrategy) { + return new CompositeBuilder(annotationBehaviorStrategy); + } + + private final AnnotationBehaviorMergeStrategy annotationBehaviorStrategy; + + CompositeBuilder(AnnotationBehaviorMergeStrategy annotationBehaviorMergeStrategy) { + super(); + this.annotationBehaviorStrategy = + Validate.notNull(annotationBehaviorMergeStrategy, "annotationBehaviorMergeStrategy"); + } + + public Collector<MetadataBuilder.ForBean, ?, MetadataBuilder.ForBean> compose() { + return Collectors.collectingAndThen(Collectors.toList(), CompositeBuilder.ForBean::new); + } + + protected <E extends AnnotatedElement> Map<Scope, Annotation[]> getConstraintsByScope( + CompositeBuilder.ForElement<? extends MetadataBuilder.ForElement<E>, E> composite, Metas<E> meta) { + return Collections.singletonMap(Scope.LOCAL_ELEMENT, composite.getDeclaredConstraints(meta)); + } + + protected List<Class<?>> getGroupSequence(CompositeBuilder.ForClass composite, Metas<Class<?>> meta) { + final List<List<Class<?>>> groupSequence = + composite.delegates.stream().map(d -> d.getGroupSequence(meta)).collect(Collectors.toList()); + Validate.validState(groupSequence.size() <= 1, + "group sequence returned from multiple composite class metadata builders"); + return groupSequence.isEmpty() ? null : groupSequence.get(0); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.java new file mode 100644 index 0000000..9808f89 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.java @@ -0,0 +1,42 @@ +/* + * 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.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.bval.util.Validate; + +public class CompositeValidatorMappingProvider extends ValidatorMappingProvider { + + private final List<ValidatorMappingProvider> delegates; + + public CompositeValidatorMappingProvider(List<ValidatorMappingProvider> delegates) { + super(); + this.delegates = Validate.notNull(delegates, "delegates"); + Validate.isTrue(!delegates.isEmpty(), "no delegates specified"); + Validate.isTrue(delegates.stream().noneMatch(Objects::isNull), "One or more supplied delegates was null"); + } + + @Override + protected <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType) { + return ValidatorMapping.merge(delegates.stream().map(d -> d.doGetValidatorMapping(constraintType)) + .filter(Objects::nonNull).collect(Collectors.toList()), AnnotationBehaviorMergeStrategy.consensus()); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/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..322a4ef --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java @@ -0,0 +1,175 @@ +/* + * 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.logging.Logger; +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.Exceptions; +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> { + private static Logger log = Logger.getLogger(ContainerElementKey.class.getName()); + + 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)) { + Exceptions.raiseIf(void.class.equals(containerType.getAnnotation(ExtractedValue.class).type()), + ValueExtractorDefinitionException::new, "%s does not specify %s type for %s", extractorType, + ExtractedValue.class.getSimpleName(), containerType); + 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)) { + if (void.class.equals(args[n].getAnnotation(ExtractedValue.class).type())) { + log.warning(String.format("Ignoring non-default %s type specified for %s by %s", + ExtractedValue.class.getSimpleName(), containerType, extractorType)); + } + 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); + this.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); + } + + public boolean represents(TypeVariable<?> var) { + return Optional.ofNullable(typeArgumentIndex) + .map(index -> getContainerClass().getTypeParameters()[index.intValue()]).filter(var::equals).isPresent(); + } + + private String containerClassName() { + return getContainerClass().getName(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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()); + } +}
