1 package org.ocltf.translation;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.URL;
6 import java.util.HashMap;
7 import java.util.Iterator;
8 import java.util.Map;
9
10 import javassist.CannotCompileException;
11 import javassist.ClassPool;
12 import javassist.CtClass;
13 import javassist.CtField;
14 import javassist.CtMethod;
15 import javassist.LoaderClassPath;
16 import javassist.NotFoundException;
17
18 import org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
20 import org.ocltf.logging.Logger;
21 import org.ocltf.utils.ExceptionUtils;
22 import org.ocltf.utils.FileResourceUtils;
23
24 /***
25 * This class allows us to trace the parsing of the expression.
26 * It is reflectively extended by Javaassist to allow the inclusion
27 * of all "inA" and "outA" methods produced by the SableCC parser. This allows
28 * us to dynamically include all handling code in each method without having
29 * to manual code each one. It used used during development of Translators
30 * since it allows you to see the execution of each node.
31 *
32 * @author Chad Brandon
33 */
34 public class TraceTranslator extends BaseTranslator {
35
36 private static final Log logger = LogFactory.getLog(TraceTranslator.class);
37
38 private static final String INA_PREFIX = "inA";
39 private static final String OUTA_PREFIX = "outA";
40
41 private Map methods = new HashMap();
42
43 /***
44 * This is added to the adapted class and
45 * then checked to see if it exists to determine
46 * if we need to adapt the class.
47 */
48 private static final String FIELD_ADAPTED = "adapted";
49
50 private ClassPool pool;
51
52 /***
53 * Constructs an instance of TraceTranslator.
54 */
55 public TraceTranslator() {}
56
57 /***
58 * Creates and returns an new Instance of this ExpressionTranslator
59 * as a Translator object. The first time this method is
60 * called this class will dynamically be adapted to handle
61 * all parser calls.
62 *
63 * @return Translator
64 * @throws IllegalArgumentException - if the
65 * analysisSuperClass doesn't implement Analysis.
66 */
67 public static Translator getInstance() {
68 String debugMethodName = "getInstance";
69 if (logger.isDebugEnabled()) {
70 logger.debug("performing " + debugMethodName);
71 }
72 try {
73 TraceTranslator oclTranslator = new TraceTranslator();
74 Translator translator = oclTranslator;
75 if (oclTranslator.needsAdaption()) {
76
77 if (logger.isInfoEnabled()) {
78 logger.info(
79 " OCL Translator has not been adapted --> adapting");
80 }
81 translator = (Translator)
82 oclTranslator.getAdaptedTranslationClass().newInstance();
83 }
84 return translator;
85 } catch (Exception ex) {
86 String errMsg = "Error performing " + debugMethodName;
87 logger.error(errMsg, ex);
88 throw new TranslatorException(errMsg, ex);
89 }
90 }
91
92 /***
93 * @see org.ocltf.translation.Translator#translate(java.lang.String, java.lang.Object, java.lang.String)
94 */
95 public Expression translate(String translationName, Object contextElement, String expression) {
96 if (logger.isInfoEnabled()) {
97 logger.info("======================== Tracing Expression ========================");
98 logger.info(TranslationUtils.removeExtraWhitespace(expression));
99 logger.info("======================== ================== ========================");
100 }
101 Expression expressionObj = super.translate(translationName, contextElement, expression);
102 if (logger.isInfoEnabled()) {
103 logger.info("======================== Tracing Complete ========================");
104 }
105 return expressionObj;
106 }
107
108 /***
109 * Checks to see if this class needs to be adapated
110 * If it has the "adapted" field then we know it already
111 * has been adapted.
112 * @return true/false, true if it needs be be adapted.
113 */
114 protected boolean needsAdaption() {
115 boolean needsAdaption = false;
116 try {
117 this.getClass().getDeclaredField(FIELD_ADAPTED);
118 } catch (NoSuchFieldException ex) {
119 needsAdaption = true;
120 }
121 return needsAdaption;
122 }
123
124 /***
125 * Creates and returns the adapted translator class.
126 * @return Class the new Class instance.
127 * @throws NotFoundException
128 * @throws CannotCompileException
129 * @throws IOException
130 */
131 protected Class getAdaptedTranslationClass()
132 throws NotFoundException, CannotCompileException, IOException {
133
134 Class thisClass = this.getClass();
135 this.pool =
136 TranslatorClassPool.getPool(thisClass.getClassLoader());
137
138 CtClass ctTranslatorClass = pool.get(thisClass.getName());
139
140 CtField adaptedField = new CtField(
141 CtClass.booleanType,
142 FIELD_ADAPTED,
143 ctTranslatorClass);
144
145 ctTranslatorClass.addField(adaptedField);
146
147
148 CtMethod[] analysisMethods = ctTranslatorClass.getMethods();
149
150 if (analysisMethods != null) {
151
152 int methodNum = analysisMethods.length;
153
154 for (int ctr = 0; ctr < methodNum; ctr++) {
155 CtMethod method = analysisMethods[ctr];
156 String methodName = method.getName();
157
158 if (methodName.startsWith(INA_PREFIX)) {
159
160 this.methods.put(method, getInAMethodBody(method));
161 } else if (methodName.startsWith(OUTA_PREFIX)) {
162
163 this.methods.put(method, getOutAMethodBody(method));
164 }
165 }
166
167
168 Iterator allMethods = this.methods.keySet().iterator();
169 while (allMethods.hasNext()) {
170 CtMethod method = (CtMethod)allMethods.next();
171 CtMethod newMethod =
172 new CtMethod(method, ctTranslatorClass, null);
173 String methodBody = (String)this.methods.get(method);
174 newMethod.setBody(methodBody);
175 ctTranslatorClass.addMethod(newMethod);
176 }
177
178 }
179 this.writeAdaptedClass();
180 return ctTranslatorClass.toClass();
181 }
182
183 /***
184 * Writes the class to the directory found by the class loader
185 * (since the class is a currently existing class)
186 */
187 protected void writeAdaptedClass() {
188 String methodName = "writeAdaptedClass";
189 if (logger.isDebugEnabled()) {
190 logger.debug("performing " + methodName);
191 }
192 try {
193 String className = this.getClass().getName();
194 File dir = this.getAdaptedClassOutputDirectory();
195 if (logger.isDebugEnabled()) {
196 logger.debug("writing className ("
197 + className + ") to directory --> "
198 + "(" + dir + ")");
199 }
200 this.pool.writeFile(
201 this.getClass().getName(),
202 dir.toString());
203 } catch (Exception ex) {
204 String errMsg = "Error performing " + methodName;
205 logger.error(errMsg, ex);
206 throw new TranslatorException(errMsg, ex);
207 }
208 }
209
210 /***
211 * Retrieves the output directory which
212 * the adapted class will be written to.
213 * @return
214 */
215 protected File getAdaptedClassOutputDirectory() {
216 String methodName = "getAdaptedClassOutputDirectory";
217 Class thisClass = this.getClass();
218 URL classAsResource = FileResourceUtils.getClassResource(thisClass);
219 File file = new File(classAsResource.getFile());
220 File dir = file.getParentFile();
221 if (dir == null) {
222 throw new TranslatorException(
223 methodName
224 + " - can not retrieve directory for file ("
225 + file
226 + ")");
227 }
228 String className = thisClass.getName();
229 int index = className.indexOf('.');
230 String basePackage = null;
231 if (index != -1) {
232 basePackage = className.substring(0, index);
233 }
234 if (basePackage != null) {
235 while (!dir.toString().endsWith(basePackage)) {
236 dir = dir.getParentFile();
237 }
238 dir = dir.getParentFile();
239 }
240 return dir;
241 }
242
243 /***
244 * Creates and returns the method body for each "inA" method
245 * @param method
246 * @return String
247 * @throws NotFoundException
248 */
249 protected String getInAMethodBody(CtMethod method) throws NotFoundException {
250 String methodDebugName = "getInAMethodBody";
251 ExceptionUtils.checkNull(methodDebugName, "method", method);
252 StringBuffer methodBody = new StringBuffer("{");
253 String methodName = method.getName();
254 methodBody.append("String methodName = \"" + methodName + "\";");
255 methodBody.append(this.getMethodTrace(method));
256
257
258 methodBody.append("super." + methodName + "($1);");
259 methodBody.append("}");
260 return methodBody.toString();
261 }
262
263 /***
264 * Creates and returns the method body for each "inA" method
265 * @param method
266 * @return String
267 * @throws NotFoundException
268 */
269 protected String getOutAMethodBody(CtMethod method) throws NotFoundException {
270 String methodDebugName = "getOutAMethodBody";
271 ExceptionUtils.checkNull(methodDebugName, "method", method);
272 StringBuffer methodBody = new StringBuffer("{");
273 String methodName = method.getName();
274 methodBody.append("String methodName = \"" + methodName + "\";");
275 methodBody.append(this.getMethodTrace(method));
276
277
278 methodBody.append("super." + methodName + "($1);");
279 methodBody.append("}");
280 return methodBody.toString();
281 }
282
283 /***
284 * Returns the OCL fragment name that must have
285 * a matching name in the library translation template in order to
286 * be placed into the Expression translated expression buffer.
287 * @param method
288 * @return String
289 */
290 protected String getOclFragmentName(CtMethod method) {
291 String methodName = "getOclFragmentName";
292 ExceptionUtils.checkNull(methodName, "method", method);
293 String fragment = method.getName();
294 String prefix = this.getMethodPrefix(method);
295 int index = fragment.indexOf(prefix);
296 if (index != -1) {
297 fragment =
298 fragment.substring(index + prefix.length(), fragment.length());
299 }
300 return fragment;
301 }
302
303 /***
304 * Returns the prefix for the method (inA or outA)
305 * @param method
306 * @return
307 */
308 protected String getMethodPrefix(CtMethod method) {
309 String methodName = "getMethodPrefix";
310 ExceptionUtils.checkNull(methodName, "method", method);
311 String mName = method.getName();
312 String prefix = INA_PREFIX;
313 if (mName.startsWith(OUTA_PREFIX)) {
314 prefix = OUTA_PREFIX;
315 }
316 return prefix;
317 }
318
319 /***
320 * Creates the debug statement that will be output for each method
321 * @param method
322 * @return
323 * @throws NotFoundException
324 */
325 protected String getMethodTrace(CtMethod method) throws NotFoundException {
326 String methodName = "getMethodDebug";
327 ExceptionUtils.checkNull(methodName, "method", method);
328 StringBuffer buf =
329 new StringBuffer("if (logger.isInfoEnabled()) {logger.info(\"");
330 buf.append("\" + methodName + \" with ");
331
332 buf.append("'\" + org.ocltf.translation.TranslationUtils.trimToEmpty($1) + \"'\");}");
333 return buf.toString();
334 }
335
336 /***
337 * Extends the Javaassist class pool so that we can
338 * define our own ClassLoader to use from which to find, load
339 * and modify and existing class.
340 *
341 * @author Chad Brandon
342 */
343 protected static class TranslatorClassPool extends ClassPool {
344
345 private static Log logger =
346 LogFactory.getLog(TranslatorClassPool.class);
347
348 protected TranslatorClassPool() {
349 super(ClassPool.getDefault());
350 if (logger.isInfoEnabled()) {
351 logger.debug("instantiating new TranslatorClassPool");
352 }
353 }
354
355 /***
356 * Retrieves an instance of this TranslatorClassPool
357 * using the loader to find/load any classes.
358 * @param loader
359 * @return
360 */
361 protected static ClassPool getPool(ClassLoader loader) {
362 if (loader == null) {
363 loader = Thread.currentThread().getContextClassLoader();
364 }
365 TranslatorClassPool pool = new TranslatorClassPool();
366 pool.insertClassPath(new LoaderClassPath(loader));
367 return pool;
368 }
369
370 /***
371 * Returns a <code>java.lang.Class</code> object.
372 * It calls <code>write()</code> to obtain a class file and then
373 * loads the obtained class file into the JVM. The returned
374 * <code>Class</code> object represents the loaded class.
375 *
376 * <p>To load a class file, this method uses an internal class loader.
377 * Thus, that class file is not loaded by the system class loader,
378 * which should have loaded this <code>AspectClassPool</code> class.
379 * The internal class loader
380 * loads only the classes explicitly specified by this method
381 * <code>writeAsClass()</code>. The other classes are loaded
382 * by the parent class loader (the sytem class loader) by delegation.
383 * Thus, if a class <code>X</code> loaded by the internal class
384 * loader refers to a class <code>Y</code>, then the class
385 * <code>Y</code> is loaded by the parent class loader.
386 *
387 * @param classname a fully-qualified class name.
388 * @return Class the Class it writes.
389 * @throws NotFoundException
390 * @throws IOException
391 * @throws CannotCompileException
392 */
393 public Class writeAsClass(String classname)
394 throws NotFoundException, IOException, CannotCompileException {
395 try {
396 return classLoader.loadClass(classname, write(classname));
397 } catch (ClassFormatError e) {
398 throw new CannotCompileException(e, classname);
399 }
400 }
401
402 /***
403 * LocalClassLoader which allows us to dynamically construct
404 * classes on the fly using Javassist.
405 */
406 static class LocalClassLoader extends ClassLoader {
407 /***
408 * Constructs an instance of LocalClassLoader.
409 *
410 * @param parent
411 */
412 public LocalClassLoader(ClassLoader parent) {
413 super(parent);
414 }
415 /***
416 * Loads a class.
417 * @param name the name
418 * @param classfile the bytes of the class.
419 * @return Class
420 * @throws ClassFormatError
421 */
422 public Class loadClass(String name, byte[] classfile)
423 throws ClassFormatError {
424 Class c = defineClass(name, classfile, 0, classfile.length);
425 resolveClass(c);
426 return c;
427 }
428 };
429
430 /***
431 * Create the LocalClassLoader and specify the ClassLoader for this class
432 * as the parent ClassLoader. This allows classes defined outside
433 * this LocalClassLoader to be loaded (i.e. classes that already exist,
434 * and aren't being dynamically created
435 */
436 private static LocalClassLoader classLoader =
437 new LocalClassLoader(LocalClassLoader.class.getClassLoader());
438 }
439
440 /***
441 * This method is called by the main method
442 * during the build process, to "adapt"
443 * the class to the OCL parser.
444 */
445 protected static void adaptClass() {
446 if (logger.isInfoEnabled()) {
447 logger.info("adapting class for OCL parser");
448 }
449 TraceTranslator translator = new TraceTranslator();
450 if (translator.needsAdaption()) {
451 try {
452 translator.getAdaptedTranslationClass();
453 } catch (Throwable th) {
454 logger.error(th);
455 }
456 }
457 }
458
459 /***
460 * This main method is called during the build process, to "adapt"
461 * the class to the OCL parser.
462 * @param args
463 */
464 public static void main(String args[]) {
465 try {
466 Logger.configure();
467 TraceTranslator.adaptClass();
468 } catch (Throwable th) {
469 logger.error(th);
470 }
471 }
472
473 }