All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.henryco.injector.meta.ModuleStruct Maven / Gradle / Ivy

package com.github.henryco.injector.meta;

import com.github.henryco.injector.meta.annotations.*;
import com.github.henryco.injector.meta.resolver.DefaultClassFinder;
import com.github.henryco.injector.meta.resolver.IClassFinder;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Created 12/15/2017
 *
 * @author Henry
 */
@SuppressWarnings("WeakerAccess")
public final class ModuleStruct {

	private static final Map structMap = new HashMap<>();

	/*package*/ final IClassFinder classFinder;
	/*package*/ final Class module;
	/*package*/ final Map singletons;
	/*package*/ final Set included;
	/*package*/ final Set> components;
	/*package*/ final Set> targets;

	public ModuleStruct(Class module, IClassFinder classFinder) {

		Module ma = module.getDeclaredAnnotation(Module.class);
		if (ma == null)
			throw new RuntimeException("Module " + module.getName() + " must be annotated as @Module");

		this.classFinder = classFinder == null ? new DefaultClassFinder() : classFinder;

		this.module = module;
		this.targets = new HashSet<>();
		this.included = new HashSet<>();
		this.components = new HashSet<>();
		this.singletons = new HashMap<>();

		addComponents(ma);
		addComponentsFromPackage(ma);
		addTargets(ma);
		addTargetsFromPackage(ma);
		addIncluded(ma, classFinder);

		processSingletons();

		structMap.put(module.getName(), this);
	}

	public ModuleStruct(Class module) {
		this(module, null);
	}

	public ModuleStruct(ModuleStruct other) {
		this.module = null;
		this.targets = new HashSet<>();
		this.singletons = new HashMap<>();
		this.components = new HashSet<>();
		this.included = new HashSet() {{
			add(other);
		}};
		this.classFinder = other.classFinder;
	}

	public Class getModule() {
		return module;
	}

	public boolean inject(Object target) {
		if (!targets.contains(target.getClass())) return false;
		Injector.injectDependenciesToInstance(target, new ModuleStruct(this));
		return true;
	}


	public boolean inject(Object target, String... components) {
		if (!targets.contains(target.getClass())) return false;
		Injector.injectDependenciesToInstance(target, new ModuleStruct(this), (Object[]) components);
		return true;
	}


	public boolean inject(Object target, Class... components) {
		if (!targets.contains(target.getClass())) return false;
		Injector.injectDependenciesToInstance(target, new ModuleStruct(this), (Object[]) components);
		return true;
	}


	public  T findOrInstanceByName(String name) {
		return Injector.findOrInstanceByName(name, new ModuleStruct(this));
	}


	public  T findOrInstanceByType(Class type) {
		return Injector.findOrInstanceByType(type, new ModuleStruct(this));
	}


	private  T findOrInstance(String name, Class type) {
		T o = Injector.findOrInstanceByName(name, new ModuleStruct(this));
		if (o != null)
			return o;
		return Injector.findOrInstanceByType(type, new ModuleStruct(this));
	}


	private void addIncluded(Module m, IClassFinder classFinder) {
		for (Class icl : m.include()) {
			ModuleStruct struct;
			if (!structMap.containsKey(icl.getName()))
				struct = new ModuleStruct(icl, classFinder);
			else struct = structMap.get(icl.getName());
			included.add(struct);
		}
	}


	private void addComponents(Module m) {
		for (Class component : m.components()) {
			Provide c = component.getDeclaredAnnotation(Provide.class);
			if (c == null) throw new RuntimeException("Component must be annotated as @Component");
			components.add(component);
		}
	}


	private void addComponentsFromPackage(Module m) {
		for (String path : m.componentsRootPath())
			processComponentPackagePath(path);
		for (Class cClass : m.componentsRootClass())
			processComponentPackagePath(cClass.getPackage().getName());
	}


	private void addTarget(Class target) {

		TargetInterface tia = target.getDeclaredAnnotation(TargetInterface.class);
		Module a = target.getDeclaredAnnotation(Module.class);
		Provide b = target.getDeclaredAnnotation(Provide.class);
		if (a != null || b != null) return;

		if (tia == null) {
			this.targets.add(target);
			return;
		}

		if (!target.isInterface())
			throw new RuntimeException("@TargetInterface annotation acceptable only for interfaces");

		for (Method method : target.getDeclaredMethods()) {
			if (method.getDeclaredAnnotation(Inject.class) == null)
				continue;
			if (method.getParameterCount() != 1)
				throw new RuntimeException("Injection target method must contain only one argument");
			this.targets.add(method.getParameters()[0].getType());
		}
	}


	private void addTargets(Module m) {
		for (Class target : m.targets())
			addTarget(target);
	}


	private void addTargetsFromPackage(Module m) {
		for (String path : m.targetsRootPath())
			processTargetPackagePath(path);
		for (Class aClass : m.targetsRootClass())
			processTargetPackagePath(aClass.getPackage().getName());
	}


	private void processTargetPackagePath(String path) {

		try {
			List> classes = classFinder.getClassesForPackage(path);
			for (Class target : classes) {
				addTarget(target);
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}


	private void processComponentPackagePath(String path) {

		try {

			List> classes = classFinder.getClassesForPackage(path);
			for (Class component : classes) {
				Provide c = component.getDeclaredAnnotation(Provide.class);
				if (c == null) continue;
				components.add(component);
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}


	private void processSingletons() {

		Method[] methods = module.getDeclaredMethods();

		for (Method method : methods) {
			if (method.getDeclaredAnnotation(Singleton.class) == null) continue;

			Provide provide = method.getDeclaredAnnotation(Provide.class);
			if (provide == null) continue;
			String name = provide.value().isEmpty() ? method.getName() : provide.value();

			if (singletons.containsKey(name)) continue;
			Object instance = findOrInstance(name, method.getReturnType());
			singletons.put(name, instance);
		}

		for (Class component : components) {
			if (component.getDeclaredAnnotation(Singleton.class) == null) continue;

			Provide c = component.getDeclaredAnnotation(Provide.class);
			if (c == null) continue;
			String name = c.value().isEmpty() ? component.getSimpleName() : c.value();

			if (singletons.containsKey(name)) continue;
			Object instance = findOrInstance(name, component);
			singletons.put(name, instance);
		}
	}


	@Override
	public String toString() {
		return module == null ? "ROOT_MODULE" : (module.getSimpleName() + ".class");
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy