Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,169 @@ +/* + * 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.xml; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.apache.bval.jsr.metadata.Signature; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.ObjectUtils; +import org.apache.bval.util.StringUtils; +import org.apache.bval.util.reflection.Reflection; + +/** + * Description: <br/> + * InvocationHandler implementation of <code>Annotation</code> that pretends it is a "real" source code annotation. + * <p/> + */ +class AnnotationProxy implements Annotation, InvocationHandler, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 1L; + + private Signature EQUALS = new Signature("equals", Object.class); + + private final Class<? extends Annotation> annotationType; + private final SortedMap<String, Object> values; + + /** + * Create a new AnnotationProxy instance. + * + * @param <A> + * @param descriptor + */ + <A extends Annotation> AnnotationProxy(AnnotationProxyBuilder<A> descriptor) { + this.annotationType = descriptor.getType(); + values = new TreeMap<>(); + int processedValuesFromDescriptor = 0; + for (final Method m : descriptor.getMethods()) { + if (descriptor.contains(m.getName())) { + values.put(m.getName(), descriptor.getValue(m.getName())); + processedValuesFromDescriptor++; + } else { + if (m.getDefaultValue() == null) { + Exceptions.raise(IllegalArgumentException::new, "No value provided for %s", m.getName()); + } + values.put(m.getName(), m.getDefaultValue()); + } + } + Exceptions.raiseUnless(processedValuesFromDescriptor == descriptor.size() || Valid.class.equals(annotationType), + IllegalArgumentException::new, "Trying to instantiate %s with unknown parameters.", + f -> f.args(annotationType.getName())); + } + + /** + * {@inheritDoc} + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (values.containsKey(method.getName())) { + return values.get(method.getName()); + } + if (EQUALS.equals(Signature.of(method))) { + return equalTo(args[0]); + } + return method.invoke(this, args); + } + + /** + * {@inheritDoc} + */ + @Override + public Class<? extends Annotation> annotationType() { + return annotationType; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return values.entrySet().stream() + .map(e -> String.format("%s=%s", e.getKey(), StringUtils.valueOf(e.getValue()))) + .collect(Collectors.joining(", ", String.format("@%s(", annotationType().getName()), ")")); + } + + @Override + public int hashCode() { + return values.entrySet().stream().mapToInt(e -> { + return (127 * e.getKey().hashCode()) ^ ObjectUtils.hashCode(e.getValue()); + }).sum(); + } + + private boolean equalTo(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Annotation) { + final Annotation other = (Annotation) obj; + return other.annotationType().equals(annotationType) + && values.entrySet().stream().allMatch(e -> memberEquals(other, e.getKey(), e.getValue())); + } + return false; + } + + private boolean memberEquals(Annotation other, String name, Object value) { + final Method member = Reflection.getDeclaredMethod(annotationType, name); + final Object otherValue; + try { + otherValue = member.invoke(other); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new IllegalStateException(e); + } + Exceptions.raiseIf(otherValue == null || !otherValue.getClass().equals(value.getClass()), + IllegalStateException::new, "Unexpected value %s for member %s of %s", otherValue, name, other); + + if (value instanceof Object[]) { + return Arrays.equals((Object[]) value, (Object[]) otherValue); + } + if (value instanceof byte[]) { + return Arrays.equals((byte[]) value, (byte[]) otherValue); + } + if (value instanceof short[]) { + return Arrays.equals((short[]) value, (short[]) otherValue); + } + if (value instanceof int[]) { + return Arrays.equals((int[]) value, (int[]) otherValue); + } + if (value instanceof char[]) { + return Arrays.equals((char[]) value, (char[]) otherValue); + } + if (value instanceof long[]) { + return Arrays.equals((long[]) value, (long[]) otherValue); + } + if (value instanceof float[]) { + return Arrays.equals((float[]) value, (float[]) otherValue); + } + if (value instanceof double[]) { + return Arrays.equals((double[]) value, (double[]) otherValue); + } + if (value instanceof boolean[]) { + return Arrays.equals((boolean[]) value, (boolean[]) otherValue); + } + return value.equals(otherValue); + } +}
Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,268 @@ +/* + * 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.xml; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.enterprise.util.AnnotationLiteral; +import javax.validation.ConstraintTarget; +import javax.validation.Payload; +import javax.validation.Valid; +import javax.validation.ValidationException; +import javax.validation.groups.ConvertGroup; + +import org.apache.bval.cdi.EmptyAnnotationLiteral; +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.util.AnnotationsManager; +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privileged; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +/** + * Description: Holds the information and creates an annotation proxy during xml + * parsing of validation mapping constraints. <br/> + */ +@Privilizing(@CallTo(Reflection.class)) +public final class AnnotationProxyBuilder<A extends Annotation> { + private static final ConcurrentMap<Class<?>, Method[]> METHODS_CACHE = new ConcurrentHashMap<>(); + + public static <A> Method[] findMethods(final Class<A> annotationType) { + // cache only built-in constraints to avoid memory leaks: + // TODO use configurable cache size property? + if (annotationType.getName().startsWith("javax.validation.constraints.")) { + return METHODS_CACHE.computeIfAbsent(annotationType, Reflection::getDeclaredMethods); + } + return Reflection.getDeclaredMethods(annotationType); + } + + private final Class<A> type; + private final Map<String, Object> elements = new HashMap<>(); + private final Method[] methods; + private boolean changed; + + /** + * Create a new AnnotationProxyBuilder instance. + * + * @param annotationType + */ + public AnnotationProxyBuilder(final Class<A> annotationType) { + this.type = annotationType; + this.methods = findMethods(annotationType); + } + + /** + * Create a new AnnotationProxyBuilder instance. + * + * @param annotationType + * @param elements + */ + public AnnotationProxyBuilder(Class<A> annotationType, Map<String, Object> elements) { + this(annotationType); + elements.forEach(this.elements::put); + } + + /** + * Create a builder initially configured to create an annotation equivalent + * to {@code annot}. + * + * @param annot + * Annotation to be replicated. + */ + @SuppressWarnings("unchecked") + public AnnotationProxyBuilder(A annot) { + this((Class<A>) annot.annotationType()); + elements.putAll(AnnotationsManager.readAttributes(annot)); + } + + public Method[] getMethods() { + return methods; + } + + /** + * Add an element to the configuration. + * + * @param elementName + * @param value + */ + @Deprecated + public Object putValue(String elementName, Object value) { + return elements.put(elementName, value); + } + + /** + * Add an element to the configuration. + * + * @param elementName + * @param value + * @return whether any change occurred + */ + public boolean setValue(String elementName, Object value) { + final boolean result = !Objects.equals(elements.put(elementName, value), value); + changed |= result; + return result; + } + + /** + * Get the specified element value from the current configuration. + * + * @param elementName + * @return Object value + */ + public Object getValue(String elementName) { + return elements.get(elementName); + } + + /** + * Learn whether a given element has been configured. + * + * @param elementName + * @return <code>true</code> if an <code>elementName</code> element is found + * on this annotation + */ + public boolean contains(String elementName) { + return elements.containsKey(elementName); + } + + /** + * Get the number of configured elements. + * + * @return int + */ + public int size() { + return elements.size(); + } + + /** + * Get the configured Annotation type. + * + * @return Class<A> + */ + public Class<A> getType() { + return type; + } + + /** + * Configure the well-known JSR303 "message" element. + * + * @param message + * @return + */ + public boolean setMessage(String message) { + return setValue(ConstraintAnnotationAttributes.MESSAGE.getAttributeName(), message); + } + + /** + * Configure the well-known JSR303 "groups" element. + * + * @param groups + * @return + */ + public boolean setGroups(Class<?>[] groups) { + return setValue(ConstraintAnnotationAttributes.GROUPS.getAttributeName(), groups); + } + + /** + * Configure the well-known JSR303 "payload" element. + * + * @param payload + * @return + */ + public boolean setPayload(Class<? extends Payload>[] payload) { + return setValue(ConstraintAnnotationAttributes.PAYLOAD.getAttributeName(), payload); + } + + /** + * Configure the well-known "validationAppliesTo" element. + * + * @param constraintTarget + */ + public boolean setValidationAppliesTo(ConstraintTarget constraintTarget) { + return setValue(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName(), constraintTarget); + } + + public boolean isChanged() { + return changed; + } + + /** + * Create the annotation represented by this builder. + * + * @return {@link Annotation} + */ + public A createAnnotation() { + final ClassLoader classLoader = Reflection.getClassLoader(getType()); + @SuppressWarnings("unchecked") + final Class<A> proxyClass = (Class<A>) Proxy.getProxyClass(classLoader, getType()); + return doCreateAnnotation(proxyClass, new AnnotationProxy(this)); + } + + @Privileged + private A doCreateAnnotation(final Class<A> proxyClass, final InvocationHandler handler) { + try { + final Constructor<A> constructor = proxyClass.getConstructor(InvocationHandler.class); + final boolean mustUnset = Reflection.setAccessible(constructor, true); // java + // 8 + try { + return constructor.newInstance(handler); + } finally { + if (mustUnset) { + Reflection.setAccessible(constructor, false); + } + } + } catch (Exception e) { + throw new ValidationException("Unable to create annotation for configured constraint", e); + } + } + + public static final class ValidAnnotation extends EmptyAnnotationLiteral<Valid> implements Valid { + private static final long serialVersionUID = 1L; + + public static final ValidAnnotation INSTANCE = new ValidAnnotation(); + } + + public static final class ConvertGroupAnnotation extends AnnotationLiteral<ConvertGroup> implements ConvertGroup { + private static final long serialVersionUID = 1L; + + private final Class<?> from; + private final Class<?> to; + + public ConvertGroupAnnotation(final Class<?> from, final Class<?> to) { + this.from = from; + this.to = to; + } + + @Override + public Class<?> from() { + return from; + } + + @Override + public Class<?> to() { + return to; + } + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/MappingValidator.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.jsr.xml; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.ValidationException; + +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.metadata.ContainerElementKey; +import org.apache.bval.jsr.metadata.Meta; +import org.apache.bval.jsr.metadata.Meta.ForConstructor; +import org.apache.bval.jsr.metadata.Signature; +import org.apache.bval.jsr.util.Methods; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.Reflection; +import org.apache.bval.util.reflection.TypeUtils; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +public class MappingValidator { + private static final Set<ConstraintAnnotationAttributes> RESERVED_CONSTRAINT_ELEMENT_NAMES = Collections + .unmodifiableSet(EnumSet.of(ConstraintAnnotationAttributes.GROUPS, ConstraintAnnotationAttributes.MESSAGE, + ConstraintAnnotationAttributes.PAYLOAD, ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO)); + + private static <T> BinaryOperator<T> enforceUniqueness(String message, Function<? super T, ?> describe) { + return (t, u) -> { + throw Exceptions.create(ValidationException::new, message, describe.apply(t)); + }; + } + + private final ConstraintMappingsType constraintMappings; + private final Function<String, Class<?>> resolveClass; + + public MappingValidator(ConstraintMappingsType constraintMappings, Function<String, Class<?>> resolveClass) { + super(); + this.constraintMappings = Validate.notNull(constraintMappings, "constraintMappings"); + this.resolveClass = Validate.notNull(resolveClass, "resolveClass"); + } + + public void validateMappings() { + constraintMappings.getBean().stream().map(this::applyChecks).collect(Collectors.toMap(Function.identity(), + Function.identity(), enforceUniqueness("Duplicate XML constrained bean %s", Class::getName))); + } + + private Class<?> applyChecks(BeanType bean) { + final Class<?> t = resolveClass.apply(bean.getClazz()); + + final ClassType classType = bean.getClassType(); + if (classType != null) { + constraints(new Meta.ForClass<>(t), classType.getConstraint()); + } + final Set<String> fieldProperties = fieldProperties(t, bean.getField()); + final Set<String> getterProperties = getterProperties(t, bean.getGetter()); + final Set<Signature> methods = methods(t, bean.getMethod()); + @SuppressWarnings("unused") + final Set<Signature> constructors = constructors(t, bean.getConstructor()); + + final Set<String> propertyOverlap = new HashSet<>(fieldProperties); + propertyOverlap.retainAll(getterProperties); + + if (!propertyOverlap.isEmpty()) { + Exceptions.raise(ValidationException::new, + "The following %s properties were specified via XML field and getter: %s", bean.getClazz(), + propertyOverlap); + } + final Set<String> getterMethodOverlap = methods.stream().filter(s -> s.getParameterTypes().length == 0) + .map(Signature::getName).filter(Methods::isGetter).map(Methods::propertyName) + .filter(getterProperties::contains).collect(Collectors.toSet()); + + if (!getterMethodOverlap.isEmpty()) { + Exceptions.raise(ValidationException::new, + "The following %s getters were specified via XML getter and method: %s", bean.getClazz(), + getterMethodOverlap); + } + return t; + } + + private Set<String> fieldProperties(Class<?> t, List<FieldType> fields) { + return fields.stream().peek(f -> { + final Field fld = Reflection.find(t, c -> Reflection.getDeclaredField(c, f.getName())); + if (fld == null) { + Exceptions.raise(ValidationException::new, "Unknown XML constrained field %s of %s", f.getName(), t); + } + final Meta.ForField metaField = new Meta.ForField(fld); + constraints(metaField, f.getConstraint()); + containerElements(metaField, f.getContainerElementType()); + }).collect(Collectors.toMap(FieldType::getName, Function.identity(), + enforceUniqueness("Duplicate XML constrained field %s of " + t, FieldType::getName))).keySet(); + } + + private Set<String> getterProperties(Class<?> t, List<GetterType> getters) { + return getters.stream().peek(g -> { + final Method getter = Methods.getter(t, g.getName()); + if (getter == null) { + Exceptions.raise(ValidationException::new, "Unknown XML constrained getter for property %s of %s", + g.getName(), t); + } + final Meta.ForMethod metaGetter = new Meta.ForMethod(getter); + constraints(metaGetter, g.getConstraint()); + containerElements(metaGetter, g.getContainerElementType()); + }).collect(Collectors.toMap(GetterType::getName, Function.identity(), + enforceUniqueness("Duplicate XML constrained getter %s of " + t, GetterType::getName))).keySet(); + } + + private Set<Signature> methods(Class<?> t, List<MethodType> methods) { + return methods.stream().map(mt -> { + final Class<?>[] parameterTypes = getParameterTypes(mt.getParameter()); + final Signature result = new Signature(mt.getName(), parameterTypes); + final Method m = Reflection.find(t, c -> Reflection.getDeclaredMethod(c, mt.getName(), parameterTypes)); + Exceptions.raiseIf(m == null, ValidationException::new, "Unknown method %s of %s", result, t); + + Optional.of(mt).map(MethodType::getReturnValue).ifPresent(rv -> { + final Meta.ForMethod metaMethod = new Meta.ForMethod(m); + constraints(metaMethod, rv.getConstraint()); + containerElements(metaMethod, rv.getContainerElementType()); + }); + final Parameter[] params = m.getParameters(); + + IntStream.range(0, parameterTypes.length).forEach(n -> { + final Meta.ForParameter metaParam = new Meta.ForParameter(params[n], params[n].getName()); + final ParameterType parameterType = mt.getParameter().get(n); + constraints(metaParam, parameterType.getConstraint()); + containerElements(metaParam, parameterType.getContainerElementType()); + }); + + return result; + }).collect(Collectors.toSet()); + } + + private Set<Signature> constructors(Class<?> t, List<ConstructorType> ctors) { + return ctors.stream().map(ctor -> { + final Class<?>[] parameterTypes = getParameterTypes(ctor.getParameter()); + final Signature result = new Signature(t.getSimpleName(), parameterTypes); + final Constructor<?> dc = Reflection.getDeclaredConstructor(t, parameterTypes); + Exceptions.raiseIf(dc == null, ValidationException::new, "Unknown %s constructor %s", t, result); + + Optional.of(ctor).map(ConstructorType::getReturnValue).ifPresent(rv -> { + final ForConstructor<?> metaCtor = new Meta.ForConstructor<>(dc); + constraints(metaCtor, rv.getConstraint()); + containerElements(metaCtor, rv.getContainerElementType()); + }); + final Parameter[] params = dc.getParameters(); + + IntStream.range(0, parameterTypes.length).forEach(n -> { + final Meta.ForParameter metaParam = new Meta.ForParameter(params[n], params[n].getName()); + final ParameterType parameterType = ctor.getParameter().get(n); + constraints(metaParam, parameterType.getConstraint()); + containerElements(metaParam, parameterType.getContainerElementType()); + }); + return result; + }).collect(Collectors.toSet()); + } + + private Class<?>[] getParameterTypes(List<ParameterType> paramElements) { + return paramElements.stream().map(ParameterType::getType).map(resolveClass).toArray(Class[]::new); + } + + private Set<ContainerElementKey> containerElements(Meta<?> meta, + List<ContainerElementTypeType> containerElementTypes) { + if (containerElementTypes.isEmpty()) { + return Collections.emptySet(); + } + final Class<?> containerType = TypeUtils.getRawType(meta.getType(), null); + final int typeParameterCount = containerType.getTypeParameters().length; + if (typeParameterCount == 0) { + Exceptions.raise(ValidationException::new, "Cannot specify container element types for %s", + meta.describeHost()); + } + return containerElementTypes.stream().map(e -> { + Integer typeArgumentIndex = e.getTypeArgumentIndex(); + if (typeArgumentIndex == null) { + if (typeParameterCount > 1) { + Exceptions.raise(ValidationException::new, + "Unable to resolve unspecified type argument index for %s", meta.describeHost()); + } + typeArgumentIndex = Integer.valueOf(0); + } + final ContainerElementKey result = new ContainerElementKey(containerType, typeArgumentIndex); + + final Meta.ForContainerElement elementMeta = new Meta.ForContainerElement(meta, result); + + constraints(elementMeta, e.getConstraint()); + containerElements(elementMeta, e.getContainerElementType()); + + return result; + }).collect(Collectors.toMap(Function.identity(), ContainerElementKey::getTypeArgumentIndex, enforceUniqueness( + "Duplicate XML constrained container element %d of " + meta.describeHost(), Function.identity()))).keySet(); + } + + private void constraints(Meta<?> meta, List<ConstraintType> constraints) { + constraints.forEach(constraint -> { + final Class<?> annotation = resolveClass.apply(constraint.getAnnotation()); + Exceptions.raiseUnless(annotation.isAnnotation(), ValidationException::new, "%s is not an annotation", + annotation); + + final Set<String> missingElements = Stream.of(Reflection.getDeclaredMethods(annotation)) + .filter(m -> m.getParameterCount() == 0 && m.getDefaultValue() == null).map(Method::getName) + .collect(Collectors.toSet()); + + for (final ElementType elementType : constraint.getElement()) { + final String name = elementType.getName(); + if (RESERVED_CONSTRAINT_ELEMENT_NAMES.stream().map(ConstraintAnnotationAttributes::getAttributeName) + .anyMatch(Predicate.isEqual(name))) { + Exceptions.raise(ValidationException::new, "Constraint of %s declares reserved parameter name %s.", + meta.describeHost(), name); + } + missingElements.remove(name); + } + Exceptions.raiseUnless(missingElements.isEmpty(), ValidationException::new, + "Missing required elements of %s: %s", annotation, missingElements); + }); + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,294 @@ +/* + * 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.xml; + +import java.net.URL; +import java.util.Collections; +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.validation.ValidationException; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.UnmarshallerHandler; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.ValidatorHandler; + +import org.apache.bval.util.Exceptions; +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.w3c.dom.Document; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.XMLFilterImpl; + +/** + * Unmarshals XML converging on latest schema version. Presumes backward compatiblity between schemae. + */ +public class SchemaManager { + public static class Builder { + private final SortedMap<Key, Lazy<Schema>> data = new TreeMap<>(); + + public Builder add(String version, String ns, String resource) { + data.put(new Key(version, ns), new Lazy<>(() -> SchemaManager.loadSchema(resource))); + return this; + } + + public SchemaManager build() { + return new SchemaManager(new TreeMap<>(data)); + } + } + + private static class Key implements Comparable<Key> { + private static final Comparator<Key> CMP = Comparator.comparing(Key::getVersion).thenComparing(Key::getNs); + + final String version; + final String ns; + + Key(String version, String ns) { + super(); + Validate.isTrue(StringUtils.isNotBlank(version), "version cannot be null/empty/blank"); + this.version = version; + Validate.isTrue(StringUtils.isNotBlank(ns), "ns cannot be null/empty/blank"); + this.ns = ns; + } + + public String getVersion() { + return version; + } + + public String getNs() { + return ns; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + return Optional.ofNullable(obj).filter(SchemaManager.Key.class::isInstance) + .map(SchemaManager.Key.class::cast) + .filter(k -> Objects.equals(this.version, k.version) && Objects.equals(this.ns, k.ns)).isPresent(); + } + + @Override + public int hashCode() { + return Objects.hash(version, ns); + } + + @Override + public String toString() { + return String.format("%s:%s", version, ns); + } + + @Override + public int compareTo(Key o) { + return CMP.compare(this, o); + } + } + + private class DynamicValidatorHandler extends XMLFilterImpl { + ContentHandler ch; + SAXParseException e; + + @Override + public void setContentHandler(ContentHandler handler) { + super.setContentHandler(handler); + this.ch = handler; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + if (getContentHandler() == ch) { + final String version = Objects.toString(atts.getValue("version"), data.firstKey().getVersion()); + final Key schemaKey = new Key(version, uri); + Exceptions.raiseUnless(data.containsKey(schemaKey), ValidationException::new, + "Unknown validation schema %s", schemaKey); + + final Schema schema = data.get(schemaKey).get(); + final ValidatorHandler vh = schema.newValidatorHandler(); + vh.startDocument(); + vh.setContentHandler(ch); + super.setContentHandler(vh); + } + try { + super.startElement(uri, localName, qName, atts); + } catch (SAXParseException e) { + this.e = e; + } + } + + @Override + public void error(SAXParseException e) throws SAXException { + this.e = e; + super.error(e); + } + + @Override + public void fatalError(SAXParseException e) throws SAXException { + this.e = e; + super.fatalError(e); + } + + void validate() throws SAXParseException { + if (e != null) { + throw e; + } + } + } + + //@formatter:off + private enum XmlAttributeType { + CDATA, ID, IDREF, IDREFS, NMTOKEN, NMTOKENS, ENTITY, ENTITIES, NOTATION; + //@formatter:on + } + + private class SchemaRewriter extends XMLFilterImpl { + private boolean root = true; + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + final Key schemaKey = + new Key(Objects.toString(atts.getValue("version"), data.firstKey().getVersion()), uri); + + if (!target.equals(schemaKey) && data.containsKey(schemaKey)) { + uri = target.ns; + if (root) { + atts = rewrite(atts); + root = false; + } + } + super.startElement(uri, localName, qName, atts); + } + + private Attributes rewrite(Attributes atts) { + final AttributesImpl result; + if (atts instanceof AttributesImpl) { + result = (AttributesImpl) atts; + } else { + result = new AttributesImpl(atts); + } + set(result, "", VERSION_ATTRIBUTE, "", XmlAttributeType.CDATA, target.version); + return result; + } + + private void set(AttributesImpl attrs, String uri, String localName, String qName, XmlAttributeType type, + String value) { + for (int i = 0, sz = attrs.getLength(); i < sz; i++) { + if (Objects.equals(qName, attrs.getQName(i)) + || Objects.equals(uri, attrs.getURI(i)) && Objects.equals(localName, attrs.getLocalName(i))) { + attrs.setAttribute(i, uri, localName, qName, type.name(), value); + return; + } + } + attrs.addAttribute(uri, localName, qName, type.name(), value); + } + } + + public static final String VERSION_ATTRIBUTE = "version"; + + private static final Logger log = Logger.getLogger(SchemaManager.class.getName()); + private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + private static final SAXParserFactory SAX_PARSER_FACTORY; + + static { + SAX_PARSER_FACTORY = SAXParserFactory.newInstance(); + SAX_PARSER_FACTORY.setNamespaceAware(true); + } + + static Schema loadSchema(String resource) { + final URL schemaUrl = Reflection.getClassLoader(XmlUtils.class).getResource(resource); + try { + return SCHEMA_FACTORY.newSchema(schemaUrl); + } catch (SAXException e) { + log.log(Level.WARNING, String.format("Unable to parse schema: %s", resource), e); + return null; + } + } + + private static Class<?> getObjectFactory(Class<?> type) throws ClassNotFoundException { + final String className = String.format("%s.%s", type.getPackage().getName(), "ObjectFactory"); + return Reflection.toClass(className, type.getClassLoader()); + } + + private final Key target; + private final SortedMap<Key, Lazy<Schema>> data; + private final String description; + + private SchemaManager(SortedMap<Key, Lazy<Schema>> data) { + super(); + this.data = Collections.unmodifiableSortedMap(data); + this.target = data.lastKey(); + this.description = target.ns.substring(target.ns.lastIndexOf('/') + 1); + } + + public Optional<Schema> getSchema(String ns, String version) { + return Optional.of(new Key(version, ns)).map(data::get).map(Lazy::get); + } + + public Optional<Schema> getSchema(Document document) { + return Optional.ofNullable(document).map(Document::getDocumentElement) + .map(e -> getSchema(e.getAttribute(XMLConstants.XMLNS_ATTRIBUTE), e.getAttribute(VERSION_ATTRIBUTE))).get(); + } + + public <E extends Exception> Schema requireSchema(Document document, Function<String, E> exc) throws E { + return getSchema(document).orElseThrow(() -> Objects.requireNonNull(exc, "exc") + .apply(String.format("Unknown %s schema", Objects.toString(description, "")))); + } + + public <T> T unmarshal(InputSource input, Class<T> type) throws Exception { + final XMLReader xmlReader = SAX_PARSER_FACTORY.newSAXParser().getXMLReader(); + + // validate specified schema: + final DynamicValidatorHandler schemaValidator = new DynamicValidatorHandler(); + xmlReader.setContentHandler(schemaValidator); + + // rewrite to latest schema, if required: + final SchemaRewriter schemaRewriter = new SchemaRewriter(); + schemaValidator.setContentHandler(schemaRewriter); + + JAXBContext jc = JAXBContext.newInstance(getObjectFactory(type)); + // unmarshal: + final UnmarshallerHandler unmarshallerHandler = jc.createUnmarshaller().getUnmarshallerHandler(); + schemaRewriter.setContentHandler(unmarshallerHandler); + + xmlReader.parse(input); + schemaValidator.validate(); + + @SuppressWarnings("unchecked") + final JAXBElement<T> result = (JAXBElement<T>) unmarshallerHandler.getResult(); + return result.getValue(); + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationMappingParser.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,159 @@ +/* + * 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.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import javax.validation.ValidationException; +import javax.validation.spi.ConfigurationState; + +import org.apache.bval.jsr.metadata.MetadataBuilder; +import org.apache.bval.jsr.metadata.MetadataBuilder.ForBean; +import org.apache.bval.jsr.metadata.MetadataSource; +import org.apache.bval.jsr.metadata.ValidatorMappingProvider; +import org.apache.bval.jsr.metadata.XmlBuilder; +import org.apache.bval.jsr.metadata.XmlValidationMappingProvider; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; +import org.xml.sax.InputSource; + +/** + * Uses JAXB to parse constraints.xml based on the validation-mapping XML schema. + */ +@Privilizing(@CallTo(Reflection.class)) +public class ValidationMappingParser implements MetadataSource { + private static final SchemaManager SCHEMA_MANAGER = new SchemaManager.Builder() + .add(XmlBuilder.Version.v10.getId(), "http://jboss.org/xml/ns/javax/validation/mapping", + "META-INF/validation-mapping-1.0.xsd") + .add(XmlBuilder.Version.v11.getId(), "http://jboss.org/xml/ns/javax/validation/mapping", + "META-INF/validation-mapping-1.1.xsd") + .add(XmlBuilder.Version.v20.getId(), "http://xmlns.jcp.org/xml/ns/validation/mapping", + "META-INF/validation-mapping-2.0.xsd") + .build(); + + @Override + public void process(ConfigurationState configurationState, + Consumer<ValidatorMappingProvider> addValidatorMappingProvider, BiConsumer<Class<?>, ForBean<?>> addBuilder) { + if (configurationState.isIgnoreXmlConfiguration()) { + return; + } + final Set<Class<?>> beanTypes = new HashSet<>(); + for (final InputStream xmlStream : configurationState.getMappingStreams()) { + final ConstraintMappingsType mapping = parseXmlMappings(xmlStream); + + Optional.of(mapping).map(this::toMappingProvider).ifPresent(addValidatorMappingProvider); + + final Map<Class<?>, MetadataBuilder.ForBean<?>> builders = new XmlBuilder(mapping).forBeans(); + if (Collections.disjoint(beanTypes, builders.keySet())) { + builders.forEach(addBuilder::accept); + beanTypes.addAll(builders.keySet()); + } else { + Exceptions.raise(ValidationException::new, + builders.keySet().stream().filter(beanTypes::contains).map(Class::getName).collect(Collectors + .joining("bean classes specified multiple times for XML validation mapping: [", "; ", "]"))); + } + } + } + + /** + * @param in + * XML stream to parse using the validation-mapping-1.0.xsd + */ + private ConstraintMappingsType parseXmlMappings(final InputStream in) { + try { + return SCHEMA_MANAGER.unmarshal(new InputSource(in), ConstraintMappingsType.class); + } catch (Exception e) { + throw new ValidationException("Failed to parse XML deployment descriptor file.", e); + } finally { + try { + in.reset(); // can be read several times + we ensured it was + // re-readable in addMapping() + } catch (final IOException e) { + // no-op + } + } + } + + private ValidatorMappingProvider toMappingProvider(ConstraintMappingsType mapping) { + if (mapping.getConstraintDefinition().isEmpty()) { + return null; + } + final Map<Class<? extends Annotation>, ValidatedByType> validatorMappings = new HashMap<>(); + + for (ConstraintDefinitionType constraintDefinition : mapping.getConstraintDefinition()) { + final String annotationClassName = constraintDefinition.getAnnotation(); + + final Class<?> clazz = loadClass(annotationClassName, mapping.getDefaultPackage()); + + Exceptions.raiseUnless(clazz.isAnnotation(), ValidationException::new, "%s is not an annotation", + annotationClassName); + + final Class<? extends Annotation> annotationClass = clazz.asSubclass(Annotation.class); + + Exceptions.raiseIf(validatorMappings.containsKey(annotationClass), ValidationException::new, + "XML constraint validator(s) for %s already configured.", annotationClass); + + validatorMappings.put(annotationClass, constraintDefinition.getValidatedBy()); + } + return new XmlValidationMappingProvider(validatorMappings, + cn -> toQualifiedClassName(cn, mapping.getDefaultPackage())); + } + + private Class<?> loadClass(String className, String defaultPackage) { + return loadClass(toQualifiedClassName(className, defaultPackage)); + } + + private String toQualifiedClassName(String className, String defaultPackage) { + if (!isQualifiedClass(className)) { + if (className.startsWith("[L") && className.endsWith(";")) { + className = "[L" + defaultPackage + '.' + className.substring(2); + } else { + className = defaultPackage + '.' + className; + } + } + return className; + } + + private boolean isQualifiedClass(String clazz) { + return clazz.indexOf('.') >= 0; + } + + private Class<?> loadClass(final String className) { + ClassLoader loader = Reflection.getClassLoader(ValidationMappingParser.class); + if (loader == null) { + loader = getClass().getClassLoader(); + } + try { + return Reflection.toClass(className, loader); + } catch (ClassNotFoundException ex) { + throw Exceptions.create(ValidationException::new, ex, "Unable to load class: %s", className); + } + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationParser.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationParser.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationParser.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/ValidationParser.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,149 @@ +/* + * 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.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import javax.validation.BootstrapConfiguration; +import javax.validation.ValidationException; +import javax.validation.executable.ExecutableType; + +import org.apache.bval.jsr.BootstrapConfigurationImpl; +import org.apache.bval.jsr.ConfigurationImpl; +import org.apache.bval.jsr.metadata.XmlBuilder; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privileged; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; +import org.xml.sax.InputSource; + +/** + * Description: uses jaxb to parse validation.xml<br/> + */ +@Privilizing(@CallTo(Reflection.class)) +public class ValidationParser { + + private static final String DEFAULT_VALIDATION_XML_FILE = "META-INF/validation.xml"; + private static final Logger log = Logger.getLogger(ValidationParser.class.getName()); + + private static final SchemaManager SCHEMA_MANAGER = new SchemaManager.Builder() + .add(XmlBuilder.Version.v10.getId(), "http://jboss.org/xml/ns/javax/validation/configuration", + "META-INF/validation-configuration-1.0.xsd") + .add(XmlBuilder.Version.v11.getId(), "http://jboss.org/xml/ns/javax/validation/configuration", + "META-INF/validation-configuration-1.1.xsd") + .add(XmlBuilder.Version.v20.getId(), "http://xmlns.jcp.org/xml/ns/validation/configuration", + "META-INF/validation-configuration-2.0.xsd") + .build(); + + public static String getValidationXmlFile(String file) { + return file == null ? DEFAULT_VALIDATION_XML_FILE : file; + } + + public static BootstrapConfiguration processValidationConfig(final String file, + final ConfigurationImpl targetConfig) { + final ValidationConfigType xmlConfig = parseXmlConfig(file); + if (xmlConfig == null) { + return null; + } + final boolean executableValidationEnabled; + final Set<ExecutableType> defaultValidatedExecutableTypes; + + if (xmlConfig.getExecutableValidation() == null) { + defaultValidatedExecutableTypes = EnumSet.of(ExecutableType.IMPLICIT); + executableValidationEnabled = true; + } else { + final Optional<ExecutableValidationType> executableValidation = + Optional.of(xmlConfig).map(ValidationConfigType::getExecutableValidation); + executableValidationEnabled = executableValidation.map(ExecutableValidationType::getEnabled) + .filter(Predicate.isEqual(Boolean.TRUE)).isPresent(); + + defaultValidatedExecutableTypes = executableValidation.filter(x -> executableValidationEnabled) + .map(ExecutableValidationType::getDefaultValidatedExecutableTypes) + .map(DefaultValidatedExecutableTypesType::getExecutableType).map(EnumSet::copyOf) + .orElse(EnumSet.noneOf(ExecutableType.class)); + } + return new BootstrapConfigurationImpl(xmlConfig.getDefaultProvider(), xmlConfig.getConstraintValidatorFactory(), + xmlConfig.getMessageInterpolator(), xmlConfig.getTraversableResolver(), + xmlConfig.getParameterNameProvider(), new HashSet<>(xmlConfig.getConstraintMapping()), + executableValidationEnabled, defaultValidatedExecutableTypes, toMap(xmlConfig.getProperty()), + xmlConfig.getClockProvider(), new HashSet<>(xmlConfig.getValueExtractor())); + } + + private static Map<String, String> toMap(final List<PropertyType> property) { + return property == null || property.isEmpty() ? Collections.emptyMap() + : property.stream().collect(Collectors.toMap(PropertyType::getName, PropertyType::getValue)); + } + + @Privileged + private static ValidationConfigType parseXmlConfig(final String validationXmlFile) { + try (InputStream inputStream = getInputStream(getValidationXmlFile(validationXmlFile))) { + if (inputStream == null) { + log.log(Level.FINEST, + String.format("No %s found. Using annotation based configuration only.", validationXmlFile)); + return null; + } + log.log(Level.FINEST, String.format("%s found.", validationXmlFile)); + + return SCHEMA_MANAGER.unmarshal(new InputSource(inputStream), ValidationConfigType.class); + } catch (Exception e) { + throw Exceptions.create(ValidationException::new, e, "Unable to parse %s", validationXmlFile); + } + } + + public static InputStream open(String mappingFileName) { + if (mappingFileName.charAt(0) == '/') { + // Classloader needs a path without a starting / + mappingFileName = mappingFileName.substring(1); + } + try { + final InputStream in = getInputStream(mappingFileName); + Exceptions.raiseIf(in == null, ValidationException::new, + "Unable to open input stream for mapping file %s", mappingFileName); + return(in); + } catch (IOException e) { + throw Exceptions.create(ValidationException::new, e, "Unable to open input stream for mapping file %s", + mappingFileName); + } + } + + static InputStream getInputStream(final String path) throws IOException { + final ClassLoader loader = Reflection.getClassLoader(ValidationParser.class); + final List<URL> urls = Collections.list(loader.getResources(path)); + Exceptions.raiseIf(urls.stream().distinct().count() > 1, ValidationException::new, + "More than one %s is found in the classpath", path); + return urls.isEmpty() ? null : urls.get(0).openStream(); + } + + private ValidationParser() { + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/XmlUtils.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/XmlUtils.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/XmlUtils.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/xml/XmlUtils.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,67 @@ +/* + * 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.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.helpers.DefaultValidationEventHandler; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.apache.bval.util.reflection.Reflection; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +class XmlUtils { + private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + private static final Logger log = Logger.getLogger(XmlUtils.class.getName()); + private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + + static Schema loadSchema(String resource) { + final URL schemaUrl = Reflection.getClassLoader(XmlUtils.class).getResource(resource); + try { + return SCHEMA_FACTORY.newSchema(schemaUrl); + } catch (SAXException e) { + log.log(Level.WARNING, String.format("Unable to parse schema: %s", resource), e); + return null; + } + } + + static Document parse(InputStream in) throws SAXException, IOException, ParserConfigurationException { + return DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().parse(in); + } + + static <T> T unmarshal(Document document, Schema schema, Class<T> type) throws JAXBException { + final JAXBContext jc = JAXBContext.newInstance(type); + final Unmarshaller unmarshaller = jc.createUnmarshaller(); + unmarshaller.setSchema(schema); + unmarshaller.setEventHandler(new DefaultValidationEventHandler()); + final JAXBElement<T> root = unmarshaller.unmarshal(document, type); + return root.getValue(); + } +} \ No newline at end of file Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/routines/EMailValidationUtils.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/routines/EMailValidationUtils.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/routines/EMailValidationUtils.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/routines/EMailValidationUtils.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bval.routines; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Description: holds the regexp to validate an email address<br> + * User: roman.stumm<br> + * Date: 17.06.2010<br> + * Time: 10:40:59<br> + */ +public class EMailValidationUtils { + private static String ATOM = "[^\\x00-\\x1F\\(\\)\\<\\>\\@\\,\\;\\:\\\\\\\"\\.\\[\\]\\s]"; + private static String DOMAIN = "(" + ATOM + "+(\\." + ATOM + "+)*"; + private static String IP_DOMAIN = "\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\]"; + public static final Pattern DEFAULT_EMAIL_PATTERN; + + static { + DEFAULT_EMAIL_PATTERN = Pattern.compile("^" + ATOM + "+(\\." + ATOM + "+)*@" + DOMAIN + "|" + IP_DOMAIN + ")$", + Pattern.CASE_INSENSITIVE); + } + + /** + * Learn whether a given object is a valid email address. + * + * @param value + * to check + * @return <code>true</code> if the validation passes + */ + public static boolean isValid(Object value) { + return isValid(value, DEFAULT_EMAIL_PATTERN); + } + + /** + * Learn whether a particular value matches a given pattern per + * {@link Matcher#matches()}. + * + * @param value + * @param aPattern + * @return <code>true</code> if <code>value</code> was a <code>String</code> + * matching <code>aPattern</code> + */ + // TODO it would seem to make sense to move or reduce the visibility of this + // method as it is more general than email. + public static boolean isValid(Object value, Pattern aPattern) { + if (value == null) { + return true; + } + if (!(value instanceof CharSequence)) { + return false; + } + CharSequence seq = (CharSequence) value; + if (seq.length() == 0) { + return true; + } + return aPattern.matcher(seq).matches(); + } + +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/BValVersion.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/BValVersion.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/BValVersion.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/BValVersion.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.bval.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.apache.bval.util.reflection.Reflection; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +/** + * This class contains version information for BVal. + * It uses Ant's filter tokens to convert the template into a java + * file with current information. + */ +@Privilizing(@CallTo(Reflection.class)) +public class BValVersion { + + /** Project name */ + public static final String PROJECT_NAME = "Apache BVal"; + /** Unique id of the current project/version/revision */ + public static final String PROJECT_ID; + /** Version number */ + public static final String VERSION_NUMBER; + /** Major release number */ + public static final int MAJOR_RELEASE; + /** Minor release number */ + public static final int MINOR_RELEASE; + /** Patch/point release number */ + public static final int PATCH_RELEASE; + /** Release status */ + public static final String RELEASE_STATUS; + /** Version control revision number */ + public static final String REVISION_NUMBER; + + static { + Properties revisionProps = new Properties(); + try (InputStream in = BValVersion.class.getResourceAsStream("/META-INF/org.apache.bval.revision.properties")) { + if (in != null) { + revisionProps.load(in); + } + } catch (IOException ioe) { + } + + String vers = revisionProps.getProperty("project.version"); + if (StringUtils.isBlank(vers)) { + vers = "0.0.0"; + } + VERSION_NUMBER = vers; + + StringTokenizer tok = new StringTokenizer(VERSION_NUMBER, ".-"); + int major, minor, patch; + try { + major = tok.hasMoreTokens() ? Integer.parseInt(tok.nextToken()) : 0; + } catch (Exception e) { + major = 0; + } + + try { + minor = tok.hasMoreTokens() ? Integer.parseInt(tok.nextToken()) : 0; + } catch (Exception e) { + minor = 0; + } + + try { + patch = tok.hasMoreTokens() ? Integer.parseInt(tok.nextToken()) : 0; + } catch (Exception e) { + patch = 0; + } + + String revision = revisionProps.getProperty("svn.revision"); + if (StringUtils.isBlank(revision)) { + revision = "unknown"; + } else { + tok = new StringTokenizer(revision, ":"); + String strTok = null; + while (tok.hasMoreTokens()) { + try { + strTok = tok.nextToken(); + } catch (Exception e) { + } + } + if (strTok != null) { + revision = strTok; + } + } + + MAJOR_RELEASE = major; + MINOR_RELEASE = minor; + PATCH_RELEASE = patch; + RELEASE_STATUS = tok.hasMoreTokens() ? tok.nextToken("!") : ""; + REVISION_NUMBER = revision; + PROJECT_ID = PROJECT_NAME + " " + VERSION_NUMBER + "-r" + REVISION_NUMBER; + } + + /** + * Get the project version number. + * @return String + */ + public static String getVersion() { + return VERSION_NUMBER; + } + + /** + * Get the version control revision number. + * @return String + */ + public static String getRevision() { + return REVISION_NUMBER; + } + + /** + * Get the project name. + * @return String + */ + public static String getName() { + return PROJECT_NAME; + } + + /** + * Get the fully-qualified project id. + * @return String + */ + public static String getID() { + return PROJECT_ID; + } + + /** + * Main method of this class that prints the {@link #toString()} to <code>System.out</code>. + * @param args ignored + */ + public static void main(String[] args) { + System.out.println(new BValVersion().toString()); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(80 * 40); + appendBanner(buf); + buf.append("\n"); + + appendProperty("os.name", buf).append("\n"); + appendProperty("os.version", buf).append("\n"); + appendProperty("os.arch", buf).append("\n\n"); + + appendProperty("java.version", buf).append("\n"); + appendProperty("java.vendor", buf).append("\n\n"); + + buf.append("java.class.path:\n"); + final StringTokenizer tok = new StringTokenizer(Reflection.getProperty("java.class.path")); + while (tok.hasMoreTokens()) { + buf.append("\t").append(tok.nextToken()); + buf.append("\n"); + } + buf.append("\n"); + + appendProperty("user.dir", buf).append("\n"); + return buf.toString(); + } + + private void appendBanner(StringBuilder buf) { + buf.append("Project").append(": ").append(getName()); + buf.append("\n"); + buf.append("Version").append(": ").append(getVersion()); + buf.append("\n"); + buf.append("Revision").append(": ").append(getRevision()); + buf.append("\n"); + } + + private StringBuilder appendProperty(String prop, StringBuilder buf) { + return buf.append(prop).append(": ").append(Reflection.getProperty(prop)); + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/CloseableAble.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/CloseableAble.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/CloseableAble.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/CloseableAble.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,27 @@ +/* + * 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.util; + +import java.io.Closeable; + +@FunctionalInterface +public interface CloseableAble { + + Closeable getCloseable(); +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Comparators.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Comparators.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Comparators.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Comparators.java Fri Oct 12 15:00:48 2018 @@ -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.util; + +import java.util.Comparator; +import java.util.Iterator; + +/** + * {@link Comparator} related utilities. + */ +public class Comparators { + + /** + * Get a {@link Comparator} capable of comparing {@link Iterable}s. + * + * @param each + * @return {@link Comparator} + */ + public static <T, I extends Iterable<T>> Comparator<I> comparingIterables(Comparator<? super T> each) { + return (quid, quo) -> { + final Iterator<T> quids = quid.iterator(); + final Iterator<T> quos = quo.iterator(); + + while (quids.hasNext()) { + if (quos.hasNext()) { + final int rz = each.compare(quids.next(), quos.next()); + if (rz != 0) { + return rz; + } + continue; + } + return 1; + } + return quos.hasNext() ? -1 : 0; + }; + } + + private Comparators() { + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,125 @@ +/* + * 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.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.AnnotatedTypeVariable; +import java.lang.reflect.AnnotatedWildcardType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Optional; +import java.util.stream.Stream; + +public class EmulatedAnnotatedType<T extends Type> implements AnnotatedType { + private static class Parameterized extends EmulatedAnnotatedType<ParameterizedType> + implements AnnotatedParameterizedType { + + Parameterized(ParameterizedType wrapped) { + super(wrapped); + } + + @Override + public AnnotatedType[] getAnnotatedActualTypeArguments() { + return wrapArray(wrapped.getActualTypeArguments()); + } + } + + private static class Variable extends EmulatedAnnotatedType<TypeVariable<?>> implements AnnotatedTypeVariable { + + Variable(TypeVariable<?> wrapped) { + super(wrapped); + } + + @Override + public AnnotatedType[] getAnnotatedBounds() { + return wrapped.getAnnotatedBounds(); + } + } + + private static class Wildcard extends EmulatedAnnotatedType<WildcardType> implements AnnotatedWildcardType { + + Wildcard(WildcardType wrapped) { + super(wrapped); + } + + @Override + public AnnotatedType[] getAnnotatedLowerBounds() { + return wrapArray(wrapped.getLowerBounds()); + } + + @Override + public AnnotatedType[] getAnnotatedUpperBounds() { + return wrapArray(wrapped.getUpperBounds()); + } + } + + public static EmulatedAnnotatedType<?> wrap(Type type) { + if (type instanceof ParameterizedType) { + return new EmulatedAnnotatedType.Parameterized((ParameterizedType) type); + } + if (type instanceof TypeVariable<?>) { + return new EmulatedAnnotatedType.Variable((TypeVariable<?>) type); + } + if (type instanceof WildcardType) { + return new EmulatedAnnotatedType.Wildcard((WildcardType) type); + } + return new EmulatedAnnotatedType<>(type); + } + + private static EmulatedAnnotatedType<?>[] wrapArray(Type[] types) { + return Stream.of(types).map(EmulatedAnnotatedType::wrap).toArray(EmulatedAnnotatedType[]::new); + } + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = {}; + + protected final T wrapped; + private final Optional<AnnotatedElement> annotated; + + private EmulatedAnnotatedType(T wrapped) { + super(); + this.wrapped = Validate.notNull(wrapped); + this.annotated = + Optional.of(wrapped).filter(AnnotatedElement.class::isInstance).map(AnnotatedElement.class::cast); + } + + @Override + public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { + return annotated.map(e -> e.getAnnotation(annotationClass)).orElse(null); + } + + @Override + public Annotation[] getAnnotations() { + return annotated.map(AnnotatedElement::getAnnotations).orElse(EMPTY_ANNOTATION_ARRAY); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return annotated.map(AnnotatedElement::getDeclaredAnnotations).orElse(EMPTY_ANNOTATION_ARRAY); + } + + @Override + public Type getType() { + return wrapped; + } +} Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Escapes.java URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Escapes.java?rev=1843674&view=auto ============================================================================== --- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Escapes.java (added) +++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/util/Escapes.java Fri Oct 12 15:00:48 2018 @@ -0,0 +1,203 @@ +/** + * Copyright (C) 2006-2018 Talend Inc. - www.talend.com + * <p> + * Licensed 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.util; + +import java.io.IOException; +import java.io.Writer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +// taken from commons-lang3 +public final class Escapes { + private static final CharSequenceTranslator UNESCAPE_JAVA = + new AggregateTranslator( + new OctalUnescaper(), // .between('\1', '\377'), + new UnicodeUnescaper(), + new LookupTranslator(new String[][] { + {"\\b", "\b"}, + {"\\n", "\n"}, + {"\\t", "\t"}, + {"\\f", "\f"}, + {"\\r", "\r"} + }), + new LookupTranslator( + new String[][] { + {"\\\\", "\\"}, + {"\\\"", "\""}, + {"\\'", "'"}, + {"\\", ""} + }) + ); + + private Escapes() { + // no-op + } + + public static int unescapeJava(final CharSequence from, final int offset, final Writer output) { + // return StringEscapeUtils.UNESCAPE_JAVA.translate(path, pos.getIndex(), target); + return UNESCAPE_JAVA.translate(from, offset, output); + } + + protected interface CharSequenceTranslator { + int translate(CharSequence input, int index, Writer out); + } + + private static class AggregateTranslator implements CharSequenceTranslator { + private final CharSequenceTranslator[] translators; + + private AggregateTranslator(final CharSequenceTranslator... translators) { + this.translators = translators; + } + + @Override + public int translate(final CharSequence input, final int index, final Writer out) { + for (final CharSequenceTranslator translator : translators) { + final int consumed = translator.translate(input, index, out); + if(consumed != 0) { + return consumed; + } + } + return 0; + } + } + + private static class OctalUnescaper implements CharSequenceTranslator { + @Override + public int translate(final CharSequence input, final int index, final Writer out) { + final int remaining = input.length() - index - 1; + final StringBuilder builder = new StringBuilder(); + if (input.charAt(index) == '\\' && remaining > 0 && isOctalDigit(input.charAt(index + 1))) { + final int next = index + 1; + final int next2 = index + 2; + final int next3 = index + 3; + + builder.append(input.charAt(next)); + + if (remaining > 1 && isOctalDigit(input.charAt(next2))) { + builder.append(input.charAt(next2)); + if (remaining > 2 && isZeroToThree(input.charAt(next)) && isOctalDigit(input.charAt(next3))) { + builder.append(input.charAt(next3)); + } + } + + try { + out.write(Integer.parseInt(builder.toString(), 8)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + return 1 + builder.length(); + } + return 0; + } + + private boolean isOctalDigit(final char ch) { + return ch >= '0' && ch <= '7'; + } + + private boolean isZeroToThree(final char ch) { + return ch >= '0' && ch <= '3'; + } + } + + public static class UnicodeUnescaper implements CharSequenceTranslator { + @Override + public int translate(final CharSequence input, final int index, final Writer out) { + if (input.charAt(index) == '\\' && index + 1 < input.length() && input.charAt(index + 1) == 'u') { + int i = 2; + while (index + i < input.length() && input.charAt(index + i) == 'u') { + i++; + } + + if (index + i < input.length() && input.charAt(index + i) == '+') { + i++; + } + + if (index + i + 4 <= input.length()) { + // Get 4 hex digits + final CharSequence unicode = input.subSequence(index + i, index + i + 4); + + try { + final int value = Integer.parseInt(unicode.toString(), 16); + out.write((char) value); + } catch (final NumberFormatException nfe) { + throw new IllegalArgumentException("Unable to parse unicode value: " + unicode, nfe); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + return i + 4; + } + throw new IllegalArgumentException("Less than 4 hex digits in unicode value: '" + input.subSequence(index, input.length()) + + "' due to end of CharSequence"); + } + return 0; + } + } + + public static class LookupTranslator implements CharSequenceTranslator { + private final Map<String, String> lookupMap; + private final Set<Character> prefixSet; + private final int shortest; + private final int longest; + + private LookupTranslator(final String[][] lookup) { + lookupMap = new HashMap<>(); + prefixSet = new HashSet<>(); + int _shortest = Integer.MAX_VALUE; + int _longest = 0; + if (lookup != null) { + for (final CharSequence[] seq : lookup) { + this.lookupMap.put(seq[0].toString(), seq[1].toString()); + this.prefixSet.add(seq[0].charAt(0)); + final int sz = seq[0].length(); + if (sz < _shortest) { + _shortest = sz; + } + if (sz > _longest) { + _longest = sz; + } + } + } + shortest = _shortest; + longest = _longest; + } + + @Override + public int translate(final CharSequence input, final int index, final Writer out) { + if (prefixSet.contains(input.charAt(index))) { + int max = longest; + if (index + longest > input.length()) { + max = input.length() - index; + } + for (int i = max; i >= shortest; i--) { + final CharSequence subSeq = input.subSequence(index, index + i); + final String result = lookupMap.get(subSeq.toString()); + + if (result != null) { + try { + out.write(result); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + return i; + } + } + } + return 0; + } + } +}