001: package jaxx.tools.jaxxcapture;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import java.beans.*;
006: import java.io.*;
007: import java.lang.reflect.*;
008: import java.net.*;
009: import java.util.*;
010: import java.util.jar.*;
011: import java.util.jar.Attributes;
012: import javax.swing.*;
013: import org.w3c.dom.*;
014: import org.xml.sax.*;
015:
016: import jaxx.*;
017: import jaxx.compiler.*;
018: import jaxx.reflect.*;
019: import jaxx.tools.jaxxcapture.handlers.*;
020: import jaxx.types.*;
021:
022: public class JAXXCapture {
023: private static ClassMap objectHandlers = new ClassMap();
024:
025: static {
026: objectHandlers.put(ClassDescriptorLoader
027: .getClassDescriptor(Object.class), new ObjectHandler());
028: objectHandlers.put(ClassDescriptorLoader
029: .getClassDescriptor(JTabbedPane.class),
030: new JTabbedPaneHandler());
031: objectHandlers.put(ClassDescriptorLoader
032: .getClassDescriptor(jaxx.runtime.swing.Table.class),
033: new TableHandler());
034: }
035:
036: private Map/*<String, Object>*/sourceObjects = new HashMap/*<String, Object>*/();
037: private Map/*<String, CapturedObject>*/capturedObjects = new HashMap/*<String, CapturedObject>*/();
038: private ClassLoader classLoader;
039: private int count;
040:
041: private static class CaptureEventQueue extends EventQueue {
042: private ClassLoader classLoader;
043:
044: private CaptureEventQueue(ClassLoader classLoader) {
045: this .classLoader = classLoader;
046: }
047:
048: public void dispatchEvent(AWTEvent event) {
049: if (event.getID() == MouseEvent.MOUSE_PRESSED
050: && ((MouseEvent) event).isControlDown()) {
051: Component target = ((MouseEvent) event).getComponent();
052: if (!(target instanceof Window))
053: target = SwingUtilities.getWindowAncestor(target);
054: if (target instanceof JFrame)
055: target = ((JFrame) target).getContentPane();
056: else if (target instanceof JDialog)
057: target = ((JDialog) target).getContentPane();
058: if (target instanceof JWindow)
059: target = ((JWindow) target).getContentPane();
060: if (target != null) {
061: Thread.currentThread().setContextClassLoader(
062: classLoader);
063: JAXXCapture capture = new JAXXCapture(classLoader);
064: capture.applyNames(target);
065: ByteArrayOutputStream buffer = new ByteArrayOutputStream();
066: XMLEncoder encoder = new XMLEncoder(buffer);
067: encoder.writeObject(target);
068: encoder.close();
069: try {
070: System.err.println(new String(buffer
071: .toByteArray()));
072: System.out
073: .println(capture
074: .convertToJAXX(new ByteArrayInputStream(
075: buffer.toByteArray())));
076: } catch (IOException e) {
077: throw new RuntimeException(e);
078: }
079: }
080: }
081: super .dispatchEvent(event);
082: }
083: }
084:
085: private JAXXCapture(ClassLoader classLoader) {
086: this .classLoader = classLoader;
087: }
088:
089: public ClassLoader getClassLoader() {
090: return classLoader;
091: }
092:
093: public Map/*<String, CapturedObject>*/getCapturedObjects() {
094: return capturedObjects;
095: }
096:
097: private void applyNames(Component target) {
098: String name = target.getName();
099: if (name == null || sourceObjects.containsKey(name)
100: || !CompiledObject.isValidID(name)) {
101: do {
102: name = "Object" + ++count;
103: } while (sourceObjects.containsKey(name));
104: }
105: target.setName(name);
106: assert !sourceObjects.containsKey(name) : "ID " + name
107: + " is already registered";
108: sourceObjects.put(name, target);
109:
110: if (target instanceof Container) {
111: Container container = (Container) target;
112: for (int i = 0; i < container.getComponentCount(); i++)
113: applyNames(container.getComponent(i));
114: }
115: }
116:
117: public static String getText(Element tag) { // NOT a safe general-purpose implementation!
118: return ((Text) tag.getChildNodes().item(0)).getData();
119: }
120:
121: private String getArgumentsCode(ContextNode[] arguments) {
122: StringBuffer result = new StringBuffer();
123: result.append('(');
124: for (int i = 0; i < arguments.length; i++) {
125: if (i != 0)
126: result.append(", ");
127: result.append(getJavaCode(arguments[i]));
128: }
129: result.append(')');
130: return result.toString();
131: }
132:
133: public String getJavaCode(ContextNode node) {
134: StringBuffer result = new StringBuffer();
135: if (node instanceof PropertyNode) {
136: ContextNode[] arguments = node.getArguments();
137: result.append(arguments.length == 0 ? "get" : "set");
138: result.append(JAXXCompiler.capitalize(((PropertyNode) node)
139: .getProperty()));
140: result.append(getArgumentsCode(arguments));
141: } else if (node instanceof MethodNode) {
142: result.append((((MethodNode) node).getMethodName()));
143: result.append(getArgumentsCode(node.getArguments()));
144: } else if (node instanceof CapturedObject) {
145: CapturedObject object = (CapturedObject) node;
146: if (object.isInlineable()) {
147: result.append("new ");
148: result.append(object.getClassName());
149: result.append(getArgumentsCode(node.getArguments()));
150: } else {
151: String id = object.getProperty("id");
152: assert id != null;
153: result.append(id);
154: }
155: } else if (node instanceof ValueNode)
156: result.append(TypeManager.getJavaCode(((ValueNode) node)
157: .getValue()));
158: else if (node instanceof LiteralNode)
159: result.append(((LiteralNode) node).getJavaCode());
160: else
161: throw new IllegalArgumentException(
162: "unrecognized node type: " + node);
163: return result.toString();
164: }
165:
166: // returns the best matching method for the specified argument types
167: private static Method getMethod(Class target, String methodName,
168: Class[] arguments) {
169: try {
170: // use the package-private class java.beans.ReflectionUtils to resolve the method. This isn't 100% safe, but it's better than
171: // having to rewrite the resolution myself.
172: Class reflectionUtils = Class
173: .forName("java.beans.ReflectionUtils");
174: Method getMethod = reflectionUtils.getDeclaredMethod(
175: "getMethod", new Class[] { Class.class,
176: String.class, Class[].class });
177: getMethod.setAccessible(true);
178: return (Method) getMethod.invoke(null, new Object[] {
179: target, methodName, arguments });
180: } catch (Exception e) {
181: throw new RuntimeException(e);
182: }
183: }
184:
185: // returns the best matching constructor for the specified argument types
186: private static Constructor getConstructor(Class target,
187: Class[] arguments) {
188: try {
189: // use the package-private class java.beans.ReflectionUtils to resolve the constructor. This isn't 100% safe, but it's better than
190: // having to rewrite the resolution myself.
191: Class reflectionUtils = Class
192: .forName("java.beans.ReflectionUtils");
193: Method getConstructor = reflectionUtils.getDeclaredMethod(
194: "getConstructor", new Class[] { Class.class,
195: Class[].class });
196: getConstructor.setAccessible(true);
197: return (Constructor) getConstructor.invoke(null,
198: new Object[] { target, arguments });
199: } catch (Exception e) {
200: throw new RuntimeException(e);
201: }
202: }
203:
204: private Object createInstance(CapturedObject object) {
205: try {
206: ContextNode[] argumentNodes = object.getArguments();
207: Object[] arguments = new Object[argumentNodes.length];
208: Class[] argumentTypes = new Class[argumentNodes.length];
209: for (int j = 0; j < argumentNodes.length; j++) {
210: if (argumentNodes[j] instanceof ValueNode) {
211: arguments[j] = ((ValueNode) argumentNodes[j])
212: .getValue();
213: argumentTypes[j] = arguments[j] != null ? arguments[j]
214: .getClass()
215: : null;
216: } else if (argumentNodes[j] instanceof CapturedObject) {
217: arguments[j] = createInstance((CapturedObject) argumentNodes[j]);
218: argumentTypes[j] = arguments[j] != null ? arguments[j]
219: .getClass()
220: : null;
221: }
222: }
223: Constructor constructor = getConstructor(Class.forName(
224: object.getClassName(), true, classLoader),
225: argumentTypes);
226: return constructor.newInstance(arguments);
227: } catch (Exception e) {
228: throw new RuntimeException(e);
229: }
230: }
231:
232: public String getJavaCode(Stack/*<ContextNode>*/context) {
233: CapturedObject contextCapturedObject = (CapturedObject) context
234: .get(0);
235: StringBuffer result = new StringBuffer();
236: int start = 1;
237: for (int i = context.size() - 1; i > 1; i--) {
238: if (context.get(i) instanceof CapturedObject) {
239: start = i;
240: contextCapturedObject = (CapturedObject) context.get(i);
241: break;
242: }
243: }
244: Object contextObject = sourceObjects.get(contextCapturedObject
245: .getProperty("id"));
246: Class contextClass = contextObject != null ? contextObject
247: .getClass() : null;
248:
249: for (int i = start; i < context.size(); i++) {
250: ContextNode node = (ContextNode) context.get(i);
251: if (contextObject != null
252: && (node instanceof MethodNode || node instanceof PropertyNode)) {
253: // need to follow the call chain so we can insert typecasts as necessary
254: try {
255: String methodName;
256: ContextNode[] argumentNodes = node.getArguments();
257: if (node instanceof MethodNode)
258: methodName = ((MethodNode) node)
259: .getMethodName();
260: else
261: methodName = (argumentNodes.length == 0 ? "get"
262: : "set")
263: + JAXXCompiler
264: .capitalize(((PropertyNode) node)
265: .getProperty());
266: Object[] arguments = new Object[argumentNodes.length];
267: Class[] argumentTypes = new Class[argumentNodes.length];
268: for (int j = 0; j < argumentNodes.length; j++) {
269: if (argumentNodes[j] instanceof ValueNode) {
270: arguments[j] = ((ValueNode) argumentNodes[j])
271: .getValue();
272: argumentTypes[j] = arguments[j] != null ? arguments[j]
273: .getClass()
274: : null;
275: } else if (argumentNodes[j] instanceof CapturedObject) {
276: arguments[j] = createInstance((CapturedObject) argumentNodes[j]);
277: argumentTypes[j] = arguments[j].getClass();
278: } else if (argumentNodes[j] instanceof LiteralNode) {
279: arguments[j] = ((LiteralNode) argumentNodes[j])
280: .getValue();
281: argumentTypes[j] = arguments[j].getClass();
282: } else
283: throw new IllegalArgumentException(
284: "unsupported argument type: "
285: + argumentNodes[j]);
286: }
287:
288: Method method = getMethod(contextClass, methodName,
289: argumentTypes);
290: if (method == null) {
291: // could not find method in contextClass, must be defined in a subclass -- insert a typecast
292: result.insert(0, "(("
293: + getOutputName(contextObject
294: .getClass()) + ") ");
295: result.append(')');
296: method = getMethod(contextObject.getClass(),
297: methodName, argumentTypes);
298: }
299: if (method == null)
300: throw new RuntimeException(
301: "could not find method " + methodName
302: + Arrays.asList(argumentTypes)
303: + " in "
304: + contextObject.getClass()
305: + " (context: " + context + ")");
306: contextObject = method.invoke(contextObject,
307: arguments);
308: contextClass = method.getReturnType();
309: } catch (Exception e) {
310: throw new RuntimeException(e);
311: }
312: }
313:
314: if (i > start)
315: result.append('.');
316:
317: result.append(getJavaCode(node));
318: }
319: return result + ";";
320: }
321:
322: private String getOutputName(Class c) {
323: return c.getName();
324: }
325:
326: public CapturedObject processObject(Element objectTag,
327: Stack/*<ContextNode>*/context) {
328: String className = objectTag.getAttribute("class");
329: ObjectHandler handler;
330: if (className.length() > 0) {
331: try {
332: ClassDescriptor descriptor = ClassDescriptorLoader
333: .getClassDescriptor(className, classLoader);
334: handler = (ObjectHandler) objectHandlers
335: .get(descriptor);
336: } catch (ClassNotFoundException e) {
337: throw new RuntimeException(e);
338: }
339: } else
340: handler = (ObjectHandler) objectHandlers
341: .get(ClassDescriptorLoader
342: .getClassDescriptor(Object.class));
343:
344: return handler.processObject(objectTag, context, this );
345: }
346:
347: private synchronized String convertToJAXX(InputStream beansXML)
348: throws IOException {
349: try {
350: Document document = JAXXCompiler.parseDocument(beansXML);
351: Element rootElement = document.getDocumentElement();
352: NodeList nodes = rootElement.getChildNodes();
353: Stack/*<ContextNode>*/context = new Stack/*<ContextNode>*/();
354: for (int i = 0; i < nodes.getLength(); i++) {
355: Node child = nodes.item(i);
356: if (child.getNodeType() == Node.ELEMENT_NODE) {
357: Element element = (Element) child;
358: if (!element.getTagName().equals("object"))
359: throw new Error(
360: "expected tag 'object', found '"
361: + element.getTagName() + "'");
362: CapturedObject root = processObject(element,
363: context);
364: Iterator/*<CapturedObject>*/iter = capturedObjects
365: .values().iterator();
366: while (iter.hasNext()) { // add all orphan objects to the root, so any non-inlineable ones have their XML created
367: CapturedObject object = (CapturedObject) iter
368: .next();
369: if (object.getParent() == null
370: && object != root)
371: root.addChild(object, null);
372: }
373: return root.getXML(this );
374: }
375: }
376: return null;
377: } catch (SAXException e) {
378: throw new RuntimeException(e);
379: } finally {
380: reset();
381: }
382: }
383:
384: private void reset() {
385: sourceObjects.clear();
386: capturedObjects.clear();
387: count = 0;
388: }
389:
390: public static void main(String[] arg) throws Exception {
391: File file = new File(arg[0]);
392: JarFile jarFile = new JarFile(file);
393: ClassLoader classLoader = new URLClassLoader(new URL[] { file
394: .toURL() });
395: Thread.currentThread().setContextClassLoader(classLoader);
396: EventQueue systemQueue = Toolkit.getDefaultToolkit()
397: .getSystemEventQueue();
398: systemQueue.push(new CaptureEventQueue(classLoader));
399: Manifest mf = jarFile.getManifest();
400: String mainClassName = mf.getMainAttributes().getValue(
401: Attributes.Name.MAIN_CLASS);
402: Class mainClass = Class.forName(mainClassName, true,
403: classLoader);
404: Method main = mainClass.getMethod("main",
405: new Class[] { String[].class });
406: main.invoke(null, new Object[] { new String[0] });
407: }
408: }
|