package net.sourceforge.javautil.groovy.builder.interceptor.objectfactory;
import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import net.sourceforge.javautil.groovy.builder.GroovyBuilder;
import net.sourceforge.javautil.groovy.builder.interceptor.annotation.Alias;
import net.sourceforge.javautil.groovy.builder.interceptor.annotation.Node;
import org.codehaus.groovy.runtime.InvokerHelper;
/**
* This will take one or more packages as the source of objects who's
* names should match the node names called with a capitalized first letter.
*
* @author elponderador
*
* @param <T> The type of object this instantiator should be producing.
*/
public class PackageInstantiator<B extends GroovyBuilder,T> implements ObjectFactoryInstantiator<B,T> {
private static final Object[] EMPTY_ARRAY = new Object[0];
public static String[] getPackageNames (Class... classes) {
String[] array = new String[classes.length];
for (int c=0; c<classes.length; c++) array[c] = classes[c].getPackage().getName();
return array;
}
private List<String> pkgs = new CopyOnWriteArrayList<String>();
private Map<String, Class> aliases = new LinkedHashMap<String, Class>();
/**
* This is convenience constructor that passes the package(s) name of the class passed onto the {@link #PackageInstantiator(String...)} constructor.
*
* @param clazz The class(es) whose package(s) will provide the classes that are targets of node building calls
*/
public PackageInstantiator(Class... classes) {
this(getPackageNames(classes));
for (Class<?> clazz : classes) {
Node node = clazz.getAnnotation(Node.class);
if (node != null && node.aliases().length > 0) {
for (Alias alias : node.aliases()) {
this.aliases.put(alias.name(), alias.type());
}
}
}
}
/**
* This allows you to specify if you want the proxy to be called before any possible found
* meta methods on the builder.
*
* @param passParentToChild See {@link #isPassParentToConstructor()}
* @param callProxyFirst See {@link #isCallProxyFirst()}
* @param pkgs The package(s) that contain the object that will be the targets of node building calls.
*
* @see #handleInvokedMethod(GroovyBuilder, GroovyBuilderStack, MetaMethod, String, Object[])
* @see {@link #isCallProxyFirst()}
*/
public PackageInstantiator(String... pkgs) {
Collections.addAll(this.pkgs, pkgs);
}
/**
* This can be used to allow certain classes to be referenced by different names.
*
* @param name The alias for the type
* @param type The type the alias routes to
*/
public void alias (String name, Class type) {
this.aliases.put(name, type);
}
public T instantiateNode (B builder, ObjectFactoryInterceptor ofi, String nodeName, T parent) {
MetaClass mc = ofi.getMetaClassForNode(nodeName);
if (mc == null) {
if (this.aliases.containsKey(nodeName)) {
ofi.setMetaClassForNode(nodeName, InvokerHelper.getMetaClass(this.aliases.get(nodeName)));
} else {
String newNodeName = nodeName.substring(0, 1).toUpperCase() + nodeName.substring(1);
for (String pkg : this.pkgs) {
try {
Class clazz = Class.forName(pkg + "." + newNodeName, false, Thread.currentThread().getContextClassLoader());
ofi.setMetaClassForNode(nodeName, mc = InvokerHelper.getMetaClass(clazz));
break;
} catch (ClassNotFoundException e) {}
}
if (mc == null) ofi.setMetaClassForNode(nodeName, null);
}
}
return (T) (mc == null ? null : mc.invokeConstructor(parent == null || !ofi.isPassParentToConstructor() ? EMPTY_ARRAY : new Object[] { parent }));
}
}