/*
* MicroJIAC - A Lightweight Agent Framework
* This file is part of MicroJIAC Config.
*
* Copyright (c) 2007-2012 DAI-Labor, Technische Universität Berlin
*
* This library includes software developed at DAI-Labor, Technische
* Universität Berlin (http://www.dai-labor.de)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* $Id$
*/
package de.jiac.micro.config.generator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import de.dailab.jiac.common.aamm.ComplexType;
import de.dailab.jiac.common.aamm.IEntryType;
import de.dailab.jiac.common.aamm.IItemType;
import de.dailab.jiac.common.aamm.IModelBase;
import de.dailab.jiac.common.aamm.IPropertyType;
import de.dailab.jiac.common.aamm.IReferencableComplexType;
import de.dailab.jiac.common.aamm.ISimpleType;
import de.dailab.jiac.common.aamm.ListPropertyType;
import de.dailab.jiac.common.aamm.MapPropertyType;
import de.dailab.jiac.common.aamm.ReferencableAgentElementType;
import de.dailab.jiac.common.aamm.ReferencableAgentType;
import de.dailab.jiac.common.aamm.ReferencableNodeType;
import de.dailab.jiac.common.aamm.ReferencableObjectType;
import de.dailab.jiac.common.aamm.ReferencePropertyType;
import de.dailab.jiac.common.aamm.ReferenceType;
import de.dailab.jiac.common.aamm.SimplePropertyType;
import de.dailab.jiac.common.aamm.beans.Introspector;
import de.dailab.jiac.common.aamm.check.Expression;
import de.dailab.jiac.common.aamm.check.DefinitionChecker.CheckerResult;
import de.dailab.jiac.common.aamm.ext.Reference;
import de.dailab.jiac.common.aamm.resolve.MergedConfiguration;
import de.dailab.jiac.common.aamm.resolve.ResolutionException;
import de.dailab.jiac.common.aamm.util.IMetaDataConstants;
import de.jiac.micro.config.analysis.ConventionEnforcer;
import de.jiac.micro.config.util.MicroJIACToolContext;
/**
* Like all other generator classes, this one is a mess. So far, I hadn't any idea
* how to make it more readable/maintainable :-/
*
*
* @author Erdene-Ochir Tuguldur
* @author Marcel Patzlaff
* @version $Revision$
*/
public class ConfigurationGenerator {
private final ClassLoader _loader;
private final Logger _logger;
private final DateFormat _dateFormat;
private ConfigurationGenerator(ClassLoader loader, Logger log) {
_loader= loader;
_logger= log;
_dateFormat= DateFormat.getDateTimeInstance();
}
public AbstractConfiguration[] generate(String packageName, MergedConfiguration configuration) throws ResolutionException {
ArrayList<AbstractConfiguration> result= new ArrayList<AbstractConfiguration>();
for(Reference nodeReference : configuration.nodesToUse) {
HashSet<String> fqacn= new HashSet<String>();
String className= nodeReference.toJavaIdentifier();
StringWriter buffer= new StringWriter();
PrintWriter writer= new PrintWriter(buffer);
ReferencableNodeType node= configuration.getNode(nodeReference);
writeHeader(writer);
visitNode(configuration, writer, node, packageName, className, fqacn);
writer.flush();
writer.close();
result.add(new NodeConfiguration(packageName + "." + className, buffer.getBuffer(), fqacn.toArray(new String[fqacn.size()])));
}
for(Reference agentReference : configuration.agentsToUse) {
String className= agentReference.toJavaIdentifier();
StringWriter buffer= new StringWriter();
PrintWriter writer= new PrintWriter(buffer);
ReferencableAgentType agent= configuration.getAgent(agentReference);
writeHeader(writer);
visitAgent(configuration, writer, agent, packageName, className);
writer.flush();
writer.close();
result.add(new AgentConfiguration(packageName + "." + className, buffer.getBuffer()));
}
return result.toArray(new AbstractConfiguration[result.size()]);
}
public AbstractConfiguration[] generate(File targetDir, String packageName, MergedConfiguration configuration) throws IOException, ResolutionException {
HashSet<AbstractConfiguration> descriptors = new HashSet<AbstractConfiguration>();
for (Reference nodeReference : configuration.nodesToUse) {
HashSet<String> fqacn= new HashSet<String>();
String className= nodeReference.toJavaIdentifier();
File sourceFile= new File(targetDir, className + ".java");
PrintWriter writer = new PrintWriter(new FileOutputStream(sourceFile));
ReferencableNodeType node= configuration.getNode(nodeReference);
writeHeader(writer);
visitNode(configuration, writer, node, packageName, className, fqacn);
writer.flush();
writer.close();
descriptors.add(new NodeConfiguration(packageName + "." + className, null, fqacn.toArray(new String[fqacn.size()])));
}
for (Reference agentReference : configuration.agentsToUse) {
String className= agentReference.toJavaIdentifier();
File sourceFile= new File(targetDir, className + ".java");
PrintWriter writer = new PrintWriter(new FileOutputStream(sourceFile));
ReferencableAgentType agent= configuration.getAgent(agentReference);
writeHeader(writer);
visitAgent(configuration, writer, agent, packageName, className);
writer.flush();
writer.close();
descriptors.add(new AgentConfiguration(packageName + "." + className, null));
}
return descriptors.toArray(new AbstractConfiguration[descriptors.size()]);
}
protected void visitNode(MergedConfiguration configuration, PrintWriter writer, ReferencableNodeType nodeType, String packageName, String className, HashSet<String> fullQualifiedAgentConfigurationNames) {
String id = getJavaExpression(Reference.createFor(nodeType).toString());
String displayName = getJavaExpression(nodeType.getDisplayName() == null ? nodeType.getId() : nodeType.getDisplayName());
_logger.info("generate configuration class: " + className);
writer.println("package " + packageName + ";");
writer.println("import de.jiac.micro.core.*;");
writer.println("import de.jiac.micro.internal.core.*;");
writer.println();
writer.println("public class " + className + " extends AbstractNodeConfiguration {");
writer.println("\t");
// constructor of GeneratedNodeConfiguration
_logger.debug("generate constructor of node configuration");
writer.println("\tpublic " + className + "() {");
writer.println("\t\tsuper(" + id + ", " + displayName + ", " + nodeType.getClazz() + ".class, new String[]{");
for(Iterator<ReferenceType> iter= nodeType.getAgentRefs().iterator(); iter.hasNext();) {
ReferenceType agentRef= iter.next();
String fullQualifiedAgentConfigurationName= packageName + "." + Reference.createFrom(agentRef).toJavaIdentifier();
fullQualifiedAgentConfigurationNames.add(fullQualifiedAgentConfigurationName);
writer.append("\t\t\t\"").append(fullQualifiedAgentConfigurationName).print('\"');
if(iter.hasNext()) {
writer.print(',');
}
writer.println();
}
writer.println("\t\t});");
writer.println("\t}");
writer.println();
HashSet<Reference> toProcess= new HashSet<Reference>();
_logger.debug("generate 'configureNode(AbstractNode node)'");
writer.println("\tprotected void configureNode(AbstractNode node) {");
insertPropertySetters("\t\t", writer, nodeType, "node", toProcess);
writer.println("\t}");
writer.println();
// now the delayed objects
_logger.debug("generate get<full-qualified-id> object methods");
HashSet<Reference> processed= new HashSet<Reference>();
do {
HashSet<Reference> temporarySet= new HashSet<Reference>(toProcess);
toProcess.clear();
for(Reference ref : temporarySet) {
visitObject("\t", writer, configuration, ref, toProcess);
processed.add(ref);
}
toProcess.removeAll(processed);
} while(!toProcess.isEmpty());
writer.println("}");
}
protected void visitAgent(MergedConfiguration configuration, PrintWriter writer, ReferencableAgentType agentType, String packageName, String className) throws ResolutionException {
String id = getJavaExpression(Reference.createFor(agentType).toString());
String displayName = getJavaExpression(agentType.getDisplayName() == null ? agentType.getId() : agentType.getDisplayName());
Hashtable<String, ComplexType> properties = new Hashtable<String, ComplexType>();
properties.put(id, agentType);
_logger.info("generate configuration class: " + className);
writer.println("package " + packageName + ";");
writer.println("import de.jiac.micro.core.*;");
writer.println("import de.jiac.micro.internal.core.*;");
writer.println();
writer.println("public class " + className + " extends AbstractAgentConfiguration {");
writer.println("\t");
// constructor of GeneratedNodeConfiguration
_logger.debug("generate constructor of agent configuration");
writer.println("\tpublic " + className + "() {");
writer.append("\t\tsuper(").append(id).append(", ").append(displayName).append(", ").append(agentType.getClazz()).println(".class);");
writer.println("\t}");
writer.println();
HashSet<Reference> toProcess= new HashSet<Reference>();
_logger.debug("generate 'configureAgent(AbstractAgent agent)'");
writer.println("\tprotected void configureAgent(AbstractAgent agent){");
for(IModelBase agentElementReference : agentType.getAgentElements()) {
ReferencableAgentElementType ae= configuration.getAgentElement(agentElementReference);
Reference ref= Reference.createFor(ae);
toProcess.add(ref);
writer.append("\t\t").append("agent.addAgentElement(get").append(ref.toJavaIdentifier()).println("());");
}
insertPropertySetters("\t\t", writer, agentType, "agent", toProcess);
writer.println("\t}");
writer.println();
// now the delayed objects
_logger.debug("generate get<full-qualified-id> object methods");
HashSet<Reference> processed= new HashSet<Reference>();
do {
HashSet<Reference> temporarySet= new HashSet<Reference>(toProcess);
toProcess.clear();
for(Reference ref : temporarySet) {
visitObject("\t", writer, configuration, ref, toProcess);
processed.add(ref);
}
toProcess.removeAll(processed);
} while(!toProcess.isEmpty());
writer.println("}");
}
protected void visitObject(String indent, PrintWriter writer, MergedConfiguration config, Reference ref, HashSet<Reference> toProcess) {
IReferencableComplexType object= config.cache.get(ref);
boolean singleton= (object instanceof ReferencableObjectType) && ((ReferencableObjectType)object).isSingleton();
String identifier= ref.toJavaIdentifier();
if(singleton) {
writer.println(indent + "private " + object.getClazz() + " " + identifier + "= null;");
}
// method body
writer.println(indent + "protected final " + object.getClazz() + " get" + identifier + "() {");
String workIndent;
if(singleton) {
writer.println(indent + "\tif(" + identifier + " == null) {");
workIndent= indent + "\t\t";
writer.println(workIndent + identifier + "= new " + object.getClazz() + "();");
} else {
workIndent= indent + "\t";
writer.println(workIndent + object.getClazz() + " " + identifier + "= new " + object.getClazz() + "();");
}
insertPropertySetters(workIndent, writer, object, identifier, toProcess);
if(singleton) {
writer.println(indent + "\t}");
}
writer.println(indent + "\treturn " + identifier + ";");
writer.println(indent + "}");
}
protected void visitSimpleProperty(String indent, PrintWriter writer, String variable, SimplePropertyType simplePropertyType) {
Method setter= (Method) simplePropertyType.getMetaData(IMetaDataConstants.SETTER_METHOD_KEY);
writer.append(indent);
insertCastExpression(writer, variable, setter.getDeclaringClass());
writer.println("." + setter.getName() + "(" + getJavaExpression(simplePropertyType) + ");");
}
private void insertListPropertyArrayInitialisation(String indent, PrintWriter writer, String typeName, List<IItemType> list, HashSet<Reference> toProcess) {
writer.println("new " + typeName + "[]{");
for(Iterator<IItemType> iter= list.iterator(); iter.hasNext(); ) {
IItemType item= iter.next();
writer.print(indent + "\t");
if(item instanceof ReferenceType) {
Reference ref= Reference.createFrom((ReferenceType) item);
toProcess.add(ref);
writer.print("get" + ref.toJavaIdentifier() + "()");
} else {
writer.print(getJavaExpression((ISimpleType) item));
}
writer.println(iter.hasNext() ? "," : "");
}
writer.print(indent + "}");
}
private void insertListPropertyListInitialisation(String indent, PrintWriter writer, String listVariable, Class<?> listType, List<IItemType> list, HashSet<Reference> toProcess) {
String addMethod;
try {
Class<?> vectorClass= Introspector.loadClass(listType, "java.util.Vector");
if(vectorClass.isAssignableFrom(listType)) {
writer.println(indent + listType.getName() + " " + listVariable + "= new " + listType.getName() + "();");
addMethod= "addElement";
} else {
if(listType.isInterface()) {
writer.println(indent + "java.util.ArrayList " + listVariable + "= new java.util.ArrayList();");
} else {
writer.println(indent + listType.getName() + " " + listVariable + "= " + listType.getName() + "();");
}
addMethod= "add";
}
} catch (Exception e) {
throw new AssertionError(e);
}
for(IItemType item : list) {
writer.print(indent + listVariable + "." + addMethod + "(");
if(item instanceof ReferenceType) {
Reference ref= Reference.createFrom((ReferenceType) item);
toProcess.add(ref);
writer.print("get" + ref.toJavaIdentifier() + "()");
} else {
writer.print(getJavaExpression((ISimpleType) item));
}
writer.println(");");
}
}
protected void visitListProperty(String indent, PrintWriter writer, String variable, ListPropertyType listPropertyType, HashSet<Reference> toProcess) {
List<IItemType> list= listPropertyType.getItems();
if(list.size() <= 0) {
return;
}
Method setter= (Method) listPropertyType.getMetaData(IMetaDataConstants.SETTER_METHOD_KEY);
if(setter != null) {
Class<?> paramType= setter.getParameterTypes()[0];
if(paramType.isArray()) {
writer.append(indent);
insertCastExpression(writer, variable, setter.getDeclaringClass());
writer.print("." + setter.getName() + "(");
insertListPropertyArrayInitialisation(indent, writer, paramType.getComponentType().getName(), list, toProcess);
writer.println(");");
} else {
String listVariable= listPropertyType.getName();
insertListPropertyListInitialisation(indent, writer, listVariable, paramType, list, toProcess);
writer.append(indent);
insertCastExpression(writer, listVariable, setter.getDeclaringClass());
writer.println("." + setter.getName() + "(" + listVariable + ");");
}
} else {
// we initialise the list item per item
Method indexedSetter= null;
for(int i= 0, last= list.size() - 1; i <= last; ++i) {
IItemType item= list.get(i);
if(indexedSetter == null) {
indexedSetter= (Method) item.getMetaData(IMetaDataConstants.SETTER_METHOD_KEY);
}
writer.append(indent);
insertCastExpression(writer, variable, indexedSetter.getDeclaringClass());
writer.print("." + indexedSetter.getName() + "(" + i + ", ");
if(item instanceof ReferenceType) {
Reference ref= Reference.createFrom((ReferenceType) item);
toProcess.add(ref);
writer.print("get" + ref.toJavaIdentifier() + "()");
} else {
writer.print(getJavaExpression((ISimpleType) item));
}
writer.println(");");
}
}
}
protected void visitMapProperty(String indent, PrintWriter writer, String variable, MapPropertyType mapPropertyType, HashSet<Reference> toProcess) {
Method setter= (Method) mapPropertyType.getMetaData(IMetaDataConstants.SETTER_METHOD_KEY);
if(setter != null) {
// we build the map
Class<?> paramType= setter.getParameterTypes()[0];
String typeName= paramType.isInterface() ? "java.util.Hashtable" : paramType.getName();
writer.println(indent + typeName + " " + mapPropertyType.getName() + "= new " + typeName + "();");
for(IEntryType entry : mapPropertyType.getEntries()) {
writer.print(indent + mapPropertyType.getName() + ".put(" + getJavaExpression(entry.getKey()) + ", ");
if(entry instanceof ReferenceType) {
Reference ref= Reference.createFrom((ReferenceType) entry);
toProcess.add(ref);
writer.print("get" + ref.toJavaIdentifier() + "()");
} else {
writer.print(getJavaExpression((ISimpleType) entry));
}
writer.println(");");
writer.append(indent);
insertCastExpression(writer, variable, setter.getDeclaringClass());
writer.println("." + setter.getName() + "(" + mapPropertyType.getName() + ");");
}
} else {
// we initialise the map entry per entry
Method mappedSetter= null;
for(IEntryType entry : mapPropertyType.getEntries()) {
if(mappedSetter == null) {
mappedSetter= (Method) entry.getMetaData(IMetaDataConstants.SETTER_METHOD_KEY);
}
writer.append(indent);
insertCastExpression(writer, variable, mappedSetter.getDeclaringClass());
writer.print("." + mappedSetter.getName() + "(" + getJavaExpression(entry.getKey()) + ", ");
if(entry instanceof ReferenceType) {
Reference ref= Reference.createFrom((ReferenceType) entry);
toProcess.add(ref);
writer.print("get" + ref.toJavaIdentifier() + "()");
} else {
writer.print(getJavaExpression((ISimpleType) entry));
}
writer.println(");");
}
}
}
protected void visitObjectProperty(String indent, PrintWriter writer, String variable, ReferencePropertyType objectReferencePropertyType, HashSet<Reference> toProcess) {
Method setter= (Method) objectReferencePropertyType.getMetaData(IMetaDataConstants.SETTER_METHOD_KEY);
Reference ref= Reference.createFrom(objectReferencePropertyType);
toProcess.add(ref);
writer.append(indent);
insertCastExpression(writer, variable, setter.getDeclaringClass());
writer.println("." + setter.getName() + "(get" + ref.toJavaIdentifier() + "());");
}
private String getJavaExpression(ISimpleType value) {
Expression expr= Expression.parseAndConvertToJavaExpression(value.getValue(), _loader);
return expr.convertToJavaExpression((Class<?>) value.getMetaData(ISimpleType.KEY_CLASSTYPE));
}
private String getJavaExpression(String string) {
Expression expr= Expression.parseAndConvertToJavaExpression(string, _loader);
return expr.convertToJavaExpression(String.class);
}
private void insertCastExpression(PrintWriter writer, String variable, Class<?> type) {
writer.append("((").append(type.getName()).append(')').append(variable).append(')');
}
private void insertPropertySetters(String workIndent, PrintWriter writer, IReferencableComplexType object, String identifier, HashSet<Reference> toProcess) {
for(IPropertyType property : object.getProperties()) {
if(property instanceof SimplePropertyType) {
visitSimpleProperty(workIndent, writer, identifier, (SimplePropertyType) property);
} else if(property instanceof ListPropertyType) {
visitListProperty(workIndent, writer, identifier, (ListPropertyType) property, toProcess);
} else if(property instanceof MapPropertyType) {
visitMapProperty(workIndent, writer, identifier, (MapPropertyType) property, toProcess);
} else {
visitObjectProperty(workIndent, writer, identifier, (ReferencePropertyType) property, toProcess);
}
}
}
private void writeHeader(PrintWriter writer) {
writer.println("//");
writer.println("// This file was generated by the Configuration Generator for MicroJIAC.");
writer.println("// Any modifications to this file will be lost upon re-running the configurator!");
writer.append("// Generated on: ").println(_dateFormat.format(new Date(System.currentTimeMillis())));
writer.println("//");
writer.println();
writer.println();
}
/**
* Return the array of generated node configurations.
*
* @param applicationNamespace the namespace where the application definition is located
* @param loader the classloader which has access to all required resources and classes
* @param log the log that outputs the plugin informations
* @return full-qualified class names of all generated node configurations
*
* @throws Exception if an error occures during execution
*/
public static AbstractConfiguration[] execute(String applicationNamespace, ClassLoader loader, Logger log) throws Exception {
String packageName= "de.jiac.micro.internal.latebind";
MicroJIACToolContext context= new MicroJIACToolContext(loader);
ConfigurationGenerator generator= new ConfigurationGenerator(context.getLoader(), log);
MergedConfiguration conf= context.createResolver().resolveAndMerge(applicationNamespace);
CheckerResult checkerResult= context.createChecker().flattenAndCheck(conf);
// first print the warnings
for(String warning : checkerResult.warnings) {
log.warn(warning);
}
// print errors
for(String error : checkerResult.errors) {
log.error(error);
}
if(checkerResult.hasErrors()) {
throw new GeneratorException("checker has found errors");
}
return generator.generate(packageName, conf);
}
/**
* Return the array of descriptors for each generated configuration.
*
* @param rootDirectory the directory to place the package and classes into
* @param applicationNamespace the namespace where the application definition is located
* @param loader the classloader which has access to all required resources and classes
* @param log the log that outputs the plugin informations
* @return descriptors of the generated configurations
*
* @throws Exception if an error occures during execution
*/
public static AbstractConfiguration[] execute(File rootDirectory, String applicationNamespace, ClassLoader loader, Logger log) throws Exception {
String packageName= "de.jiac.micro.internal.latebind";
File lateBindDirectory= new File(rootDirectory, packageName.replace('.', File.separatorChar));
if(!lateBindDirectory.exists()) {
lateBindDirectory.mkdirs();
}
MicroJIACToolContext context= new MicroJIACToolContext(loader);
ConfigurationGenerator generator= new ConfigurationGenerator(context.getLoader(), log);
MergedConfiguration conf= context.createResolver().resolveAndMerge(applicationNamespace);
CheckerResult checkerResult= context.createChecker().flattenAndCheck(conf);
// first print the warnings
for(String warning : checkerResult.warnings) {
log.warn(warning);
}
// print errors
for(String error : checkerResult.errors) {
log.error(error);
}
if(checkerResult.hasErrors()) {
throw new GeneratorException("checker has found errors");
}
//ConventionEnforcer analyser= context.createEnforcer();
//analyser.analyseAndEnforce(conf);
return generator.generate(lateBindDirectory, packageName, conf);
}
// public static void main(String args[]) throws Exception {
// execute(
// new File(System.getProperty("java.io.tmpdir")),
// args[0],
// ConfigurationGenerator.class.getClassLoader(),
// new SimpleLog(ConfigurationGenerator.class.getName())
// );
// }
}