http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java new file mode 100644 index 0000000..3bc69b2 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java @@ -0,0 +1,715 @@ +/* + * 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.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Array; +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.math.BigDecimal; +import java.util.Collections; +import java.util.EnumSet; +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 java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.ConstraintDeclarationException; +import javax.validation.ConstraintTarget; +import javax.validation.Payload; +import javax.validation.ValidationException; +import javax.xml.bind.JAXBElement; + +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.Exceptions; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.jsr.xml.AnnotationProxyBuilder; +import org.apache.bval.jsr.xml.AnnotationType; +import org.apache.bval.jsr.xml.BeanType; +import org.apache.bval.jsr.xml.ClassType; +import org.apache.bval.jsr.xml.ConstraintMappingsType; +import org.apache.bval.jsr.xml.ConstraintType; +import org.apache.bval.jsr.xml.ConstructorType; +import org.apache.bval.jsr.xml.ContainerElementTypeType; +import org.apache.bval.jsr.xml.CrossParameterType; +import org.apache.bval.jsr.xml.ElementType; +import org.apache.bval.jsr.xml.FieldType; +import org.apache.bval.jsr.xml.GetterType; +import org.apache.bval.jsr.xml.GroupConversionType; +import org.apache.bval.jsr.xml.GroupSequenceType; +import org.apache.bval.jsr.xml.GroupsType; +import org.apache.bval.jsr.xml.MethodType; +import org.apache.bval.jsr.xml.ParameterType; +import org.apache.bval.jsr.xml.PayloadType; +import org.apache.bval.jsr.xml.ReturnValueType; +import org.apache.bval.jsr.xml.ValidationMappingParser; +import org.apache.bval.util.Lazy; +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 XmlBuilder { + //@formatter:off + public enum Version { + v10("1.0"), v11("1.1"), v20("2.0"); + + final BigDecimal number; + private final String id; + + private Version(String number) { + this.id = number; + this.number = new BigDecimal(number); + } + + public String getId() { + return id; + } + } + //@formatter:on + + private class ForBean implements MetadataBuilder.ForBean { + + private final BeanType descriptor; + + ForBean(BeanType descriptor) { + super(); + this.descriptor = Validate.notNull(descriptor, "descriptor"); + } + + Class<?> getBeanClass() { + return resolveClass(descriptor.getClazz()); + } + + @Override + public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) { + return new XmlBuilder.ForClass(descriptor.getClassType()); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) { + return descriptor.getField().stream() + .collect(ToUnmodifiable.map(FieldType::getName, XmlBuilder.ForField::new)); + } + + @Override + public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) { + return descriptor.getGetter().stream() + .collect(ToUnmodifiable.map(GetterType::getName, XmlBuilder.ForGetter::new)); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) { + if (!atLeast(Version.v11)) { + return Collections.emptyMap(); + } + final Function<ConstructorType, Class<?>[]> params = ct -> ct.getParameter().stream() + .map(ParameterType::getType).map(XmlBuilder.this::resolveClass).toArray(Class[]::new); + + final Function<ConstructorType, Signature> signature = + ct -> new Signature(meta.getHost().getSimpleName(), params.apply(ct)); + + return descriptor.getConstructor().stream() + .collect(Collectors.toMap(signature, XmlBuilder.ForConstructor::new)); + } + + @Override + public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) { + if (!atLeast(Version.v11)) { + return Collections.emptyMap(); + } + final Function<MethodType, Class<?>[]> params = mt -> mt.getParameter().stream().map(ParameterType::getType) + .map(XmlBuilder.this::resolveClass).toArray(Class[]::new); + + final Function<MethodType, Signature> signature = mt -> new Signature(mt.getName(), params.apply(mt)); + + return descriptor.getMethod().stream().collect(Collectors.toMap(signature, XmlBuilder.ForMethod::new)); + } + + @Override + public final AnnotationBehavior getAnnotationBehavior() { + return descriptor.getIgnoreAnnotations() ? AnnotationBehavior.EXCLUDE : AnnotationBehavior.INCLUDE; + } + } + + private class NonRootLevel<SELF extends NonRootLevel<SELF, D>, D> implements MetadataBuilder.Level { + protected final D descriptor; + private Lazy<Boolean> getIgnoreAnnotations; + + public NonRootLevel(D descriptor) { + super(); + this.descriptor = Validate.notNull(descriptor, "descriptor"); + } + + @Override + public final AnnotationBehavior getAnnotationBehavior() { + return Optional.ofNullable(getIgnoreAnnotations).map(Lazy::get) + .map(b -> b.booleanValue() ? AnnotationBehavior.EXCLUDE : AnnotationBehavior.INCLUDE) + .orElse(AnnotationBehavior.ABSTAIN); + } + + @SuppressWarnings("unchecked") + final SELF withGetIgnoreAnnotations(Function<D, Boolean> getIgnoreAnnotations) { + Validate.notNull(getIgnoreAnnotations); + this.getIgnoreAnnotations = new Lazy<>(() -> getIgnoreAnnotations.apply(descriptor)); + return (SELF) this; + } + } + + private class ForElement<SELF extends XmlBuilder.ForElement<SELF, E, D>, E extends AnnotatedElement, D> + extends NonRootLevel<SELF, D> implements MetadataBuilder.ForElement<E> { + + private Lazy<Annotation[]> getDeclaredConstraints; + + ForElement(D descriptor) { + super(descriptor); + } + + @Override + public final Annotation[] getDeclaredConstraints(Metas<E> meta) { + return lazy(getDeclaredConstraints, "getDeclaredConstraints"); + } + + final SELF withGetConstraintTypes(Function<D, List<ConstraintType>> getConstraintTypes) { + return withGetDeclaredConstraints(getConstraintTypes + .andThen(l -> l.stream().map(XmlBuilder.this::createConstraint).toArray(Annotation[]::new))); + } + + @SuppressWarnings("unchecked") + final SELF withGetDeclaredConstraints(Function<D, Annotation[]> getDeclaredConstraints) { + this.getDeclaredConstraints = new Lazy<>(() -> getDeclaredConstraints.apply(descriptor)); + return (SELF) this; + } + } + + private class ForClass extends ForElement<ForClass, Class<?>, ClassType> implements MetadataBuilder.ForClass { + + ForClass(ClassType descriptor) { + super(descriptor); + this.withGetConstraintTypes(ct -> ct.getConstraint()); + } + + @Override + public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) { + final GroupSequenceType groupSequence = descriptor.getGroupSequence(); + return groupSequence == null ? null + : groupSequence.getValue().stream().map(XmlBuilder.this::resolveClass).collect(ToUnmodifiable.list()); + } + } + + private class ForContainer<SELF extends XmlBuilder.ForContainer<SELF, E, D>, E extends AnnotatedElement, D> + extends XmlBuilder.ForElement<SELF, E, D> implements MetadataBuilder.ForContainer<E> { + + private Lazy<Boolean> isCascade; + private Lazy<Set<GroupConversion>> getGroupConversions; + private Lazy<List<ContainerElementTypeType>> getContainerElementTypes; + + ForContainer(D descriptor) { + super(descriptor); + } + + @Override + public boolean isCascade(Metas<E> meta) { + Validate.validState(isCascade != null, "isCascade not set"); + return isCascade.get().booleanValue(); + } + + @Override + public Set<GroupConversion> getGroupConversions(Metas<E> meta) { + Validate.validState(getGroupConversions != null, "getGroupConversions not set"); + return getGroupConversions.get(); + } + + @Override + public Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes( + Metas<E> meta) { + + if (!atLeast(Version.v20)) { + return Collections.emptyMap(); + } + + Validate.validState(getContainerElementTypes != null, "getContainerElementTypes not set"); + + final List<ContainerElementTypeType> elements = lazy(getContainerElementTypes, "getContainerElementTypes"); + + final AnnotatedType annotatedType = meta.getAnnotatedType(); + + final E host = meta.getHost(); + + if (annotatedType instanceof AnnotatedParameterizedType) { + final AnnotatedType[] actualTypeArguments = + ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments(); + + return elements.stream().collect(ToUnmodifiable.map(cet -> { + Integer typeArgumentIndex = cet.getTypeArgumentIndex(); + if (typeArgumentIndex == null) { + if (actualTypeArguments.length > 1) { + Exceptions.raise(ValidationException::new, "Missing required type argument index for %s", + host); + } + typeArgumentIndex = Integer.valueOf(0); + } + return new ContainerElementKey((AnnotatedParameterizedType) annotatedType, typeArgumentIndex); + }, XmlBuilder.ForContainerElementType::new)); + } + if (!elements.isEmpty()) { + Exceptions.raise(ValidationException::new, "Illegally specified %d container element type(s) for %s", + elements.size(), host); + } + return Collections.emptyMap(); + } + + @SuppressWarnings("unchecked") + SELF withGetValid(Function<D, String> getValid) { + Validate.notNull(getValid); + this.isCascade = new Lazy<>(() -> getValid.apply(descriptor) != null); + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetGroupConversions(Function<D, List<GroupConversionType>> getGroupConversions) { + Validate.notNull(getGroupConversions); + + this.getGroupConversions = new Lazy<>(() -> { + return getGroupConversions.apply(descriptor).stream().map(gc -> { + final Class<?> source = resolveClass(gc.getFrom()); + final Class<?> target = resolveClass(gc.getTo()); + return GroupConversion.from(source).to(target); + }).collect(ToUnmodifiable.set()); + }); + + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetContainerElementTypes(Function<D, List<ContainerElementTypeType>> getContainerElementTypes) { + Validate.notNull(getContainerElementTypes); + this.getContainerElementTypes = new Lazy<>(() -> getContainerElementTypes.apply(descriptor)); + return (SELF) this; + } + } + + private class ForContainerElementType + extends ForContainer<ForContainerElementType, AnnotatedType, ContainerElementTypeType> { + + ForContainerElementType(ContainerElementTypeType descriptor) { + super(descriptor); + this.withGetConstraintTypes(ContainerElementTypeType::getConstraint) + .withGetValid(ContainerElementTypeType::getValid) + .withGetGroupConversions(ContainerElementTypeType::getConvertGroup) + .withGetContainerElementTypes(ContainerElementTypeType::getContainerElementType); + } + } + + private class ForField extends XmlBuilder.ForContainer<ForField, Field, FieldType> { + + ForField(FieldType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(FieldType::getIgnoreAnnotations) + .withGetConstraintTypes(FieldType::getConstraint).withGetValid(FieldType::getValid) + .withGetGroupConversions(FieldType::getConvertGroup) + .withGetContainerElementTypes(FieldType::getContainerElementType); + } + } + + private class ForGetter extends XmlBuilder.ForContainer<ForGetter, Method, GetterType> { + + ForGetter(GetterType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(GetterType::getIgnoreAnnotations) + .withGetConstraintTypes(GetterType::getConstraint).withGetValid(GetterType::getValid) + .withGetGroupConversions(GetterType::getConvertGroup) + .withGetContainerElementTypes(GetterType::getContainerElementType); + } + } + + private abstract class ForExecutable<SELF extends ForExecutable<SELF, E, D>, E extends Executable, D> + extends NonRootLevel<SELF, D> implements MetadataBuilder.ForExecutable<E> { + + Lazy<ReturnValueType> getReturnValue; + Lazy<CrossParameterType> getCrossParameter; + Lazy<List<ParameterType>> getParameters; + + ForExecutable(D descriptor) { + super(descriptor); + + } + + @Override + public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) { + return new XmlBuilder.ForCrossParameter<>(lazy(getCrossParameter, "getCrossParameter")); + } + + @Override + public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) { + return new XmlBuilder.ForReturnValue<>(lazy(getReturnValue, "getReturnValue")); + } + + @Override + public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) { + return lazy(getParameters, "getParameters").stream().map(XmlBuilder.ForParameter::new) + .collect(Collectors.toList()); + } + + @SuppressWarnings("unchecked") + SELF withGetReturnValue(Function<D, ReturnValueType> getReturnValue) { + Validate.notNull(getReturnValue); + this.getReturnValue = new Lazy<>(() -> getReturnValue.apply(descriptor)); + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetCrossParameter(Function<D, CrossParameterType> getCrossParameter) { + Validate.notNull(getCrossParameter); + this.getCrossParameter = new Lazy<>(() -> getCrossParameter.apply(descriptor)); + return (SELF) this; + } + + @SuppressWarnings("unchecked") + SELF withGetParameters(Function<D, List<ParameterType>> getParameters) { + Validate.notNull(getParameters); + this.getParameters = new Lazy<>(() -> getParameters.apply(descriptor)); + return (SELF) this; + } + } + + private class ForConstructor extends ForExecutable<ForConstructor, Constructor<?>, ConstructorType> { + + ForConstructor(ConstructorType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(ConstructorType::getIgnoreAnnotations) + .withGetReturnValue(ConstructorType::getReturnValue) + .withGetCrossParameter(ConstructorType::getCrossParameter) + .withGetParameters(ConstructorType::getParameter); + } + } + + private class ForMethod extends ForExecutable<ForMethod, Method, MethodType> { + + ForMethod(MethodType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(MethodType::getIgnoreAnnotations) + .withGetReturnValue(MethodType::getReturnValue).withGetCrossParameter(MethodType::getCrossParameter) + .withGetParameters(MethodType::getParameter); + } + } + + private class ForParameter extends ForContainer<ForParameter, Parameter, ParameterType> { + + ForParameter(ParameterType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(ParameterType::getIgnoreAnnotations) + .withGetConstraintTypes(ParameterType::getConstraint).withGetValid(ParameterType::getValid) + .withGetGroupConversions(ParameterType::getConvertGroup) + .withGetContainerElementTypes(ParameterType::getContainerElementType); + } + } + + private class ForCrossParameter<E extends Executable> + extends ForElement<ForCrossParameter<E>, E, CrossParameterType> { + + ForCrossParameter(CrossParameterType descriptor) { + super(descriptor); + this.withGetIgnoreAnnotations(CrossParameterType::getIgnoreAnnotations) + .withGetDeclaredConstraints(d -> d.getConstraint().stream() + .map(ct -> createConstraint(ct, ConstraintTarget.PARAMETERS)).toArray(Annotation[]::new)); + } + } + + private class ForReturnValue<E extends Executable> extends ForContainer<ForReturnValue<E>, E, ReturnValueType> { + + ForReturnValue(ReturnValueType descriptor) { + super(descriptor); + this.withGetDeclaredConstraints(d -> d.getConstraint().stream() + .map(ct -> createConstraint(ct, ConstraintTarget.RETURN_VALUE)).toArray(Annotation[]::new)) + .withGetContainerElementTypes(d -> d.getContainerElementType()); + } + } + + private static final Set<ConstraintAnnotationAttributes> RESERVED_PARAMS = Collections + .unmodifiableSet(EnumSet.of(ConstraintAnnotationAttributes.GROUPS, ConstraintAnnotationAttributes.MESSAGE, + ConstraintAnnotationAttributes.PAYLOAD, ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO)); + + static final <T> T lazy(Lazy<T> lazy, String name) { + Validate.validState(lazy != null, "%s not set", name); + return lazy.get(); + } + + private final ConstraintMappingsType constraintMappings; + private final BigDecimal version; + + public XmlBuilder(ConstraintMappingsType constraintMappings) { + super(); + this.constraintMappings = constraintMappings; + Validate.notNull(constraintMappings, "constraintMappings"); + + BigDecimal v; + try { + v = new BigDecimal(constraintMappings.getVersion()); + } catch (NumberFormatException e) { + v = Version.v10.number; + } + this.version = v; + } + + public Map<Class<?>, MetadataBuilder.ForBean> forBeans() { + return constraintMappings.getBean().stream().map(XmlBuilder.ForBean::new) + .collect(ToUnmodifiable.map(XmlBuilder.ForBean::getBeanClass, Function.identity())); + } + + public String getDefaultPackage() { + return constraintMappings.getDefaultPackage(); + } + + boolean atLeast(Version v) { + return version.compareTo(v.number) >= 0; + } + + <T> Class<T> resolveClass(String className) { + return loadClass(toQualifiedClassName(className)); + } + + private String toQualifiedClassName(String className) { + if (isQualifiedClass(className)) { + return className; + } + if (className.startsWith("[L") && className.endsWith(";")) { + return "[L" + getDefaultPackage() + "." + className.substring(2); + } + return getDefaultPackage() + "." + className; + } + + private boolean isQualifiedClass(String clazz) { + return clazz.indexOf('.') >= 0; + } + + @SuppressWarnings("unchecked") + private <T> Class<T> loadClass(final String fqn) { + ClassLoader loader = Reflection.getClassLoader(XmlBuilder.class); + if (loader == null) { + loader = getClass().getClassLoader(); + } + try { + return (Class<T>) Class.forName(fqn, true, loader); + } catch (ClassNotFoundException ex) { + throw Exceptions.create(ValidationException::new, ex, "Unable to load class: %d", fqn); + } + } + + private Class<?>[] loadClasses(Supplier<Stream<String>> classNames) { + return streamClasses(classNames).toArray(Class[]::new); + } + + private Stream<Class<?>> streamClasses(Supplier<Stream<String>> classNames) { + return classNames.get().map(c -> loadClass(c)); + } + + private <A extends Annotation, T> A createConstraint(final ConstraintType constraint) { + return createConstraint(constraint, ConstraintTarget.IMPLICIT); + } + + @SuppressWarnings("unchecked") + private <A extends Annotation, T> A createConstraint(final ConstraintType constraint, ConstraintTarget target) { + + final Class<A> annotationClass = (Class<A>) loadClass(toQualifiedClassName(constraint.getAnnotation())); + final AnnotationProxyBuilder<A> annoBuilder = new AnnotationProxyBuilder<A>(annotationClass); + + if (constraint.getMessage() != null) { + annoBuilder.setMessage(constraint.getMessage()); + } + annoBuilder.setGroups(getGroups(constraint.getGroups())); + annoBuilder.setPayload(getPayload(constraint.getPayload())); + + if (AnnotationsManager.declaresAttribute(annotationClass, + ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName())) { + annoBuilder.setValidationAppliesTo(target); + } + + for (final ElementType elementType : constraint.getElement()) { + final String name = elementType.getName(); + checkValidName(name); + + final Class<?> returnType = getAnnotationParameterType(annotationClass, name); + final Object elementValue = getElementValue(elementType, returnType); + annoBuilder.putValue(name, elementValue); + } + return annoBuilder.createAnnotation(); + } + + private void checkValidName(String name) { + Exceptions.raiseIf(RESERVED_PARAMS.stream().map(ConstraintAnnotationAttributes::getAttributeName) + .anyMatch(Predicate.isEqual(name)), ValidationException::new, "%s is a reserved parameter name.", name); + } + + private <A extends Annotation> Class<?> getAnnotationParameterType(final Class<A> annotationClass, + final String name) { + final Method m = Reflection.getPublicMethod(annotationClass, name); + Exceptions.raiseIf(m == null, ValidationException::new, + "Annotation of type %s does not contain a parameter %s.", annotationClass.getName(), name); + return m.getReturnType(); + } + + private Object getElementValue(ElementType elementType, Class<?> returnType) { + removeEmptyContentElements(elementType); + + final List<Serializable> content = elementType.getContent(); + final int sz = content.size(); + if (returnType.isArray()) { + final Object result = Array.newInstance(returnType.getComponentType(), sz); + for (int i = 0; i < sz; i++) { + Array.set(result, i, getSingleValue(content.get(i), returnType.getComponentType())); + } + return result; + } + Exceptions.raiseIf(sz != 1, ValidationException::new, + "Attempt to specify an array where single value is expected."); + + return getSingleValue(content.get(0), returnType); + } + + private void removeEmptyContentElements(ElementType elementType) { + for (Iterator<Serializable> iter = elementType.getContent().iterator(); iter.hasNext();) { + final Serializable content = iter.next(); + if (content instanceof String && ((String) content).matches("[\\n ].*")) { + iter.remove(); + } + } + } + + @SuppressWarnings("unchecked") + private Object getSingleValue(Serializable serializable, Class<?> returnType) { + if (serializable instanceof String) { + return convertToResultType(returnType, (String) serializable); + } + if (serializable instanceof JAXBElement<?>) { + final JAXBElement<?> elem = (JAXBElement<?>) serializable; + if (String.class.equals(elem.getDeclaredType())) { + return convertToResultType(returnType, (String) elem.getValue()); + } + if (AnnotationType.class.equals(elem.getDeclaredType())) { + AnnotationType annotationType = (AnnotationType) elem.getValue(); + try { + return createAnnotation(annotationType, (Class<? extends Annotation>) returnType); + } catch (ClassCastException e) { + throw new ValidationException("Unexpected parameter value"); + } + } + } + throw new ValidationException("Unexpected parameter value"); + } + + private Object convertToResultType(Class<?> returnType, String value) { + /** + * Class is represented by the fully qualified class name of the class. spec: Note that if the raw string is + * unqualified, default package is taken into account. + */ + if (String.class.equals(returnType)) { + return value; + } + if (Class.class.equals(returnType)) { + ClassLoader cl = Reflection.getClassLoader(ValidationMappingParser.class); + try { + return Reflection.toClass(toQualifiedClassName(value), cl); + } catch (Exception e) { + throw new ValidationException(e); + } + } + if (returnType.isEnum()) { + try { + @SuppressWarnings({ "rawtypes", "unchecked" }) + final Enum e = Enum.valueOf(returnType.asSubclass(Enum.class), value); + return e; + } catch (IllegalArgumentException e) { + throw new ConstraintDeclarationException(e); + } + } + if (Byte.class.equals(returnType) || byte.class.equals(returnType)) { // spec mandates it + return Byte.parseByte(value); + } + if (Short.class.equals(returnType) || short.class.equals(returnType)) { + return Short.parseShort(value); + } + if (Integer.class.equals(returnType) || int.class.equals(returnType)) { + return Integer.parseInt(value); + } + if (Long.class.equals(returnType) || long.class.equals(returnType)) { + return Long.parseLong(value); + } + if (Float.class.equals(returnType) || float.class.equals(returnType)) { + return Float.parseFloat(value); + } + if (Double.class.equals(returnType) || double.class.equals(returnType)) { + return Double.parseDouble(value); + } + if (Boolean.class.equals(returnType) || boolean.class.equals(returnType)) { + return Boolean.parseBoolean(value); + } + if (Character.class.equals(returnType) || char.class.equals(returnType)) { + if (value.length() > 1) { + Exceptions.raise(ConstraintDeclarationException::new, "a char must have a length of 1"); + } + return value.charAt(0); + } + throw new ValidationException(String.format("Unknown annotation value type %s", returnType.getName())); + } + + private <A extends Annotation> Annotation createAnnotation(AnnotationType annotationType, Class<A> returnType) { + final AnnotationProxyBuilder<A> metaAnnotation = new AnnotationProxyBuilder<>(returnType); + for (ElementType elementType : annotationType.getElement()) { + final String name = elementType.getName(); + metaAnnotation.putValue(name, getElementValue(elementType, getAnnotationParameterType(returnType, name))); + } + return metaAnnotation.createAnnotation(); + } + + private Class<?>[] getGroups(GroupsType groupsType) { + if (groupsType == null) { + return ObjectUtils.EMPTY_CLASS_ARRAY; + } + return loadClasses(groupsType.getValue()::stream); + } + + @SuppressWarnings("unchecked") + private Class<? extends Payload>[] getPayload(PayloadType payloadType) { + if (payloadType == null) { + return (Class<? extends Payload>[]) ObjectUtils.EMPTY_CLASS_ARRAY; + } + return streamClasses(payloadType.getValue()::stream).peek(pc -> { + if (!Payload.class.isAssignableFrom(pc)) { + Exceptions.raise(ConstraintDeclarationException::new, + "Specified payload class %s does not implement %s", pc.getName(), Payload.class.getName()); + } + }).<Class<? extends Payload>> map(pc -> pc.asSubclass(Payload.class)).toArray(Class[]::new); + } +}
http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java new file mode 100644 index 0000000..aac8ec1 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java @@ -0,0 +1,279 @@ +/* + * 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.util; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.Constraint; +import javax.validation.ConstraintDefinitionException; +import javax.validation.OverridesAttribute; +import javax.validation.ValidationException; +import javax.validation.constraintvalidation.ValidationTarget; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.ConfigurationImpl; +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo; +import org.apache.bval.jsr.xml.AnnotationProxyBuilder; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.StringUtils; +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; + +/** + * Manages (constraint) annotations according to the BV spec. + * + * @since 2.0 + */ +@Privilizing(@CallTo(Reflection.class)) +public class AnnotationsManager { + private static final class OverriddenAnnotationSpecifier { + final Class<? extends Annotation> annotationType; + final int constraintIndex; + + OverriddenAnnotationSpecifier(OverridesAttribute annotation) { + this.annotationType = annotation.annotationType(); + this.constraintIndex = annotation.constraintIndex(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !obj.getClass().equals(getClass())) { + return false; + } + final OverriddenAnnotationSpecifier other = (OverriddenAnnotationSpecifier) obj; + return Objects.equals(annotationType, other.annotationType) && constraintIndex == other.constraintIndex; + } + + @Override + public int hashCode() { + return Objects.hash(annotationType, constraintIndex); + } + + } + + private static class Composition { + final Lazy<Map<OverriddenAnnotationSpecifier, Map<String, String>>> overrides = new Lazy<>(HashMap::new); + final Annotation[] components; + + Composition(Class<? extends Annotation> annotationType) { + components = Stream.of(annotationType.getDeclaredAnnotations()) + .filter(a -> a.annotationType().isAnnotationPresent(Constraint.class)).toArray(Annotation[]::new); + + if (!isComposed()) { + return; + } + for (Method m : Reflection.getDeclaredMethods(annotationType)) { + final String from = m.getName(); + for (OverridesAttribute overridesAttribute : m.getDeclaredAnnotationsByType(OverridesAttribute.class)) { + final String to = + Optional.of(overridesAttribute.name()).filter(StringUtils::isNotBlank).orElse(from); + final Map<String, String> attributeMapping = overrides.get() + .computeIfAbsent(new OverriddenAnnotationSpecifier(overridesAttribute), k -> new HashMap<>()); + if (attributeMapping.containsKey(to)) { + throw new IllegalStateException( + String.format("Attempt to override %s#%s() index %d from multiple sources", + overridesAttribute.constraint(), to, overridesAttribute.constraintIndex())); + } + attributeMapping.put(to, from); + } + } + } + + boolean isComposed() { + return components.length > 0; + } + + Annotation[] getComponents(Annotation source) { + final Annotation[] result = components.clone(); + if (overrides.optional().isPresent()) { + final Map<Class<? extends Annotation>, List<Annotation>> constraints = new HashMap<>(); + for (Annotation constraint : result) { + constraints.computeIfAbsent(constraint.annotationType(), k -> new ArrayList<>()).add(constraint); + } + final Map<String, Object> sourceAttributes = readAttributes(source); + overrides.get().forEach((spec, mappings) -> { + final List<Annotation> ofType = constraints.get(spec.annotationType); + final int actualIndex; + if (spec.constraintIndex < 0) { + Validate.validState(ofType.size() == 1, "Expected a single composing %s constraint", + spec.annotationType); + actualIndex = 0; + } else { + actualIndex = spec.constraintIndex; + } + final AnnotationProxyBuilder<Annotation> proxyBuilder = + new AnnotationProxyBuilder<>(ofType.get(actualIndex)); + + boolean changed = false; + for (Map.Entry<String, String> e : mappings.entrySet()) { + final Object value = sourceAttributes.get(e.getValue()); + changed = Objects.equals(proxyBuilder.putValue(e.getKey(), value), value) || changed; + } + if (changed) { + ofType.set(actualIndex, proxyBuilder.createAnnotation()); + } + }); + // now write the results back into the result array: + constraints.values().stream().flatMap(Collection::stream).toArray(n -> result); + } + return result; + } + } + + public static Map<String, Object> readAttributes(Annotation a) { + final Lazy<Map<String, Object>> result = new Lazy<>(LinkedHashMap::new); + + Stream.of(Reflection.getDeclaredMethods(a.annotationType())).filter(m -> m.getParameterCount() == 0) + .forEach(m -> { + final boolean mustUnset = Reflection.setAccessible(m, true); + try { + result.get().put(m.getName(), m.invoke(a)); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new ValidationException("Caught exception reading attributes of " + a, e); + } finally { + if (mustUnset) { + Reflection.setAccessible(m, false); + } + } + }); + return result.optional().map(Collections::unmodifiableMap).orElseGet(Collections::emptyMap); + } + + /** + * Meta-annotation aware. + * + * @param e + * @param t + * @return {@code boolean} + * @see AnnotatedElement#isAnnotationPresent(Class) + */ + public static boolean isAnnotationPresent(AnnotatedElement e, Class<? extends Annotation> t) { + if (e.isAnnotationPresent(t)) { + return true; + } + return Stream.of(e.getAnnotations()).map(Annotation::annotationType).anyMatch(a -> isAnnotationPresent(a, t)); + } + + /** + * Get declared annotations with a particular meta-annotation. + * + * @param e + * @param meta + * @return {@link Annotation}[] + */ + public static Annotation[] getDeclared(AnnotatedElement e, Class<? extends Annotation> meta) { + return Stream.of(e.getDeclaredAnnotations()).filter(ann -> isAnnotationPresent(ann.annotationType(), meta)) + .toArray(Annotation[]::new); + } + + /** + * Accounts for {@link Constraint} meta-annotation AND {@link Repeatable} constraint annotations. + * + * @param e + * @return Annotation[] + */ + public static Annotation[] getDeclaredConstraints(AnnotatedElement e) { + return Stream.of(e.getDeclaredAnnotations()).flatMap((Function<Annotation, Stream<Annotation>>) a -> { + final ConstraintAnnotationAttributes.Worker<? extends Annotation> analyzer = + ConstraintAnnotationAttributes.VALUE.analyze(a.annotationType()); + if (analyzer.isValid()) { + return Stream.of(analyzer.<Annotation[]> read(a)); + } + return Stream.of(a); + }).filter(a -> a.annotationType().isAnnotationPresent(Constraint.class)).toArray(Annotation[]::new); + } + + public static boolean declaresAttribute(Class<? extends Annotation> annotationType, String name) { + try { + annotationType.getDeclaredMethod(name); + return true; + } catch (NoSuchMethodException | SecurityException e) { + return false; + } + } + + private final ApacheValidatorFactory validatorFactory; + private final LRUCache<Class<? extends Annotation>, Composition> compositions; + + public AnnotationsManager(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory); + final String cacheSize = + validatorFactory.getProperties().get(ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE); + try { + compositions = new LRUCache<>(Integer.parseInt(cacheSize)); + } catch (NumberFormatException e) { + throw new IllegalStateException(String.format("Cannot parse value %s for configuration property %s", + cacheSize, ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE)); + } + } + + /** + * Retrieve the composing constraints for the specified constraint {@link Annotation}. + * + * @param a + * @return {@link Annotation}[] + */ + public Annotation[] getComposingConstraints(Annotation a) { + return compositions.computeIfAbsent(a.annotationType(), this::getComposition).getComponents(a); + } + + public Set<ValidationTarget> supportedTargets(Class<? extends Annotation> constraintType) { + return validatorFactory.getConstraintsCache().getConstraintValidatorInfo(constraintType) + .orElseGet(Collections::emptySet).stream().map(ConstraintValidatorInfo::getSupportedTargets) + .flatMap(Collection::stream).collect(Collectors.toSet()); + } + + private Composition getComposition(Class<? extends Annotation> annotationType) { + final Set<ValidationTarget> composedTargets = supportedTargets(annotationType); + final Composition result = new Composition(annotationType); + Stream.of(result.components).map(Annotation::annotationType).forEach(at -> { + final Set<ValidationTarget> composingTargets = supportedTargets(at); + if (Collections.disjoint(composingTargets, composedTargets)) { + throw new ConstraintDefinitionException( + String.format("Attempt to compose %s of %s but validator types are incompatible", + annotationType.getName(), at.getName())); + } + }); + return result; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java new file mode 100644 index 0000000..c0cff10 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java @@ -0,0 +1,77 @@ +/* + * 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.util; + +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeContextBuilder; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + +public class ContainerElementNodeBuilderCustomizableContextImpl + implements ContainerElementNodeBuilderCustomizableContext { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; + private NodeImpl node; + + public ContainerElementNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl<?> context, String template, + PathImpl path, String name, Class<?> containerType, Integer typeArgumentIndex) { + super(); + this.context = context; + this.path = path; + this.template = template; + this.node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + } + + @Override + public ContainerElementNodeContextBuilder inIterable() { + node.setInIterable(true); + return new ContainerElementNodeContextBuilderImpl(context, template, path, node); + } + + @Override + public NodeBuilderCustomizableContext addPropertyNode(String name) { + path.addNode(node); + return new NodeBuilderCustomizableContextImpl(context, template, path, name); + } + + @Override + public LeafNodeBuilderCustomizableContext addBeanNode() { + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + path.addNode(node); + node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + return this; + } + + @Override + public ConstraintValidatorContext addConstraintViolation() { + context.addError(template, path); + return context; + } + +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java new file mode 100644 index 0000000..6077d87 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java @@ -0,0 +1,65 @@ +/* + * 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.util; + +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderDefinedContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + +public class ContainerElementNodeBuilderDefinedContextImpl implements ContainerElementNodeBuilderDefinedContext { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; + + ContainerElementNodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl<?> context, String template, + PathImpl path) { + super(); + this.context = context; + this.template = template; + this.path = path; + } + + @Override + public NodeBuilderCustomizableContext addPropertyNode(String name) { + return new NodeBuilderCustomizableContextImpl(context, template, path, name); + } + + @Override + public LeafNodeBuilderCustomizableContext addBeanNode() { + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + return new ContainerElementNodeBuilderCustomizableContextImpl(context, name, path, name, containerType, + typeArgumentIndex); + } + + @Override + public ConstraintValidatorContext addConstraintViolation() { + context.addError(template, path); + return context; + } + +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java new file mode 100644 index 0000000..f05ef76 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.bval.jsr.util; + +import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderDefinedContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeContextBuilder; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + +public class ContainerElementNodeContextBuilderImpl implements ContainerElementNodeContextBuilder { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; + private final NodeImpl node; + + ContainerElementNodeContextBuilderImpl(ConstraintValidatorContextImpl<?> context, String template, + PathImpl path, NodeImpl node) { + super(); + this.context = context; + this.template = template; + this.path = path; + this.node = node; + } + + @Override + public ContainerElementNodeBuilderDefinedContext atKey(Object key) { + node.setKey(key); + path.addNode(node); + return new ContainerElementNodeBuilderDefinedContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderDefinedContext atIndex(Integer index) { + node.setIndex(index); + path.addNode(node); + return new ContainerElementNodeBuilderDefinedContextImpl(context, template, path); + } + + @Override + public NodeBuilderCustomizableContext addPropertyNode(String name) { + path.addNode(node); + return new NodeBuilderCustomizableContextImpl(context, name, path, name); + } + + @Override + public LeafNodeBuilderCustomizableContext addBeanNode() { + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + path.addNode(node); + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); + } + + @Override + public ConstraintValidatorContext addConstraintViolation() { + context.addError(template, path); + return context; + } + +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java new file mode 100644 index 0000000..f847424 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java @@ -0,0 +1,65 @@ +/* + * 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.util; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public class Exceptions { + + public static <E extends Exception> E create(Function<String, E> fn, String format, Object... args) { + return fn.apply(String.format(format, args)); + } + + public static <E extends Exception, C extends Throwable> E create(BiFunction<String, ? super C, E> fn, C cause, + String format, Object... args) { + return fn.apply(String.format(format, args), cause); + } + + public static <E extends Exception> void raise(Function<String, E> fn, String format, Object... args) throws E { + throw create(fn, format, args); + } + + public static <E extends Exception> void raiseIf(boolean condition, Function<String, E> fn, String format, + Object... args) throws E { + if (condition) { + raise(fn, format, args); + } + } + + public static <E extends Exception> void raiseUnless(boolean condition, Function<String, E> fn, String format, + Object... args) throws E { + raiseIf(!condition, fn, format, args); + } + + public static <E extends Exception, C extends Throwable> void raise(BiFunction<String, ? super C, E> fn, C cause, + String format, Object... args) throws E { + throw create(fn, cause, format, args); + } + + public static <E extends Exception, C extends Throwable> void raiseIf(boolean condition, + BiFunction<String, ? super C, E> fn, C cause, String format, Object... args) throws E { + if (condition) { + raise(fn, cause, format, args); + } + } + + public static <E extends Exception, C extends Throwable> void raiseUnless(boolean condition, + BiFunction<String, ? super C, E> fn, C cause, String format, Object... args) throws E { + raiseIf(!condition, fn, cause, format, args); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java new file mode 100644 index 0000000..3434017 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java @@ -0,0 +1,40 @@ +/* + * 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.util; + +import java.util.LinkedHashMap; + +public class LRUCache<K, V> extends LinkedHashMap<K, V> { + private static final long serialVersionUID = 1L; + + private final int maximumCapacity; + + public LRUCache(int maximumCapacity) { + super(16, 0.75f, true); + if (maximumCapacity < 1) { + throw new IllegalArgumentException("maximumCapacity must be > 0"); + } + this.maximumCapacity = maximumCapacity; + } + + @Override + protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { + return super.removeEldestEntry(eldest) || size() >= maximumCapacity; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java index efa9aeb..99305be 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java @@ -18,9 +18,10 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext; import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderDefinedContext; import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeContextBuilder; @@ -43,8 +44,7 @@ public class LeafNodeBuilderCustomizableContextImpl } @Override - public LeafNodeBuilderDefinedContext atIndex( - Integer index) { + public LeafNodeBuilderDefinedContext atIndex(Integer index) { node.setIndex(index); return definedContext; } @@ -55,16 +55,16 @@ public class LeafNodeBuilderCustomizableContextImpl } } - private final ConstraintValidatorContextImpl context; + private final ConstraintValidatorContextImpl<?> context; private final PathImpl path; private final String template; private final NodeImpl node; - public LeafNodeBuilderCustomizableContextImpl(final ConstraintValidatorContextImpl parent, String messageTemplate, - PathImpl propertyPath) { - context = parent; - template = messageTemplate; - path = propertyPath; + public LeafNodeBuilderCustomizableContextImpl(final ConstraintValidatorContextImpl<?> context, String template, + PathImpl path) { + this.context = context; + this.template = template; + this.path = path; node = new NodeImpl.BeanNodeImpl(); } @@ -81,4 +81,9 @@ public class LeafNodeBuilderCustomizableContextImpl return context; } + @Override + public LeafNodeBuilderCustomizableContext inContainer(Class<?> containerType, Integer typeArgumentIndex) { + node.inContainer(containerType, typeArgumentIndex); + return this; + } } http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java new file mode 100644 index 0000000..9f98311 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import java.beans.Introspector; +import java.lang.reflect.Method; + +import org.apache.bval.util.Validate; + +public final class Methods { + public static boolean isGetter(Method m) { + if (m.getParameterCount() > 0) { + return false; + } + // TODO look for capital letter after verb? + if (Boolean.TYPE.equals(m.getReturnType()) && m.getName().startsWith("is")) { + return true; + } + return !Void.TYPE.equals(m.getReturnType()) && m.getName().startsWith("get"); + } + + public static String propertyName(Method getter) { + Validate.isTrue(isGetter(getter), "%s is not a getter", getter); + final String name = getter.getName(); + final String suffix = name.startsWith("is") ? name.substring(2) : name.substring(3); + return Introspector.decapitalize(suffix); + } + + private Methods() { + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java index ca058fc..6ec977c 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java @@ -18,38 +18,40 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; - import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext; import javax.validation.ElementKind; +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; + /** * Description: implementation of {@link javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext}.<br/> */ public final class NodeBuilderCustomizableContextImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext { - private final ConstraintValidatorContextImpl parent; - private final String messageTemplate; - private final PathImpl propertyPath; + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; private NodeImpl node; /** * Create a new NodeBuilderCustomizableContextImpl instance. - * @param contextImpl + * @param context * @param template * @param path * @param name */ - public NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl contextImpl, String template, PathImpl path, + public NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl<?> context, String template, PathImpl path, String name) { - parent = contextImpl; - messageTemplate = template; - propertyPath = path; + this.context = context; + this.template = template; + this.path = path; - if (propertyPath.isRootPath() || propertyPath.getLeafNode().getKind() != null) { + if (path.isRootPath() || path.getLeafNode().getKind() != null) { node = new NodeImpl.PropertyNodeImpl(name); } else { - node = propertyPath.removeLeafNode(); + node = path.removeLeafNode(); node.setName(name); node.setKind(ElementKind.PROPERTY); // enforce it } @@ -61,7 +63,7 @@ public final class NodeBuilderCustomizableContextImpl @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder inIterable() { node.setInIterable(true); - return new NodeContextBuilderImpl(parent, messageTemplate, propertyPath, node); + return new NodeContextBuilderImpl(context, template, path, node); } /** @@ -75,15 +77,15 @@ public final class NodeBuilderCustomizableContextImpl @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext addPropertyNode( String name) { - propertyPath.addNode(node); + path.addNode(node); node = new NodeImpl.PropertyNodeImpl(name); return this; } @Override public ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext addBeanNode() { - propertyPath.addNode(node); - return new LeafNodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); } /** @@ -91,10 +93,25 @@ public final class NodeBuilderCustomizableContextImpl */ @Override public ConstraintValidatorContext addConstraintViolation() { - propertyPath.addNode(node); + path.addNode(node); node = null; - parent.addError(messageTemplate, propertyPath); - return parent; + context.addError(template, path); + return context; + } + + @Override + public NodeBuilderCustomizableContext inContainer(Class<?> containerClass, Integer typeArgumentIndex) { + path.getLeafNode().inContainer(containerClass, typeArgumentIndex); + return this; + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + path.addNode(node); + node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); } } http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java index 5ce20b5..f695e84 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java @@ -18,18 +18,19 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; - import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; + +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; /** * Description: Implementation of {@link NodeBuilderDefinedContext}.<br/> */ public final class NodeBuilderDefinedContextImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext { - private final ConstraintValidatorContextImpl parent; - private final String messageTemplate; - private final PathImpl propertyPath; + private final ConstraintValidatorContextImpl context; + private final String template; + private final PathImpl path; /** * Create a new NodeBuilderDefinedContextImpl instance. @@ -38,9 +39,9 @@ public final class NodeBuilderDefinedContextImpl * @param path */ public NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl contextImpl, String template, PathImpl path) { - parent = contextImpl; - messageTemplate = template; - propertyPath = path; + this.context = contextImpl; + this.template = template; + this.path = path; } /** @@ -54,12 +55,12 @@ public final class NodeBuilderDefinedContextImpl @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext addPropertyNode( String name) { - return new NodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath, name); + return new NodeBuilderCustomizableContextImpl(context, template, path, name); } @Override public ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext addBeanNode() { - return new LeafNodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); } /** @@ -67,7 +68,14 @@ public final class NodeBuilderDefinedContextImpl */ @Override public ConstraintValidatorContext addConstraintViolation() { - parent.addError(messageTemplate, propertyPath); - return parent; + context.addError(template, path); + return context; + } + + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); } } http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java index 80e5b8f..f32db9f 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java @@ -18,18 +18,19 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder; /** * Description: Implementation of {@link NodeContextBuilder}.<br/> */ -final class NodeContextBuilderImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder { - private final ConstraintValidatorContextImpl parent; - private final String messageTemplate; - private final PathImpl propertyPath; +public final class NodeContextBuilderImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; // The name of the last "added" node, it will only be added if it has a non-null name // The actual incorporation in the path will take place when the definition of the current leaf node is complete private final NodeImpl node; @@ -40,10 +41,10 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra * @param template * @param path */ - NodeContextBuilderImpl(ConstraintValidatorContextImpl contextImpl, String template, PathImpl path, NodeImpl node) { - parent = contextImpl; - messageTemplate = template; - propertyPath = path; + NodeContextBuilderImpl(ConstraintValidatorContextImpl<?> contextImpl, String template, PathImpl path, NodeImpl node) { + this.context = contextImpl; + this.template = template; + this.path = path; this.node = node; } @@ -53,8 +54,8 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext atKey(Object key) { node.setKey(key); - propertyPath.addNode(node); - return new NodeBuilderDefinedContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new NodeBuilderDefinedContextImpl(context, template, path); } /** @@ -63,8 +64,8 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext atIndex(Integer index) { node.setIndex(index); - propertyPath.addNode(node); - return new NodeBuilderDefinedContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new NodeBuilderDefinedContextImpl(context, template, path); } /** @@ -78,14 +79,14 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext addPropertyNode( String name) { - propertyPath.addNode(node); - return new NodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath, name); + path.addNode(node); + return new NodeBuilderCustomizableContextImpl(context, template, path, name); } @Override public ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext addBeanNode() { - propertyPath.addNode(node); - return new LeafNodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); } /** @@ -93,9 +94,18 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra */ @Override public ConstraintValidatorContext addConstraintViolation() { - propertyPath.addNode(node); - parent.addError(messageTemplate, propertyPath); - return parent; + path.addNode(node); + context.addError(template, path); + return context; } -} \ No newline at end of file + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + path.addNode(node); + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); + } + +} http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java index 5a08f0e..fcdc4cc 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java @@ -21,16 +21,23 @@ package org.apache.bval.jsr.util; import javax.validation.ElementKind; import javax.validation.Path; import javax.validation.Path.Node; + import java.io.Serializable; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; public class NodeImpl implements Path.Node, Serializable { private static final long serialVersionUID = 1L; private static final String INDEX_OPEN = "["; private static final String INDEX_CLOSE = "]"; - private List<Class<?>> parameterTypes; + + private static <T extends Path.Node> Optional<T> optional(Class<T> type, Object o) { + return Optional.ofNullable(o).filter(type::isInstance).map(type::cast); + } /** * Append a Node to the specified StringBuilder. @@ -85,6 +92,9 @@ public class NodeImpl implements Path.Node, Serializable { private int parameterIndex; private Object key; private ElementKind kind; + private List<Class<?>> parameterTypes; + private Class<?> containerType; + private Integer typeArgumentIndex; /** * Create a new NodeImpl instance. @@ -99,13 +109,18 @@ public class NodeImpl implements Path.Node, Serializable { * @param node */ NodeImpl(Path.Node node) { - this.name = node.getName(); + this(node.getName()); this.inIterable = node.isInIterable(); this.index = node.getIndex(); this.key = node.getKey(); this.kind = node.getKind(); } + <T extends Path.Node> NodeImpl(Path.Node node, Class<T> nodeType, Consumer<T> handler) { + this(node); + Optional.of(node).filter(nodeType::isInstance).map(nodeType::cast).ifPresent(handler); + } + private NodeImpl() { } @@ -213,29 +228,14 @@ public class NodeImpl implements Path.Node, Serializable { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || !getClass().equals(o.getClass())) { return false; } NodeImpl node = (NodeImpl) o; - if (inIterable != node.inIterable) { - return false; - } - if (index != null ? !index.equals(node.index) : node.index != null) { - return false; - } - if (key != null ? !key.equals(node.key) : node.key != null) { - return false; - } - if (name != null ? !name.equals(node.name) : node.name != null) { - return false; - } - if (kind != null ? !kind.equals(node.kind) : node.kind != null) { - return false; - } - - return true; + return inIterable == node.inIterable && Objects.equals(index, node.index) && Objects.equals(key, node.key) + && Objects.equals(name, node.name) && kind == node.kind; } /** @@ -243,12 +243,7 @@ public class NodeImpl implements Path.Node, Serializable { */ @Override public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (inIterable ? 1 : 0); - result = 31 * result + (index != null ? index.hashCode() : 0); - result = 31 * result + (key != null ? key.hashCode() : 0); - result = 31 * result + (kind != null ? kind.hashCode() : 0); - return result; + return Objects.hash(name, Boolean.valueOf(inIterable), index, key, kind); } public int getParameterIndex() { @@ -263,12 +258,24 @@ public class NodeImpl implements Path.Node, Serializable { this.parameterTypes = parameterTypes; } + public Class<?> getContainerClass() { + return containerType; + } + + public Integer getTypeArgumentIndex() { + return typeArgumentIndex; + } + + public void inContainer(Class<?> containerType, Integer typeArgumentIndex) { + this.containerType = containerType; + this.typeArgumentIndex = typeArgumentIndex; + } + + @SuppressWarnings("serial") public static class ParameterNodeImpl extends NodeImpl implements Path.ParameterNode { public ParameterNodeImpl(final Node cast) { super(cast); - if (ParameterNodeImpl.class.isInstance(cast)) { - setParameterIndex(ParameterNodeImpl.class.cast(cast).getParameterIndex()); - } + optional(Path.ParameterNode.class, cast).ifPresent(n -> setParameterIndex(n.getParameterIndex())); } public ParameterNodeImpl(final String name, final int idx) { @@ -282,12 +289,11 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class ConstructorNodeImpl extends NodeImpl implements Path.ConstructorNode { public ConstructorNodeImpl(final Node cast) { super(cast); - if (NodeImpl.class.isInstance(cast)) { - setParameterTypes(NodeImpl.class.cast(cast).parameterTypes); - } + optional(Path.ConstructorNode.class, cast).ifPresent(n -> setParameterTypes(n.getParameterTypes())); } public ConstructorNodeImpl(final String simpleName, List<Class<?>> paramTypes) { @@ -301,6 +307,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class CrossParameterNodeImpl extends NodeImpl implements Path.CrossParameterNode { public CrossParameterNodeImpl() { super("<cross-parameter>"); @@ -316,12 +323,11 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class MethodNodeImpl extends NodeImpl implements Path.MethodNode { public MethodNodeImpl(final Node cast) { super(cast); - if (MethodNodeImpl.class.isInstance(cast)) { - setParameterTypes(MethodNodeImpl.class.cast(cast).getParameterTypes()); - } + optional(Path.MethodNode.class, cast).ifPresent(n -> setParameterTypes(n.getParameterTypes())); } public MethodNodeImpl(final String name, final List<Class<?>> classes) { @@ -335,6 +341,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class ReturnValueNodeImpl extends NodeImpl implements Path.ReturnValueNode { public ReturnValueNodeImpl(final Node cast) { super(cast); @@ -350,6 +357,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class PropertyNodeImpl extends NodeImpl implements Path.PropertyNode { public PropertyNodeImpl(final String name) { super(name); @@ -357,6 +365,8 @@ public class NodeImpl implements Path.Node, Serializable { public PropertyNodeImpl(final Node cast) { super(cast); + optional(Path.PropertyNode.class, cast) + .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex())); } @Override @@ -365,6 +375,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class BeanNodeImpl extends NodeImpl implements Path.BeanNode { public BeanNodeImpl() { // no-op @@ -372,6 +383,8 @@ public class NodeImpl implements Path.Node, Serializable { public BeanNodeImpl(final Node cast) { super(cast); + optional(Path.BeanNode.class, cast) + .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex())); } @Override @@ -379,4 +392,24 @@ public class NodeImpl implements Path.Node, Serializable { return ElementKind.BEAN; } } + + @SuppressWarnings("serial") + public static class ContainerElementNodeImpl extends NodeImpl implements Path.ContainerElementNode { + + public ContainerElementNodeImpl(String name, Class<?> containerType, Integer typeArgumentIndex) { + super(name); + inContainer(containerType, typeArgumentIndex); + } + + public ContainerElementNodeImpl(final Node cast) { + super(cast); + optional(Path.ContainerElementNode.class, cast) + .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex())); + } + + @Override + public ElementKind getKind() { + return ElementKind.CONTAINER_ELEMENT; + } + } }
