package com.github.czyzby.autumn.context;
import java.lang.annotation.Annotation;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IdentityMap;
import com.badlogic.gdx.utils.IdentityMap.Entry;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.reflect.ClassReflection;
import com.badlogic.gdx.utils.reflect.Constructor;
import com.badlogic.gdx.utils.reflect.Field;
import com.badlogic.gdx.utils.reflect.Method;
import com.badlogic.gdx.utils.reflect.ReflectionException;
import com.github.czyzby.autumn.annotation.Component;
import com.github.czyzby.autumn.annotation.Dispose;
import com.github.czyzby.autumn.annotation.OnEvent;
import com.github.czyzby.autumn.annotation.OnMessage;
import com.github.czyzby.autumn.annotation.Processor;
import com.github.czyzby.autumn.annotation.Provider;
import com.github.czyzby.autumn.context.error.ContextInitiationException;
import com.github.czyzby.autumn.context.impl.method.ContextConsumer;
import com.github.czyzby.autumn.context.impl.method.MethodInvocation;
import com.github.czyzby.autumn.processor.AnnotationProcessor;
import com.github.czyzby.autumn.processor.event.EventDispatcher;
import com.github.czyzby.autumn.processor.event.MessageDispatcher;
import com.github.czyzby.autumn.processor.impl.ComponentAnnotationProcessor;
import com.github.czyzby.autumn.processor.impl.DestroyAnnotationProcessor;
import com.github.czyzby.autumn.processor.impl.DisposeAnnotationProcessor;
import com.github.czyzby.autumn.processor.impl.InitiateAnnotationProcessor;
import com.github.czyzby.autumn.processor.impl.InjectAnnotationProcessor;
import com.github.czyzby.autumn.processor.impl.MetaAnnotationProcessor;
import com.github.czyzby.autumn.processor.impl.ProviderAnnotationProcessor;
import com.github.czyzby.autumn.scanner.ClassScanner;
import com.github.czyzby.kiwi.util.common.Exceptions;
import com.github.czyzby.kiwi.util.common.Strings;
import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays;
import com.github.czyzby.kiwi.util.gdx.collection.GdxMaps;
import com.github.czyzby.kiwi.util.gdx.collection.lazy.LazyObjectMap;
/** A single-use context initializer object. Scans the selected packages for annotated classes and initiates them, using
* registered {@link AnnotationProcessor}s. After {@link #initiate()} call, clears the context meta-data (to allow
* proper garbage collection) and becomes unusable.
*
* @author MJ */
public class ContextInitializer {
/* Annotations. */
/** Annotations that are scanned for before other components. Usually processors. */
private final Array<Class<? extends Annotation>> scannedMetaAnnotations = GdxArrays.newArray();
/** Annotations of regular components. */
private final Array<Class<? extends Annotation>> scannedAnnotations = GdxArrays.newArray();
/* Annotation processors. */
/** Contains all annotation processors. */
private final Array<AnnotationProcessor<?>> processors = GdxArrays.newArray();
/** Contains all annotation processors that were added manually and require initiation. */
private final Array<AnnotationProcessor<?>> manuallyAddedProcessors = GdxArrays.newArray();
/** These handle annotated fields. */
private final ObjectMap<Class<? extends Annotation>, Array<AnnotationProcessor<?>>> fieldProcessors = LazyObjectMap
.newMapOfArrays();
/** These handle annotated methods. */
private final ObjectMap<Class<? extends Annotation>, Array<AnnotationProcessor<?>>> methodProcessors = LazyObjectMap
.newMapOfArrays();
/** These handle annotated classes. */
private final ObjectMap<Class<? extends Annotation>, Array<AnnotationProcessor<?>>> typeProcessors = LazyObjectMap
.newMapOfArrays();
/* Scanners, components. */
/** Contains roots and class scanners that process the actual scanning. */
private final IdentityMap<Class<?>, ClassScanner> scanners = GdxMaps.newIdentityMap();
/** Contains components added with {@link #addComponent(Object)}. */
private final Array<Object> manuallyAddedComponents = GdxArrays.newArray();
/* Control variables. */
/** Contains constructors of components that contain unresolved constructor dependencies. */
private Array<Constructor> delayedConstructions = GdxArrays.newArray();
/** If unable to resolve dependencies after this amount of iterations, context fails to build. */
private int maxInitiationIterations = 100;
/** See {@link Context#setCreateMissingDependencies(boolean)}. */
private boolean createMissingDependencies = true;
/** If true, scanners and processors are cleared after initiation. */
private boolean clearProcessors = true;
/** If true, components are removed from the context after initiation. They still hold references to each other
* (through dependency injection), but the {@link Context} object will be empty. */
private boolean clearContextAfterInitiation = true;
/** Consumes constructed {@link Context} instance. */
private ContextConsumer doBeforeInitiation;
/** Consume fully initiated {@link Context} instance. */
private ContextConsumer doAfterInitiation;
/** Creates a new context initializer with default annotation processors. */
public ContextInitializer() {
// Default annotations:
scannedMetaAnnotations.add(Processor.class);
scannedMetaAnnotations.add(Provider.class);
scannedAnnotations.add(Component.class);
scannedAnnotations.add(Dispose.class);
scannedAnnotations.add(OnEvent.class);
scannedAnnotations.add(OnMessage.class);
// Default processors:
addProcessor(new ComponentAnnotationProcessor()); // Maps components by interfaces.
addProcessor(new MetaAnnotationProcessor()); // Registers annotation processors.
addProcessor(new ProviderAnnotationProcessor()); // Registers dependency providers.
addProcessor(new InjectAnnotationProcessor()); // Injects field dependencies.
addProcessor(new InitiateAnnotationProcessor()); // Invokes initiation methods.
addProcessor(new DestroyAnnotationProcessor()); // Schedules destruction methods upon disposing.
addProcessor(new DisposeAnnotationProcessor()); // Schedules disposing of fields and components.
addProcessor(new EventDispatcher()); // Registers event listeners. Allows to post events.
addProcessor(new MessageDispatcher()); // Registers message listeners. Allows to post messages.
}
/** Static factory method. Creates a new context initializer with default, no-arg constuctor.
*
* @return a new instance of ContextInitializer.
* @see ContextInitializer#initiate() */
public static ContextInitializer newContext() {
return new ContextInitializer();
}
/** @param annotation classes annotated with this annotation will be searched for.
* @return this for chaining. */
public ContextInitializer scanFor(final Class<? extends Annotation> annotation) {
scannedAnnotations.add(annotation);
return this;
}
/** @param annotations classes annotated with these annotations will be searched for.
* @return this for chaining. */
public ContextInitializer scanFor(final Class<? extends Annotation>... annotations) {
scannedAnnotations.addAll(annotations);
return this;
}
/** @param metaAnnnotation classes annotated with this annotation will be searched for and initiated before others.
* Meta annotations are usually reserved for annotation processors - without adding processors first,
* other components will not be initiated properly.
* @return this for chaining. */
public ContextInitializer scanForMeta(final Class<? extends Annotation> metaAnnnotation) {
scannedMetaAnnotations.add(metaAnnnotation);
return this;
}
/** @param processors process fields, methods and types annotated with specific annotations. Processors will be also
* available in context for injection. This method is reserved for fully initiated processors or
* processors during initiation; if you need the processors to be fully initiated, use
* {@link #addComponents(Object...)} instead.
* @return this for chaining. */
public ContextInitializer addProcessors(final AnnotationProcessor<?>... processors) {
for (final AnnotationProcessor<?> processor : processors) {
addProcessor(processor);
}
return this;
}
/** @param processor processes fields, methods and types annotated with a specific annotation. Processor will be
* also available in context for injection. This method is reserved for fully initiated processors or
* processors during initiation; if you need a processor to be fully initiated, use
* {@link #addComponent(Object)} instead.
* @return this for chaining. */
public ContextInitializer addProcessor(final AnnotationProcessor<?> processor) {
processors.add(processor);
if (processor.isSupportingFields()) {
fieldProcessors.get(processor.getSupportedAnnotationType()).add(processor);
}
if (processor.isSupportingMethods()) {
methodProcessors.get(processor.getSupportedAnnotationType()).add(processor);
}
if (processor.isSupportingTypes()) {
typeProcessors.get(processor.getSupportedAnnotationType()).add(processor);
}
return this;
}
/** @param maxInitiationIterations some components need dependencies for their constructors. They initiation is
* basically delayed, until the requested components appear in the context. There is no specialized
* mechanism of detecting circular constructor dependencies; instead, if after a certain number of
* iterations some components are still missing, it is assumed that the context cannot be built. This
* value is the said limit. It defaults to 100, which is more than enough for most - even big - contexts,
* but if you are SURE there are no circular references in your context and it still fails to build after
* 100 iterations, change this value to a higher one.
* @return this for chaining. */
public ContextInitializer maxInitiationIterationsAmount(final int maxInitiationIterations) {
this.maxInitiationIterations = maxInitiationIterations;
return this;
}
/** @param createMissingDependencies if true, field and method dependencies with no components or providers in the
* concept will be created with default no-arg constructor. Defaults to true.
* @return this for chaining.
* @see Context#setCreateMissingDependencies(boolean) */
public ContextInitializer createMissingDependencies(final boolean createMissingDependencies) {
this.createMissingDependencies = createMissingDependencies;
return this;
}
/** @param clearProcessors if true, scanners and annotation processors will be cleared just after creating context
* with {@link #initiate()}. This generally prevents from keeping unnecessary references to context
* meta-data. Defaults to true; change to false only if you plan to use the initializer multiple times
* and using separate initializers is not an option for some reason.
* @return this for chaining. */
public ContextInitializer clearProcessors(final boolean clearProcessors) {
this.clearProcessors = clearProcessors;
return this;
}
/**
* @param clearContextAfterInitiation if true, all components from the {@link Context} instance will be removed
* after the context is fully initiated.
* @return this for chaining.
* @see Context#setClear(boolean)
*/
public ContextInitializer clearContextAfterInitiation(final boolean clearContextAfterInitiation) {
this.clearContextAfterInitiation = clearContextAfterInitiation;
return this;
}
/**
* @param doBeforeInitiation will be invoked right after the {@link Context} is created. The consumed context
* instance should be empty, but will never be null.
* @return this for chaining.
* @see ContextConsumer
*/
public ContextInitializer doBeforeInitiation(final ContextConsumer doBeforeInitiation) {
this.doBeforeInitiation = doBeforeInitiation;
return this;
}
/**
* @param doAfterInitiation will be invoked right after the {@link Context} is fully initiated, but before the
* processors and components meta-data is cleared. The consumed context instance will never be null.
* @return this for chaining.
* @see ContextConsumer
*/
public ContextInitializer doAfterInitiation(final ContextConsumer doAfterInitiation) {
this.doAfterInitiation = doAfterInitiation;
return this;
}
/** @param root scanning root. Will look for classes sharing the same root package.
* @param scanner will process the actual class scanning.
* @return this for chaining. */
public ContextInitializer scan(final Class<?> root, final ClassScanner scanner) {
scanners.put(root, scanner);
return this;
}
/** @param component will be added to the context manually, rather than scanned for. Note that this method has any
* effect only BEFORE scanning, not DURING component processing: annotation processors should not invoke
* this method. If the added component is an annotation processor, it will be also added and will
* properly process annotations.
* @return this for chaining. */
public ContextInitializer addComponent(final Object component) {
if (component instanceof AnnotationProcessor<?>) {
final AnnotationProcessor<?> processor = (AnnotationProcessor<?>) component;
manuallyAddedProcessors.add(processor);
addProcessor(processor);
} else {
manuallyAddedComponents.add(component);
}
return this;
}
/** @param components will be added to the context manually, rather than scanned for. Note that this method has any
* effect only BEFORE scanning, not DURING component processing: annotation processors should not invoke
* this method. If any of the added components is an annotation processor, it will be also added and will
* properly process annotations.
* @return this for chaining. */
public ContextInitializer addComponents(final Object... components) {
for (final Object component : components) {
addComponent(component);
}
return this;
}
/** This is finalizing method that actually scans and initiates the context. After this method invocation, context
* initialized becomes unusable; if - for some reason - you need to create multiple contexts, use separate
* initializers. Make sure to add some class scanners and scanning roots before invoking this method - otherwise
* there will be no classes to scan for.
*
* @return {@link ContextDestroyer} instance, implementing {@link com.badlogic.gdx.utils.Disposable} interface. On
* {@link ContextDestroyer#dispose()}, this object will invoke registered destruction methods (that might
* include invoking finalizing component methods or disposing of heavy objects). Make sure to dispose of
* this object before closing the application - or as soon as the context is no longer needed.
* @see #scan(Class, ClassScanner) */
public ContextDestroyer initiate() {
validateScanners(); // Making sure scanners are properly defined.
final Context context = createContext(); // Creating new instance, applying user's settings.
final ContextDestroyer contextDestroyer = new ContextDestroyer();
mapInContext(context, processors); // Now context contains default processors. They are injectable.
mapInContext(context, manuallyAddedComponents); // Now manually added components are in the context.
initiateMetaComponents(context, contextDestroyer); // Now context contains custom annotation processors.
invokeProcessorActionsBeforeInitiation(); // Processors are ready to process!
initiateRegularComponents(context, contextDestroyer);// Now context contains all regular components.
invokeProcessorActionsAfterInitiation(context, contextDestroyer); // Processors finish up their work.
finishContext(context); // Clearing processors and components.
return contextDestroyer;
}
private Context createContext() {
final Context context = new Context();
context.setClear(clearContextAfterInitiation);
context.setCreateMissingDependencies(createMissingDependencies);
if (doBeforeInitiation != null) {
doBeforeInitiation.handleContext(context);
doBeforeInitiation = null;
}
return context;
}
private void finishContext(Context context) {
if (doAfterInitiation != null) {
doAfterInitiation.handleContext(context);
doAfterInitiation = null;
}
context.clear(); // Removing all components from context (if not disabled)
if (clearProcessors) {
destroyInitializer(); // Clearing meta-data. Processors are no longer available.
}
}
/** Throws exception if user did not specify any scanners. */
private void validateScanners() {
if (scanners.size == 0) {
throw new ContextInitiationException(
"Cannot initiate context without any class scanners and scanning roots.");
}
}
/** @param context will contain instances of passed components.
* @param components will be mapped by their class tree. */
private static void mapInContext(final Context context, final Iterable<?> components) {
for (final Object component : components) {
context.map(component);
}
}
/** Calls {@link AnnotationProcessor#doBeforeScanning(ContextInitializer)} with "this" argument on each
* processor. */
private void invokeProcessorActionsBeforeInitiation() {
for (final AnnotationProcessor<?> processor : processors) {
processor.doBeforeScanning(this);
}
}
/** Calls {@link AnnotationProcessor#doAfterScanning(ContextInitializer, Context, ContextDestroyer)} with "this"
* argument on each processor.
* @param context might be required by some processors to finish up.
* @param destroyer used to register destruction callbacks. */
private void invokeProcessorActionsAfterInitiation(final Context context, final ContextDestroyer destroyer) {
for (final AnnotationProcessor<?> processor : processors) {
processor.doAfterScanning(this, context, destroyer);
}
}
/** @param context will contain instances of scanned annotation procesors.
* @param contextDestroyer used to register destruction callbacks. */
private void initiateMetaComponents(final Context context, final ContextDestroyer contextDestroyer) {
final Array<Class<?>> metaComponentTypes = GdxArrays.newArray();
for (final Entry<Class<?>, ClassScanner> scannerData : scanners) {
metaComponentTypes
.addAll(scannerData.value.findClassesAnnotatedWith(scannerData.key, scannedMetaAnnotations));
}
final Array<Object> metaComponents = createComponents(metaComponentTypes, context);
metaComponents.addAll(manuallyAddedProcessors);
manuallyAddedProcessors.clear();
initiateComponents(metaComponents, context, contextDestroyer);
}
/** @param context will contain instances of scanned components. This method will clear scanners, as they are no
* longer expected to be needed.
* @param contextDestroyer used to register destruction callbacks. */
private void initiateRegularComponents(final Context context, final ContextDestroyer contextDestroyer) {
final Array<Class<?>> componentTypes = GdxArrays.newArray();
for (final Entry<Class<?>, ClassScanner> scannerData : scanners) {
componentTypes.addAll(scannerData.value.findClassesAnnotatedWith(scannerData.key, scannedAnnotations));
}
final Array<Object> components = createComponents(componentTypes, context);
// Manually added components are already mapped. Now they will be initiated:
components.addAll(manuallyAddedComponents);
manuallyAddedComponents.clear();
initiateComponents(components, context, contextDestroyer);
// Scanners no longer needed, all components found:
if (clearProcessors) {
scanners.clear();
}
}
/** Clears meta-data collections. */
private void destroyInitializer() {
GdxMaps.clearAll(fieldProcessors, methodProcessors, typeProcessors);
GdxArrays.clearAll(scannedMetaAnnotations, scannedAnnotations, processors, delayedConstructions,
manuallyAddedComponents, manuallyAddedProcessors);
}
/* COMPONENTS CREATIONS. Constructor management, resolving dependencies. */
/** @param types classes of components to initiate.
* @param context will contain components mapped by their classes tree.
* @return an array containing all created components. */
private Array<Object> createComponents(final Array<Class<?>> types, final Context context) {
final Array<Object> components = GdxArrays.newArray();
for (final Class<?> type : types) {
final Constructor[] constructors = ClassReflection.getConstructors(type);
if (constructors == null || constructors.length == 0) {
throw new ContextInitiationException(
type + " has no available public constructors. Unable to create component.");
} else if (constructors.length == 1) {
// Single constructor - trying to invoke it:
addIfNotNull(components, processConstructor(constructors[0], context));
} else {
// Multiple constructors - trying to find a suitable one:
addIfNotNull(components, processConstructor(findSuitableConstructor(constructors), context));
}
}
int initiationIterations = 0;
while (GdxArrays.isNotEmpty(delayedConstructions)) {
validateIterationsAmount(initiationIterations);
processDelayedConstructions(context, components);
initiationIterations++;
}
return components;
}
/** @param initiationIterations if greater than {@link #maxInitiationIterations}, throws an an exception. */
private void validateIterationsAmount(final int initiationIterations) {
if (initiationIterations >= maxInitiationIterations) {
throw new ContextInitiationException("Context could not have been built after " + initiationIterations
+ " iterations. Some constructor dependencies are missing (not annotated, not available in the context) or some components have circular dependencies. Unable to invoke constructors: "
+ GdxArrays.newArray(delayedConstructions));
}
}
/** @param constructors an array of at least 2 constructors.
* @return no-arg constructor or the first found constructor. */
private static Constructor findSuitableConstructor(final Constructor[] constructors) {
for (final Constructor constructor : constructors) {
if (constructor.getParameterTypes().length == 0) {
// Using no-arg constructor by default.
return constructor;
}
}
// Constructor annotations not available, so we can't just select a constructor with @Inject or whatever.
final Constructor constructor = constructors[0];
Gdx.app.error("WARN", constructor.getDeclaringClass()
+ " has multiple public constructors, but no public no-arg constructor. Using first found constructor to initiate component.");
return constructor;
}
/** Simple array utility.
*
* @param array will contain the object if it's not null.
* @param object if not null, will be added to the array.
* @param <Type> type of values stored in the array. */
protected <Type> void addIfNotNull(final Array<Type> array, final Type object) {
if (object != null) {
array.add(object);
}
}
/** @param constructor if all its parameter types are available in context, will be invoked and the instance will be
* added to context.
* @param context will be used to retrieve constructor parameters.
* @return an instance of the component or null if it cannot be constructed yet. */
private Object processConstructor(final Constructor constructor, final Context context) {
Object component;
if (constructor.getParameterTypes().length == 0) {
// Passing any empty object array avoid unnecessary array allocation.
component = invokeConstructor(constructor, Strings.EMPTY_ARRAY);
} else {
if (areContructorParametersAvailable(constructor, context)) {
final Object[] parameters = MethodInvocation.getParametersFromContext(constructor.getParameterTypes(),
context);
component = invokeConstructor(constructor, parameters);
} else {
delayedConstructions.add(constructor);
return null;
}
}
context.map(component);
return component;
}
/** @param constructor its parameters will be validated.
* @param context contains components.
* @return true if all constructor parameters can be injected. */
private static boolean areContructorParametersAvailable(final Constructor constructor, final Context context) {
for (final Class<?> parameterType : constructor.getParameterTypes()) {
if (!context.isPresent(parameterType) && !context.isProviderPresentFor(parameterType)) {
return false;
}
}
return true;
}
/** @param constructor will be invoked.
* @param parameters will be used to invoke the constructor.
* @return a new instance of the class.
* @throws ContextInitiationException if unable to invoke the constructor. */
private static Object invokeConstructor(final Constructor constructor, final Object[] parameters) {
try {
return constructor.newInstance(parameters);
} catch (final ReflectionException exception) {
throw new ContextInitiationException("Unable to create a new instance of class: "
+ constructor.getDeclaringClass() + " with constructor: " + constructor + " with parameters: "
+ GdxArrays.newArray(parameters), exception);
}
}
/** @param context used to get constructor parameters.
* @param components will contain created components. */
private void processDelayedConstructions(final Context context, final Array<Object> components) {
final Array<Constructor> stillMissingConstructions = GdxArrays.newArray();
for (final Constructor constructor : delayedConstructions) {
final Object component = processConstructor(constructor, context);
if (component != null) {
components.add(component);
} else {
stillMissingConstructions.add(constructor);
}
}
delayedConstructions = stillMissingConstructions;
}
/* CONTEXT INITIATION. Processing types', methods' and fields' annotations. */
/** @param components will be initiated.
* @param context contains components. Used to resolve dependencies.
* @param contextDestroyer used to register destruction callbacks. */
private void initiateComponents(final Array<Object> components, final Context context,
final ContextDestroyer contextDestroyer) {
// Class annotations are usually the most important. They can change the way component is mapped or processed.
for (final Object component : components) {
processType(component, context, contextDestroyer);
}
// Fields annotations might be used to inject component's dependencies and change the state of the component, so
// they should be processed before methods - which might actually depend on the fields.
for (final Object component : components) {
processFields(component, context, contextDestroyer);
}
// Methods are processed last, when the component is (almost) fully initiated and all method dependencies should
// be present.
for (final Object component : components) {
processMethods(component, context, contextDestroyer);
}
}
/** Processes components' class annotations.
*
* @param component its class annotations will be processed.
* @param context used to resolve dependencies
* @param contextDestroyer will register destruction callbacks. */
@SuppressWarnings({ "rawtypes", "unchecked" }) // Using correct types, but wildcards fail to see that.
private void processType(final Object component, final Context context, final ContextDestroyer contextDestroyer) {
final com.badlogic.gdx.utils.reflect.Annotation[] annotations = getAnnotations(component.getClass());
if (annotations == null || annotations.length == 0) {
return;
}
for (final com.badlogic.gdx.utils.reflect.Annotation annotation : annotations) {
if (typeProcessors.containsKey(annotation.getAnnotationType())) {
final Array<AnnotationProcessor<?>> typeProcessorsForAnnotation = typeProcessors
.get(annotation.getAnnotationType());
// This might get resized in the process, so we're not using an iterator or assigning size.
for (int index = 0; index < typeProcessorsForAnnotation.size; index++) {
final AnnotationProcessor processor = typeProcessorsForAnnotation.get(index);
processor.processType(component.getClass(),
annotation.getAnnotation(annotation.getAnnotationType()), component, context, this,
contextDestroyer);
}
}
}
}
/** @param type will return an array of its annotations.
* @return array of annotations or null. GWT utility. */
private static com.badlogic.gdx.utils.reflect.Annotation[] getAnnotations(final Class<?> type) {
try {
return ClassReflection.getAnnotations(type);
} catch (final Exception exception) {
Exceptions.ignore(exception);
return null;
}
}
/** Scans class tree of component to process all its methods.
*
* @param component all methods of its class tree will be processed.
* @param context used to resolve dependencies.
* @param contextDestroyer used to register destruction callbacks. */
private void processMethods(final Object component, final Context context,
final ContextDestroyer contextDestroyer) {
Class<?> componentClass = component.getClass();
while (componentClass != null && !componentClass.equals(Object.class)) {
final Method[] methods = ClassReflection.getDeclaredMethods(componentClass);
if (methods != null && methods.length > 0) {
processMethods(component, methods, context, contextDestroyer);
}
componentClass = componentClass.getSuperclass();
}
}
/** Does the actual processing of found methods.
*
* @param component owner of the methods.
* @param methods present in one of superclasses of the component.
* @param context used to resolve dependencies.
* @param contextDestroyer used to register destruction callbacks. */
@SuppressWarnings({ "rawtypes", "unchecked" }) // Using correct types, but wildcards fail to see that.
private void processMethods(final Object component, final Method[] methods, final Context context,
final ContextDestroyer contextDestroyer) {
for (final Method method : methods) {
final com.badlogic.gdx.utils.reflect.Annotation[] annotations = getAnnotations(method);
if (annotations == null || annotations.length == 0) {
continue;
}
for (final com.badlogic.gdx.utils.reflect.Annotation annotation : annotations) {
if (methodProcessors.containsKey(annotation.getAnnotationType())) {
for (final AnnotationProcessor processor : methodProcessors.get(annotation.getAnnotationType())) {
processor.processMethod(method, annotation.getAnnotation(annotation.getAnnotationType()),
component, context, this, contextDestroyer);
}
}
}
}
}
/** @param method will return an array of its annotations.
* @return array of annotations or null. GWT utility. */
private static com.badlogic.gdx.utils.reflect.Annotation[] getAnnotations(final Method method) {
try {
return method.getDeclaredAnnotations();
} catch (final Exception exception) {
Exceptions.ignore(exception);
return null;
}
}
/** Scans class tree of component to process all its fields.
*
* @param component all fields of its class tree will be processed.
* @param context used to resolve dependencies.
* @param contextDestroyer used to register destruction callbacks. */
private void processFields(final Object component, final Context context, final ContextDestroyer contextDestroyer) {
Class<?> componentClass = component.getClass();
while (componentClass != null && !componentClass.equals(Object.class)) {
final Field[] fields = ClassReflection.getDeclaredFields(componentClass);
if (fields != null && fields.length > 0) {
processFields(component, fields, context, contextDestroyer);
}
componentClass = componentClass.getSuperclass();
}
}
/** Does the actual processing of found fields.
*
* @param component owner of the fields.
* @param fields present in one of superclasses of the component.
* @param context used to resolve dependencies.
* @param contextDestroyer used to register destruction callbacks. */
@SuppressWarnings({ "rawtypes", "unchecked" }) // Using correct types, but wildcards fail to see that.
private void processFields(final Object component, final Field[] fields, final Context context,
final ContextDestroyer contextDestroyer) {
for (final Field field : fields) {
final com.badlogic.gdx.utils.reflect.Annotation[] annotations = getAnnotations(field);
if (annotations == null || annotations.length == 0) {
continue;
}
for (final com.badlogic.gdx.utils.reflect.Annotation annotation : annotations) {
if (fieldProcessors.containsKey(annotation.getAnnotationType())) {
for (final AnnotationProcessor processor : fieldProcessors.get(annotation.getAnnotationType())) {
processor.processField(field, annotation.getAnnotation(annotation.getAnnotationType()),
component, context, this, contextDestroyer);
}
}
}
}
}
/** @param field will return an array of its annotations.
* @return array of annotations or null. GWT utility. */
private static com.badlogic.gdx.utils.reflect.Annotation[] getAnnotations(final Field field) {
try {
return field.getDeclaredAnnotations();
} catch (final Exception exception) {
Exceptions.ignore(exception);
return null;
}
}
}