![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.bval.jsr.metadata.HierarchyBuilder Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.bval.jsr.metadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import jakarta.validation.ElementKind;
import jakarta.validation.ParameterNameProvider;
import org.apache.bval.jsr.ApacheValidatorFactory;
import org.apache.bval.jsr.groups.GroupConversion;
import org.apache.bval.jsr.util.Methods;
import org.apache.bval.jsr.util.Proxies;
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.Reflection.Interfaces;
import org.apache.commons.weaver.privilizer.Privilizing;
import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
@Privilizing(@CallTo(Reflection.class))
public class HierarchyBuilder extends CompositeBuilder {
static abstract class HierarchyDelegate {
final D delegate;
final Meta hierarchyElement;
HierarchyDelegate(D delegate, Meta hierarchyElement) {
super();
this.delegate = Validate.notNull(delegate, "delegate");
this.hierarchyElement = Validate.notNull(hierarchyElement, "hierarchyElement");
}
Meta getHierarchyElement() {
return hierarchyElement;
}
}
static abstract class ElementDelegate>
extends HierarchyDelegate {
ElementDelegate(T delegate, Meta hierarchyElement) {
super(delegate, hierarchyElement);
}
Annotation[] getDeclaredConstraints() {
return delegate.getDeclaredConstraints(hierarchyElement);
}
}
private class BeanDelegate extends HierarchyDelegate, MetadataBuilder.ForBean>
implements MetadataBuilder.ForBean {
BeanDelegate(MetadataBuilder.ForBean delegate, Class hierarchyType) {
super(delegate, new Meta.ForClass(hierarchyType));
}
@Override
public MetadataBuilder.ForClass getClass(Meta> meta) {
return new ClassDelegate<>(delegate.getClass(hierarchyElement), hierarchyElement);
}
@Override
public Map> getFields(Meta> meta) {
final Map> fields = delegate.getFields(hierarchyElement);
if (fields.isEmpty()) {
return fields;
}
final Map> result = new LinkedHashMap<>();
fields.forEach((k, v) -> {
final Field fld = Reflection.getDeclaredField(hierarchyElement.getHost(), k);
Exceptions.raiseIf(fld == null, IllegalStateException::new, "delegate builder specified unknown field");
result.put(k, new ContainerDelegate(v, new Meta.ForField(fld)));
});
return result;
}
@Override
public Map>> getConstructors(Meta> meta) {
if (hierarchyElement.equals(meta)) {
@SuppressWarnings("unchecked")
final Map>> result =
((MetadataBuilder.ForBean) delegate).getConstructors(meta);
return result;
}
// ignore hierarchical ctors:
return Collections.emptyMap();
}
@Override
public Map> getGetters(Meta> meta) {
final Map> getters = delegate.getGetters(hierarchyElement);
if (getters.isEmpty()) {
return getters;
}
final Map> result = new LinkedHashMap<>();
final List> delegates = new ArrayList<>();
getters.forEach((k, v) -> {
final Method getter = Methods.getter(hierarchyElement.getHost(), k);
Exceptions.raiseIf(getter == null, IllegalStateException::new,
"delegate builder specified unknown getter");
final ContainerDelegate d = new ContainerDelegate<>(v, new Meta.ForMethod(getter));
result.put(k, d);
delegates.add(d);
});
Liskov.validateValidateOnExecution(delegates);
return result;
}
@Override
public Map> getMethods(Meta> meta) {
final Map> methods = delegate.getMethods(hierarchyElement);
if (methods.isEmpty()) {
return methods;
}
final Map> result = new LinkedHashMap<>();
final List> delegates = new ArrayList<>();
methods.forEach((k, v) -> {
final ExecutableDelegate d = new ExecutableDelegate<>(v,
new Meta.ForMethod(
Reflection.getDeclaredMethod(hierarchyElement.getHost(), k.getName(), k.getParameterTypes())),
ParameterNameProvider::getParameterNames);
result.put(k, d);
delegates.add(d);
});
Liskov.validateValidateOnExecution(delegates);
return result;
}
}
private class ClassDelegate extends ElementDelegate, MetadataBuilder.ForClass>
implements MetadataBuilder.ForClass {
ClassDelegate(MetadataBuilder.ForClass delegate, Meta> hierarchyType) {
super(delegate, hierarchyType);
}
@Override
public List> getGroupSequence(Meta> meta) {
return delegate.getGroupSequence(hierarchyElement);
}
@Override
public Annotation[] getDeclaredConstraints(Meta> meta) {
return getDeclaredConstraints();
}
}
class ContainerDelegate extends ElementDelegate>
implements MetadataBuilder.ForContainer {
ContainerDelegate(MetadataBuilder.ForContainer delegate, Meta hierarchyElement) {
super(delegate, hierarchyElement);
}
boolean isCascade() {
return delegate.isCascade(hierarchyElement);
}
@Override
public final boolean isCascade(Meta meta) {
return isCascade();
}
Set getGroupConversions() {
return delegate.getGroupConversions(hierarchyElement);
}
@Override
public final Set getGroupConversions(Meta meta) {
return getGroupConversions();
}
@Override
public Map> getContainerElementTypes(
Meta meta) {
final Map> containerElementTypes =
delegate.getContainerElementTypes(hierarchyElement);
final Map> result = new LinkedHashMap<>();
containerElementTypes.forEach((k, v) -> {
result.put(k, new ContainerDelegate<>(v, new Meta.ForContainerElement(hierarchyElement, k)));
});
return result;
}
@Override
public Annotation[] getDeclaredConstraints(Meta meta) {
return getDeclaredConstraints();
}
}
private class ExecutableDelegate
extends HierarchyDelegate> implements MetadataBuilder.ForExecutable {
final BiFunction> getParameterNames;
ExecutableDelegate(MetadataBuilder.ForExecutable delegate, Meta hierarchyElement,
BiFunction> getParameterNames) {
super(delegate, hierarchyElement);
this.getParameterNames = Validate.notNull(getParameterNames, "getParameterNames");
}
@Override
public MetadataBuilder.ForContainer getReturnValue(Meta meta) {
return new ContainerDelegate<>(delegate.getReturnValue(hierarchyElement), hierarchyElement);
}
@Override
public MetadataBuilder.ForElement getCrossParameter(Meta meta) {
return new CrossParameterDelegate<>(delegate.getCrossParameter(hierarchyElement), hierarchyElement);
}
@Override
public List> getParameters(Meta meta) {
final List> parameterDelegates =
delegate.getParameters(hierarchyElement);
if (parameterDelegates.isEmpty()) {
return parameterDelegates;
}
final List> metaParameters = getMetaParameters(hierarchyElement, getParameterNames);
if (metaParameters.size() != parameterDelegates.size()) {
Exceptions.raise(IllegalStateException::new, "Got wrong number of parameter delegates for %s",
meta.getHost());
}
return IntStream.range(0, parameterDelegates.size())
.mapToObj(n -> new ContainerDelegate<>(parameterDelegates.get(n), metaParameters.get(n)))
.collect(Collectors.toList());
}
}
private class CrossParameterDelegate
extends ElementDelegate> implements MetadataBuilder.ForElement {
CrossParameterDelegate(MetadataBuilder.ForElement delegate, Meta hierarchyElement) {
super(delegate, hierarchyElement);
}
@Override
public Annotation[] getDeclaredConstraints(Meta meta) {
return getDeclaredConstraints();
}
}
private class ForCrossParameter
extends CompositeBuilder.ForElement, E> {
ForCrossParameter(List> delegates) {
super(delegates);
Liskov.validateCrossParameterHierarchy(delegates);
}
}
private class ForContainer
extends CompositeBuilder.ForContainer, E> {
ForContainer(List> delegates, ElementKind elementKind) {
super(delegates);
Liskov.validateContainerHierarchy(delegates, Validate.notNull(elementKind, "elementKind"));
}
}
private final Function, MetadataBuilder.ForBean>> getBeanBuilder;
public HierarchyBuilder(ApacheValidatorFactory validatorFactory,
Function, MetadataBuilder.ForBean>> getBeanBuilder) {
super(validatorFactory, AnnotationBehaviorMergeStrategy.first());
this.getBeanBuilder = Validate.notNull(getBeanBuilder, "getBeanBuilder function was null");
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public MetadataBuilder.ForBean forBean(Class beanClass) {
final List> delegates = new ArrayList<>();
/*
* First add the delegate for the requested bean class, forcing to empty if absent. This is important for the
* same reason that we use the #first() AnnotationBehaviorMergeStrategy: namely, that custom metadata overrides
* only from the immediately available mapping per the BV spec.
*/
delegates.add(Optional.of(beanClass).map(getBeanBuilder).orElseGet(() -> EmptyBuilder.instance().forBean()));
// iterate the hierarchy, skipping the first (i.e. beanClass handled above)
final Iterator> hierarchy = Reflection.hierarchy(beanClass, Interfaces.INCLUDE).iterator();
hierarchy.next();
// filter; map; skip empty hierarchy builders, mapping others to BeanDelegate
hierarchy.forEachRemaining(t -> Optional.of(t).filter(this::canValidate).map(getBeanBuilder)
.filter(b -> !b.isEmpty()).map(b -> new BeanDelegate(b, t)).ifPresent(delegates::add));
if (delegates.size() == 1) {
return (MetadataBuilder.ForBean) delegates.get(0);
}
// pretend:
// note: stream split for java 11 compilation
final Stream> forBeanStream = delegates.stream()
.map(MetadataBuilder.ForBean.class::cast);
return forBeanStream.collect(compose());
}
@Override
protected Map, Annotation[]> getConstraintDeclarationMap(
CompositeBuilder.ForElement extends MetadataBuilder.ForElement, E> composite, Meta meta) {
@SuppressWarnings("unchecked")
final Function, Meta> keyMapper =
d -> Optional.of(d).filter(HierarchyDelegate.class::isInstance).map(HierarchyDelegate.class::cast)
.map(HierarchyDelegate::getHierarchyElement).map(Meta.class::cast).orElse(meta);
return composite.delegates.stream().collect(Collectors.toMap(keyMapper, d -> d.getDeclaredConstraints(meta),
(u, v) -> Stream.of(u, v).flatMap(Stream::of).toArray(Annotation[]::new), LinkedHashMap::new));
}
@Override
protected List> getGroupSequence(CompositeBuilder.ForClass composite, Meta> meta) {
return composite.delegates.get(0).getGroupSequence(meta);
}
@Override
protected , E extends AnnotatedElement> MetadataBuilder.ForContainer forContainer(
List delegates, Meta meta, ElementKind elementKind) {
if (delegates.isEmpty()) {
return super.forContainer(delegates, meta, elementKind);
}
final List> hierarchyDelegates = delegates.stream()
.> map(
d -> d instanceof ContainerDelegate> ? (ContainerDelegate) d : new ContainerDelegate<>(d, meta))
.collect(Collectors.toList());
@SuppressWarnings("unchecked")
final CompositeBuilder.ForContainer result =
(CompositeBuilder.ForContainer) new HierarchyBuilder.ForContainer(hierarchyDelegates,
elementKind);
return result;
}
@Override
protected , E extends Executable> MetadataBuilder.ForElement forCrossParameter(
List delegates, Meta meta) {
if (delegates.isEmpty()) {
return super.forCrossParameter(delegates, meta);
}
final List> hierarchyDelegates =
delegates.stream()
.> map(d -> d instanceof CrossParameterDelegate>
? (CrossParameterDelegate) d : new CrossParameterDelegate<>(d, meta))
.collect(Collectors.toList());
return new HierarchyBuilder.ForCrossParameter<>(hierarchyDelegates);
}
private boolean canValidate(Class> t) {
return !(t.getName().startsWith("java.") || Proxies.isProxyClass(t));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy