All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.andromda.translation.ocl.testsuite.TraceTranslator Maven / Gradle / Ivy

The newest version!
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 methods = new HashMap();

    /**
     * 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 case 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 inA 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 outA 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 Class object. It calls write() to obtain a class file and then
         * loads the obtained class file into the JVM. The returned Class object represents the loaded
         * class.
         * 

* 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 AspectClassPool class. The internal class * loader loads only the classes explicitly specified by this method writeAsClass(). The other * classes are loaded by the parent class loader (the system class loader) by delegation. Thus, if a class * X loaded by the internal class loader refers to a class Y, then the class * Y 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 } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy