001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.planning.servlet;
028:
029: import java.beans.BeanInfo;
030: import java.beans.Beans;
031: import java.beans.IntrospectionException;
032: import java.beans.Introspector;
033: import java.beans.PropertyDescriptor;
034: import java.lang.reflect.Array;
035: import java.lang.reflect.InvocationTargetException;
036: import java.lang.reflect.Method;
037: import java.lang.reflect.Modifier;
038: import java.util.Collections;
039: import java.util.Comparator;
040: import java.util.HashSet;
041: import java.util.Set;
042: import java.util.Vector;
043:
044: import org.cougaar.core.util.PropertyNameValue;
045: import org.cougaar.core.util.UID;
046: import org.cougaar.core.util.UniqueObject;
047: import org.cougaar.planning.ldm.asset.Asset;
048: import org.cougaar.planning.ldm.asset.AssetIntrospection;
049: import org.cougaar.planning.ldm.asset.LockedPG;
050: import org.cougaar.planning.ldm.asset.LockedPGSchedule;
051: import org.cougaar.planning.ldm.measure.AbstractMeasure;
052: import org.cougaar.planning.ldm.measure.Capacity;
053: import org.w3c.dom.Document;
054: import org.w3c.dom.Element;
055:
056: /**
057: * Create and return xml for first class log plan objects.
058: * <p>
059: * Element name is extracted from object class, by taking the
060: * last field of the object class, and dropping a trailing "Impl",
061: * if it exists.
062: */
063:
064: public class XMLize {
065:
066: /** Maximum search depth -- Integer.MAX_VALUE means unlimited. **/
067: public static final int DEFAULT_UID_DEPTH = 3;
068: public static Class numberClass;
069: public static Class booleanClass;
070:
071: static {
072: try {
073: numberClass = Class.forName("java.lang.Number");
074: booleanClass = Class.forName("java.lang.Boolean");
075: } catch (ClassNotFoundException cnfe) {
076: }
077: }
078:
079: public static Element getPlanObjectXML(Object obj, Document doc) {
080: return getPlanObjectXML(obj, doc, DEFAULT_UID_DEPTH);
081: }
082:
083: public static Element getPlanObjectXML(Object obj, Document doc,
084: int searchDepth) {
085: String className;
086: if (Asset.class.isInstance(obj)) {
087: className = "Asset";
088: } else {
089: className = obj.getClass().getName();
090: int i = className.lastIndexOf(".");
091: if (i >= 0) {
092: className = className.substring(i + 1);
093: }
094: i = className.lastIndexOf("Impl");
095: if (i >= 0) {
096: className = className.substring(0, i);
097: }
098: }
099: Element root;
100:
101: // fix for bug where inner classes would cause XMLize to throw
102: // exception
103: try {
104: className = className.replaceAll("\\$", "_");
105:
106: root = doc.createElement(className);
107: } catch (Exception e) {
108: System.err.println("Got exception " + e
109: + " trying to create element '" + className + "'");
110: root = doc.createElement("Invalid_Name");
111: }
112:
113: Set seenObjs = new HashSet();
114: addNodes(doc, obj, root, seenObjs, searchDepth);
115: return root;
116: }
117:
118: /*
119: Handle the case in which an object's class is a private or protected
120: implementation class which implements one or more public interfaces or
121: abstract classes.
122: First check for abstract classes extended by, or interfaces implemented by,
123: the class or its superclasses.
124: (Note that this stops when it finds the first abstract class or
125: interfaces in the class chain.)
126: Then, introspect on the abstract class or all the interfaces
127: found (note that these are assumed to be public)
128: and invoke read property methods
129: on the object cast to that class or those interfaces.
130: */
131:
132: private static Vector specialIntrospection(Object obj) {
133: //System.out.println(
134: // "Performing special introspection for:" + obj);
135: Class objClass = obj.getClass();
136: Class[] interfaces;
137: while (true) {
138: if (objClass == null) {
139: return null;
140: }
141: if (Modifier.isAbstract(objClass.getModifiers())) {
142: interfaces = new Class[1];
143: interfaces[0] = objClass;
144: break;
145: }
146: interfaces = objClass.getInterfaces();
147: if (interfaces.length != 0) {
148: break;
149: }
150: objClass = objClass.getSuperclass();
151: }
152: Vector propertyNameValues = new Vector(10);
153: for (int i = 0; i < interfaces.length; i++) {
154: // System.out.println("Interface:" + interfaces[i].toString());
155: try {
156: BeanInfo info = Introspector.getBeanInfo(interfaces[i]);
157: PropertyDescriptor[] properties = info
158: .getPropertyDescriptors();
159: Vector tmp = getPropertyNamesAndValues(properties,
160: Beans.getInstanceOf(obj, interfaces[i]));
161: if (tmp != null)
162: for (int j = 0; j < tmp.size(); j++)
163: propertyNameValues.addElement(tmp.elementAt(j));
164: } catch (IntrospectionException e) {
165: System.err
166: .println("Exception generating XML for plan object:"
167: + e.getMessage());
168: }
169: }
170: // for debugging
171: // for (int i = 0; i < propertyNameValues.size(); i++) {
172: // PropertyNameValue p = (PropertyNameValue)(propertyNameValues.elementAt(i));
173: // System.out.println("Property Name: " + p.name + " Property Value: " + p.value);
174: // }
175: return propertyNameValues;
176: }
177:
178: /**
179: * Get the property names and values (returned in a vector)
180: * from the given property descriptors and for the given object.
181: *
182: * Removes redundant properties from measure objects.
183: */
184:
185: private static Vector getPropertyNamesAndValues(
186: PropertyDescriptor[] properties, Object obj) {
187: Vector pv = new Vector();
188:
189: // IGNORE JAVA.SQL.DATE CLASS
190: if (java.sql.Date.class.isInstance(obj)) {
191: return pv;
192: }
193:
194: if ((obj instanceof AbstractMeasure)
195: && (!(obj instanceof Capacity))) {
196: // Special case code. Capacity is a duple of Measures so there's no common
197: // unit to pull out.
198: properties = prunePropertiesFromMeasure(
199: (AbstractMeasure) obj, properties);
200: }
201:
202: for (int i = 0; i < properties.length; i++) {
203: PropertyDescriptor pd = properties[i];
204: Method rm = pd.getReadMethod();
205: if (rm == null) {
206: continue;
207: }
208:
209: // invoke the read method for each property
210: Object childObject = getReadResult(obj, rm);
211: if (childObject == null) {
212: continue;
213: }
214:
215: // add property name and value to vectorarray
216: String name = pd.getName();
217: if (pd.getPropertyType().isArray()) {
218: int length = Array.getLength(childObject);
219: for (int j = 0; j < length; j++) {
220: Object value = Array.get(childObject, j);
221: if (value == null) {
222: value = "null";
223: } else if (value.getClass().isPrimitive()) {
224: value = String.valueOf(value);
225: }
226: pv.add(new PropertyNameValue(name, value));
227: }
228: } else {
229: if (isPrimitive(childObject.getClass()))
230: childObject = String.valueOf(childObject);
231:
232: pv.add(new PropertyNameValue(name, childObject));
233: }
234: }
235:
236: Collections.sort(pv, lessStringIgnoreCase);
237: return pv;
238: }
239:
240: private static final Comparator lessStringIgnoreCase = new Comparator() {
241: public int compare(Object first, Object second) {
242: String firstName = ((PropertyNameValue) first).name;
243: String secondName = ((PropertyNameValue) second).name;
244: return firstName.compareToIgnoreCase(secondName);
245: }
246: };
247:
248: /**
249: * Invoke read method on object
250: *
251: * @return Object that is the result of the read
252: */
253: protected static Object getReadResult(Object obj, Method rm) {
254: // invoke the read method for each property
255: Object childObject = null;
256: try {
257: childObject = rm.invoke(obj, null);
258: } catch (InvocationTargetException ie) {
259: System.err.println("Invocation target exception invoking: "
260: + rm.getName() + " on object of class:"
261: + obj.getClass().getName());
262: System.err.println(ie.getTargetException().getMessage());
263: } catch (Exception e) {
264: System.err
265: .println("Exception " + e.toString()
266: + " invoking: " + rm.getName()
267: + " on object of class:"
268: + obj.getClass().getName());
269: System.err.println(e.getMessage());
270: }
271: return childObject;
272: }
273:
274: /**
275: * Removes redundant measure properties.
276: * Returns only the common unit measure.
277: * For example, for Distance, returns only the meters property and discards furlongs.
278: *
279: * (Converts underscores in common unit names.)
280: *
281: * @param measure needed so can get common unit
282: * @param properties initial complete set of measure properties
283: * @return array containing the one property descriptor for the common unit property
284: */
285: protected static PropertyDescriptor[] prunePropertiesFromMeasure(
286: AbstractMeasure measure, PropertyDescriptor[] properties) {
287:
288: String cu = measure.getUnitName(measure.getCommonUnit());
289: if (cu == null) {
290: return new PropertyDescriptor[0];
291: }
292:
293: int pos = 0;
294: int underIndex = -1;
295: String noUnders = "";
296: while ((underIndex = cu.indexOf('_', pos)) != -1) {
297: noUnders = noUnders
298: + cu.substring(pos, underIndex)
299: + cu.substring(underIndex + 1, underIndex + 2)
300: .toUpperCase();
301: pos = underIndex + 2;
302: }
303: while ((underIndex = cu.indexOf('/', pos)) != -1) {
304: noUnders = noUnders
305: + cu.substring(pos, underIndex)
306: + "Per"
307: + cu.substring(underIndex + 1, underIndex + 2)
308: .toUpperCase();
309: pos = underIndex + 2;
310: }
311: noUnders = noUnders + cu.substring(pos);
312: for (int i = 0; i < properties.length; i++) {
313: PropertyDescriptor pd = properties[i];
314:
315: if (pd.getName().equals(noUnders)) {
316: return new PropertyDescriptor[] { pd };
317: }
318: }
319: return null;
320: }
321:
322: /**
323: * <b>Recursively</b> introspect and add nodes to the XML document.
324: * <p>
325: * Keeps a Set of objects (as these can be circular) and stops
326: * when it tries to introspect over an object a second time.
327: * <p>
328: * Also keeps a depth counter, decrements for each call to addNodes,
329: * and stops when the counter is zero. Use Integer.MAX_VALUE to
330: * indicate an unlimited search.
331: */
332:
333: private static void addNodes(Document doc, Object obj,
334: Element parentElement, Set seenObjs, int searchDepth) {
335: if (obj == null) {
336: return;
337: }
338:
339: Class objectClass;
340:
341: if (LockedPG.class.isInstance(obj)) {
342: objectClass = ((LockedPG) obj).getIntrospectionClass();
343: } else if (LockedPGSchedule.class.isInstance(obj)) {
344: objectClass = ((LockedPGSchedule) obj)
345: .getIntrospectionClass();
346: } else {
347: objectClass = obj.getClass();
348: }
349:
350: if (((searchDepth <= 0) && (obj instanceof UniqueObject))
351: || (!(seenObjs.add(obj)))) {
352: // Already seen this object or reached maximum depth.
353: // Write the UID if possible, otherwise write the "toString".
354: //
355: // System.out.println(
356: // "Object traversed already/max depth: " +
357: // obj.getClass().toString() + " " + obj);
358: String sID;
359: UID uid;
360: if ((obj instanceof UniqueObject)
361: && ((uid = (((UniqueObject) obj).getUID())) != null)
362: && ((sID = uid.toString()) != null)) {
363: Element item = doc.createElement("UID");
364: item.appendChild(doc.createTextNode(sID));
365: parentElement.appendChild(item);
366: } else {
367: parentElement.appendChild(doc.createTextNode(obj
368: .toString()));
369: }
370: return;
371: }
372:
373: BeanInfo info = null;
374: Vector propertyNameValues;
375:
376: if (Asset.class.isInstance(obj)) {
377: propertyNameValues = AssetIntrospection
378: .fetchAllProperties((Asset) obj);
379: } else {
380: int mods = objectClass.getModifiers();
381: //System.out.println("Introspecting on: " + objectClass +
382: // " modifiers: " + Modifier.toString(mods));
383: if (!Modifier.isPublic(mods)) {
384: propertyNameValues = specialIntrospection(obj);
385: } else {
386: try {
387: info = Introspector.getBeanInfo(objectClass);
388: } catch (IntrospectionException e) {
389: System.err
390: .println("Exception in converting object to XML: "
391: + e.getMessage());
392: }
393:
394: propertyNameValues = getPropertyNamesAndValues(info
395: .getPropertyDescriptors(), obj);
396: }
397: }
398:
399: // add the nodes for the properties and values
400: for (int i = 0; i < propertyNameValues.size(); i++) {
401: PropertyNameValue pnv = (PropertyNameValue) propertyNameValues
402: .elementAt(i);
403: Object propertyValue = pnv.value;
404: String propertyName = pnv.name;
405: // check if this should be a leaf
406: boolean isLeaf;
407: Class propertyClass = propertyValue.getClass();
408:
409: String propertyClassName = propertyClass.getName();
410: isLeaf = ((propertyClassName.equals("java.lang.String")) || (propertyClassName
411: .equals("java.lang.Class")));
412:
413: if (isLeaf) {
414: // leaf node
415: Element item = doc.createElement(propertyName);
416: item.appendChild(doc.createTextNode(propertyValue
417: .toString()));
418: parentElement.appendChild(item);
419: } else {
420: // this removes the class name following the $ for Locked classes
421: int index = propertyName.indexOf('$');
422: if (index > 0) {
423: propertyName = propertyName.substring(0, index);
424: }
425: Element item = doc.createElement(propertyName);
426: parentElement.appendChild(item);
427: // recurse!
428: addNodes(doc, propertyValue, item, seenObjs,
429: (searchDepth - 1));
430: }
431: }
432: }
433:
434: /**
435: * Includes Double, Integer, etc. and Boolean as primitive types.
436: *
437: * Checks to see if class is a direct descendant of Number or a
438: * Boolean.
439: *
440: * @return true when class is of a primitive type
441: */
442: protected static boolean isPrimitive(Class propertyClass) {
443: if (propertyClass.isPrimitive())
444: return true;
445: try {
446: Class super Class = propertyClass.getSuperclass();
447: if (super Class.equals(numberClass))
448: return true;
449: if (propertyClass.equals(booleanClass))
450: return true;
451: } catch (Exception e) {
452: System.err.println("Exception " + e);
453: }
454:
455: return false;
456: }
457: }
|