Improve DependencyHandler for DhApiEventHandler support

Specifically dependencyHandler can now optionally store multiple implementations for each dependency interface.
This commit is contained in:
James Seibel
2022-07-16 22:02:37 -05:00
parent 207eded4b4
commit 88915ca92f
4 changed files with 121 additions and 58 deletions
@@ -19,8 +19,7 @@
package com.seibel.lod.core.handlers.dependencyInjection;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -29,66 +28,80 @@ import java.util.Map;
*
* @param <BindableType> extends IBindable and defines what interfaces this dependency handler can deal with.
* @author James Seibel
* @version 2022-7-15
* @version 2022-7-16
*/
public class DependencyHandler<BindableType extends IBindable>
{
protected final Logger logger;
protected final Map<Class<? extends BindableType>, Object> dependencies = new HashMap<Class<? extends BindableType>, Object>();
protected final Map<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
/** Internal class reference to BindableType since we can't get it any other way. */
protected final Class<? extends BindableType> bindableInterface;
protected final boolean allowDuplicateBindings;
public DependencyHandler(Class<BindableType> newBindableInterface, Logger newLogger)
public DependencyHandler(Class<BindableType> newBindableInterface)
{
this.bindableInterface = newBindableInterface;
this.logger = newLogger;
this.allowDuplicateBindings = false;
}
public DependencyHandler(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings)
{
this.bindableInterface = newBindableInterface;
this.allowDuplicateBindings = newAllowDuplicateBindings;
}
/**
* Links the given implementation object to an interface, so it can be referenced later.
*
* @param dependencyInterface The interface the implementation object should implement.
*
* @param dependencyInterface The interface (or parent class) the implementation object should implement.
* @param dependencyImplementation An object that implements the dependencyInterface interface.
* @throws IllegalStateException if the implementation object doesn't implement
* @throws IllegalStateException if the implementation object doesn't implement
* the interface or the interface has already been bound.
*/
public void bind(Class<? extends BindableType> dependencyInterface, Object dependencyImplementation) throws IllegalStateException
public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException
{
// make sure we haven't already bound this dependency
if (dependencies.containsKey(dependencyInterface))
// duplicate check if requested
if (dependencies.containsKey(dependencyInterface) && !this.allowDuplicateBindings)
{
throw new IllegalStateException("The dependency [" + dependencyInterface.getSimpleName() + "] has already been bound.");
}
// make sure the given dependency implements the necessary interfaces
boolean implementsInterface = checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface);
boolean implementsInterface = checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface)
|| checkIfClassExtends(dependencyImplementation.getClass(), dependencyInterface);
boolean implementsBindable = checkIfClassImplements(dependencyImplementation.getClass(), this.bindableInterface);
// display any errors
if (!implementsInterface)
{
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface [" + dependencyInterface.getSimpleName() + "].");
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement or extend: [" + dependencyInterface.getSimpleName() + "].");
}
if (!implementsBindable)
{
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface [" + IBindable.class.getSimpleName() + "].");
throw new IllegalStateException("The dependency [" + dependencyImplementation.getClass().getSimpleName() + "] doesn't implement the interface: [" + IBindable.class.getSimpleName() + "].");
}
dependencies.put(dependencyInterface, dependencyImplementation);
// make sure the hashSet has an array to hold the dependency
if (!dependencies.containsKey(dependencyInterface))
{
dependencies.put(dependencyInterface, new ArrayList<BindableType>());
}
// add the dependency
dependencies.get(dependencyInterface).add(dependencyImplementation);
}
/**
* Checks if classToTest (or one of its ancestors)
* implements the given interface.
*/
private boolean checkIfClassImplements(Class<?> classToTest, Class<?> interfaceToLookFor)
protected boolean checkIfClassImplements(Class<?> classToTest, Class<?> interfaceToLookFor)
{
// check the parent class (if applicable)
if (classToTest.getSuperclass() != Object.class && classToTest.getSuperclass() != null)
@@ -120,40 +133,100 @@ public class DependencyHandler<BindableType extends IBindable>
return false;
}
/** Checks if classToTest extends the given class. */
protected boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor)
{
return extensionToLookFor.isAssignableFrom(classToTest);
}
/**
* Returns a dependency of type T if one has been bound.
* Returns null otherwise.
*
* @param <T> class of the dependency
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the dependency
* @return the dependency of type T
* @throws ClassCastException If the dependency isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
* This does not return incomplete dependencies. <Br>
* See {@link #get(Class, boolean) get(Class, boolean)} for full documentation.
*
* @see #get(Class, boolean)
*/
@SuppressWarnings("unchecked")
public <T extends BindableType> T get(Class<?> interfaceClass) throws ClassCastException
{
T dependency = (T) dependencies.get(interfaceClass);
if (dependency != null && !dependency.getDelayedSetupComplete())
return (T) getInternalLogic(interfaceClass, false).get(0);
}
/**
* Returns all dependencies of type T that have been bound. <br>
* Returns an empty list if no dependencies have been bound.
*
* @param <T> class of the dependency
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the dependency
* @return the dependency of type T
* @throws ClassCastException If the dependency isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
public <T extends BindableType> ArrayList<T> getAll(Class<?> interfaceClass) throws ClassCastException
{
return getInternalLogic(interfaceClass, false);
}
/**
* Returns a dependency of type T if one has been bound. <br>
* Returns null if a dependency hasn't been bound. <br> <br>
*
* If the handler's {@link #allowDuplicateBindings} is true this returns the first bound dependency.
*
* @param <T> class of the dependency
* (inferred from the objectClass parameter)
* @param interfaceClass Interface of the dependency
* @param allowIncompleteDependencies If true this method will also return dependencies that haven't completed their delayed setup.
* @return the dependency of type T
* @throws ClassCastException If the dependency isn't able to be cast to type T.
* (this shouldn't normally happen, unless the bound object changed somehow)
*/
@SuppressWarnings("unchecked")
public <T extends BindableType> T get(Class<?> interfaceClass, boolean allowIncompleteDependencies) throws ClassCastException
{
return (T) getInternalLogic(interfaceClass, allowIncompleteDependencies).get(0);
}
/**
* Always returns a list of size 1 or greater,
* if no dependencies have been bound the list will contain null.
*/
@SuppressWarnings("unchecked")
private <T extends BindableType> ArrayList<T> getInternalLogic(Class<?> interfaceClass, boolean allowIncompleteDependencies) throws ClassCastException
{
ArrayList<BindableType> dependencyList = dependencies.get(interfaceClass);
if (dependencyList != null && dependencyList.size() != 0)
{
// a warning can be used here instead if desired
//this.logger.warn("Got dependency of type [" + interfaceClass.getSimpleName() + "], but the dependency's delayed setup hasn't been run!");
throw new IllegalStateException("Got dependency of type [" + interfaceClass.getSimpleName() + "], but the dependency's delayed setup hasn't been run!");
// check if each dependencies' delayed setup has been completed
for (IBindable dependency : dependencyList)
{
if (!dependency.getDelayedSetupComplete() && !allowIncompleteDependencies)
{
// a warning can be used here instead if desired
//this.logger.warn("Got dependency of type [" + interfaceClass.getSimpleName() + "], but the dependency's delayed setup hasn't been run!");
throw new IllegalStateException("Got dependency of type [" + interfaceClass.getSimpleName() + "], but the dependency's delayed setup hasn't been run!");
}
}
return (ArrayList<T>) dependencyList;
}
return dependency;
// return an empty list to prevent null pointers
ArrayList<T> emptyList = new ArrayList<T>();
emptyList.add(null);
return emptyList;
}
/** Runs delayed setup for any dependencies that require it. */
public void runDelayedSetup()
{
for (Class<?> interfaceKey : dependencies.keySet())
{
IBindable concreteObject = get(interfaceKey);
IBindable concreteObject = get(interfaceKey, true);
if (!concreteObject.getDelayedSetupComplete())
{
concreteObject.finishDelayedSetup();
@@ -23,7 +23,7 @@ package com.seibel.lod.core.handlers.dependencyInjection;
* Necessary for all singletons that can be dependency injected.
*
* @author James Seibel
* @version 3-4-2022
* @version 2022-7-16
*/
public interface IBindable
{
@@ -34,30 +34,30 @@ import java.lang.invoke.MethodHandles;
*
* @author James Seibel
* @author Leetom
* @version 2022-7-15
* @version 2022-7-16
*/
public class ModAccessorHandler extends DependencyHandler<IModAccessor>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
public static final ModAccessorHandler INSTANCE = new ModAccessorHandler(IModAccessor.class, LOGGER);
public static final ModAccessorHandler INSTANCE = new ModAccessorHandler(IModAccessor.class);
public ModAccessorHandler(Class<IModAccessor> newBindableInterface, Logger newLogger)
public ModAccessorHandler(Class<IModAccessor> newBindableInterface)
{
super(newBindableInterface, LOGGER);
super(newBindableInterface, false);
}
/**
* Go to {@link DependencyHandler#bind(Class, Object) DependencyHandler.bind()}
* Go to {@link DependencyHandler#bind(Class, IBindable)} DependencyHandler.bind()}
* for this method's javadocs.
*/
public void bind(Class<? extends IModAccessor> interfaceClass, IModAccessor modAccessor)
throws IllegalStateException
{
super.bind(interfaceClass, modAccessor);
LOGGER.info("Registered mod compatibility accessor for " + modAccessor.getModName());
LOGGER.info("Registered mod compatibility accessor for: [" + modAccessor.getModName() + "].");
}
}
@@ -19,28 +19,18 @@
package com.seibel.lod.core.handlers.dependencyInjection;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.wrapperInterfaces.modAccessor.IModAccessor;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandles;
/**
* This class takes care of dependency injection
* for singletons.
*
* @author James Seibel
* @version 2022-7-15
* @version 2022-7-16
*/
public class SingletonHandler extends DependencyHandler<IBindable>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
public static final SingletonHandler INSTANCE = new SingletonHandler(IBindable.class, LOGGER);
public static final SingletonHandler INSTANCE = new SingletonHandler(IBindable.class);
public SingletonHandler(Class<IBindable> newBindableInterface, Logger newLogger)
{
super(newBindableInterface, newLogger);
}
public SingletonHandler(Class<IBindable> newBindableInterface) { super(newBindableInterface, false); }
}