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
}
}