View Javadoc

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 		//get the "inA" methods from the analysisClass
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 					//add the new overriden "inA" methods  
160                     this.methods.put(method, getInAMethodBody(method));                        
161 				} else if (methodName.startsWith(OUTA_PREFIX)) {
162 					//add the new overriden "outA" methods
163                     this.methods.put(method, getOutAMethodBody(method));
164 				}
165 			}
166             
167             //now add all the methods to the class
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 		//add the call of the super class method, so that any methods in sub classes
257 		//can provide functionality
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 		//add the call of the super class method, so that any methods in sub classes
277 		//can provide functionality
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 		//javaassist names the arguments $1,$2,$3, etc.
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 }