001: /*
002: * Copyright 2006 Ethan Nicholas. All rights reserved.
003: * Use is subject to license terms.
004: */
005: package jaxx.compiler;
006:
007: import java.awt.*;
008: import java.lang.reflect.*;
009: import java.util.*;
010: import java.util.List;
011:
012: import jaxx.*;
013: import jaxx.reflect.*;
014: import jaxx.tags.*;
015: import jaxx.types.*;
016:
017: /** Represents an object in the <code>.java</code> file being generated during compilation. There is
018: * a <code>CompiledObject</code> for each class tag encountered, and certain tags may generate
019: * additional objects for various reasons.
020: */
021: public class CompiledObject {
022: /** The object's id. */
023: private String id;
024:
025: /** Java code referring to the object. */
026: private String javaCode;
027:
028: /** The object's class. */
029: private ClassDescriptor objectClass;
030:
031: /** The style class. */
032: private String styleClass;
033:
034: /** The container containing this CompiledObject. */
035: private CompiledObject parent;
036:
037: /** true if this object overrides an object of the same id in a superclass of the object being compiled */
038: private boolean override;
039:
040: /** Comma-separated Java code snippets representing the parameters that should be passed to the object's
041: * constructor.
042: */
043: private String constructorParams;
044:
045: /** Java code snippet which performs basic initialization of the object (after it has already been constructed).
046: * Because CompiledObject initialization order cannot be guaranteed, it is not safe to refer to other
047: * CompiledObjects from initializationCode -- you must refer to them from additionCode instead.
048: */
049: private StringBuffer initializationCode = new StringBuffer();
050:
051: /** Java code snippet which completes setup by adding any child objects, or otherwise manipulates any refererenced
052: * objects. Because CompiledObject initialization order cannot be guaranteed, it is not safe to refer to other
053: * CompiledObjects from initializationCode -- you must refer to them from additionCode instead.
054: */
055: private StringBuffer additionCode = new StringBuffer();
056:
057: /** List of all registered event handlers. */
058: private List/*<EventHandler>*/eventHandlers = new ArrayList/*<EventHandler>*/();
059:
060: /** All properties that have been applied to this CompiledObject. */
061: private Map/*<String, String>*/properties = new HashMap/*<String, String>*/();
062:
063: /** Creates a new <code>CompiledObject</code>. To be useful, the object should be registered with a
064: * <code>JAXXCompiler</code> using {@link JAXXCompiler#registerCompiledObject registerCompiledObject}.
065: *
066: *@param id the object's id
067: *@param objectClass the object's class
068: *@param compiler the current <code>JAXXCompiler</code>
069: *@throws NullPointerException if id or class is null
070: */
071: public CompiledObject(String id, ClassDescriptor objectClass,
072: JAXXCompiler compiler) {
073: this (id, objectClass, compiler, false);
074: }
075:
076: /** Creates a new <code>CompiledObject</code>. To be useful, the object should be registered with a
077: * <code>JAXXCompiler</code> using {@link JAXXCompiler#registerCompiledObject registerCompiledObject}.
078: *
079: *@param id the object's id
080: *@param objectClass the object's class
081: *@param compiler the current <code>JAXXCompiler</code>
082: *@param force <code>true</code> to force acceptance of invalid ids
083: *@throws NullPointerException if id or class is null
084: */
085: public CompiledObject(String id, ClassDescriptor objectClass,
086: JAXXCompiler compiler, boolean force) {
087: this (id, id, objectClass, compiler, force);
088: }
089:
090: /** Creates a new <code>CompiledObject</code>. To be useful, the object should be registered with a
091: * <code>JAXXCompiler</code> using {@link JAXXCompiler#registerCompiledObject registerCompiledObject}.
092: *
093: *@param id the object's id
094: *@param javaCode Java code referring to the object
095: *@param objectClass the object's class
096: *@param force <code>true</code> to force acceptance of invalid ids
097: *@param compiler the current <code>JAXXCompiler</code>
098: *@throws CompilerException if the id is not a valid Java identifier
099: *@throws NullPointerException if id or class is null
100: */
101: public CompiledObject(String id, String javaCode,
102: ClassDescriptor objectClass, JAXXCompiler compiler,
103: boolean force) throws CompilerException {
104: if (!force) {
105: if (!isValidID(id))
106: compiler.reportError("the id '" + id
107: + "' is not a valid Java identifier");
108: }
109: this .id = id;
110: this .javaCode = javaCode;
111:
112: if (objectClass == null)
113: throw new NullPointerException();
114: this .objectClass = objectClass;
115: }
116:
117: public static boolean isValidID(String id) {
118: boolean valid = true;
119: if (id.length() == 0)
120: valid = false;
121: if (valid) {
122: if (!Character.isJavaIdentifierStart(id.charAt(0)))
123: valid = false;
124: if (valid) {
125: for (int i = 1; i < id.length(); i++) {
126: if (!Character.isJavaIdentifierPart(id.charAt(i))) {
127: valid = false;
128: break;
129: }
130: }
131: }
132: }
133: return valid;
134: }
135:
136: /** True if this object overrides an object in the superclass of the class being compiled. For this to be true, the
137: * class currently being compiled must be a subclass of another <code>JAXXObject</code> which has an
138: * identically-named object.
139: *
140: *@return <code>true</code> if this object is an override
141: *@see #setOverride
142: */
143: public boolean isOverride() {
144: return override;
145: }
146:
147: /** Sets whether this class overrides an identically-named object in the parent class.
148: *
149: *@param override <code>true</code> if this object is an override
150: *@see #isOverride
151: */
152: public void setOverride(boolean override) {
153: this .override = override;
154: }
155:
156: /** Returns this object's CSS style class.
157: *
158: *@return the value of the <code>styleClass</code> attribute
159: */
160: public String getStyleClass() {
161: return styleClass;
162: }
163:
164: /** Sets this object's CSS style class.
165: *
166: *@param styleClass the new style class
167: */
168: public void setStyleClass(String styleClass) {
169: this .styleClass = styleClass;
170: }
171:
172: /** Returns this object's parent container. Non-visual components (and the root container) return <code>null</code>.
173: *
174: *@return the object's parent container
175: */
176: public CompiledObject getParent() {
177: return parent;
178: }
179:
180: /** Sets this object's parent container.
181: *
182: *@param parent the parent container
183: */
184: public void setParent(CompiledObject parent) {
185: if (!ClassDescriptorLoader.getClassDescriptor(Container.class)
186: .isAssignableFrom(parent.getObjectClass()))
187: throw new IllegalArgumentException(
188: "parent must descend from java.awt.Container");
189: this .parent = parent;
190: }
191:
192: /** Returns the name of the method that should be generated in the compiled <code>.java</code> file
193: * in order to create this object. This is just a suggestion and may be ignored.
194: *
195: *@return the suggested name of the method which initializes this object
196: */
197: public String getCreationMethodName() {
198: return "create" + JAXXCompiler.capitalize(getId());
199: }
200:
201: /** Returns the name of the method that should be generated in the compiled <code>.java</code> file
202: * in order to add children to this object. This is just a suggestion and may be ignored.
203: *
204: *@return the suggested name of the method which completes this object's setup
205: */
206: public String getAdditionMethodName() {
207: return "addChildrenTo" + JAXXCompiler.capitalize(getId());
208: }
209:
210: /** Returns the type of this object.
211: *
212: *@return the class this <code>CompiledObject</code> represents
213: */
214: public ClassDescriptor getObjectClass() {
215: return objectClass;
216: }
217:
218: /** Returns this object's id. Generally, a field with this name will be created in the compiled <code>.java</code>
219: * file in order to represent this object.
220: *
221: *@return the id used to refer to this object
222: */
223: public String getId() {
224: return id;
225: }
226:
227: /** Returns Java code used to refer to this object in the compiled Java file. This is usually the same as its
228: * id.
229: *
230: *@return the Java code for this object
231: */
232: public String getJavaCode() {
233: if (isOverride()) // handle cases where object is overridden to be a different class
234: return "(("
235: + JAXXCompiler.getCanonicalName(getObjectClass())
236: + ") " + javaCode + ")";
237: else
238: return javaCode;
239: }
240:
241: /** Returns a list of comma-separated Java code snippets that represent the parameters to pass to this
242: * object's constructor.
243: *
244: *@return the raw constructor params
245: *@see #setConstructorParams
246: */
247: public String getConstructorParams() {
248: return constructorParams;
249: }
250:
251: /** Sets the parameters to pass to this object's constructor.
252: *
253: *@param constructorParams comma-separated Java code snippets representing constructor params
254: *@see #getConstructorParams
255: */
256: public void setConstructorParams(String constructorParams) {
257: this .constructorParams = constructorParams;
258: }
259:
260: /** Returns the code that performs basic initialization of this object, after it has already been constructed.
261: * This basic code should not reference any other <code>CompiledObjects</code> as they may not have
262: * been created yet.
263: *
264: *@return the code which initializes this object
265: */
266: public String getInitializationCode(JAXXCompiler compiler) {
267: StringBuffer result = new StringBuffer(initializationCode
268: .toString());
269: Iterator/*<EventHandler>*/i = eventHandlers.iterator();
270: while (i.hasNext()) {
271: EventHandler handler = (EventHandler) i.next();
272: result.append(getInitializationCode(handler, compiler));
273: }
274: return result.toString();
275: }
276:
277: protected String getInitializationCode(EventHandler handler,
278: JAXXCompiler compiler) {
279: MethodDescriptor addMethod = handler.getAddMethod();
280: ClassDescriptor listenerClass = addMethod.getParameterTypes()[0];
281: return getJavaCode()
282: + '.'
283: + addMethod.getName()
284: + "(("
285: + JAXXCompiler.getCanonicalName(listenerClass)
286: + ") jaxx.runtime.Util.getEventListener("
287: + JAXXCompiler.getCanonicalName(listenerClass)
288: + ".class, "
289: + TypeManager.getJavaCode(handler.getListenerMethod()
290: .getName())
291: + ", "
292: + compiler.getRootObject().getJavaCode()
293: + ", "
294: + TypeManager.getJavaCode(compiler
295: .getEventHandlerMethodName(handler)) + "));"
296: + JAXXCompiler.getLineSeparator();
297: }
298:
299: /** Returns Java code to complete final setup on this object. This code may reference other
300: * <code>CompiledObjects</code>, as they are guaranteed to have all been created by this point.
301: *
302: *@return code which adds children and performs final setup
303: */
304: public String getAdditionCode() {
305: return additionCode.toString();
306: }
307:
308: /** Appends code to the initialization code block. A line separator is automatically appended to the end.
309: *
310: *@param code the code to add to the initialization block
311: *@see #getInitializationCode
312: */
313: public void appendInitializationCode(String code) {
314: this .initializationCode.append(code);
315: this .initializationCode.append(JAXXCompiler.getLineSeparator());
316: }
317:
318: /** Appends code to the addition code block. A line separator is automatically appended to the end.
319: *
320: *@param code the code to add to the addition block
321: *@see #getAdditionCode
322: */
323: public void appendAdditionCode(String code) {
324: this .additionCode.append(code);
325: this .additionCode.append(JAXXCompiler.getLineSeparator());
326: }
327:
328: /** Stores a property for this object. The only effect of calling this method is that the property will
329: * be returned by <code>getProperties()</code>.
330: *
331: *@param property the name of the property
332: *@param value the property's value
333: *@see #getProperties
334: */
335: public void addProperty(String property, String value) {
336: properties.put(property, value);
337: }
338:
339: /** Returns all properties which have been set for this object.
340: *
341: *@return a <code>Map</code> containing all properties defined for this object
342: *@see #addProperty
343: */
344: public Map/*<String, String>*/getProperties() {
345: return properties;
346: }
347:
348: // TODO: remove this temporary method and complete switchover to MethodDescriptors
349: public void addEventHandler(String eventId, Method addMethod,
350: Method listenerMethod, String code, JAXXCompiler compiler) {
351: try {
352: ClassDescriptor descriptor = ClassDescriptorLoader
353: .getClassDescriptor(getObjectClass().getName());
354: String listenerClassName = addMethod.getParameterTypes()[0]
355: .getName();
356: ClassDescriptor listenerDescriptor = ClassDescriptorLoader
357: .getClassDescriptor(listenerClassName);
358: MethodDescriptor addMethodDescriptor = descriptor
359: .getMethodDescriptor(
360: addMethod.getName(),
361: new ClassDescriptor[] { listenerDescriptor });
362: MethodDescriptor listenerMethodDescriptor = listenerDescriptor
363: .getMethodDescriptor(
364: listenerMethod.getName(),
365: new ClassDescriptor[] { ClassDescriptorLoader
366: .getClassDescriptor(listenerMethod
367: .getParameterTypes()[0]
368: .getName()) });
369: addEventHandler(eventId, addMethodDescriptor,
370: listenerMethodDescriptor, code, compiler);
371: } catch (Exception e) {
372: throw new RuntimeException(e);
373: }
374: }
375:
376: /** Adds an event listener to this object. The generated code will appear in the initialization block.
377: *
378: *@param eventId unique (per CompiledObject) identifier for the event handler
379: *@param addMethod the method which adds the event listener
380: *@param listenerMethod the method (in the listener class) which is called when the event is fired
381: *@param code the Java code for the listenerMethod's body
382: *@param compiler the current <code>JAXXCompiler</code>
383: *@see #getInitializationCode
384: */
385: public void addEventHandler(String eventId,
386: MethodDescriptor addMethod,
387: MethodDescriptor listenerMethod, String code,
388: JAXXCompiler compiler) {
389: EventHandler handler = new EventHandler(
390: getId() + "." + eventId, getJavaCode(), addMethod,
391: addMethod.getParameterTypes()[0], listenerMethod, code);
392: compiler.registerEventHandler(handler);
393: eventHandlers.add(handler);
394:
395: if (getJavaCode().indexOf(".") != -1) { // object lives in another JAXX file and consequently its initialization code won't be output
396: compiler.initializer.append(getInitializationCode(handler,
397: compiler));
398: }
399: }
400:
401: /** Adds a child component to this container. The child is added without layout constraints.
402: *
403: *@param child the component to add
404: *@param compiler the current <code>JAXXCompiler</code>
405: *@throws CompilerException if this object is not a container
406: *@see #addChild(CompiledObject, String, JAXXCompiler)
407: */
408: public void addChild(CompiledObject child, JAXXCompiler compiler)
409: throws CompilerException {
410: addChild(child, null, compiler);
411: }
412:
413: /** Adds a child component to this container. This variant allows the Java code for a layout constraints
414: * object to be specified.
415: *
416: *@param child the component to add
417: *@param constraints Java code for the layout constraints object
418: *@param compiler the current <code>JAXXCompiler</code>
419: *@throws CompilerException if this object is not a container
420: *@see #addChild(CompiledObject, JAXXCompiler)
421: */
422: public void addChild(CompiledObject child, String constraints,
423: JAXXCompiler compiler) throws CompilerException {
424: try {
425: if (constraints != null)
426: constraints = compiler.checkJavaCode(constraints);
427: } catch (CompilerException e) {
428: compiler
429: .reportError("While parsing 'constraints' attribute: "
430: + e.getMessage());
431: }
432:
433: if (!child.isOverride()) {
434: TagHandler tagHandler = TagManager
435: .getTagHandler(getObjectClass());
436: if (tagHandler instanceof DefaultComponentHandler
437: && !((DefaultComponentHandler) tagHandler)
438: .isContainer())
439: compiler.reportError("component " + this
440: + " may not have children");
441:
442: String containerDelegate = ((DefaultComponentHandler) tagHandler)
443: .getContainerDelegate();
444: String delegateCode = containerDelegate != null ? "."
445: + containerDelegate + "()" : "";
446:
447: child.setParent(this );
448:
449: if (constraints != null)
450: appendAdditionCode(getJavaCode() + delegateCode
451: + ".add(" + child.getJavaCode() + ", "
452: + constraints + ");");
453: else
454: appendAdditionCode(getJavaCode() + delegateCode
455: + ".add(" + child.getJavaCode() + ");");
456: }
457: }
458:
459: public String toString() {
460: return getObjectClass().getName() + "[id='" + id + "']";
461: }
462:
463: public void registerDataBinding(String src, String property,
464: String assignment, JAXXCompiler compiler)
465: throws CompilerException {
466: compiler.registerDataBinding(src, getId() + "." + property,
467: assignment);
468: }
469: }
|