io.datakernel.di.util.ReflectionUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datakernel-di Show documentation
Show all versions of datakernel-di Show documentation
DataKernel has an extremely lightweight DI with ground-breaking design principles.
It supports nested scopes, singletons, object factories, modules and plugins which
allow to transform graph of dependencies at startup time without any reflection.
The newest version!
package io.datakernel.di.util;
import io.datakernel.di.annotation.Optional;
import io.datakernel.di.annotation.*;
import io.datakernel.di.core.*;
import io.datakernel.di.impl.BindingInitializer;
import io.datakernel.di.impl.BindingLocator;
import io.datakernel.di.impl.CompiledBinding;
import io.datakernel.di.impl.CompiledBindingInitializer;
import io.datakernel.di.module.BindingDesc;
import io.datakernel.di.module.Module;
import io.datakernel.di.module.ModuleBuilder;
import io.datakernel.di.module.ModuleBuilderBinder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static io.datakernel.di.core.Name.uniqueName;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
/**
* These are various reflection utilities that are used by the DSL.
* While you should not use them normally, they are pretty well organized and thus are left public.
*/
public final class ReflectionUtils {
private static final String IDENT = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
private static final Pattern PACKAGE = Pattern.compile("(?:" + IDENT + "\\.)*");
private static final Pattern PACKAGE_AND_PARENT = Pattern.compile(PACKAGE.pattern() + "(?:" + IDENT + "\\$\\d*)?");
private static final Pattern ARRAY_SIGNATURE = Pattern.compile("\\[L(.*?);");
private static final Pattern RAW_PART = Pattern.compile("^" + IDENT);
public static String getDisplayName(Type type) {
Class> raw = Types.getRawType(type);
String typeName;
if (raw.isAnonymousClass()) {
Type superclass = raw.getGenericSuperclass();
typeName = "? extends " + superclass.getTypeName();
} else {
typeName = type.getTypeName();
}
String defaultName = PACKAGE_AND_PARENT.matcher(ARRAY_SIGNATURE.matcher(typeName).replaceAll("$1[]")).replaceAll("");
ShortTypeName override = raw.getDeclaredAnnotation(ShortTypeName.class);
return override != null ?
RAW_PART.matcher(defaultName).replaceFirst(override.value()) :
defaultName;
}
public static String getShortName(Type type) {
return PACKAGE.matcher(ARRAY_SIGNATURE.matcher(type.getTypeName()).replaceAll("$1[]")).replaceAll("");
}
@Nullable
public static Name nameOf(AnnotatedElement annotatedElement) {
Set names = Arrays.stream(annotatedElement.getDeclaredAnnotations())
.filter(annotation -> annotation.annotationType().isAnnotationPresent(NameAnnotation.class))
.collect(toSet());
switch (names.size()) {
case 0:
return null;
case 1:
return Name.of(names.iterator().next());
default:
throw new DIException("More than one name annotation on " + annotatedElement);
}
}
public static Key keyOf(@Nullable Type container, Type type, AnnotatedElement annotatedElement) {
Type resolved = container != null ? Types.resolveTypeVariables(type, container) : type;
return Key.ofType(resolved, nameOf(annotatedElement));
}
public static Scope[] getScope(AnnotatedElement annotatedElement) {
Annotation[] annotations = annotatedElement.getDeclaredAnnotations();
Set scopes = Arrays.stream(annotations)
.filter(annotation -> annotation.annotationType().isAnnotationPresent(ScopeAnnotation.class))
.collect(toSet());
Scopes nested = (Scopes) Arrays.stream(annotations)
.filter(annotation -> annotation.annotationType() == Scopes.class)
.findAny()
.orElse(null);
if (nested != null) {
if (scopes.isEmpty()) {
return Arrays.stream(nested.value()).map(Scope::of).toArray(Scope[]::new);
}
throw new DIException("Cannot have both @Scoped and a scope annotation on " + annotatedElement);
}
switch (scopes.size()) {
case 0:
return Scope.UNSCOPED;
case 1:
return new Scope[]{Scope.of(scopes.iterator().next())};
default:
throw new DIException("More than one scope annotation on " + annotatedElement);
}
}
public static List getAnnotatedElements(Class> cls,
Class extends Annotation> annotationType, Function, T[]> extractor, boolean allowStatic) {
List result = new ArrayList<>();
while (cls != null) {
for (T element : extractor.apply(cls)) {
if (element.isAnnotationPresent(annotationType)) {
if (!allowStatic && Modifier.isStatic(element.getModifiers())) {
throw new DIException("@" + annotationType.getSimpleName() + " annotation is not allowed on " + element);
}
result.add(element);
}
}
cls = cls.getSuperclass();
}
return result;
}
// private static class Found extends RuntimeException {
// private final int lineNumber;
//
// public Found(int lineNumber) {
// this.lineNumber = lineNumber;
// }
// }
//
// public int getLineNumber(Method method) {
// try {
// String resource = method.getDeclaringClass().getName().replace('.', '/') + ".class";
// InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
// if (is == null) {
// return 0;
// }
//
// Type target = Type.getType(method);
// ClassReader cr = new ClassReader(is);
// cr.accept(new ClassVisitor(ASM5) {
// @Override
// public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
// MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
// if (!Type.getType(descriptor).equals(target)) {
// return mv;
// }
// return new MethodVisitor(ASM5, mv) {
// @Override
// public void visitLineNumber(int line, Label start) {
// throw new Found(line);
// }
// };
// }
// }, 0);
// return 0;
// } catch (Found found) {
// return found.lineNumber;
// } catch (IOException ignored) {
// return 0;
// }
// }
public static Binding generateImplicitBinding(Key key) {
Binding binding = generateConstructorBinding(key);
return binding != null ?
binding.initializeWith(generateInjectingInitializer(key)) :
null;
}
@SuppressWarnings("unchecked")
@Nullable
public static Binding generateConstructorBinding(Key key) {
Class> cls = key.getRawType();
Inject classInjectAnnotation = cls.getAnnotation(Inject.class);
Set> injectConstructors = Arrays.stream(cls.getDeclaredConstructors())
.filter(c -> c.isAnnotationPresent(Inject.class))
.collect(toSet());
Set factoryMethods = Arrays.stream(cls.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(Inject.class)
&& method.getReturnType() == cls
&& Modifier.isStatic(method.getModifiers()))
.collect(toSet());
if (classInjectAnnotation != null) {
if (!injectConstructors.isEmpty()) {
throw failedImplicitBinding(key, "inject annotation on class with inject constructor");
}
if (!factoryMethods.isEmpty()) {
throw failedImplicitBinding(key, "inject annotation on class with inject factory method");
}
Class> enclosingClass = cls.getEnclosingClass();
if (enclosingClass != null && !Modifier.isStatic(cls.getModifiers())) {
try {
return bindingFromConstructor(key, (Constructor) cls.getDeclaredConstructor(enclosingClass));
} catch (NoSuchMethodException e) {
throw failedImplicitBinding(key, "inject annotation on local class that closes over outside variables and/or has no default constructor");
}
}
try {
return bindingFromConstructor(key, (Constructor) cls.getDeclaredConstructor());
} catch (NoSuchMethodException e) {
throw failedImplicitBinding(key, "inject annotation on class with no default constructor");
}
} else {
if (injectConstructors.size() > 1) {
throw failedImplicitBinding(key, "more than one inject constructor");
}
if (!injectConstructors.isEmpty()) {
if (!factoryMethods.isEmpty()) {
throw failedImplicitBinding(key, "both inject constructor and inject factory method are present");
}
return bindingFromConstructor(key, (Constructor) injectConstructors.iterator().next());
}
}
if (factoryMethods.size() > 1) {
throw failedImplicitBinding(key, "more than one inject factory method");
}
if (!factoryMethods.isEmpty()) {
return bindingFromMethod(null, factoryMethods.iterator().next());
}
return null;
}
private static DIException failedImplicitBinding(Key> requestedKey, String message) {
return new DIException("Failed to generate implicit binding for " + requestedKey.getDisplayString() + ", " + message);
}
public static BindingInitializer generateInjectingInitializer(Key container) {
Class rawType = container.getRawType();
List> initializers = Stream.concat(
getAnnotatedElements(rawType, Inject.class, Class::getDeclaredFields, false).stream()
.map(field -> fieldInjector(container, field, !field.isAnnotationPresent(Optional.class))),
getAnnotatedElements(rawType, Inject.class, Class::getDeclaredMethods, true).stream()
.filter(method -> !Modifier.isStatic(method.getModifiers())) // we allow them and just filter out to allow static factory methods
.map(method -> methodInjector(container, method)))
.collect(toList());
return BindingInitializer.combine(initializers);
}
public static BindingInitializer fieldInjector(Key container, Field field, boolean required) {
field.setAccessible(true);
Key © 2015 - 2025 Weber Informatics LLC | Privacy Policy