com.tngtech.archunit.core.domain.AnnotationProxy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of archunit Show documentation
Show all versions of archunit Show documentation
A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit'
/*
* Copyright 2014-2022 TNG Technology Consulting GmbH
*
* 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
*
* 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 com.tngtech.archunit.core.domain;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.tngtech.archunit.base.MayResolveTypesViaReflection;
import com.tngtech.archunit.core.InitialConfiguration;
import static com.google.common.base.Preconditions.checkArgument;
@MayResolveTypesViaReflection(reason = "We depend on the classpath, if we proxy an annotation type")
class AnnotationProxy {
private static final InitialConfiguration propertiesFormatter = new InitialConfiguration<>();
static {
DomainPlugin.Loader.loadForCurrentPlatform().plugInAnnotationPropertiesFormatter(propertiesFormatter);
}
public static A of(Class annotationType, JavaAnnotation> toProxy) {
checkArgument(annotationType.getName().equals(toProxy.getRawType().getName()),
"Requested annotation type %s is incompatible with %s of type %s",
annotationType.getSimpleName(), JavaAnnotation.class.getSimpleName(), toProxy.getRawType().getSimpleName());
return newProxy(annotationType, toProxy);
}
@SuppressWarnings("unchecked") // annotationType A will be implemented
private static A newProxy(Class annotationType, JavaAnnotation> toProxy) {
return (A) Proxy.newProxyInstance(
annotationType.getClassLoader(),
new Class[]{annotationType},
new AnnotationMethodInvocationHandler(annotationType, toProxy));
}
private static class AnnotationMethodInvocationHandler implements InvocationHandler {
private final JavaAnnotation> toProxy;
private final Conversions conversions;
private final Map handlersByMethod;
private AnnotationMethodInvocationHandler(Class> annotationType, JavaAnnotation> toProxy) {
this.toProxy = toProxy;
conversions = initConversions(annotationType);
handlersByMethod = initHandlersByMethod(annotationType, toProxy, conversions);
}
private Conversions initConversions(Class> annotationType) {
JavaClassConversion javaClassConversion = new JavaClassConversion(annotationType.getClassLoader());
JavaEnumConstantConversion enumConversion = new JavaEnumConstantConversion();
JavaAnnotationConversion annotationConversion = new JavaAnnotationConversion(annotationType.getClassLoader());
return new Conversions(
javaClassConversion,
new JavaClassArrayConversion(javaClassConversion),
enumConversion,
new JavaEnumConstantArrayConversion(enumConversion),
annotationConversion,
new JavaAnnotationArrayConversion(annotationConversion));
}
private ImmutableMap initHandlersByMethod(
Class> annotationType, JavaAnnotation> toProxy, Conversions conversions) {
return ImmutableMap.of(
new MethodKey("annotationType"), new ConstantReturnValueHandler(annotationType),
new MethodKey("equals", Object.class.getName()), new EqualsHandler(),
new MethodKey("hashCode"), new HashCodeHandler(),
new MethodKey("toString"), new ToStringHandler(annotationType, toProxy, conversions)
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
SpecificHandler handler = handlersByMethod.get(MethodKey.of(method));
if (handler != null) {
return handler.handle(proxy, method, args);
}
Object result = toProxy.get(method.getName()).orElse(method.getDefaultValue());
return conversions.convertIfNecessary(result, method.getReturnType());
}
}
private interface Conversion {
Object convert(F input, Class> returnType);
boolean canHandle(Class> returnType);
}
private static class JavaClassConversion implements Conversion {
private final ClassLoader classLoader;
private JavaClassConversion(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public Class> convert(JavaClass input, Class> returnType) {
return JavaClassDescriptor.From.javaClass(input).resolveClass(classLoader);
}
@Override
public boolean canHandle(Class> returnType) {
return Class.class.isAssignableFrom(returnType);
}
}
private static class JavaClassArrayConversion implements Conversion