TraceTranslator.java

  1. package org.andromda.translation.ocl.testsuite;

  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.net.URL;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. import javassist.CannotCompileException;
  8. import javassist.ClassPool;
  9. import javassist.CtClass;
  10. import javassist.CtField;
  11. import javassist.CtMethod;
  12. import javassist.LoaderClassPath;
  13. import javassist.NotFoundException;
  14. import org.andromda.core.common.AndroMDALogger;
  15. import org.andromda.core.common.ExceptionUtils;
  16. import org.andromda.core.common.ResourceUtils;
  17. import org.andromda.core.translation.Expression;
  18. import org.andromda.core.translation.TranslationUtils;
  19. import org.andromda.core.translation.Translator;
  20. import org.andromda.core.translation.TranslatorException;
  21. import org.andromda.translation.ocl.BaseTranslator;
  22. import org.apache.log4j.Logger;

  23. /**
  24.  * This class allows us to trace the parsing of the expression. It is reflectively extended by Javassist to allow the
  25.  * inclusion of all "inA" and "outA" methods produced by the SableCC parser. This allows us to dynamically include all
  26.  * handling code in each method without having to manual code each one. It used used during development of Translators
  27.  * since it allows you to see the execution of each node.
  28.  *
  29.  * @author Chad Brandon
  30.  */
  31. public class TraceTranslator
  32.         extends BaseTranslator
  33. {
  34.     private static final Logger logger = Logger.getLogger(TraceTranslator.class);

  35.     private static final String INA_PREFIX = "inA";
  36.     private static final String OUTA_PREFIX = "outA";
  37.     private static final String CASE_PREFIX = "case";

  38.     private Map<CtMethod, String> methods = new HashMap<CtMethod, String>();

  39.     /**
  40.      * This is added to the adapted class and then checked to see if it exists to determine if we need to adapt the
  41.      * class.
  42.      */
  43.     private static final String FIELD_ADAPTED = "adapted";

  44.     private ClassPool pool;

  45.     /**
  46.      * Constructs an instance of TraceTranslator.
  47.      */
  48.     public TraceTranslator()
  49.     {
  50.     }

  51.     /**
  52.      * Creates and returns an new Instance of this ExpressionTranslator as a Translator object. The first time this
  53.      * method is called this class will dynamically be adapted to handle all parser calls.
  54.      *
  55.      * @return Translator
  56.      */
  57.     public static Translator getInstance()
  58.     {
  59.         final String debugMethodName = "TraceTranslator.getInstance";
  60.         if (logger.isDebugEnabled())
  61.         {
  62.             logger.debug("performing " + debugMethodName);
  63.         }
  64.         try
  65.         {
  66.             TraceTranslator oclTranslator = new TraceTranslator();
  67.             Translator translator = oclTranslator;
  68.             if (oclTranslator.needsAdaption())
  69.             {

  70.                 if (logger.isInfoEnabled())
  71.                 {
  72.                     logger.info(" OCL Translator has not been adapted --> adapting");
  73.                 }
  74.                 translator = (Translator) oclTranslator.getAdaptedTranslationClass().newInstance();
  75.             }
  76.             return translator;
  77.         }
  78.         catch (Exception ex)
  79.         {
  80.             String errMsg = "Error performing " + debugMethodName;
  81.             logger.error(errMsg, ex);
  82.             throw new TranslatorException(errMsg, ex);
  83.         }
  84.     }

  85.     /**
  86.      * @see org.andromda.core.translation.Translator#translate(String, String, Object)
  87.      */
  88.     public Expression translate(String translationName, String expression, Object contextElement)
  89.     {
  90.         if (logger.isInfoEnabled())
  91.         {
  92.             logger.info("======================== Tracing Expression ========================");
  93.             logger.info(TranslationUtils.removeExtraWhitespace(expression));
  94.             logger.info("======================== ================== ========================");
  95.         }
  96.         Expression expressionObj = super.translate(translationName, expression, contextElement);
  97.         if (logger.isInfoEnabled())
  98.         {
  99.             logger.info("========================  Tracing Complete  ========================");
  100.         }
  101.         return expressionObj;
  102.     }

  103.     /**
  104.      * Checks to see if this class needs to be adapted If it has the "adapted" field then we know it already has been
  105.      * adapted.
  106.      *
  107.      * @return true/false, true if it needs be be adapted.
  108.      */
  109.     protected boolean needsAdaption()
  110.     {
  111.         boolean needsAdaption = false;
  112.         try
  113.         {
  114.             this.getClass().getDeclaredField(FIELD_ADAPTED);
  115.         }
  116.         catch (NoSuchFieldException ex)
  117.         {
  118.             needsAdaption = true;
  119.         }
  120.         return needsAdaption;
  121.     }

  122.     /**
  123.      * Creates and returns the adapted translator class.
  124.      *
  125.      * @return Class the new Class instance.
  126.      * @throws NotFoundException
  127.      * @throws CannotCompileException
  128.      * @throws IOException
  129.      */
  130.     protected Class getAdaptedTranslationClass() throws NotFoundException, CannotCompileException, IOException
  131.     {

  132.         Class thisClass = this.getClass();
  133.         this.pool = TranslatorClassPool.getPool(thisClass.getClassLoader());

  134.         CtClass ctTranslatorClass = pool.get(thisClass.getName());

  135.         CtField adaptedField = new CtField(CtClass.booleanType, FIELD_ADAPTED, ctTranslatorClass);

  136.         ctTranslatorClass.addField(adaptedField);

  137.         //get the "inA" methods from the analysisClass
  138.         CtMethod[] analysisMethods = ctTranslatorClass.getMethods();

  139.         if (analysisMethods != null)
  140.         {

  141.             int methodNum = analysisMethods.length;

  142.             for (int ctr = 0; ctr < methodNum; ctr++)
  143.             {
  144.                 CtMethod method = analysisMethods[ctr];
  145.                 String methodName = method.getName();

  146.                 if (methodName.startsWith(INA_PREFIX))
  147.                 {
  148.                     // add the new overridden "inA" methods
  149.                     this.methods.put(method, this.getInAMethodBody(method));
  150.                 } else if (methodName.startsWith(OUTA_PREFIX))
  151.                 {
  152.                     // add the new overridden "outA" methods
  153.                     this.methods.put(method, this.getOutAMethodBody(method));
  154.                 } else if (methodName.startsWith(CASE_PREFIX))
  155.                 {
  156.                     // add the new overridden "case" methods
  157.                     this.methods.put(method, this.getCaseMethodBody(method));
  158.                 }
  159.             }

  160.             //now add all the methods to the class
  161.             for (CtMethod method : this.methods.keySet())
  162.             {
  163.                 CtMethod newMethod = new CtMethod(method, ctTranslatorClass, null);
  164.                 String methodBody = this.methods.get(method);
  165.                 newMethod.setBody(methodBody);
  166.                 ctTranslatorClass.addMethod(newMethod);
  167.             }

  168.         }
  169.         this.writeAdaptedClass(ctTranslatorClass);
  170.         return ctTranslatorClass.toClass();
  171.     }

  172.     /**
  173.      * Writes the class to the directory found by the class loader (since the class is a currently existing class)
  174.      * @param pTranslatorClass
  175.      */
  176.     protected void writeAdaptedClass(CtClass pTranslatorClass)
  177.     {
  178.         final String methodName = "TraceTranslator.writeAdaptedClass";
  179.         if (logger.isDebugEnabled())
  180.         {
  181.             logger.debug("performing " + methodName);
  182.         }
  183.         try
  184.         {
  185.             File dir = this.getAdaptedClassOutputDirectory();
  186.             if (logger.isDebugEnabled())
  187.             {
  188.                 final String className = this.getClass().getName();
  189.                 logger.debug("writing className '" + className + "' to directory --> " + '\'' + dir + '\'');
  190.             }
  191.             pTranslatorClass.writeFile(dir.getPath());
  192.         }
  193.         catch (Exception ex)
  194.         {
  195.             String errMsg = "Error performing " + methodName;
  196.             logger.error(errMsg, ex);
  197.             throw new TranslatorException(errMsg, ex);
  198.         }
  199.     }

  200.     /**
  201.      * Retrieves the output directory which the adapted class will be written to.
  202.      *
  203.      * @return AdaptedClassOutputDirectory
  204.      */
  205.     protected File getAdaptedClassOutputDirectory()
  206.     {
  207.         final String methodName = "TraceTranslator.getAdaptedClassOutputDirectory";
  208.         Class thisClass = this.getClass();
  209.         URL classAsResource = ResourceUtils.getClassResource(thisClass.getName());
  210.         File file = new File(classAsResource.getFile());
  211.         File dir = file.getParentFile();
  212.         if (dir == null)
  213.         {
  214.             throw new TranslatorException(methodName + " - can not retrieve directory for file '" + file + '\'');
  215.         }
  216.         String className = thisClass.getName();
  217.         int index = className.indexOf('.');
  218.         String basePackage = null;
  219.         if (index != -1)
  220.         {
  221.             basePackage = className.substring(0, index);
  222.         }
  223.         if (basePackage != null)
  224.         {
  225.             while (!dir.toString().endsWith(basePackage))
  226.             {
  227.                 dir = dir.getParentFile();
  228.             }
  229.             dir = dir.getParentFile();
  230.         }
  231.         return dir;
  232.     }

  233.     /**
  234.      * Creates and returns the method body for each "caseA" method
  235.      *
  236.      * @param method
  237.      * @return String the <code>case</code> method body
  238.      */
  239.     protected String getCaseMethodBody(CtMethod method)
  240.     {
  241.         ExceptionUtils.checkNull("method", method);
  242.         StringBuilder methodBody = new StringBuilder("{");
  243.         String methodName = method.getName();
  244.         methodBody.append("String methodName = \"").append(methodName).append("\";");
  245.         methodBody.append(this.getMethodTrace(method));
  246.         //add the call of the super class method, so that any methods in sub
  247.         // classes
  248.         //can provide functionality
  249.         methodBody.append("super.").append(methodName).append("($1);");
  250.         methodBody.append('}');
  251.         return methodBody.toString();
  252.     }

  253.     /**
  254.      * Creates and returns the method body for each "inA" method
  255.      *
  256.      * @param method
  257.      * @return String the <code>inA</code> method body
  258.      */
  259.     protected String getInAMethodBody(CtMethod method)
  260.     {
  261.         ExceptionUtils.checkNull("method", method);
  262.         StringBuilder methodBody = new StringBuilder("{");
  263.         String methodName = method.getName();
  264.         methodBody.append("String methodName = \"").append(methodName).append("\";");
  265.         methodBody.append(this.getMethodTrace(method));
  266.         //add the call of the super class method, so that any methods in sub
  267.         // classes
  268.         //can provide functionality
  269.         methodBody.append("super.").append(methodName).append("($1);");
  270.         methodBody.append('}');
  271.         return methodBody.toString();
  272.     }

  273.     /**
  274.      * Creates and returns the method body for each "inA" method
  275.      *
  276.      * @param method
  277.      * @return String the <code>outA</code> method body.
  278.      */
  279.     protected String getOutAMethodBody(CtMethod method)
  280.     {
  281.         ExceptionUtils.checkNull("method", method);
  282.         StringBuilder methodBody = new StringBuilder("{");
  283.         String methodName = method.getName();
  284.         methodBody.append("String methodName = \"").append(methodName).append("\";");
  285.         methodBody.append(this.getMethodTrace(method));
  286.         //add the call of the super class method, so that any methods in sub
  287.         // classes
  288.         //can provide functionality
  289.         methodBody.append("super.").append(methodName).append("($1);");
  290.         methodBody.append('}');
  291.         return methodBody.toString();
  292.     }

  293.     /**
  294.      * Returns the OCL fragment name that must have a matching name in the library translation template in order to be
  295.      * placed into the Expression translated expression buffer.
  296.      *
  297.      * @param method
  298.      * @return String
  299.      */
  300.     protected String getOclFragmentName(CtMethod method)
  301.     {
  302.         ExceptionUtils.checkNull("method", method);
  303.         String fragment = method.getName();
  304.         String prefix = this.getMethodPrefix(method);
  305.         int index = fragment.indexOf(prefix);
  306.         if (index != -1)
  307.         {
  308.             fragment = fragment.substring(index + prefix.length(), fragment.length());
  309.         }
  310.         return fragment;
  311.     }

  312.     /**
  313.      * Returns the prefix for the method (inA or outA)
  314.      *
  315.      * @param method
  316.      * @return MethodPrefix
  317.      */
  318.     protected String getMethodPrefix(CtMethod method)
  319.     {
  320.         ExceptionUtils.checkNull("method", method);
  321.         String mName = method.getName();
  322.         String prefix = INA_PREFIX;
  323.         if (mName.startsWith(OUTA_PREFIX))
  324.         {
  325.             prefix = OUTA_PREFIX;
  326.         }
  327.         return prefix;
  328.     }

  329.     /**
  330.      * Creates the debug statement that will be output for each method
  331.      *
  332.      * @param method
  333.      * @return @throws NotFoundException
  334.      */
  335.     protected String getMethodTrace(CtMethod method)
  336.     {
  337.         ExceptionUtils.checkNull("method", method);
  338.         StringBuilder buf = new StringBuilder("if (logger.isInfoEnabled()) {logger.info(\"");
  339.         buf.append("\" + methodName + \" --> ");
  340.         //javassist names the arguments $1,$2,$3, etc.
  341.         buf.append("'\" + org.andromda.core.translation.TranslationUtils.trimToEmpty($1) + \"'\");}");
  342.         return buf.toString();
  343.     }

  344.     /**
  345.      * Extends the Javassist class pool so that we can define our own ClassLoader to use from which to find, load and
  346.      * modify and existing class.
  347.      *
  348.      * @author Chad Brandon
  349.      */
  350.     private static class TranslatorClassPool
  351.             extends ClassPool
  352.     {

  353.         private static final Logger logger = Logger.getLogger(TranslatorClassPool.class);

  354.         protected TranslatorClassPool()
  355.         {
  356.             super(ClassPool.getDefault());
  357.             if (logger.isInfoEnabled())
  358.             {
  359.                 logger.debug("instantiating new TranslatorClassPool");
  360.             }
  361.         }

  362.         /**
  363.          * Retrieves an instance of this TranslatorClassPool using the loader to find/load any classes.
  364.          *
  365.          * @param loader
  366.          * @return pool
  367.          */
  368.         protected static ClassPool getPool(ClassLoader loader)
  369.         {
  370.             if (loader == null)
  371.             {
  372.                 loader = Thread.currentThread().getContextClassLoader();
  373.             }
  374.             TranslatorClassPool pool = new TranslatorClassPool();
  375.             pool.insertClassPath(new LoaderClassPath(loader));
  376.             return pool;
  377.         }

  378.         /**
  379.          * Returns a <code>Class</code> object. It calls <code>write()</code> to obtain a class file and then
  380.          * loads the obtained class file into the JVM. The returned <code>Class</code> object represents the loaded
  381.          * class.
  382.          * <p/>
  383.          * To load a class file, this method uses an internal class loader. Thus, that class file is not loaded by the
  384.          * system class loader, which should have loaded this <code>AspectClassPool</code> class. The internal class
  385.          * loader loads only the classes explicitly specified by this method <code>writeAsClass()</code>. The other
  386.          * classes are loaded by the parent class loader (the system class loader) by delegation. Thus, if a class
  387.          * <code>X</code> loaded by the internal class loader refers to a class <code>Y</code>, then the class
  388.          * <code>Y</code> is loaded by the parent class loader.
  389.          *
  390.          * @param classname a fully-qualified class name.
  391.          * @return Class the Class it writes.
  392.          * @throws NotFoundException
  393.          * @throws IOException
  394.          * @throws CannotCompileException
  395.          */
  396.         @SuppressWarnings("unused")
  397.         public Class writeAsClass(String classname) throws NotFoundException, IOException, CannotCompileException
  398.         {
  399.             try
  400.             {
  401.                 final CtClass ctTranslatorClass = get(classname);
  402.                 return classLoader.loadClass(classname, ctTranslatorClass.toBytecode());
  403.             }
  404.             catch (ClassFormatError e)
  405.             {
  406.                 throw new CannotCompileException(e, classname);
  407.             }
  408.         }

  409.         /**
  410.          * LocalClassLoader which allows us to dynamically construct classes on the fly using Javassist.
  411.          */
  412.         static class LocalClassLoader
  413.                 extends ClassLoader
  414.         {
  415.             /**
  416.              * Constructs an instance of LocalClassLoader.
  417.              *
  418.              * @param parent
  419.              */
  420.             public LocalClassLoader(ClassLoader parent)
  421.             {
  422.                 super(parent);
  423.             }

  424.             /**
  425.              * Loads a class.
  426.              *
  427.              * @param name      the name
  428.              * @param classfile the bytes of the class.
  429.              * @return Class
  430.              * @throws ClassFormatError
  431.              */
  432.             public Class loadClass(String name, byte[] classfile) throws ClassFormatError
  433.             {
  434.                 Class c = defineClass(name, classfile, 0, classfile.length);
  435.                 resolveClass(c);
  436.                 return c;
  437.             }
  438.         }

  439.         /**
  440.          * Create the LocalClassLoader and specify the ClassLoader for this class as the parent ClassLoader. This allows
  441.          * classes defined outside this LocalClassLoader to be loaded (i.e. classes that already exist, and aren't being
  442.          * dynamically created
  443.          */
  444.         private static LocalClassLoader classLoader = new LocalClassLoader(LocalClassLoader.class.getClassLoader());
  445.     }

  446.     /**
  447.      * This method is called by the main method during the build process, to "adapt" the class to the OCL parser.
  448.      */
  449.     protected static void adaptClass()
  450.     {
  451.         if (logger.isInfoEnabled())
  452.         {
  453.             logger.info("adapting class for OCL parser");
  454.         }
  455.         TraceTranslator translator = new TraceTranslator();
  456.         if (translator.needsAdaption())
  457.         {
  458.             try
  459.             {
  460.                 translator.getAdaptedTranslationClass();
  461.             }
  462.             catch (Throwable th)
  463.             {
  464.                 logger.error(th);
  465.             }
  466.         }
  467.     }

  468.     /**
  469.      * This main method is called during the build process, to "adapt" the class to the OCL parser.
  470.      *
  471.      * @param args
  472.      */
  473.     public static void main(String[] args)
  474.     {
  475.         try
  476.         {
  477.             AndroMDALogger.initialize();
  478.             TraceTranslator.adaptClass();
  479.         }
  480.         catch (Throwable th)
  481.         {
  482.             logger.error(th);
  483.         }
  484.     }

  485.     /**
  486.      * Ancestor abstract method which must be implemented even when it does nothing.
  487.      *
  488.      * @see org.andromda.translation.ocl.BaseTranslator#postProcess()
  489.      */
  490.     @Override
  491.     public void postProcess()
  492.     {
  493.         // Do nothing
  494.     }
  495. }