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