001: package org.andromda.translation.ocl.testsuite;
002:
003: import javassist.CannotCompileException;
004: import javassist.ClassPool;
005: import javassist.CtClass;
006: import javassist.CtField;
007: import javassist.CtMethod;
008: import javassist.LoaderClassPath;
009: import javassist.NotFoundException;
010: import org.andromda.core.common.AndroMDALogger;
011: import org.andromda.core.common.ExceptionUtils;
012: import org.andromda.core.common.ResourceUtils;
013: import org.andromda.core.translation.Expression;
014: import org.andromda.core.translation.TranslationUtils;
015: import org.andromda.core.translation.Translator;
016: import org.andromda.core.translation.TranslatorException;
017: import org.andromda.translation.ocl.BaseTranslator;
018: import org.apache.log4j.Logger;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.net.URL;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.Map;
026:
027: /**
028: * This class allows us to trace the parsing of the expression. It is reflectively extended by Javaassist to allow the
029: * inclusion of all "inA" and "outA" methods produced by the SableCC parser. This allows us to dynamically include all
030: * handling code in each method without having to manual code each one. It used used during development of Translators
031: * since it allows you to see the execution of each node.
032: *
033: * @author Chad Brandon
034: */
035: public class TraceTranslator extends BaseTranslator {
036:
037: private static final Logger logger = Logger
038: .getLogger(TraceTranslator.class);
039:
040: private static final String INA_PREFIX = "inA";
041: private static final String OUTA_PREFIX = "outA";
042: private static final String CASE_PREFIX = "case";
043:
044: private Map methods = new HashMap();
045:
046: /**
047: * This is added to the adapted class and then checked to see if it exists to determine if we need to adapt the
048: * class.
049: */
050: private static final String FIELD_ADAPTED = "adapted";
051:
052: private ClassPool pool;
053:
054: /**
055: * Constructs an instance of TraceTranslator.
056: */
057: public TraceTranslator() {
058: }
059:
060: /**
061: * Creates and returns an new Instance of this ExpressionTranslator as a Translator object. The first time this
062: * method is called this class will dynamically be adapted to handle all parser calls.
063: *
064: * @return Translator
065: */
066: public static Translator getInstance() {
067: final String debugMethodName = "TraceTranslator.getInstance";
068: if (logger.isDebugEnabled()) {
069: logger.debug("performing " + debugMethodName);
070: }
071: try {
072: TraceTranslator oclTranslator = new TraceTranslator();
073: Translator translator = oclTranslator;
074: if (oclTranslator.needsAdaption()) {
075:
076: if (logger.isInfoEnabled()) {
077: logger
078: .info(" OCL Translator has not been adapted --> adapting");
079: }
080: translator = (Translator) oclTranslator
081: .getAdaptedTranslationClass().newInstance();
082: }
083: return translator;
084: } catch (Exception ex) {
085: String errMsg = "Error performing " + debugMethodName;
086: logger.error(errMsg, ex);
087: throw new TranslatorException(errMsg, ex);
088: }
089: }
090:
091: /**
092: * @see org.andromda.core.translation.Translator#translate(java.lang.String, java.lang.Object, java.lang.String)
093: */
094: public Expression translate(String translationName,
095: String expression, Object contextElement) {
096: if (logger.isInfoEnabled()) {
097: logger
098: .info("======================== Tracing Expression ========================");
099: logger.info(TranslationUtils
100: .removeExtraWhitespace(expression));
101: logger
102: .info("======================== ================== ========================");
103: }
104: Expression expressionObj = super .translate(translationName,
105: expression, contextElement);
106: if (logger.isInfoEnabled()) {
107: logger
108: .info("======================== Tracing Complete ========================");
109: }
110: return expressionObj;
111: }
112:
113: /**
114: * Checks to see if this class needs to be adapated If it has the "adapted" field then we know it already has been
115: * adapted.
116: *
117: * @return true/false, true if it needs be be adapted.
118: */
119: protected boolean needsAdaption() {
120: boolean needsAdaption = false;
121: try {
122: this .getClass().getDeclaredField(FIELD_ADAPTED);
123: } catch (NoSuchFieldException ex) {
124: needsAdaption = true;
125: }
126: return needsAdaption;
127: }
128:
129: /**
130: * Creates and returns the adapted translator class.
131: *
132: * @return Class the new Class instance.
133: * @throws NotFoundException
134: * @throws CannotCompileException
135: * @throws IOException
136: */
137: protected Class getAdaptedTranslationClass()
138: throws NotFoundException, CannotCompileException,
139: IOException {
140:
141: Class this Class = this .getClass();
142: this .pool = TranslatorClassPool.getPool(this Class
143: .getClassLoader());
144:
145: CtClass ctTranslatorClass = pool.get(this Class.getName());
146:
147: CtField adaptedField = new CtField(CtClass.booleanType,
148: FIELD_ADAPTED, ctTranslatorClass);
149:
150: ctTranslatorClass.addField(adaptedField);
151:
152: //get the "inA" methods from the analysisClass
153: CtMethod[] analysisMethods = ctTranslatorClass.getMethods();
154:
155: if (analysisMethods != null) {
156:
157: int methodNum = analysisMethods.length;
158:
159: for (int ctr = 0; ctr < methodNum; ctr++) {
160: CtMethod method = analysisMethods[ctr];
161: String methodName = method.getName();
162:
163: if (methodName.startsWith(INA_PREFIX)) {
164: // add the new overriden "inA" methods
165: this .methods.put(method, this
166: .getInAMethodBody(method));
167: } else if (methodName.startsWith(OUTA_PREFIX)) {
168: // add the new overriden "outA" methods
169: this .methods.put(method, this
170: .getOutAMethodBody(method));
171: } else if (methodName.startsWith(CASE_PREFIX)) {
172: // add the new overridden "case" methods
173: this .methods.put(method, this
174: .getCaseMethodBody(method));
175: }
176: }
177:
178: //now add all the methods to the class
179: Iterator allMethods = this .methods.keySet().iterator();
180: while (allMethods.hasNext()) {
181: CtMethod method = (CtMethod) allMethods.next();
182: CtMethod newMethod = new CtMethod(method,
183: ctTranslatorClass, null);
184: String methodBody = (String) this .methods.get(method);
185: newMethod.setBody(methodBody);
186: ctTranslatorClass.addMethod(newMethod);
187: }
188:
189: }
190: this .writeAdaptedClass();
191: return ctTranslatorClass.toClass();
192: }
193:
194: /**
195: * Writes the class to the directory found by the class loader (since the class is a currently existing class)
196: */
197: protected void writeAdaptedClass() {
198: final String methodName = "TraceTranslator.writeAdaptedClass";
199: if (logger.isDebugEnabled()) {
200: logger.debug("performing " + methodName);
201: }
202: try {
203: String className = this .getClass().getName();
204: File dir = this .getAdaptedClassOutputDirectory();
205: if (logger.isDebugEnabled()) {
206: logger.debug("writing className '" + className
207: + "' to directory --> " + "'" + dir + "'");
208: }
209: this .pool.writeFile(this .getClass().getName(), dir
210: .toString());
211: } catch (Exception ex) {
212: String errMsg = "Error performing " + methodName;
213: logger.error(errMsg, ex);
214: throw new TranslatorException(errMsg, ex);
215: }
216: }
217:
218: /**
219: * Retrieves the output directory which the adapted class will be written to.
220: *
221: * @return
222: */
223: protected File getAdaptedClassOutputDirectory() {
224: final String methodName = "TraceTranslator.getAdaptedClassOutputDirectory";
225: Class this Class = this .getClass();
226: URL classAsResource = ResourceUtils.getClassResource(this Class
227: .getName());
228: File file = new File(classAsResource.getFile());
229: File dir = file.getParentFile();
230: if (dir == null) {
231: throw new TranslatorException(methodName
232: + " - can not retrieve directory for file '" + file
233: + "'");
234: }
235: String className = this Class.getName();
236: int index = className.indexOf('.');
237: String basePackage = null;
238: if (index != -1) {
239: basePackage = className.substring(0, index);
240: }
241: if (basePackage != null) {
242: while (!dir.toString().endsWith(basePackage)) {
243: dir = dir.getParentFile();
244: }
245: dir = dir.getParentFile();
246: }
247: return dir;
248: }
249:
250: /**
251: * Creates and returns the method body for each "caseA" method
252: *
253: * @param method
254: * @return String the <code>case</code> method body
255: */
256: protected String getCaseMethodBody(CtMethod method) {
257: ExceptionUtils.checkNull("method", method);
258: StringBuffer methodBody = new StringBuffer("{");
259: String methodName = method.getName();
260: methodBody
261: .append("String methodName = \"" + methodName + "\";");
262: methodBody.append(this .getMethodTrace(method));
263: //add the call of the super class method, so that any methods in sub
264: // classes
265: //can provide functionality
266: methodBody.append("super." + methodName + "($1);");
267: methodBody.append("}");
268: return methodBody.toString();
269: }
270:
271: /**
272: * Creates and returns the method body for each "inA" method
273: *
274: * @param method
275: * @return String the <code>inA</code> method body
276: */
277: protected String getInAMethodBody(CtMethod method) {
278: ExceptionUtils.checkNull("method", method);
279: StringBuffer methodBody = new StringBuffer("{");
280: String methodName = method.getName();
281: methodBody
282: .append("String methodName = \"" + methodName + "\";");
283: methodBody.append(this .getMethodTrace(method));
284: //add the call of the super class method, so that any methods in sub
285: // classes
286: //can provide functionality
287: methodBody.append("super." + methodName + "($1);");
288: methodBody.append("}");
289: return methodBody.toString();
290: }
291:
292: /**
293: * Creates and returns the method body for each "inA" method
294: *
295: * @param method
296: * @return String the <code>outA</code> method body.
297: */
298: protected String getOutAMethodBody(CtMethod method) {
299: ExceptionUtils.checkNull("method", method);
300: StringBuffer methodBody = new StringBuffer("{");
301: String methodName = method.getName();
302: methodBody
303: .append("String methodName = \"" + methodName + "\";");
304: methodBody.append(this .getMethodTrace(method));
305: //add the call of the super class method, so that any methods in sub
306: // classes
307: //can provide functionality
308: methodBody.append("super." + methodName + "($1);");
309: methodBody.append("}");
310: return methodBody.toString();
311: }
312:
313: /**
314: * Returns the OCL fragment name that must have a matching name in the library translation template in order to be
315: * placed into the Expression translated expression buffer.
316: *
317: * @param method
318: * @return String
319: */
320: protected String getOclFragmentName(CtMethod method) {
321: ExceptionUtils.checkNull("method", method);
322: String fragment = method.getName();
323: String prefix = this .getMethodPrefix(method);
324: int index = fragment.indexOf(prefix);
325: if (index != -1) {
326: fragment = fragment.substring(index + prefix.length(),
327: fragment.length());
328: }
329: return fragment;
330: }
331:
332: /**
333: * Returns the prefix for the method (inA or outA)
334: *
335: * @param method
336: * @return
337: */
338: protected String getMethodPrefix(CtMethod method) {
339: ExceptionUtils.checkNull("method", method);
340: String mName = method.getName();
341: String prefix = INA_PREFIX;
342: if (mName.startsWith(OUTA_PREFIX)) {
343: prefix = OUTA_PREFIX;
344: }
345: return prefix;
346: }
347:
348: /**
349: * Creates the debug statement that will be output for each method
350: *
351: * @param method
352: * @return @throws NotFoundException
353: */
354: protected String getMethodTrace(CtMethod method) {
355: ExceptionUtils.checkNull("method", method);
356: StringBuffer buf = new StringBuffer(
357: "if (logger.isInfoEnabled()) {logger.info(\"");
358: buf.append("\" + methodName + \" --> ");
359: //javaassist names the arguments $1,$2,$3, etc.
360: buf
361: .append("'\" + org.andromda.core.translation.TranslationUtils.trimToEmpty($1) + \"'\");}");
362: return buf.toString();
363: }
364:
365: /**
366: * Extends the Javaassist class pool so that we can define our own ClassLoader to use from which to find, load and
367: * modify and existing class.
368: *
369: * @author Chad Brandon
370: */
371: private static class TranslatorClassPool extends ClassPool {
372:
373: private static Logger logger = Logger
374: .getLogger(TranslatorClassPool.class);
375:
376: protected TranslatorClassPool() {
377: super (ClassPool.getDefault());
378: if (logger.isInfoEnabled()) {
379: logger.debug("instantiating new TranslatorClassPool");
380: }
381: }
382:
383: /**
384: * Retrieves an instance of this TranslatorClassPool using the loader to find/load any classes.
385: *
386: * @param loader
387: * @return
388: */
389: protected static ClassPool getPool(ClassLoader loader) {
390: if (loader == null) {
391: loader = Thread.currentThread().getContextClassLoader();
392: }
393: TranslatorClassPool pool = new TranslatorClassPool();
394: pool.insertClassPath(new LoaderClassPath(loader));
395: return pool;
396: }
397:
398: /**
399: * Returns a <code>java.lang.Class</code> object. It calls <code>write()</code> to obtain a class file and then
400: * loads the obtained class file into the JVM. The returned <code>Class</code> object represents the loaded
401: * class.
402: * <p/>
403: * To load a class file, this method uses an internal class loader. Thus, that class file is not loaded by the
404: * system class loader, which should have loaded this <code>AspectClassPool</code> class. The internal class
405: * loader loads only the classes explicitly specified by this method <code>writeAsClass()</code>. The other
406: * classes are loaded by the parent class loader (the sytem class loader) by delegation. Thus, if a class
407: * <code>X</code> loaded by the internal class loader refers to a class <code>Y</code>, then the class
408: * <code>Y</code> is loaded by the parent class loader.
409: *
410: * @param classname a fully-qualified class name.
411: * @return Class the Class it writes.
412: * @throws NotFoundException
413: * @throws IOException
414: * @throws CannotCompileException
415: */
416: public Class writeAsClass(String classname)
417: throws NotFoundException, IOException,
418: CannotCompileException {
419: try {
420: return classLoader.loadClass(classname,
421: write(classname));
422: } catch (ClassFormatError e) {
423: throw new CannotCompileException(e, classname);
424: }
425: }
426:
427: /**
428: * LocalClassLoader which allows us to dynamically construct classes on the fly using Javassist.
429: */
430: static class LocalClassLoader extends ClassLoader {
431: /**
432: * Constructs an instance of LocalClassLoader.
433: *
434: * @param parent
435: */
436: public LocalClassLoader(ClassLoader parent) {
437: super (parent);
438: }
439:
440: /**
441: * Loads a class.
442: *
443: * @param name the name
444: * @param classfile the bytes of the class.
445: * @return Class
446: * @throws ClassFormatError
447: */
448: public Class loadClass(String name, byte[] classfile)
449: throws ClassFormatError {
450: Class c = defineClass(name, classfile, 0,
451: classfile.length);
452: resolveClass(c);
453: return c;
454: }
455: }
456:
457: /**
458: * Create the LocalClassLoader and specify the ClassLoader for this class as the parent ClassLoader. This allows
459: * classes defined outside this LocalClassLoader to be loaded (i.e. classes that already exist, and aren't being
460: * dynamically created
461: */
462: private static LocalClassLoader classLoader = new LocalClassLoader(
463: LocalClassLoader.class.getClassLoader());
464: }
465:
466: /**
467: * This method is called by the main method during the build process, to "adapt" the class to the OCL parser.
468: */
469: protected static void adaptClass() {
470: if (logger.isInfoEnabled()) {
471: logger.info("adapting class for OCL parser");
472: }
473: TraceTranslator translator = new TraceTranslator();
474: if (translator.needsAdaption()) {
475: try {
476: translator.getAdaptedTranslationClass();
477: } catch (Throwable th) {
478: logger.error(th);
479: }
480: }
481: }
482:
483: /**
484: * This main method is called during the build process, to "adapt" the class to the OCL parser.
485: *
486: * @param args
487: */
488: public static void main(String args[]) {
489: try {
490: AndroMDALogger.initialize();
491: TraceTranslator.adaptClass();
492: } catch (Throwable th) {
493: logger.error(th);
494: }
495: }
496:
497: }
|