001: package org.andromda.maven.plugin.andromdapp.script;
002:
003: import java.io.File;
004:
005: import java.net.URL;
006:
007: import javassist.ClassPool;
008: import javassist.CtClass;
009: import javassist.CtField;
010: import javassist.CtMethod;
011: import javassist.LoaderClassPath;
012: import javassist.Modifier;
013: import javassist.NotFoundException;
014:
015: import org.andromda.core.common.ExceptionUtils;
016: import org.apache.commons.lang.StringUtils;
017:
018: /**
019: * This class instruments a given class file in order for it be scripted. A class modified
020: * by this script generator can have its methods edited and the logic available without having
021: * to redeploy or compile the class.
022: *
023: * @author Chad Brandon
024: */
025: public class ScriptClassGenerator {
026: /**
027: * The shared instance of this class.
028: */
029: private static ScriptClassGenerator instance;
030:
031: /**
032: * The name of the script wrapper to use.
033: */
034: private String scriptWrapperName;
035:
036: /**
037: * Retrieves an instance of this class and uses the given script wrapper with
038: * the given <code>scriptWrapperName</code>.
039: *
040: * @param scriptWrapperName the fully qualified name of the script wrapper class to use.
041: * @return the instance of this class.
042: */
043: public static final ScriptClassGenerator getInstance(
044: final String scriptWrapperName) {
045: ExceptionUtils.checkEmpty("scriptWrapperName",
046: scriptWrapperName);
047: instance = new ScriptClassGenerator();
048: instance.scriptWrapperName = scriptWrapperName;
049: return instance;
050: }
051:
052: private ScriptClassGenerator() {
053: // - do not allow instantiation
054: }
055:
056: /**
057: * Modifies the <code>existingClass</code> (basically inserts the script wrapper class into
058: * the class).
059: * @param scriptDirectory the directory in which to find the script.
060: * @param existingClass the class to modify.
061: */
062: public void modifyClass(final String scriptDirectory,
063: final Class existingClass) {
064: try {
065: final String className = existingClass.getName();
066:
067: final ClassPool pool = ClassPool.getDefault();
068: final ClassLoader contextClassLoader = Thread
069: .currentThread().getContextClassLoader();
070: if (contextClassLoader != null) {
071: pool.insertClassPath(new LoaderClassPath(
072: contextClassLoader));
073: }
074: final CtClass ctClass = pool.get(className);
075:
076: // - make sure the class isn't frozen
077: ctClass.defrost();
078:
079: final String scriptWrapperFieldName = "scriptWrapper";
080: try {
081: ctClass.getField(scriptWrapperFieldName);
082: } catch (Exception exception) {
083: final CtField scriptWrapper = new CtField(convert(pool,
084: this .scriptWrapperName),
085: scriptWrapperFieldName, ctClass);
086: scriptWrapper.setModifiers(Modifier.PRIVATE
087: + Modifier.FINAL);
088: ctClass.addField(scriptWrapper,
089: getScriptWrapperInitialization(scriptDirectory,
090: className));
091: }
092:
093: final CtMethod[] existingMethods = ctClass
094: .getDeclaredMethods();
095: for (int ctr = 0; ctr < existingMethods.length; ctr++) {
096: final CtMethod method = existingMethods[ctr];
097: if (!Modifier.isStatic(method.getModifiers())) {
098: final CtClass returnType = method.getReturnType();
099: String methodBody;
100: if (returnType.equals(CtClass.voidType)) {
101: methodBody = "{"
102: + contructArgumentString(method)
103: + "scriptWrapper.invoke(\""
104: + method.getName() + "\", arguments);}";
105: } else {
106: if (returnType.isPrimitive()) {
107: methodBody = "{"
108: + contructArgumentString(method)
109: + " return (("
110: + getWrapperTypeName(returnType)
111: + ")scriptWrapper.invoke(\""
112: + method.getName()
113: + "\", arguments))."
114: + returnType.getName()
115: + "Value();}";
116: } else {
117: methodBody = "{"
118: + contructArgumentString(method)
119: + " return ("
120: + method.getReturnType().getName()
121: + ")scriptWrapper.invoke(\""
122: + method.getName()
123: + "\", arguments);}";
124: }
125: }
126: method.setBody(methodBody);
127: }
128: }
129:
130: final File directory = getClassOutputDirectory(existingClass);
131:
132: pool.writeFile(className, directory != null ? directory
133: .getAbsolutePath() : "");
134: } catch (final Throwable throwable) {
135: throwable.printStackTrace();
136: throw new ScriptClassGeneratorException(throwable);
137: }
138: }
139:
140: /**
141: * Retrieves the output directory which the adapted class will be written to.
142: *
143: * @return the output directory
144: */
145: private File getClassOutputDirectory(final Class existingClass) {
146: final String className = existingClass.getName();
147: final String classResourcePath = '/'
148: + className.replace('.', '/') + ".class";
149: final URL classResource = existingClass
150: .getResource(classResourcePath);
151: if (classResource == null) {
152: throw new ScriptClassGeneratorException(
153: "Could not find the class resource '"
154: + classResourcePath + "'");
155: }
156: final String file = classResource.getFile().replaceAll(
157: ".*(\\\\|//)", "/");
158: return new File(StringUtils
159: .replace(file, classResourcePath, ""));
160: }
161:
162: private String contructArgumentString(final CtMethod method)
163: throws NotFoundException {
164: CtClass[] argumentTypes = method.getParameterTypes();
165: final int argumentNumber = argumentTypes.length;
166: final StringBuffer arguments = new StringBuffer(
167: "final Object[] arguments = new Object["
168: + argumentNumber + "];");
169: for (int ctr = 1; ctr <= argumentNumber; ctr++) {
170: final CtClass argumentType = argumentTypes[ctr - 1];
171: arguments.append("arguments[" + (ctr - 1) + "] = ");
172: if (argumentType.isPrimitive()) {
173: arguments.append("new java.lang."
174: + getWrapperTypeName(argumentType) + "($" + ctr
175: + ");");
176: } else {
177: arguments.append("$" + ctr + ";");
178: }
179: }
180: return arguments.toString();
181: }
182:
183: private String getWrapperTypeName(CtClass ctClass) {
184: final String typeName = ctClass.getName();
185: StringBuffer name = new StringBuffer(typeName);
186: if (typeName.equalsIgnoreCase("int")) {
187: name.append("eger");
188: }
189: return StringUtils.capitalize(name.toString());
190: }
191:
192: private String getScriptWrapperInitialization(
193: final String directory, final String className) {
194: return "new "
195: + this .scriptWrapperName
196: + "(this, \""
197: + new File(directory, className.replace('.', '/'))
198: .getAbsolutePath().replace('\\', '/') + ".java"
199: + "\");";
200: }
201:
202: /**
203: * Converts the given <code>clazz</code> to a CtClass instances.
204: *
205: * @param pool the pool from which to retrieve the CtClass instance.
206: * @param clazz the class to convert.
207: * @return the CtClass instances.
208: * @throws NotFoundException
209: */
210: private CtClass convert(final ClassPool pool, final String className)
211: throws NotFoundException {
212: CtClass ctClass = null;
213: if (className != null) {
214: ctClass = pool.get(className);
215: }
216: return ctClass;
217: }
218: }
|