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.lib.vishnu.client;
028:
029: import org.cougaar.core.util.PropertyNameValue;
030: import org.cougaar.planning.ldm.asset.Asset;
031: import org.cougaar.planning.ldm.plan.Task;
032: import org.cougaar.planning.ldm.asset.LockedPG;
033:
034: import org.cougaar.planning.ldm.measure.AbstractMeasure;
035:
036: import org.cougaar.core.util.UID;
037: import org.cougaar.core.util.UniqueObject;
038:
039: import java.beans.BeanInfo;
040: import java.beans.Beans;
041: import java.beans.IndexedPropertyDescriptor;
042: import java.beans.IntrospectionException;
043: import java.beans.Introspector;
044: import java.beans.PropertyDescriptor;
045: import java.lang.reflect.Array;
046: import java.lang.reflect.InvocationTargetException;
047: import java.lang.reflect.Method;
048: import java.lang.reflect.Modifier;
049: import java.util.*;
050:
051: import org.w3c.dom.Document;
052: import org.w3c.dom.Element;
053:
054: import org.cougaar.util.log.Logger;
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 abstract class BaseXMLize {
065: /** Maximum search depth -- Integer.MAX_VALUE means unlimited. **/
066: protected final int DEFAULT_UID_DEPTH = 6;
067: protected static final String MAX_VALUE_STRING = Float
068: .toString(Float.MAX_VALUE);
069: protected static final String MIN_VALUE_STRING = Float
070: .toString(Float.MIN_VALUE);
071: protected Class numberClass;
072: protected Class booleanClass;
073: protected Class classClass;
074: protected Class stringClass;
075: protected Class abstractMeasureClass;
076: protected Map classToBeanInfo;
077: protected Map commonUnitToNoUnders;
078: protected Logger logger;
079:
080: public BaseXMLize(Logger logger) {
081: try {
082: numberClass = Class.forName("java.lang.Number");
083: booleanClass = Class.forName("java.lang.Boolean");
084: stringClass = Class.forName("java.lang.String");
085: classClass = Class.forName("java.lang.Class");
086: abstractMeasureClass = Class
087: .forName("org.cougaar.planning.ldm.measure.AbstractMeasure");
088: } catch (ClassNotFoundException cnfe) {
089: }
090: classToBeanInfo = new HashMap();
091: commonUnitToNoUnders = new HashMap();
092: this .logger = logger;
093: }
094:
095: public Element getPlanObjectXML(Object obj, Document doc,
096: String resourceClassName) {
097: return getPlanObjectXML(obj, doc, DEFAULT_UID_DEPTH,
098: resourceClassName);
099: }
100:
101: public Element getPlanObjectXML(Object obj, Document doc,
102: int searchDepth, String resourceClassName) {
103: Collection nodes = getPlanObjectXMLNodes(obj, doc,
104: DEFAULT_UID_DEPTH, resourceClassName);
105: if (nodes.isEmpty())
106: return null;
107: return (Element) nodes.iterator().next();
108: }
109:
110: public Collection getPlanObjectXMLNodes(Object obj, Document doc,
111: String resourceClassName) {
112: return getPlanObjectXMLNodes(obj, doc, DEFAULT_UID_DEPTH,
113: resourceClassName);
114: }
115:
116: public Collection getPlanObjectXMLNodes(Object obj, Document doc,
117: int searchDepth, String resourceClassName) {
118: String tag;
119: boolean isResource = false;
120: boolean isTask = false;
121:
122: if (Asset.class.isInstance(obj)) {
123: tag = "Asset";
124: isResource = true;
125: if (logger.isInfoEnabled())
126: logger
127: .info("BaseXMLize - getPlanObjectXMLNodes thinks "
128: + obj + " is a resource");
129: } else if (Task.class.isInstance(obj)) {
130: tag = ((Task) obj).getVerb().toString();
131: isTask = true;
132: if (logger.isInfoEnabled())
133: logger
134: .info("BaseXMLize - getPlanObjectXMLNodes thinks "
135: + obj + " is a task");
136: } else {
137: tag = obj.getClass().getName();
138: int i = tag.lastIndexOf(".");
139: if (i >= 0) {
140: tag = tag.substring(i + 1);
141: }
142: i = tag.lastIndexOf("Impl");
143: if (i >= 0) {
144: tag = tag.substring(0, i);
145: }
146: if (logger.isInfoEnabled())
147: logger
148: .info("BaseXMLize - getPlanObjectXMLNodes thinks "
149: + obj
150: + " is neither a task nor a resource.");
151: }
152: Element root = createRootNode(doc, tag, isTask, isResource,
153: obj, resourceClassName);
154: Set createdNodes = new HashSet();
155: createdNodes.add(root);
156: addNodes(doc, obj, root, searchDepth, createdNodes);
157: return createdNodes;
158: }
159:
160: /** subclass to generate different tag */
161: protected abstract Element createRootNode(Document doc, String tag,
162: boolean isTask, boolean isResource, Object obj,
163: String resourceClassName);
164:
165: /**
166: * <b>Recursively</b> introspect and add nodes to the XML document.
167: * <p>
168: * Keeps a Set of objects (as these can be circular) and stops
169: * when it tries to introspect over an object a second time.
170: * <p>
171: * Also keeps a depth counter, decrements for each call to addNodes,
172: * and stops when the counter is zero. Use Integer.MAX_VALUE to
173: * indicate an unlimited search.
174: */
175:
176: protected void addNodes(Document doc, Object obj,
177: Element parentElement, int searchDepth,
178: Collection createdNodes) {
179: if (obj == null) {
180: return;
181: }
182:
183: if (searchDepth <= 0) {
184: generateElementReachedMaxDepth(doc, parentElement, obj);
185: return;
186: }
187:
188: Map listProps = new HashMap();
189:
190: List propertyNameValues = getProperties(obj, listProps);
191:
192: // add the nodes for the properties and values
193: for (int i = 0; i < propertyNameValues.size(); i++) {
194: PropertyNameValue pnv = (PropertyNameValue) propertyNameValues
195: .get(i);
196: generateElem(doc, parentElement, pnv.name, pnv.value,
197: searchDepth, false, false, createdNodes);
198: }
199: }
200:
201: protected void generateElem(Document doc, Element parentElement,
202: String propertyName, Object propertyValue, int searchDepth,
203: boolean isList, boolean isFirst, Collection createdNodes) {
204: // check if this should be a leaf
205: boolean isLeaf = !isList
206: && (stringClass.isInstance(propertyValue) || classClass
207: .isInstance(propertyValue));
208:
209: if (isLeaf)
210: generateLeaf(doc, parentElement, propertyName,
211: propertyValue);
212: else {
213: generateNonLeaf(doc, parentElement, propertyName,
214: propertyValue, searchDepth, isList, isFirst,
215: createdNodes);
216: }
217: }
218:
219: /**
220: * Already seen this object or reached maximum depth.
221: * Write the UID if possible, otherwise write the "toString".
222: */
223: protected abstract void generateElementReachedMaxDepth(
224: Document doc, Element parentElement, Object obj);
225:
226: protected abstract void generateLeaf(Document doc,
227: Element parentElement, String propertyName,
228: Object propertyValue);
229:
230: protected abstract void generateNonLeaf(Document doc,
231: Element parentElement, String propertyName,
232: Object propertyValue, int searchDepth, boolean isList,
233: boolean isFirst, Collection createdNodes);
234:
235: protected boolean isUniqueObject(Object obj) {
236: return (obj instanceof UniqueObject)
237: && (((UniqueObject) obj).getUID() != null);
238: }
239:
240: protected List getProperties(Object obj, Map listProps) {
241: BeanInfo info = null;
242: List propertyNameValues;
243:
244: Class objectClass = ((!(LockedPG.class.isInstance(obj))) ? obj
245: .getClass() : ((LockedPG) obj).getIntrospectionClass());
246:
247: int mods = objectClass.getModifiers();
248: if (logger.isInfoEnabled())
249: logger.info("BaseXMLize.getProperties - Introspecting on: "
250: + objectClass + " modifiers: "
251: + Modifier.toString(mods));
252: if (!Modifier.isPublic(mods)) {
253: propertyNameValues = specialIntrospection(obj);
254: } else {
255: try {
256: info = (BeanInfo) classToBeanInfo.get(objectClass);
257: if (info == null) {
258: info = Introspector.getBeanInfo(objectClass);
259: classToBeanInfo.put(objectClass, info);
260: }
261: } catch (IntrospectionException e) {
262: logger.error("Exception in converting object to XML: ",
263: e);
264: }
265:
266: propertyNameValues = getPropertyNamesAndValues(info
267: .getPropertyDescriptors(), obj, listProps);
268: }
269:
270: return propertyNameValues;
271: }
272:
273: /*
274: Handle the case in which an object's class is a private or protected
275: implementation class which implements one or more public interfaces or
276: abstract classes.
277: First check for abstract classes extended by, or interfaces implemented by,
278: the class or its superclasses.
279: (Note that this stops when it finds the first abstract class or
280: interfaces in the class chain.)
281: Then, introspect on the abstract class or all the interfaces
282: found (note that these are assumed to be public)
283: and invoke read property methods
284: on the object cast to that class or those interfaces.
285: */
286:
287: private List specialIntrospection(Object obj) {
288: if (logger.isInfoEnabled())
289: logger.info("Performing special introspection for:" + obj);
290:
291: Class objClass = obj.getClass();
292: Class[] interfaces;
293: while (true) {
294: if (objClass == null) {
295: return null;
296: }
297: if (Modifier.isAbstract(objClass.getModifiers())) {
298: interfaces = new Class[1];
299: interfaces[0] = objClass;
300: break;
301: }
302: interfaces = objClass.getInterfaces();
303: if (interfaces.length != 0) {
304: break;
305: }
306: objClass = objClass.getSuperclass();
307: }
308: List propertyNameValues = new ArrayList(10);
309: for (int i = 0; i < interfaces.length; i++) {
310: if (logger.isInfoEnabled())
311: logger.info("Interface:" + interfaces[i].toString());
312: try {
313: BeanInfo info = (BeanInfo) classToBeanInfo
314: .get(interfaces[i]);
315: if (info == null) {
316: info = Introspector.getBeanInfo(interfaces[i]);
317: classToBeanInfo.put(interfaces[i], info);
318: }
319:
320: PropertyDescriptor[] properties = info
321: .getPropertyDescriptors();
322: Map listProps = new HashMap();
323: List tmp = getPropertyNamesAndValues(properties, Beans
324: .getInstanceOf(obj, interfaces[i]), listProps);
325: if (tmp != null)
326: propertyNameValues.addAll(tmp);
327: } catch (IntrospectionException e) {
328: logger.error(
329: "Exception generating XML for plan object:"
330: + e.getMessage(), e);
331: }
332: }
333: // for infoging
334: // for (int i = 0; i < propertyNameValues.size(); i++) {
335: // PropertyNameValue p = (PropertyNameValue)(propertyNameValues.elementAt(i));
336: // info("Property Name: " + p.name + " Property Value: " + p.value);
337: // }
338: return propertyNameValues;
339: }
340:
341: /**
342: * Get the property names and values (returned in a vector)
343: * from the given property descriptors and for the given object.
344: *
345: * Removes redundant properties from measure objects.
346: */
347:
348: private List getPropertyNamesAndValues(
349: PropertyDescriptor[] properties, Object obj, Map listProps) {
350: List pv = new ArrayList();
351:
352: // IGNORE JAVA.SQL.DATE CLASS
353: if (Date.class.isInstance(obj) || String.class.isInstance(obj)) {
354: return pv;
355: }
356:
357: if (abstractMeasureClass.isInstance(obj)) {
358: properties = prunePropertiesFromMeasure(
359: (AbstractMeasure) obj, properties);
360: }
361:
362: for (int i = 0; i < properties.length; i++) {
363: PropertyDescriptor pd = properties[i];
364: if (logger.isInfoEnabled())
365: logger.info("getPropertyNamesAndValues - "
366: + pd.getPropertyType() + " : " + pd.getName());
367: Method rm = pd.getReadMethod();
368: if (rm == null) {
369: if (logger.isInfoEnabled())
370: logger.info("\tread method was null.");
371: continue;
372: }
373:
374: // invoke the read method for each property
375: Object childObject = getReadResult(obj, rm);
376: if (childObject == null) {
377: if (logger.isInfoEnabled())
378: logger.info("\tread result of " + rm.getName()
379: + " was null.");
380: continue;
381: }
382:
383: // add property name and value to vectorarray
384: String name = pd.getName();
385: if (pd.getPropertyType().isArray()) {
386: int length = Array.getLength(childObject);
387: listProps.put(name, new Integer(length));
388: if (logger.isInfoEnabled())
389: logger.info("getProp - " + pd.getPropertyType()
390: + " : " + name + " - " + length + " now "
391: + listProps.size() + " props");
392:
393: for (int j = 0; j < length; j++) {
394: Object value = Array.get(childObject, j);
395: if (value.getClass().isPrimitive()) {
396: if (isPrimitiveFloat(value.getClass()))
397: value = getValueOfPrimitiveFloat(value);
398: else
399: value = String.valueOf(value);
400: }
401: pv.add(new PropertyNameValue(name, value));
402: }
403: } else {
404: // need first class object, can't have a reference to a primitive
405: if (isPrimitive(childObject.getClass())) {
406: childObject = String.valueOf(childObject);
407: if (logger.isInfoEnabled())
408: logger.info("getProp - " + pd.getName()
409: + " - childObject " + childObject
410: + " is a primitive.");
411: }
412: if (!ignoreClass(childObject.getClass()))
413: pv.add(new PropertyNameValue(name, childObject));
414: }
415: }
416:
417: if (Asset.class.isInstance(obj))
418: pv.addAll(getDynamicAssetProperties((Asset) obj));
419:
420: Collections.sort(pv, lessStringIgnoreCase);
421: return pv;
422: }
423:
424: protected boolean isPrimitiveFloat(Class theClass) {
425: return (theClass == Float.TYPE) || (theClass == Double.TYPE);
426: }
427:
428: protected String getValueOfPrimitiveFloat(Object value) {
429: String stringValue = String.valueOf(value);
430: char first = stringValue.charAt(0);
431:
432: if (first == 'I' || first == '-') {
433: if (stringValue.equals("Infinity"))
434: stringValue = MAX_VALUE_STRING;
435: else if (stringValue.equals("-Infinity"))
436: stringValue = MIN_VALUE_STRING;
437: }
438: return stringValue;
439: }
440:
441: /**
442: * ignore these classes when generating properties
443: * Ignore : Class, AspectScoreRange, WorkflowImpl
444: */
445: protected boolean ignoreClass(Class aClass) {
446: return (aClass == Class.class)
447: || (aClass == org.cougaar.planning.ldm.plan.AspectScoreRange.class)
448: || (aClass == org.cougaar.planning.ldm.plan.WorkflowImpl.class)
449: || (aClass == org.cougaar.planning.ldm.plan.RelationshipScheduleImpl.class)
450: || (aClass == org.cougaar.planning.ldm.plan.AspectValue.class)
451: || (aClass == org.cougaar.planning.ldm.plan.AllocationImpl.class)
452: || (org.cougaar.planning.plugin.legacy.PluginAdapter.class
453: .isAssignableFrom(aClass))
454: || (org.cougaar.planning.ldm.asset.LockedPG.class
455: .isAssignableFrom(aClass));
456: }
457:
458: private final Comparator lessStringIgnoreCase = new Comparator() {
459: public int compare(Object first, Object second) {
460: String firstName = ((PropertyNameValue) first).name;
461: String secondName = ((PropertyNameValue) second).name;
462: return firstName.compareToIgnoreCase(secondName);
463: }
464: };
465:
466: /**
467: * Invoke read method on object
468: *
469: * @return Object that is the result of the read
470: */
471: protected Object getReadResult(Object obj, Method rm) {
472: // invoke the read method for each property
473: Object childObject = null;
474: try {
475: childObject = rm.invoke(obj, null);
476: } catch (InvocationTargetException ie) {
477: if (!(ie.getTargetException() instanceof IndexOutOfBoundsException)) {
478: logger.error("Invocation target exception invoking: "
479: + rm.getName() + " on object of class:"
480: + obj.getClass().getName() + " msg "
481: + ie.getTargetException().getMessage(), ie);
482: }
483: } catch (Exception e) {
484: logger.error("Exception " + e.toString() + " invoking: "
485: + rm.getName() + " on object of class:"
486: + obj.getClass().getName(), e);
487: }
488: return childObject;
489: }
490:
491: /**
492: * Removes redundant measure properties.
493: * Returns only the common unit measure.
494: * For example, for Distance, returns only the meters property and discards furlongs.
495: *
496: * (Converts underscores in common unit names.)
497: *
498: * @param measure needed so can get common unit
499: * @param properties initial complete set of measure properties
500: * @return array containing the one property descriptor for the common unit property
501: */
502: protected PropertyDescriptor[] prunePropertiesFromMeasure(
503: AbstractMeasure measure, PropertyDescriptor[] properties) {
504:
505: String cu = measure.getUnitName(measure.getCommonUnit());
506: if (cu == null) {
507: return new PropertyDescriptor[0];
508: }
509: String noUnders;
510: if ((noUnders = (String) commonUnitToNoUnders.get(cu)) == null) {
511: int pos = 0;
512: int underIndex = -1;
513: noUnders = "";
514: while ((underIndex = cu.indexOf('_', pos)) != -1) {
515: noUnders = noUnders
516: + cu.substring(pos, underIndex)
517: + cu.substring(underIndex + 1, underIndex + 2)
518: .toUpperCase();
519: pos = underIndex + 2;
520: }
521: while ((underIndex = cu.indexOf('/', pos)) != -1) {
522: noUnders = noUnders
523: + cu.substring(pos, underIndex)
524: + "Per"
525: + cu.substring(underIndex + 1, underIndex + 2)
526: .toUpperCase();
527: pos = underIndex + 2;
528: }
529: noUnders = noUnders + cu.substring(pos);
530: commonUnitToNoUnders.put(cu, noUnders);
531: }
532:
533: for (int i = 0; i < properties.length; i++) {
534: PropertyDescriptor pd = properties[i];
535:
536: if (pd.getName().equals(noUnders))
537: return new PropertyDescriptor[] { pd };
538: }
539: return null;
540: }
541:
542: /**
543: * Includes Double, Integer, etc. and Boolean as primitive types.
544: *
545: * Checks to see if class is a direct descendant of Number or a
546: * Boolean.
547: *
548: * @return true when class is of a primitive type
549: */
550: protected boolean isPrimitive(Class propertyClass) {
551: if (propertyClass.isPrimitive())
552: return true;
553: try {
554: Class super Class = propertyClass.getSuperclass();
555: if (super Class.equals(numberClass))
556: return true;
557: if (propertyClass.equals(booleanClass))
558: return true;
559: } catch (Exception e) {
560: logger.error("Exception " + e, e);
561: }
562:
563: return false;
564: }
565:
566: public List getDynamicAssetProperties(Asset asset) {
567: List propertyNameValues = new ArrayList();
568: try {
569: // get all dynamic properties of asset
570: Enumeration dynamicProperties = asset.getOtherProperties();
571: while (dynamicProperties.hasMoreElements()) {
572: Object dynamicProperty = dynamicProperties
573: .nextElement();
574: if (logger.isInfoEnabled())
575: logger.info("Adding dynamic property: "
576: + dynamicProperty.toString() + " Value: "
577: + dynamicProperty.toString() + " Class: "
578: + dynamicProperty.getClass().toString());
579: propertyNameValues.add(new PropertyNameValue(
580: prettyName(dynamicProperty.getClass()
581: .toString()), dynamicProperty));
582: }
583: } catch (Exception e) {
584: logger.error("Asset introspection exception: "
585: + e.toString());
586: }
587: return propertyNameValues;
588: }
589:
590: // Return the last field of a fully qualified name.
591: // If the input string contains an "@" then it's assumed
592: // that the fully qualified name preceeds it.
593:
594: private String prettyName(String s) {
595: int i = s.indexOf("@");
596: if (i != -1)
597: s = s.substring(0, i);
598: return (s.substring(s.lastIndexOf(".") + 1));
599: }
600:
601: protected void reportTime(String prefix, Date start) {
602: Runtime rt = Runtime.getRuntime();
603: Date end = new Date();
604: long diff = end.getTime() - start.getTime();
605: long min = diff / 60000l;
606: long sec = (diff - (min * 60000l)) / 1000l;
607: logger.info("\n" + prefix + min + ":" + ((sec < 10) ? "0" : "")
608: + sec + " (Wall clock time)" + " free "
609: + (rt.freeMemory() / (1024 * 1024)) + "M" + " total "
610: + (rt.totalMemory() / (1024 * 1024)) + "M");
611: }
612: }
|