001: package jsx3.xml;
002:
003: import java.beans.BeanInfo;
004: import java.beans.Introspector;
005: import java.beans.PropertyDescriptor;
006: import java.lang.reflect.Method;
007: import java.util.HashMap;
008: import java.util.Iterator;
009: import java.util.List;
010: import java.util.Map;
011:
012: /**
013: * The Record class somewhat mirrors a W3C Element, but simplified because we do
014: * not need namespaces, and things like Attr and NodeList.
015: * It is also vaguely similar to the {@link List} interface in that it is a
016: * container for attributes, however being also a container for other Records
017: * the interface has some name changes and is greatly simplified.
018: * @author Joe Walker [joe at getahead dot ltd dot uk]
019: */
020: public final class Record extends Node {
021: /**
022: * Ensure all Records have unique IDs
023: * @param id the jsxid for this record
024: */
025: public Record(String id) {
026: super (id);
027: }
028:
029: public Record(Object data) {
030: introspectBean(data);
031: }
032:
033: public Record(String id, Object data) {
034: introspectBean(data);
035: setId(id);
036: }
037:
038: /**
039: * @param data
040: */
041: private void introspectBean(Object data) {
042: try {
043: BeanInfo info = Introspector.getBeanInfo(data.getClass());
044: PropertyDescriptor[] descriptors = info
045: .getPropertyDescriptors();
046:
047: for (PropertyDescriptor descriptor : descriptors) {
048: String name = descriptor.getName();
049:
050: // We don't marshall getClass()
051: if ("class".equals(name)) {
052: continue;
053: }
054:
055: Method getter = descriptor.getReadMethod();
056: if (getter != null) {
057: Object reply = getter.invoke(data);
058: if (reply != null) {
059: setAttribute(name, reply.toString());
060:
061: if ("id".equals(name)) {
062: setId(reply.toString());
063: }
064: }
065: }
066: }
067: } catch (Exception ex) {
068: throw new IllegalStateException(ex);
069: }
070: }
071:
072: // Generic attribute management ////////////////////////////////////////////
073:
074: /**
075: * Retrieves an attribute value by name.
076: * @param name The name of the attribute to retrieve.
077: * @return The attribute value as a string, or <code>null</code> if the
078: * named attribute does not exist
079: */
080: public String getAttribute(String name) {
081: checkForJsxAttribute(name);
082: return attributes.get(name);
083: }
084:
085: /**
086: * Adds a new attribute. If an attribute with that name is already present
087: * in the element, its value is changed to be that of the value
088: * parameter.
089: * @param name The name of the attribute to create or alter.
090: * @param value Value to set in string form.
091: */
092: public Record setAttribute(String name, String value) {
093: checkForJsxAttribute(name);
094: attributes.put(name, value);
095: return this ;
096: }
097:
098: /**
099: * Removes an attribute by name.
100: * <br>If no attribute with this name is found, this method has no effect.
101: * @param name The name of the attribute to remove.
102: * @return The old attribute value or null if it did not exist.
103: */
104: public String removeAttribute(String name) {
105: checkForJsxAttribute(name);
106: return attributes.remove(name);
107: }
108:
109: /**
110: * Iterate over the names of the attributes (excluding the special JSX
111: * attributes) contained in this {@link Record}.
112: * @return an attribute name {@link Iterator}
113: */
114: public Iterator<String> getAttributeNames() {
115: return attributes.keySet().iterator();
116: }
117:
118: /**
119: * An internal check to keep JSX attributes separate from normal attributes
120: * @param name The name of the attribute to check that it does not start
121: * with '<code>jsx</code>'
122: */
123: private void checkForJsxAttribute(String name) {
124: if (name.startsWith("jsx")) {
125: throw new IllegalArgumentException(
126: "Special JSX Attribute keys should be set directly");
127: }
128: }
129:
130: private Map<String, String> attributes = new HashMap<String, String>();
131:
132: // Special JSX attribute management ////////////////////////////////////////
133:
134: /**
135: * @return the disabled
136: */
137: public Boolean getDisabled() {
138: return disabled;
139: }
140:
141: /**
142: * @param disabled the disabled to set
143: */
144: public Record setDisabled(Boolean disabled) {
145: this .disabled = disabled;
146: return this ;
147: }
148:
149: /**
150: * @return the divider
151: */
152: public Boolean getDivider() {
153: return divider;
154: }
155:
156: /**
157: * @param divider the divider to set
158: */
159: public Record setDivider(Boolean divider) {
160: this .divider = divider;
161: return this ;
162: }
163:
164: /**
165: * @return the execute
166: */
167: public String getExecute() {
168: return execute;
169: }
170:
171: /**
172: * @param execute the execute to set
173: */
174: public Record setExecute(String execute) {
175: this .execute = execute;
176: return this ;
177: }
178:
179: /**
180: * @return the groupName
181: */
182: public String getGroupName() {
183: return groupName;
184: }
185:
186: /**
187: * @param groupName the groupName to set
188: */
189: public Record setGroupName(String groupName) {
190: this .groupName = groupName;
191: return this ;
192: }
193:
194: /**
195: * @return the image
196: */
197: public String getImage() {
198: return image;
199: }
200:
201: /**
202: * @param image the image to set
203: */
204: public Record setImage(String image) {
205: this .image = image;
206: return this ;
207: }
208:
209: /**
210: * @return the keycodeString
211: */
212: public String getKeycodeString() {
213: return keycodeString;
214: }
215:
216: /**
217: * @param keycodeString the keycodeString to set
218: */
219: public Record setKeycodeString(String keycodeString) {
220: this .keycodeString = keycodeString;
221: return this ;
222: }
223:
224: /**
225: * @return the noMask
226: */
227: public String getNoMask() {
228: return noMask;
229: }
230:
231: /**
232: * @param noMask the noMask to set
233: */
234: public Record setNoMask(String noMask) {
235: this .noMask = noMask;
236: return this ;
237: }
238:
239: /**
240: * @return the selected
241: */
242: public Boolean getSelected() {
243: return selected;
244: }
245:
246: /**
247: * @param selected the selected to set
248: */
249: public Record setSelected(Boolean selected) {
250: this .selected = selected;
251: return this ;
252: }
253:
254: /**
255: * @return the style
256: */
257: public String getStyle() {
258: return style;
259: }
260:
261: /**
262: * @param style the style to set
263: */
264: public Record setStyle(String style) {
265: this .style = style;
266: return this ;
267: }
268:
269: /**
270: * @return the text
271: */
272: public String getText() {
273: return text;
274: }
275:
276: /**
277: * @param text the text to set
278: */
279: public Record setText(String text) {
280: this .text = text;
281: return this ;
282: }
283:
284: /**
285: * @return the tip
286: */
287: public String getTip() {
288: return tip;
289: }
290:
291: /**
292: * @param tip the tip to set
293: */
294: public Record setTip(String tip) {
295: this .tip = tip;
296: return this ;
297: }
298:
299: /**
300: * @return the unselectable
301: */
302: public Boolean getUnselectable() {
303: return unselectable;
304: }
305:
306: /**
307: * @param unselectable the unselectable to set
308: */
309: public Record setUnselectable(Boolean unselectable) {
310: this .unselectable = unselectable;
311: return this ;
312: }
313:
314: // Support methods /////////////////////////////////////////////////////////
315:
316: /**
317: * @param depth
318: * @return The string version of this record
319: */
320: protected String toXml(int depth) {
321: // Serialize the child records
322: StringBuilder buffer = new StringBuilder();
323: for (Record record : this ) {
324: buffer.append(Node.indent(depth));
325: buffer.append(record.toXml(depth + 1));
326: buffer.append("\n");
327: }
328:
329: // Start the record tag
330: StringBuilder reply = new StringBuilder();
331: reply.append("<record jsxid=\"" + getId() + "\"");
332:
333: // Add the JSX attributes
334: createAttributeOutput(reply, "jsxdisabled", disabled);
335: createAttributeOutput(reply, "jsxdivider", divider);
336: createAttributeOutput(reply, "jsxexecute", execute);
337: createAttributeOutput(reply, "jsxgroupname", groupName);
338: createAttributeOutput(reply, "jsximage", image);
339: createAttributeOutput(reply, "jsxkeycode", keycodeString);
340: createAttributeOutput(reply, "jsxnomask", noMask);
341: createAttributeOutput(reply, "jsxselected", selected);
342: createAttributeOutput(reply, "jsxstyle", style);
343: createAttributeOutput(reply, "jsxtext", text);
344: createAttributeOutput(reply, "jsxtip", tip);
345: createAttributeOutput(reply, "jsxunselectable", unselectable);
346:
347: // Add the custom attributes
348: for (Iterator<Map.Entry<String, String>> it = attributes
349: .entrySet().iterator(); it.hasNext();) {
350: Map.Entry<String, String> entry = it.next();
351: createAttributeOutput(reply, entry.getKey(), entry
352: .getValue());
353: }
354:
355: if (buffer.length() == 0) {
356: reply.append("/>\n");
357: } else {
358: reply.append(">\n");
359: reply.append(buffer.toString());
360: reply.append("</record>");
361: }
362:
363: return reply.toString();
364: }
365:
366: /**
367: * @param reply
368: */
369: private void createAttributeOutput(StringBuilder reply,
370: String name, Object value) {
371: if (value != null) {
372: reply.append(" ");
373: reply.append(name);
374: reply.append("=\"");
375: reply.append(value);
376: reply.append("\"");
377: }
378: }
379:
380: /* (non-Javadoc)
381: * @see java.lang.Object#toString()
382: */
383: @Override
384: public String toString() {
385: return toXml(0);
386: }
387:
388: private Boolean disabled = null;
389: private Boolean divider = null;
390: private String execute = null;
391: private String groupName = null;
392: private String image = null;
393: private String keycodeString = null;
394: private String noMask = null;
395: private Boolean selected = null;
396: private String style = null;
397: private String text = null;
398: private String tip = null;
399: private Boolean unselectable = null;
400: }
|