TraceTranslator.java
- package org.andromda.translation.ocl.testsuite;
- import java.io.File;
- import java.io.IOException;
- import java.net.URL;
- import java.util.HashMap;
- import java.util.Map;
- import javassist.CannotCompileException;
- import javassist.ClassPool;
- import javassist.CtClass;
- import javassist.CtField;
- import javassist.CtMethod;
- import javassist.LoaderClassPath;
- import javassist.NotFoundException;
- import org.andromda.core.common.AndroMDALogger;
- import org.andromda.core.common.ExceptionUtils;
- import org.andromda.core.common.ResourceUtils;
- import org.andromda.core.translation.Expression;
- import org.andromda.core.translation.TranslationUtils;
- import org.andromda.core.translation.Translator;
- import org.andromda.core.translation.TranslatorException;
- import org.andromda.translation.ocl.BaseTranslator;
- import org.apache.log4j.Logger;
- /**
- * This class allows us to trace the parsing of the expression. It is reflectively extended by Javassist to allow the
- * inclusion of all "inA" and "outA" methods produced by the SableCC parser. This allows us to dynamically include all
- * handling code in each method without having to manual code each one. It used used during development of Translators
- * since it allows you to see the execution of each node.
- *
- * @author Chad Brandon
- */
- public class TraceTranslator
- extends BaseTranslator
- {
- private static final Logger logger = Logger.getLogger(TraceTranslator.class);
- private static final String INA_PREFIX = "inA";
- private static final String OUTA_PREFIX = "outA";
- private static final String CASE_PREFIX = "case";
- private Map<CtMethod, String> methods = new HashMap<CtMethod, String>();
- /**
- * This is added to the adapted class and then checked to see if it exists to determine if we need to adapt the
- * class.
- */
- private static final String FIELD_ADAPTED = "adapted";
- private ClassPool pool;
- /**
- * Constructs an instance of TraceTranslator.
- */
- public TraceTranslator()
- {
- }
- /**
- * Creates and returns an new Instance of this ExpressionTranslator as a Translator object. The first time this
- * method is called this class will dynamically be adapted to handle all parser calls.
- *
- * @return Translator
- */
- public static Translator getInstance()
- {
- final String debugMethodName = "TraceTranslator.getInstance";
- if (logger.isDebugEnabled())
- {
- logger.debug("performing " + debugMethodName);
- }
- try
- {
- TraceTranslator oclTranslator = new TraceTranslator();
- Translator translator = oclTranslator;
- if (oclTranslator.needsAdaption())
- {
- if (logger.isInfoEnabled())
- {
- logger.info(" OCL Translator has not been adapted --> adapting");
- }
- translator = (Translator) oclTranslator.getAdaptedTranslationClass().newInstance();
- }
- return translator;
- }
- catch (Exception ex)
- {
- String errMsg = "Error performing " + debugMethodName;
- logger.error(errMsg, ex);
- throw new TranslatorException(errMsg, ex);
- }
- }
- /**
- * @see org.andromda.core.translation.Translator#translate(String, String, Object)
- */
- public Expression translate(String translationName, String expression, Object contextElement)
- {
- if (logger.isInfoEnabled())
- {
- logger.info("======================== Tracing Expression ========================");
- logger.info(TranslationUtils.removeExtraWhitespace(expression));
- logger.info("======================== ================== ========================");
- }
- Expression expressionObj = super.translate(translationName, expression, contextElement);
- if (logger.isInfoEnabled())
- {
- logger.info("======================== Tracing Complete ========================");
- }
- return expressionObj;
- }
- /**
- * Checks to see if this class needs to be adapted If it has the "adapted" field then we know it already has been
- * adapted.
- *
- * @return true/false, true if it needs be be adapted.
- */
- protected boolean needsAdaption()
- {
- boolean needsAdaption = false;
- try
- {
- this.getClass().getDeclaredField(FIELD_ADAPTED);
- }
- catch (NoSuchFieldException ex)
- {
- needsAdaption = true;
- }
- return needsAdaption;
- }
- /**
- * Creates and returns the adapted translator class.
- *
- * @return Class the new Class instance.
- * @throws NotFoundException
- * @throws CannotCompileException
- * @throws IOException
- */
- protected Class getAdaptedTranslationClass() throws NotFoundException, CannotCompileException, IOException
- {
- Class thisClass = this.getClass();
- this.pool = TranslatorClassPool.getPool(thisClass.getClassLoader());
- CtClass ctTranslatorClass = pool.get(thisClass.getName());
- CtField adaptedField = new CtField(CtClass.booleanType, FIELD_ADAPTED, ctTranslatorClass);
- ctTranslatorClass.addField(adaptedField);
- //get the "inA" methods from the analysisClass
- CtMethod[] analysisMethods = ctTranslatorClass.getMethods();
- if (analysisMethods != null)
- {
- int methodNum = analysisMethods.length;
- for (int ctr = 0; ctr < methodNum; ctr++)
- {
- CtMethod method = analysisMethods[ctr];
- String methodName = method.getName();
- if (methodName.startsWith(INA_PREFIX))
- {
- // add the new overridden "inA" methods
- this.methods.put(method, this.getInAMethodBody(method));
- } else if (methodName.startsWith(OUTA_PREFIX))
- {
- // add the new overridden "outA" methods
- this.methods.put(method, this.getOutAMethodBody(method));
- } else if (methodName.startsWith(CASE_PREFIX))
- {
- // add the new overridden "case" methods
- this.methods.put(method, this.getCaseMethodBody(method));
- }
- }
- //now add all the methods to the class
- for (CtMethod method : this.methods.keySet())
- {
- CtMethod newMethod = new CtMethod(method, ctTranslatorClass, null);
- String methodBody = this.methods.get(method);
- newMethod.setBody(methodBody);
- ctTranslatorClass.addMethod(newMethod);
- }
- }
- this.writeAdaptedClass(ctTranslatorClass);
- return ctTranslatorClass.toClass();
- }
- /**
- * Writes the class to the directory found by the class loader (since the class is a currently existing class)
- * @param pTranslatorClass
- */
- protected void writeAdaptedClass(CtClass pTranslatorClass)
- {
- final String methodName = "TraceTranslator.writeAdaptedClass";
- if (logger.isDebugEnabled())
- {
- logger.debug("performing " + methodName);
- }
- try
- {
- File dir = this.getAdaptedClassOutputDirectory();
- if (logger.isDebugEnabled())
- {
- final String className = this.getClass().getName();
- logger.debug("writing className '" + className + "' to directory --> " + '\'' + dir + '\'');
- }
- pTranslatorClass.writeFile(dir.getPath());
- }
- catch (Exception ex)
- {
- String errMsg = "Error performing " + methodName;
- logger.error(errMsg, ex);
- throw new TranslatorException(errMsg, ex);
- }
- }
- /**
- * Retrieves the output directory which the adapted class will be written to.
- *
- * @return AdaptedClassOutputDirectory
- */
- protected File getAdaptedClassOutputDirectory()
- {
- final String methodName = "TraceTranslator.getAdaptedClassOutputDirectory";
- Class thisClass = this.getClass();
- URL classAsResource = ResourceUtils.getClassResource(thisClass.getName());
- File file = new File(classAsResource.getFile());
- File dir = file.getParentFile();
- if (dir == null)
- {
- throw new TranslatorException(methodName + " - can not retrieve directory for file '" + file + '\'');
- }
- String className = thisClass.getName();
- int index = className.indexOf('.');
- String basePackage = null;
- if (index != -1)
- {
- basePackage = className.substring(0, index);
- }
- if (basePackage != null)
- {
- while (!dir.toString().endsWith(basePackage))
- {
- dir = dir.getParentFile();
- }
- dir = dir.getParentFile();
- }
- return dir;
- }
- /**
- * Creates and returns the method body for each "caseA" method
- *
- * @param method
- * @return String the <code>case</code> method body
- */
- protected String getCaseMethodBody(CtMethod method)
- {
- ExceptionUtils.checkNull("method", method);
- StringBuilder methodBody = new StringBuilder("{");
- String methodName = method.getName();
- methodBody.append("String methodName = \"").append(methodName).append("\";");
- methodBody.append(this.getMethodTrace(method));
- //add the call of the super class method, so that any methods in sub
- // classes
- //can provide functionality
- methodBody.append("super.").append(methodName).append("($1);");
- methodBody.append('}');
- return methodBody.toString();
- }
- /**
- * Creates and returns the method body for each "inA" method
- *
- * @param method
- * @return String the <code>inA</code> method body
- */
- protected String getInAMethodBody(CtMethod method)
- {
- ExceptionUtils.checkNull("method", method);
- StringBuilder methodBody = new StringBuilder("{");
- String methodName = method.getName();
- methodBody.append("String methodName = \"").append(methodName).append("\";");
- methodBody.append(this.getMethodTrace(method));
- //add the call of the super class method, so that any methods in sub
- // classes
- //can provide functionality
- methodBody.append("super.").append(methodName).append("($1);");
- methodBody.append('}');
- return methodBody.toString();
- }
- /**
- * Creates and returns the method body for each "inA" method
- *
- * @param method
- * @return String the <code>outA</code> method body.
- */
- protected String getOutAMethodBody(CtMethod method)
- {
- ExceptionUtils.checkNull("method", method);
- StringBuilder methodBody = new StringBuilder("{");
- String methodName = method.getName();
- methodBody.append("String methodName = \"").append(methodName).append("\";");
- methodBody.append(this.getMethodTrace(method));
- //add the call of the super class method, so that any methods in sub
- // classes
- //can provide functionality
- methodBody.append("super.").append(methodName).append("($1);");
- methodBody.append('}');
- return methodBody.toString();
- }
- /**
- * Returns the OCL fragment name that must have a matching name in the library translation template in order to be
- * placed into the Expression translated expression buffer.
- *
- * @param method
- * @return String
- */
- protected String getOclFragmentName(CtMethod method)
- {
- ExceptionUtils.checkNull("method", method);
- String fragment = method.getName();
- String prefix = this.getMethodPrefix(method);
- int index = fragment.indexOf(prefix);
- if (index != -1)
- {
- fragment = fragment.substring(index + prefix.length(), fragment.length());
- }
- return fragment;
- }
- /**
- * Returns the prefix for the method (inA or outA)
- *
- * @param method
- * @return MethodPrefix
- */
- protected String getMethodPrefix(CtMethod method)
- {
- ExceptionUtils.checkNull("method", method);
- String mName = method.getName();
- String prefix = INA_PREFIX;
- if (mName.startsWith(OUTA_PREFIX))
- {
- prefix = OUTA_PREFIX;
- }
- return prefix;
- }
- /**
- * Creates the debug statement that will be output for each method
- *
- * @param method
- * @return @throws NotFoundException
- */
- protected String getMethodTrace(CtMethod method)
- {
- ExceptionUtils.checkNull("method", method);
- StringBuilder buf = new StringBuilder("if (logger.isInfoEnabled()) {logger.info(\"");
- buf.append("\" + methodName + \" --> ");
- //javassist names the arguments $1,$2,$3, etc.
- buf.append("'\" + org.andromda.core.translation.TranslationUtils.trimToEmpty($1) + \"'\");}");
- return buf.toString();
- }
- /**
- * Extends the Javassist class pool so that we can define our own ClassLoader to use from which to find, load and
- * modify and existing class.
- *
- * @author Chad Brandon
- */
- private static class TranslatorClassPool
- extends ClassPool
- {
- private static final Logger logger = Logger.getLogger(TranslatorClassPool.class);
- protected TranslatorClassPool()
- {
- super(ClassPool.getDefault());
- if (logger.isInfoEnabled())
- {
- logger.debug("instantiating new TranslatorClassPool");
- }
- }
- /**
- * Retrieves an instance of this TranslatorClassPool using the loader to find/load any classes.
- *
- * @param loader
- * @return pool
- */
- protected static ClassPool getPool(ClassLoader loader)
- {
- if (loader == null)
- {
- loader = Thread.currentThread().getContextClassLoader();
- }
- TranslatorClassPool pool = new TranslatorClassPool();
- pool.insertClassPath(new LoaderClassPath(loader));
- return pool;
- }
- /**
- * Returns a <code>Class</code> object. It calls <code>write()</code> to obtain a class file and then
- * loads the obtained class file into the JVM. The returned <code>Class</code> object represents the loaded
- * class.
- * <p/>
- * To load a class file, this method uses an internal class loader. Thus, that class file is not loaded by the
- * system class loader, which should have loaded this <code>AspectClassPool</code> class. The internal class
- * loader loads only the classes explicitly specified by this method <code>writeAsClass()</code>. The other
- * classes are loaded by the parent class loader (the system class loader) by delegation. Thus, if a class
- * <code>X</code> loaded by the internal class loader refers to a class <code>Y</code>, then the class
- * <code>Y</code> is loaded by the parent class loader.
- *
- * @param classname a fully-qualified class name.
- * @return Class the Class it writes.
- * @throws NotFoundException
- * @throws IOException
- * @throws CannotCompileException
- */
- @SuppressWarnings("unused")
- public Class writeAsClass(String classname) throws NotFoundException, IOException, CannotCompileException
- {
- try
- {
- final CtClass ctTranslatorClass = get(classname);
- return classLoader.loadClass(classname, ctTranslatorClass.toBytecode());
- }
- catch (ClassFormatError e)
- {
- throw new CannotCompileException(e, classname);
- }
- }
- /**
- * LocalClassLoader which allows us to dynamically construct classes on the fly using Javassist.
- */
- static class LocalClassLoader
- extends ClassLoader
- {
- /**
- * Constructs an instance of LocalClassLoader.
- *
- * @param parent
- */
- public LocalClassLoader(ClassLoader parent)
- {
- super(parent);
- }
- /**
- * Loads a class.
- *
- * @param name the name
- * @param classfile the bytes of the class.
- * @return Class
- * @throws ClassFormatError
- */
- public Class loadClass(String name, byte[] classfile) throws ClassFormatError
- {
- Class c = defineClass(name, classfile, 0, classfile.length);
- resolveClass(c);
- return c;
- }
- }
- /**
- * Create the LocalClassLoader and specify the ClassLoader for this class as the parent ClassLoader. This allows
- * classes defined outside this LocalClassLoader to be loaded (i.e. classes that already exist, and aren't being
- * dynamically created
- */
- private static LocalClassLoader classLoader = new LocalClassLoader(LocalClassLoader.class.getClassLoader());
- }
- /**
- * This method is called by the main method during the build process, to "adapt" the class to the OCL parser.
- */
- protected static void adaptClass()
- {
- if (logger.isInfoEnabled())
- {
- logger.info("adapting class for OCL parser");
- }
- TraceTranslator translator = new TraceTranslator();
- if (translator.needsAdaption())
- {
- try
- {
- translator.getAdaptedTranslationClass();
- }
- catch (Throwable th)
- {
- logger.error(th);
- }
- }
- }
- /**
- * This main method is called during the build process, to "adapt" the class to the OCL parser.
- *
- * @param args
- */
- public static void main(String[] args)
- {
- try
- {
- AndroMDALogger.initialize();
- TraceTranslator.adaptClass();
- }
- catch (Throwable th)
- {
- logger.error(th);
- }
- }
- /**
- * Ancestor abstract method which must be implemented even when it does nothing.
- *
- * @see org.andromda.translation.ocl.BaseTranslator#postProcess()
- */
- @Override
- public void postProcess()
- {
- // Do nothing
- }
- }