View Javadoc

1   /*
2    * Jour - bytecode instrumentation library
3    *
4    * Copyright (C) 2007-2008 Vlad Skarzhevskyy
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Library General Public
8    * License as published by the Free Software Foundation; either
9    * version 2 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Library General Public License for more details.
15   *
16   * You should have received a copy of the GNU Library General Public
17   * License along with this library; if not, write to the
18   * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19   * Boston, MA  02111-1307, USA.
20   * 
21   * @version $Id: SignatureImport.java 122 2008-07-23 00:09:34Z vlads $
22   * 
23   */
24  package net.sf.jour.signature;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.IOException;
28  import java.net.URL;
29  import java.util.List;
30  import java.util.StringTokenizer;
31  import java.util.Vector;
32  
33  import javassist.CannotCompileException;
34  import javassist.ClassPool;
35  import javassist.CtBehavior;
36  import javassist.CtClass;
37  import javassist.CtConstructor;
38  import javassist.CtField;
39  import javassist.CtMethod;
40  import javassist.Modifier;
41  import javassist.NotFoundException;
42  
43  import javax.xml.parsers.ParserConfigurationException;
44  
45  import net.sf.jour.ConfigException;
46  import net.sf.jour.instrumentor.MakeEmptyMethodInstrumentor;
47  import net.sf.jour.log.Logger;
48  import net.sf.jour.util.ConfigFileUtil;
49  import net.sf.jour.util.FileUtil;
50  
51  import org.w3c.dom.Document;
52  import org.w3c.dom.Node;
53  import org.w3c.dom.NodeList;
54  import org.xml.sax.SAXException;
55  
56  /**
57   * @author vlads
58   * 
59   */
60  public class SignatureImport {
61  
62  	protected static final Logger log = Logger.getLogger();
63  
64  	static final boolean createableObject = false;
65  
66  	// Since Javassist 3.8
67  	static final boolean editableObject = true;
68  
69  	static final boolean editableObjectConstructor = false;
70  
71  	static final String OBJECT_CLASS_NAME = "java.lang.Object";
72  
73  	private ClassPool classPool;
74  
75  	private List classNames = new Vector();
76  
77  	private List classes = new Vector();
78  
79  	private APIFilter filter;
80  
81  	private String stubException;
82  
83  	private String stubExceptionMessage;
84  
85  	public SignatureImport(boolean useSystemClassPath, String supportingJars) {
86  		classPool = new ClassPool();
87  		if (supportingJars != null) {
88  			try {
89  				classPool.appendPathList(supportingJars);
90  			} catch (NotFoundException e) {
91  				throw new RuntimeException(e);
92  			}
93  		}
94  		if (useSystemClassPath) {
95  			classPool.appendSystemPath();
96  		}
97  	}
98  
99  	public ClassPool getClassPool() {
100 		return classPool;
101 	}
102 
103 	public List getClasses() {
104 		return this.classes;
105 	}
106 
107 	public List getClassNames() {
108 		return this.classNames;
109 	}
110 
111 	public void setStubException(String property) {
112 		this.stubException = property;
113 	}
114 
115 	public void setStubExceptionMessage(String property) {
116 		this.stubExceptionMessage = property;
117 		if ((this.stubException == null) && (property != null)) {
118 			this.stubException = "java.lang.RuntimeException";
119 		}
120 	}
121 
122 	public void load(String xmlFileName) {
123 		load(xmlFileName, null);
124 	}
125 
126 	public void load(String xmlFileName, APIFilter filter) {
127 		if (xmlFileName == null) {
128 			throw new ConfigException("Signature File required");
129 		}
130 		URL location = FileUtil.getFile(xmlFileName);
131 		if (location == null) {
132 			throw new ConfigException("File Not found " + xmlFileName);
133 		}
134 		if (filter == null) {
135 			this.filter = APIFilter.ALL;
136 		} else {
137 			this.filter = filter;
138 		}
139 		try {
140 			Document xmlDoc = ConfigFileUtil.loadDocument(location);
141 			Node rootNode = ConfigFileUtil.getFirstElement(xmlDoc, ExportXML.rootNodeName);
142 			if (rootNode == null) {
143 				throw new ConfigException("Invalid XML root");
144 			}
145 
146 			NodeList classNodeList = rootNode.getChildNodes();
147 			for (int j = 0; j < classNodeList.getLength(); j++) {
148 				Node node = classNodeList.item(j);
149 				if ("interface".equals(node.getNodeName())) {
150 					CtClass c = loadInterface(node);
151 					if (this.filter.isAPIClass(c)) {
152 						this.classes.add(c);
153 						classNames.add(c.getName());
154 					}
155 				} else if ("class".equals(node.getNodeName())) {
156 					CtClass i = loadClass(node);
157 					if (this.filter.isAPIClass(i)) {
158 						this.classes.add(i);
159 						classNames.add(i.getName());
160 					}
161 				} else if (node.hasChildNodes()) {
162 					throw new ConfigException("Invalid XML node " + node.getNodeName());
163 				}
164 			}
165 			for (int j = 0; j < classNodeList.getLength(); j++) {
166 				Node node = classNodeList.item(j);
167 				if ("class".equals(node.getNodeName())) {
168 					updateClass(node);
169 				}
170 			}
171 		} catch (ParserConfigurationException e) {
172 			throw new ConfigException("Error parsing XML", e);
173 		} catch (SAXException e) {
174 			throw new ConfigException("Error parsing XML", e);
175 		} catch (IOException e) {
176 			throw new ConfigException("Error parsing XML", e);
177 		}
178 	}
179 
180 	private CtClass loadInterface(Node node) {
181 		CtClass klass = createInterface(node);
182 		int mod = decodeModifiers(ConfigFileUtil.getNodeAttribute(node, "modifiers"));
183 		mod |= Modifier.INTERFACE | Modifier.ABSTRACT;
184 		klass.setModifiers(mod);
185 
186 		loadHierarchy(klass, node);
187 		loadMethods(klass, node);
188 		loadFields(klass, node);
189 		return klass;
190 	}
191 
192 	private CtClass loadClass(Node node) {
193 		CtClass klass = createClass(node);
194 
195 		int mod = decodeModifiers(ConfigFileUtil.getNodeAttribute(node, "modifiers"));
196 		klass.setModifiers(mod);
197 
198 		loadHierarchy(klass, node);
199 		loadConstructors(klass, node);
200 		loadMethods(klass, node);
201 		loadFields(klass, node);
202 		return klass;
203 	}
204 
205 	private void updateClass(Node node) {
206 		String classname = ConfigFileUtil.getNodeAttribute(node, "name");
207 		if (!editableObject && OBJECT_CLASS_NAME.equals(classname)) {
208 			return;
209 		}
210 		CtClass klass;
211 		try {
212 			klass = classPool.get(classname);
213 		} catch (NotFoundException e) {
214 			throw new RuntimeException(classname + " class is missing");
215 		}
216 		if (!this.filter.isAPIClass(klass)) {
217 			return;
218 		}
219 		if (!editableObjectConstructor && OBJECT_CLASS_NAME.equals(classname)) {
220 			// /
221 		} else {
222 			updateConstructors(klass, node);
223 		}
224 		updateMethods(klass, node);
225 	}
226 
227 	private CtClass createClass(Node node) {
228 		String classname = ConfigFileUtil.getNodeAttribute(node, "name");
229 		String superclassName = ConfigFileUtil.getNodeAttribute(node, "extends");
230 
231 		if (!createableObject && OBJECT_CLASS_NAME.equals(classname)) {
232 			return createEmptyObjectClass();
233 		}
234 
235 		try {
236 			CtClass exists = classPool.get(classname);
237 			exists.detach();
238 		} catch (NotFoundException e) {
239 
240 		}
241 
242 		return classPool.makeClass(classname, createClass(superclassName));
243 	}
244 
245 	private CtClass createClass(String classname) {
246 		if (classname == null) {
247 			return null;
248 		}
249 		CtClass klass;
250 		try {
251 			klass = classPool.get(classname);
252 		} catch (NotFoundException e) {
253 			klass = classPool.makeClass(classname);
254 		}
255 		return klass;
256 	}
257 
258 	private CtClass createEmptyObjectClass() {
259 		ClassPool defaultPool = ClassPool.getDefault();
260 		CtClass klass;
261 		try {
262 			klass = defaultPool.get(OBJECT_CLASS_NAME);
263 
264 			klass.detach();
265 			CtConstructor init = klass.getClassInitializer();
266 			if (init != null) {
267 				klass.removeConstructor(init);
268 			}
269 			CtMethod[] methods = klass.getDeclaredMethods();
270 			for (int i = 0; i < methods.length; i++) {
271 				klass.removeMethod(methods[i]);
272 			}
273 			CtField[] fields = klass.getFields();
274 			for (int i = 0; i < fields.length; i++) {
275 				klass.removeField(fields[i]);
276 			}
277 		} catch (NotFoundException e) {
278 			throw new RuntimeException("Can't create class java.lang.Object", e);
279 		}
280 		try {
281 			classPool.makeClass(new ByteArrayInputStream(klass.toBytecode()));
282 			return classPool.get(OBJECT_CLASS_NAME);
283 		} catch (IOException e) {
284 			throw new RuntimeException("Can't create class java.lang.Object", e);
285 		} catch (NotFoundException e) {
286 			throw new RuntimeException("Can't create class java.lang.Object", e);
287 		} catch (CannotCompileException e) {
288 			throw new RuntimeException("Can't create class java.lang.Object", e);
289 		}
290 	}
291 
292 	private CtClass createInterface(Node node) {
293 		String classname = ConfigFileUtil.getNodeAttribute(node, "name");
294 		String superclassName = ConfigFileUtil.getNodeAttribute(node, "extends");
295 
296 		try {
297 			CtClass exists = classPool.get(classname);
298 			exists.detach();
299 		} catch (NotFoundException e) {
300 		}
301 
302 		return classPool.makeInterface(classname, createInterface(superclassName));
303 	}
304 
305 	private CtClass createInterface(String classname) {
306 		if (classname == null) {
307 			return null;
308 		}
309 		CtClass klass;
310 		try {
311 			klass = classPool.get(classname);
312 		} catch (NotFoundException e) {
313 			klass = classPool.makeInterface(classname);
314 		}
315 		return klass;
316 	}
317 
318 	private void loadHierarchy(CtClass klass, Node node) {
319 		Node implementNode = ConfigFileUtil.getChildNode(node, "implements");
320 		if (implementNode == null) {
321 			return;
322 		}
323 		Node[] interfaceList = ConfigFileUtil.getChildNodes(implementNode, "interface");
324 		for (int i = 0; i < interfaceList.length; i++) {
325 			Node interfaceNode = interfaceList[i];
326 			String interfaceName = ConfigFileUtil.getNodeAttribute(interfaceNode, "name");
327 			klass.addInterface(createInterface(interfaceName));
328 		}
329 	}
330 
331 	private CtClass[] getParameters(Node node) {
332 		Node[] partNodes = ConfigFileUtil.getChildNodes(node, "parameter");
333 		CtClass[] parameters = new CtClass[partNodes.length];
334 		for (int j = 0; j < partNodes.length; j++) {
335 			parameters[j] = createInterface(ConfigFileUtil.getNodeAttribute(partNodes[j], "type"));
336 			if (parameters[j] == null) {
337 				throw new RuntimeException("parameter " + j + " type is missing");
338 			}
339 		}
340 		return parameters;
341 	}
342 
343 	private void loadConstructors(CtClass klass, Node node) {
344 		Node[] list = ConfigFileUtil.getChildNodes(node, "constructor");
345 		boolean defaultConstructorLoaded = false;
346 		for (int i = 0; i < list.length; i++) {
347 			int modifiers = getModifiers(list[i]);
348 			CtClass[] parameters = getParameters(list[i]);
349 			if (parameters.length != 0) {
350 				if (!this.filter.isAPIModifier(modifiers)) {
351 					continue;
352 				}
353 			}
354 
355 			CtConstructor c;
356 			try {
357 				c = klass.getDeclaredConstructor(parameters);
358 			} catch (NotFoundException e) {
359 				c = new CtConstructor(parameters, klass);
360 				// for setBody see updateConstructors
361 				try {
362 					klass.addConstructor(c);
363 				} catch (CannotCompileException e2) {
364 					throw new RuntimeException(klass.getName(), e2);
365 				}
366 			}
367 			loadExceptions(c, list[i]);
368 			c.setModifiers(modifiers);
369 			if (parameters.length == 0) {
370 				defaultConstructorLoaded = true;
371 			}
372 		}
373 		if (!defaultConstructorLoaded) {
374 			CtConstructor defaultConstructor;
375 			try {
376 				defaultConstructor = klass.getDeclaredConstructor(new CtClass[0]);
377 			} catch (NotFoundException e) {
378 				defaultConstructor = new CtConstructor(new CtClass[0], klass);
379 				// for setBody see updateConstructors
380 				try {
381 					klass.addConstructor(defaultConstructor);
382 				} catch (CannotCompileException e2) {
383 					throw new RuntimeException(klass.getName(), e2);
384 				}
385 			}
386 			defaultConstructor.setModifiers(Modifier.PRIVATE);
387 		}
388 	}
389 
390 	private void updateConstructors(CtClass klass, Node node) {
391 		CtConstructor[] constructors = klass.getDeclaredConstructors();
392 		for (int i = 0; i < constructors.length; i++) {
393 			try {
394 				constructors[i].setBody(emptyBodyCode(CtClass.voidType));
395 			} catch (CannotCompileException ce) {
396 				throw new RuntimeException(klass.getName(), ce);
397 			}
398 		}
399 	}
400 
401 	private void updateMethods(CtClass klass, Node node) {
402 		if (klass.isInterface()) {
403 			return;
404 		}
405 		CtMethod[] methods = klass.getDeclaredMethods();
406 		for (int i = 0; i < methods.length; i++) {
407 			CtMethod method = methods[i];
408 			if (!Modifier.isAbstract(method.getModifiers())) {
409 				try {
410 					method.setBody(emptyBodyCode(method.getReturnType()));
411 				} catch (CannotCompileException e) {
412 					throw new RuntimeException(klass.getName() + "." + method.getName(), e);
413 				} catch (NotFoundException e) {
414 					throw new RuntimeException(klass.getName() + "." + method.getName(), e);
415 				}
416 			}
417 		}
418 	}
419 
420 	private String emptyBodyCode(CtClass returnType) {
421 		if (this.stubException == null) {
422 			return MakeEmptyMethodInstrumentor.emptyBody(returnType);
423 		} else {
424 			StringBuffer b = new StringBuffer();
425 			b.append("throw new ");
426 			b.append(this.stubException);
427 			if (this.stubExceptionMessage == null) {
428 				b.append("();");
429 			} else {
430 				b.append("(\"");
431 				b.append(this.stubExceptionMessage);
432 				b.append("\");");
433 			}
434 			return b.toString();
435 		}
436 	}
437 
438 	private int getModifiers(Node node) {
439 		return decodeModifiers(ConfigFileUtil.getNodeAttribute(node, "modifiers"));
440 	}
441 
442 	private int decodeModifiers(String modifiers) {
443 		if (modifiers == null) {
444 			return 0;
445 		}
446 		int mod = 0;
447 		StringTokenizer st = new StringTokenizer(modifiers, " ");
448 		if (st.hasMoreTokens()) {
449 			while (st.hasMoreTokens()) {
450 				mod |= decodeModifier(st.nextToken());
451 			}
452 		} else {
453 			mod = decodeModifier(modifiers);
454 		}
455 		return mod;
456 	}
457 
458 	private int decodeModifier(String modifier) {
459 		if (modifier.equalsIgnoreCase("public")) {
460 			return Modifier.PUBLIC;
461 		} else if (modifier.equalsIgnoreCase("protected")) {
462 			return Modifier.PROTECTED;
463 		} else if (modifier.equalsIgnoreCase("private")) {
464 			return Modifier.PRIVATE;
465 		} else if (modifier.equalsIgnoreCase("abstract")) {
466 			return Modifier.ABSTRACT;
467 		} else if (modifier.equalsIgnoreCase("static")) {
468 			return Modifier.STATIC;
469 		} else if (modifier.equalsIgnoreCase("final")) {
470 			return Modifier.FINAL;
471 		} else if (modifier.equalsIgnoreCase("volatile")) {
472 			return Modifier.TRANSIENT;
473 		} else if (modifier.equalsIgnoreCase("synchronized")) {
474 			return Modifier.SYNCHRONIZED;
475 		} else if (modifier.equalsIgnoreCase("native")) {
476 			return Modifier.NATIVE;
477 		} else if (modifier.equalsIgnoreCase("interface")) {
478 			return Modifier.INTERFACE;
479 		} else if (modifier.equalsIgnoreCase("strictfp")) {
480 			return Modifier.STRICT;
481 		} else {
482 			throw new RuntimeException("Invalid modifier [" + modifier + "]");
483 		}
484 	}
485 
486 	private void loadExceptions(CtBehavior m, Node node) {
487 		Node[] list = ConfigFileUtil.getChildNodes(node, "exception");
488 		if (list.length == 0) {
489 			return;
490 		}
491 		CtClass[] types = new CtClass[list.length];
492 		for (int i = 0; i < list.length; i++) {
493 			types[i] = createClass(ConfigFileUtil.getNodeAttribute(list[i], "name"));
494 		}
495 		try {
496 			m.setExceptionTypes(types);
497 		} catch (NotFoundException e) {
498 			throw new RuntimeException("Can't add exceptions", e);
499 		}
500 	}
501 
502 	private void loadMethods(CtClass klass, Node node) {
503 		Node[] list = ConfigFileUtil.getChildNodes(node, "method");
504 		for (int i = 0; i < list.length; i++) {
505 			int modifiers = getModifiers(list[i]);
506 			if (!this.filter.isAPIModifier(modifiers)) {
507 				continue;
508 			}
509 			String mname = ConfigFileUtil.getNodeAttribute(list[i], "name");
510 			CtClass[] parameters = getParameters(list[i]);
511 			CtClass returnType = createInterface(ConfigFileUtil.getNodeAttribute(list[i], "return"));
512 			CtMethod method = new CtMethod(returnType, mname, parameters, klass);
513 			method.setModifiers(modifiers);
514 			try {
515 				// See updateMethods second pass
516 				// if ((!klass.isInterface()) &&
517 				// (!Modifier.isAbstract(method.getModifiers()))) {
518 				// method.setBody(emptyBodyCode(returnType));
519 				// }
520 				loadExceptions(method, list[i]);
521 				klass.addMethod(method);
522 			} catch (CannotCompileException e) {
523 				throw new RuntimeException(klass.getName(), e);
524 			}
525 		}
526 	}
527 
528 	private void loadFields(CtClass klass, Node node) {
529 		Node[] list = ConfigFileUtil.getChildNodes(node, "field");
530 		for (int i = 0; i < list.length; i++) {
531 			int modifiers = getModifiers(list[i]);
532 			if (!this.filter.isAPIModifier(modifiers)) {
533 				continue;
534 			}
535 			String fname = ConfigFileUtil.getNodeAttribute(list[i], "name");
536 			CtClass fieldType = createInterface(ConfigFileUtil.getNodeAttribute(list[i], "type"));
537 			CtField field;
538 			try {
539 				field = new CtField(fieldType, fname, klass);
540 				field.setModifiers(modifiers);
541 				CtField.Initializer initializer = null;
542 				String constValue = ConfigFileUtil.getNodeAttribute(list[i], "constant-value");
543 				if (constValue != null) {
544 					initializer = createFieldInitializer(fieldType, constValue, klass.getName() + "." + fname);
545 				}
546 				klass.addField(field, initializer);
547 			} catch (CannotCompileException e) {
548 				throw new RuntimeException(klass.getName() + " filed " + fname, e);
549 			}
550 		}
551 	}
552 
553 	private CtField.Initializer createFieldInitializer(CtClass fieldType, String constValue, String name) {
554 		if (APIFilter.javaLangString.equals(fieldType.getName())) {
555 			return CtField.Initializer.constant(constValue);
556 		} else if (fieldType == CtClass.longType) {
557 			return CtField.Initializer.constant(Long.valueOf(constValue).longValue());
558 		} else if (fieldType == CtClass.floatType) {
559 			return CtField.Initializer.constant(Float.valueOf(constValue).floatValue());
560 		} else if (fieldType == CtClass.doubleType) {
561 			return CtField.Initializer.constant(Double.valueOf(constValue).doubleValue());
562 		} else if (fieldType == CtClass.booleanType) {
563 			return CtField.Initializer.constant(Boolean.valueOf(constValue).booleanValue());
564 		} else {
565 			return CtField.Initializer.constant(Integer.valueOf(constValue).intValue());
566 		}
567 	}
568 }