ObjectFactory.java
package com.github.jasonmfehr.combiner.factory;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import com.github.jasonmfehr.combiner.logging.ParameterizedLogger;
import com.github.jasonmfehr.tojs.exception.NotAssignableException;
import com.github.jasonmfehr.tojs.exception.ObjectInstantiationException;
public abstract class ObjectFactory {
@Requirement
private PlexusContainer container;
@Requirement
private ParameterizedLogger logger;
protected abstract Class<?> getObjectClass();
protected abstract String getDefaultPackage();
public <T> T buildObject(final String classOrRole) {
final String fullClassOrRoleName;
T obj;
Object plexusObj;
if(classOrRole == null){
throw new NullPointerException("classOrRole is null");
}
fullClassOrRoleName = this.buildFullyQualified(classOrRole);
plexusObj = this.attemptPlexusRetrieve(fullClassOrRoleName);
if(plexusObj != null){
//generics in Java are a compile time check, but this class needs to do runtime type checks, thus
//the call to checkAssignability is necessary to ensure the object who's class was determined at
//runtime can be assigned to the class specified by this factory's getObjectClass method
if(this.checkAssignability(plexusObj.getClass())){
this.logger.debugWithParams("Found object with class {0} in the plexus container, returning that object", plexusObj.getClass().getCanonicalName());
return (T)plexusObj;
}else{
this.logger.debugWithParams("Could not cast object with class {0} that was retrieved from the plexus container to the expected type {1} of this factory, moving ahead with creating a new object", plexusObj.getClass().getCanonicalName(), this.getObjectClass().getCanonicalName());
}
}
obj = this.constructObject(fullClassOrRoleName);
return obj;
}
public <T> List<T> buildObjectList(final List<String> classOrRoleNames) {
final List<T> objectList = new ArrayList<T>();
for(String cN : classOrRoleNames){
//TODO figure out why this has to be cast
objectList.add((T)this.buildObject(cN));
}
return objectList;
}
@SuppressWarnings("unchecked")
private <T> T instantiateObject(Class<?> constructionClass, Object... args) throws NoSuchMethodException {
final T obj;
try{
obj = (T) ConstructorUtils.invokeConstructor(constructionClass, args);
} catch (IllegalAccessException e) {
throw new ObjectInstantiationException(constructionClass.getName(), e);
} catch (InvocationTargetException e) {
throw new ObjectInstantiationException(constructionClass.getName(), e);
} catch (InstantiationException e) {
throw new ObjectInstantiationException(constructionClass.getName(), e);
}
return obj;
}
private String buildFullyQualified(final String classOrRole) {
final String fullyQualified;
if(classOrRole.contains(".")){
this.logger.debugWithParams("Provided class or role {0} is already fully qualified", classOrRole);
fullyQualified = classOrRole;
}else{
fullyQualified = this.getDefaultPackage() + "." + classOrRole;
this.logger.debugWithParams("Instantiating class {0} after adding default package of {1}", fullyQualified, this.getDefaultPackage());
}
return fullyQualified;
}
private Object attemptPlexusRetrieve(String fullyQualifiedObjectName) {
Object obj = null;
try {
this.logger.debugWithParams("Attempting lookup from plexus container using value {0}", fullyQualifiedObjectName);
obj = this.container.lookup(fullyQualifiedObjectName);
} catch (ComponentLookupException e) {
this.logger.debugWithParams("Did not find component {0} in plexus container", fullyQualifiedObjectName);
}
return obj;
}
private <T> T constructObject(final String fullyQualifiedName) {
final Class<?> constructionClass;
T obj;
this.logger.debugWithParams("Attempting to load class {0}", fullyQualifiedName);
try {
constructionClass = ClassUtils.getClass(fullyQualifiedName);
} catch (ClassNotFoundException e) {
throw new ObjectInstantiationException(fullyQualifiedName, e);
}
if(!this.checkAssignability(constructionClass)){
throw new NotAssignableException(fullyQualifiedName);
}
try{
this.logger.debugWithParams("Attempting to instantiate an object with class {0} using a constructor that takes only a org.apache.maven.plugin.logging.Log parameter", fullyQualifiedName);
//try invoking the constructor that has a single org.apache.maven.plugin.logging.Log argument
obj = this.instantiateObject(constructionClass, this.logger);
}catch(NoSuchMethodException e){
try {
//no single argument constructor was found, try the default no argument constructor
this.logger.debugWithParams("Could not find a single argument constructor that takes a org.apache.maven.plugin.logging.Log parameter, attempting to use the default constructor to instantiate an object with class {0}", fullyQualifiedName);
obj = this.instantiateObject(constructionClass);
} catch (NoSuchMethodException e1) {
throw new ObjectInstantiationException(fullyQualifiedName, e);
}
}
this.logger.debugWithParams("Successfully instantiated an object with class {0}", fullyQualifiedName);
return obj;
}
/**
* Determines if the class of an object with the provided class can be cast to the class specified in the {@link #getObjectClass()} method.
*
* @param o {@link Class} that will be checked for type compatibility with the class returned from {@link #getObjectClass()}
* @return {@code boolean} with {@code true} indicating an object with the specified class can be cast or {@code false} indicating it cannot be cast
*/
private boolean checkAssignability(Class<?> clazz) {
return ClassUtils.isAssignable(clazz, this.getObjectClass());
}
}