Java tutorial
/* * Copyright (c) 2000-2004 Netspective Communications LLC. All rights reserved. * * Netspective Communications LLC ("Netspective") permits redistribution, modification and use of this file in source * and binary form ("The Software") under the Netspective Source License ("NSL" or "The License"). The following * conditions are provided as a summary of the NSL but the NSL remains the canonical license and must be accepted * before using The Software. Any use of The Software indicates agreement with the NSL. * * 1. Each copy or derived work of The Software must preserve the copyright notice and this notice unmodified. * * 2. Redistribution of The Software is allowed in object code form only (as Java .class files or a .jar file * containing the .class files) and only as part of an application that uses The Software as part of its primary * functionality. No distribution of the package is allowed as part of a software development kit, other library, * or development tool without written consent of Netspective. Any modified form of The Software is bound by these * same restrictions. * * 3. Redistributions of The Software in any form must include an unmodified copy of The License, normally in a plain * ASCII text file unless otherwise agreed to, in writing, by Netspective. * * 4. The names "Netspective", "Axiom", "Commons", "Junxion", and "Sparx" are trademarks of Netspective and may not be * used to endorse or appear in products derived from The Software without written consent of Netspective. * * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, * ARE HEREBY DISCLAIMED. * * NETSPECTIVE AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A * RESULT OF USING OR DISTRIBUTING THE SOFTWARE. IN NO EVENT WILL NETSPECTIVE OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN * IF IT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. */ package com.netspective.commons.xdm; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.netspective.commons.command.Command; import com.netspective.commons.command.CommandNotFoundException; import com.netspective.commons.command.Commands; import com.netspective.commons.io.InputSourceLocator; import com.netspective.commons.io.InputSourceTracker; import com.netspective.commons.io.PropertiesLoader; import com.netspective.commons.lang.ClassJavaDoc; import com.netspective.commons.lang.JavaDocs; import com.netspective.commons.lang.MethodJavaDoc; import com.netspective.commons.text.TextUtils; import com.netspective.commons.value.ValueSource; import com.netspective.commons.value.ValueSources; import com.netspective.commons.value.source.RedirectValueSource; import com.netspective.commons.xdm.exception.DataModelException; import com.netspective.commons.xdm.exception.UnsupportedAttributeException; import com.netspective.commons.xdm.exception.UnsupportedElementException; import com.netspective.commons.xdm.exception.UnsupportedTextException; import com.netspective.commons.xml.template.TemplateConsumer; import com.netspective.commons.xml.template.TemplateProducer; import com.netspective.commons.xml.template.TemplateProducerParent; import com.netspective.commons.xml.template.TemplateProducers; /** * This class is used to introspect existing classes and allow parsing of XML * and unmarshalling the XML elements into an exact replica of a Java object * model. This is very useful when an XML structure needs to be unmarshalled * into a set of java classes (called a DataModel) that mimics the XML. * This class's original code (and indeed the very idea) was taken from the * Jakarta Ant project but this version is very generic (whereas the Ant * code was specific to TaskDefs). The DataModelSchema is most appropriate * for cases where unmarshalling of XML into a structured Java object model * is critical; this class does not (yet) handle the marshalling of Java into * XML using any particular rules (though it wouldn't be hard to add that * capability). * * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> * @author <a href="mailto:snshah@netspective.com">Shahid N. Shah</a> */ public class XmlDataModelSchema { private static final Log log = LogFactory.getLog(XmlDataModelSchema.class); public static final String XMLDATAMODEL_SCHEMA_OPTIONS_FIELD_NAME = "XML_DATA_MODEL_SCHEMA_OPTIONS"; public static final String ADD_TEXT_DEFAULT_METHOD_NAME = "addText"; public interface CustomElementCreator { public Object createCustomDataModelElement(XdmParseContext pc, XmlDataModelSchema schema, Object parent, String elementName, String alternateClassName) throws DataModelException, InvocationTargetException, IllegalAccessException, InstantiationException; } public interface CustomElementStorer { public void storeCustomDataModelElement(XdmParseContext pc, XmlDataModelSchema schema, Object parent, Object child, String elementName) throws DataModelException, InvocationTargetException, IllegalAccessException, InstantiationException; } public interface CustomElementAttributeSetter { public void setCustomDataModelElementAttribute(XdmParseContext pc, XmlDataModelSchema schema, Object parent, String attrName, String attrValue) throws DataModelException, InvocationTargetException, IllegalAccessException, DataModelException; } public interface ConstructionFinalizeListener { public void finalizeConstruction(XdmParseContext pc, Object element, String elementName) throws DataModelException; } public interface InputSourceTrackerListener { public void setInputSourceTracker(InputSourceTracker tracker); } public interface InputSourceLocatorListener { public InputSourceLocator getInputSourceLocator(); public void setInputSourceLocator(InputSourceLocator locator); } public static class Options { private String pcDataHandlerMethodName = ADD_TEXT_DEFAULT_METHOD_NAME; private boolean generateAutoAliases = true; private boolean ignorePcData; private Map aliases = new HashMap(); private Set ignoreNestedElements = new HashSet(); private Set ignoreAttributes = new HashSet(); private Set requiredAttributes = new HashSet(); private Set requiredNestedElements = new HashSet(); private Map subclassedItemsClasses = new HashMap(); public Options() { } public Options(Options copy) { pcDataHandlerMethodName = copy.pcDataHandlerMethodName; generateAutoAliases = copy.generateAutoAliases; ignorePcData = copy.ignorePcData; aliases.putAll(copy.aliases); ignoreNestedElements.addAll(copy.ignoreNestedElements); ignoreAttributes.addAll(copy.ignoreAttributes); requiredAttributes.addAll(copy.requiredAttributes); requiredNestedElements.addAll(copy.requiredNestedElements); } public void setPropertyNames(PropertyNames propertyNames) { String[] allNames = propertyNames.getAllNames(); for (int i = 0; i < allNames.length; i++) { String alias = allNames[i]; if (ignoreAttributes.contains(alias)) ignoreAttributes.addAll(propertyNames.getAliases()); if (ignoreNestedElements.contains(alias)) ignoreNestedElements.addAll(propertyNames.getAliases()); } } public boolean isGenerateAutoAliases() { return generateAutoAliases; } public boolean ignoreAttribute(String attrName) { return attrName.startsWith("__") || ignoreAttributes.contains(attrName); } public boolean ignoreNestedElement(String elementName) { return ignoreNestedElements.contains(elementName); } public Set getIgnoreAttributes() { return ignoreAttributes; } public Set getIgnoreNestedElements() { return ignoreNestedElements; } public Set getRequiredAttributes() { return requiredAttributes; } public Set getRequiredNestedElements() { return requiredNestedElements; } public boolean isIgnorePcData() { return ignorePcData; } public String getPcDataHandlerMethodName() { return pcDataHandlerMethodName; } public Options setIgnoreAttributes(Set ignoreAttributes) { this.ignoreAttributes = ignoreAttributes; return this; } public Options setIgnoreNestedElements(Set ignoreNestedElements) { this.ignoreNestedElements = ignoreNestedElements; return this; } public Options addIgnoreAttributes(String[] ignoreAttributesList) { for (int i = 0; i < ignoreAttributesList.length; i++) ignoreAttributes.add(ignoreAttributesList[i]); return this; } public Options addIgnoreNestedElements(String[] ignoreElementsList) { for (int i = 0; i < ignoreElementsList.length; i++) ignoreNestedElements.add(ignoreElementsList[i]); return this; } public Options setRequiredAttributes(Set requiredAttributes) { this.requiredAttributes = requiredAttributes; return this; } public Options setRequiredNestedElements(Set requiredNestedElements) { this.requiredNestedElements = requiredNestedElements; return this; } public Options addRequiredAttributes(String[] requiredAttributesList) { for (int i = 0; i < requiredAttributesList.length; i++) requiredAttributes.add(requiredAttributesList[i]); return this; } public Options addRequiredNestedElements(String[] requiredElementsList) { for (int i = 0; i < requiredElementsList.length; i++) requiredNestedElements.add(requiredElementsList[i]); return this; } public Options setIgnorePcData(boolean ignorePcData) { this.ignorePcData = ignorePcData; return this; } public Options setPcDataHandlerMethodName(String pcDataHandlerMethodName) { this.pcDataHandlerMethodName = pcDataHandlerMethodName; return this; } public void setGenerateAutoAliases(boolean generateAutoAliases) { this.generateAutoAliases = generateAutoAliases; } public Map getAliases() { return aliases; } public void addAliases(String primaryName, String[] alternateNames) { if (null == alternateNames) return; Set altNames = new HashSet(); for (int i = 0; i < alternateNames.length; i++) altNames.add(alternateNames[i]); Set set = (Set) aliases.get(primaryName); if (set == null) aliases.put(primaryName, altNames); else set.addAll(altNames); } public void addAliases(String primaryName, Set alternateNames) { if (null == alternateNames) return; Set set = (Set) aliases.get(primaryName); if (set == null) aliases.put(primaryName, alternateNames); else set.addAll(alternateNames); } public Map getSubclassedItemsClasses() { return subclassedItemsClasses; } public Class getSubclassedItemClass(String item, Class clsDefault) { Class cls = (Class) subclassedItemsClasses.get(item); return cls != null ? cls : clsDefault; } public void addSubclassedItemClass(String item, Class cls) { subclassedItemsClasses.put(item, cls); } } /** * Holds options parameters for this schema */ private Options options; /** * holds the names of the properties (key is a name, value is the PropertyName object associated with it) */ private Map propertyNames; /** * holds the types of the attributes that could be set. */ private Map attributeTypes; /** * holds the attribute setter anonymous classes. */ private Map attributeSetters; /** * holds the attribute setter methods */ private Map attributeSetterMethods; /** * holds the classes that have accessors */ private Map attributeAccessors; /** * holds the classes that can manage bitmasked flags */ private Map flagsAttributeAccessors; /** * Holds the types of nested elements that could be created. */ private Map nestedTypes; /** * Holds methods to create nested elements. */ private Map nestedCreators; /** * Holds methods to create nested elements. */ private Map nestedAltClassNameCreators; /** * Holds methods to create nested elements. */ private Map nestedAdders; /** * Holds methods to store configured or custom nested elements. */ private Map nestedStorers; /** * The method to add PCDATA text. */ private Method addText = null; /** * The Class that's been introspected. */ private Class bean; /** * The settable attributes list useful for creating DTDs and other documentation for this bean */ private AttributeDetailList settableAttrsDetailWithFlagsExpanded; /** * The settable attributes list useful for creating DTDs and other documentation for this bean */ private AttributeDetailList settableAttrsDetailWithoutFlagsExpanded; /** * The list of nested elements useful for creating DTDs and other documentation for this bean */ private ElementDetailList nestedElementsDetail; /** * instances we've already created */ private static Map schemas = new HashMap(); public static Map getSchemas() { return schemas; } /** * Factory method for schema objects. */ public synchronized static XmlDataModelSchema getSchema(Class c) { XmlDataModelSchema schema = (XmlDataModelSchema) schemas.get(c); if (schema == null) { schema = new XmlDataModelSchema(c); schemas.put(c, schema); } return schema; } public Options getOptions() { return options; } public Map getPropertyNames() { return propertyNames; } /** * Gets the description of the bean represented by this Schema by using the runtime JavaDoc XML resources. * * @return The description of the class provided in the JavaDoc XML */ public ClassJavaDoc getJavaDoc() { return JavaDocs.getInstance().getClassJavaDoc(getBean()); } public class ElementDetail { private String elemName; private Class elemType; private TemplateProducer templateProducer; private boolean required; public ElementDetail(String name) throws DataModelException { this.elemName = name; this.elemType = getElementType(name); this.required = getOptions().getRequiredAttributes().contains(name); } public ElementDetail(String name, TemplateProducer templateProducer) { this.elemName = name; this.elemType = TemplateProducer.class; this.required = false; this.templateProducer = templateProducer; } public String getElemName() { return elemName; } public Class getElemType() { return elemType; } public boolean isRequired() { return required; } public boolean isInherited() { NestedStorer storer = (NestedStorer) nestedStorers.get(elemName); if (storer != null) return storer.isInherited(); else { NestedCreator creator = (NestedCreator) nestedCreators.get(elemName); if (creator != null) return creator.isInherited(); else return false; } } public Class getDeclaringClass() { if (isTemplateProducer()) return XmlDataModelSchema.this.bean; NestedStorer storer = (NestedStorer) nestedStorers.get(elemName); NestedCreator creator = (NestedCreator) nestedCreators.get(elemName); if ((storer != null && storer.isInherited()) && (creator != null && creator.isInherited())) return creator.getDeclaringClass(); if ((storer != null && storer.isInherited()) && (creator != null && !creator.isInherited())) return creator.getDeclaringClass(); if ((storer != null && !storer.isInherited()) && (creator != null && creator.isInherited())) return storer.getDeclaringClass(); return creator != null ? creator.getDeclaringClass() : (storer != null ? storer.getDeclaringClass() : XmlDataModelSchema.this.bean); } public boolean isTemplateProducer() { return templateProducer != null; } public TemplateProducer getTemplateProducer() { return templateProducer; } public boolean isTemplateConsumer() { return TemplateConsumer.class.isAssignableFrom(elemType); } public ClassJavaDoc getJavaDoc() { return JavaDocs.getInstance().getClassJavaDoc(elemType); } } public class ElementDetailList extends ArrayList { } public ElementDetailList getNestedElementsDetail() throws DataModelException { if (nestedElementsDetail == null) { nestedElementsDetail = new ElementDetailList(); TemplateProducers templateProducers = null; if (TemplateProducerParent.class.isAssignableFrom(getBean())) { try { Object instance = getBean().newInstance(); templateProducers = ((TemplateProducerParent) instance).getTemplateProducers(); } catch (InstantiationException e) { log.warn("Unable to create instance for template producers", e); } catch (IllegalAccessException e) { log.error("Unable to create instance for template producers", e); } } Map childPropertyNames = getPropertyNames(); Set sortedChildPropertyNames = new TreeSet(getNestedElements().keySet()); if (templateProducers != null) sortedChildPropertyNames.addAll(templateProducers.getElementNames()); Iterator iterator = sortedChildPropertyNames.iterator(); while (iterator.hasNext()) { String attrName = (String) iterator.next(); TemplateProducer producer = templateProducers != null ? templateProducers.get(attrName) : null; if (producer != null) { nestedElementsDetail.add(new ElementDetail(attrName, producer)); continue; } if (getOptions().ignoreAttribute(attrName)) continue; XmlDataModelSchema.PropertyNames attrNames = (XmlDataModelSchema.PropertyNames) childPropertyNames .get(attrName); if (attrNames != null && !attrNames.isPrimaryName(attrName)) continue; nestedElementsDetail.add(new ElementDetail(attrName)); } } return nestedElementsDetail; } public class AttributeDetail { private static final String ATTRNAME_CLASS = "class"; private String attrName; private Class attrType; private String primaryFlagsAttrName; private XdmBitmaskedFlagsAttribute flags; private XdmBitmaskedFlagsAttribute.FlagDefn flagAlias; private boolean required; public AttributeDetail(String name) throws DataModelException { if (name.equals(ATTRNAME_CLASS)) { this.attrName = name; this.attrType = Class.class; this.required = false; } else { this.attrName = name; this.attrType = getAttributeType(name); this.required = getOptions().getRequiredAttributes().contains(name); } } public AttributeDetail(String name, XdmBitmaskedFlagsAttribute flags) { this.attrName = name; this.attrType = flags.getClass(); this.flags = flags; this.required = getOptions().getRequiredAttributes().contains(name); } public AttributeDetail(String name, String primaryFlagsAttrName, XdmBitmaskedFlagsAttribute flags, XdmBitmaskedFlagsAttribute.FlagDefn flagAlias) { this.attrName = name; this.attrType = flags.getClass(); this.primaryFlagsAttrName = primaryFlagsAttrName; this.flags = flags; this.flagAlias = flagAlias; this.required = getOptions().getRequiredAttributes().contains(name); } public boolean hasAccessor() { return attributeAccessors.get(attrName) != null; } public AttributeAccessor getAccessor() { return (AttributeAccessor) attributeAccessors.get(attrName); } public String getAccessorValue(Object parent, String valueIfNull) throws DataModelException, IllegalAccessException, InvocationTargetException { AttributeAccessor accessor = getAccessor(); if (accessor == null) return valueIfNull; Object value = accessor.get(null, parent); if (value == null) return valueIfNull; if (value instanceof String[]) return TextUtils.getInstance().join((String[]) value, ", "); return value.toString(); } public boolean isRequired() { return required; } public boolean isInherited() { if (attrName.equals(ATTRNAME_CLASS)) return true; AttributeSetter setter = (AttributeSetter) attributeSetters .get(primaryFlagsAttrName != null ? primaryFlagsAttrName : attrName); if (setter != null) return setter.isInherited(); else return false; } public Class getDeclaringClass() { if (attrName.equals(ATTRNAME_CLASS)) return Object.class; AttributeSetter setter = (AttributeSetter) attributeSetters .get(primaryFlagsAttrName != null ? primaryFlagsAttrName : attrName); if (setter != null) return setter.getDeclaringClass(); else return XmlDataModelSchema.this.bean; } public MethodJavaDoc getJavaDoc() { if (isFlagAlias()) return JavaDocs.getInstance().getClassJavaDoc(getBean()).getMethodDoc( "set" + TextUtils.getInstance().xmlTextToJavaIdentifier(primaryFlagsAttrName, true)); else return JavaDocs.getInstance().getClassJavaDoc(getBean()) .getMethodDoc("set" + TextUtils.getInstance().xmlTextToJavaIdentifier(attrName, true)); } public boolean isFlagsPrimary() { return flags != null && flagAlias == null; } public boolean isFlagAlias() { return flags != null && flagAlias != null; } public XdmBitmaskedFlagsAttribute.FlagDefn getFlagAlias() { return flagAlias; } public XdmBitmaskedFlagsAttribute getFlags() { return flags; } public String getAttrName() { return attrName; } public Class getAttrType() { return attrType; } public String getPrimaryFlagsAttrName() { return primaryFlagsAttrName; } public boolean hasChoices() { if (attrType == null) return false; return isFlagAlias() || isFlagsPrimary() || XdmEnumeratedAttribute.class.isAssignableFrom(attrType) || (Boolean.class.isAssignableFrom(attrType) || (attrType == boolean.class)); } public String getChoices() { if (attrType == null) return ""; TextUtils textUtils = TextUtils.getInstance(); if (isFlagAlias()) return textUtils.join(textUtils.getBooleanChoices(), ", "); if (isFlagsPrimary()) return textUtils.join(flags.getFlagNamesWithXdmAccess(), " | "); if (XdmEnumeratedAttribute.class.isAssignableFrom(attrType)) { XdmEnumeratedAttribute ea; try { ea = (XdmEnumeratedAttribute) attrType.newInstance(); } catch (Exception e) { log.error("Error retrieving enumeration data", e); return e.toString(); } return textUtils.join(ea.getValues(), ", "); } if (Boolean.class.isAssignableFrom(attrType) || (attrType == boolean.class)) return textUtils.join(textUtils.getBooleanChoices(), ", "); return ""; } } public class AttributeDetailList extends ArrayList { } public AttributeDetailList getSettableAttributesDetail(boolean expandFlagAliases) throws IllegalAccessException, InstantiationException, InvocationTargetException, DataModelException { AttributeDetailList result = expandFlagAliases ? settableAttrsDetailWithFlagsExpanded : settableAttrsDetailWithoutFlagsExpanded; if (result != null) return result; Map childPropertyNames = getPropertyNames(); Map flagSetterPrimaries = new HashMap(); Map flagSetterAliases = new HashMap(); for (Iterator i = getAttributes().iterator(); i.hasNext();) { String attrName = (String) i.next(); Class attrType = getAttributeType(attrName); if (!XdmBitmaskedFlagsAttribute.class.isAssignableFrom(attrType)) continue; PropertyNames pNames = (PropertyNames) childPropertyNames.get(attrName); if (!pNames.isPrimaryName(attrName)) continue; XdmBitmaskedFlagsAttribute bfa; XmlDataModelSchema.NestedCreator creator = (XmlDataModelSchema.NestedCreator) getNestedCreators() .get(attrName); if (creator != null) { Object flagsGetterInstance; try { flagsGetterInstance = createInstance(); bfa = (XdmBitmaskedFlagsAttribute) creator.create(flagsGetterInstance); } catch (Exception e) { try { bfa = (XdmBitmaskedFlagsAttribute) attrType.newInstance(); } catch (Exception e1) { log.error(e1); bfa = null; } } } else bfa = (XdmBitmaskedFlagsAttribute) attrType.newInstance(); if (bfa != null) { flagSetterPrimaries.put(attrName, bfa); if (expandFlagAliases) { Map xmlNodeNames = bfa.getFlagSetterXmlNodeNames(); for (Iterator xmliter = xmlNodeNames.keySet().iterator(); xmliter.hasNext();) { String xmlNodeName = (String) xmliter.next(); if (!childPropertyNames.containsKey(xmlNodeName)) flagSetterAliases.put(xmlNodeName, new Object[] { attrName, bfa, xmlNodeNames.get(xmlNodeName) }); } } } } result = new AttributeDetailList(); Set sortedChildPropertyNames = new TreeSet(getAttributes()); sortedChildPropertyNames.addAll(flagSetterAliases.keySet()); sortedChildPropertyNames.add(AttributeDetail.ATTRNAME_CLASS); Iterator iterator = sortedChildPropertyNames.iterator(); while (iterator.hasNext()) { String attrName = (String) iterator.next(); if (flagSetterAliases.containsKey(attrName)) { Object[] flagSetterInfo = (Object[]) flagSetterAliases.get(attrName); result.add(new AttributeDetail(attrName, (String) flagSetterInfo[0], (XdmBitmaskedFlagsAttribute) flagSetterInfo[1], (XdmBitmaskedFlagsAttribute.FlagDefn) flagSetterInfo[2])); continue; } if (flagSetterPrimaries.containsKey(attrName)) { result.add(new AttributeDetail(attrName, (XdmBitmaskedFlagsAttribute) flagSetterPrimaries.get(attrName))); continue; } if (getOptions().ignoreAttribute(attrName)) continue; XmlDataModelSchema.PropertyNames attrNames = (XmlDataModelSchema.PropertyNames) childPropertyNames .get(attrName); if (attrNames != null && !attrNames.isPrimaryName(attrName)) continue; result.add(new AttributeDetail(attrName)); } if (expandFlagAliases) settableAttrsDetailWithFlagsExpanded = result; else settableAttrsDetailWithoutFlagsExpanded = result; return result; } public Map getAttributeSetters() { return attributeSetters; } public Map getAttributeAccessors() { return attributeAccessors; } public Map getAttributeSetterMethods() { return attributeSetterMethods; } public Map getFlagsAttributeAccessors() { return flagsAttributeAccessors; } public Map getAttributeTypes() { return attributeTypes; } public Map getNestedTypes() { return nestedTypes; } public Map getNestedStorers() { return nestedStorers; } public Map getNestedCreators() { return nestedCreators; } public Map getNestedAdders() { return nestedAdders; } public Class getBean() { return bean; } public boolean isTemplateConsumer() { return TemplateConsumer.class.isAssignableFrom(bean); } public Object createInstance() throws IllegalAccessException, InstantiationException { return bean.newInstance(); } private static Object getStaticFieldValue(final Class bean, final String name) { try { Field field = bean.getField(name); return field.get(null); } catch (NoSuchFieldException e) { return null; } catch (SecurityException e) { return null; } catch (IllegalArgumentException e) { return null; } catch (IllegalAccessException e) { return null; } } private XmlDataModelSchema(final Class bean) { propertyNames = new HashMap(); attributeTypes = new HashMap(); attributeSetters = new HashMap(); attributeSetterMethods = new HashMap(); attributeAccessors = new HashMap(); flagsAttributeAccessors = new HashMap(); nestedTypes = new HashMap(); nestedCreators = new HashMap(); nestedAltClassNameCreators = new HashMap(); nestedAdders = new HashMap(); nestedStorers = new HashMap(); this.bean = bean; Options customOptions = (Options) getStaticFieldValue(bean, XMLDATAMODEL_SCHEMA_OPTIONS_FIELD_NAME); options = customOptions != null ? customOptions : new Options(); Method[] methods = bean.getMethods(); for (int i = 0; i < methods.length; i++) { final Method m = methods[i]; final String name = m.getName(); Class returnType = m.getReturnType(); Class[] args = m.getParameterTypes(); if (name.equals(options.pcDataHandlerMethodName) && java.lang.Void.TYPE.equals(returnType) && args.length == 1 && java.lang.String.class.equals(args[0])) { addText = methods[i]; } else if (name.startsWith("get") && args.length == 0) { String[] propNames = getPropertyNames(name, "get"); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() == 0) continue; AttributeAccessor aa = createAttributeAccessor(m, propNames[pn], returnType); if (aa != null) attributeAccessors.put(propNames[pn], aa); } } else if (name.startsWith("is") && args.length == 0) { String[] propNames = getPropertyNames(name, "is"); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() == 0) continue; AttributeAccessor aa = createAttributeAccessor(m, propNames[pn], returnType); if (aa != null) attributeAccessors.put(propNames[pn], aa); } } else if (name.startsWith("set") && java.lang.Void.TYPE.equals(returnType) && args.length == 1 && (!args[0].isArray() || java.lang.String[].class.equals(args[0]))) { String[] propNames = getPropertyNames(name, "set"); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() == 0) continue; attributeSetterMethods.put(propNames[pn], m); AttributeSetter as = createAttributeSetter(m, propNames[pn], args[0]); if (as != null) { attributeTypes.put(propNames[pn], args[0]); attributeSetters.put(propNames[pn], as); } } } else if (name.startsWith("create") && !returnType.isArray() && !returnType.isPrimitive() && (args.length == 0)) { // prevent infinite recursion for nested recursive elements if (!returnType.getClass().equals(bean.getClass())) getSchema(returnType); String[] propNames = getPropertyNames(name, "create"); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() == 0) continue; nestedTypes.put(propNames[pn], returnType); nestedCreators.put(propNames[pn], new NestedCreator() { public Object create(Object parent) throws InvocationTargetException, IllegalAccessException { return m.invoke(parent, new Object[] {}); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }); } } else if (name.startsWith("create") && !returnType.isArray() && !returnType.isPrimitive() && (args.length == 1 && args[0] == Class.class)) { // prevent infinite recursion for nested recursive elements if (!returnType.getClass().equals(bean.getClass())) getSchema(returnType); String[] propNames = getPropertyNames(name, "create"); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() == 0) continue; nestedTypes.put(propNames[pn], returnType); nestedAltClassNameCreators.put(propNames[pn], new NestedAltClassCreator() { public Object create(Object parent, Class cls) throws InvocationTargetException, IllegalAccessException { return m.invoke(parent, new Object[] { cls }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }); } } else if (name.startsWith("add") && java.lang.Void.TYPE.equals(returnType) && args.length == 1 && !java.lang.String.class.equals(args[0]) && !args[0].isArray() && !args[0].isPrimitive()) { String[] propNames = getPropertyNames(name, "add"); try { final Constructor c = args[0].getConstructor(new Class[] {}); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() == 0) continue; if (!nestedCreators.containsKey(propNames[pn])) { nestedTypes.put(propNames[pn], args[0]); nestedCreators.put(propNames[pn], new NestedCreator() { public boolean allowAlternateClass() { return false; } public Object create(Object parent) throws InvocationTargetException, IllegalAccessException, InstantiationException { return c.newInstance(new Object[] {}); } public Object create(Object parent, Class cls) throws InvocationTargetException, IllegalAccessException, InstantiationException { return c.newInstance(new Object[] { cls }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }); } } } catch (NoSuchMethodException nse) { //log.warn("Unable to create nestedCreator for " + name + " " + args[0] + ", registering type only without a creator.", nse); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() > 0) nestedTypes.put(propNames[pn], args[0]); } } // prevent infinite recursion for nested recursive elements if (!args[0].getClass().equals(bean.getClass())) getSchema(args[0]); for (int pn = 0; pn < propNames.length; pn++) { if (propNames[pn].length() == 0) continue; nestedStorers.put(propNames[pn], new NestedStorer() { public void store(Object parent, Object child) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Object[] { child }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }); } } } } /** * Sets the named attribute. */ public void setAttribute(XdmParseContext pc, Object element, String attributeName, String value, boolean withinCustom) throws DataModelException, UnsupportedAttributeException { boolean hasCustom = element instanceof CustomElementAttributeSetter; AttributeSetter as = (AttributeSetter) attributeSetters.get(attributeName); if (as == null && (withinCustom || !hasCustom)) { // see if we're trying to set a named flag for (Iterator i = flagsAttributeAccessors.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); AttributeAccessor accessor = (AttributeAccessor) entry.getValue(); Object returnVal = null; try { returnVal = accessor.get(pc, element); } catch (Exception e) { pc.addError("Unable to set attribute '" + attributeName + "' to '" + value + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + e.getMessage()); log.error(e); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); } if (returnVal instanceof XdmBitmaskedFlagsAttribute) { XdmBitmaskedFlagsAttribute bfa = (XdmBitmaskedFlagsAttribute) returnVal; if (bfa.updateFlag(attributeName, TextUtils.getInstance().toBoolean(value))) return; } } UnsupportedAttributeException e = new UnsupportedAttributeException(pc, element, attributeName); pc.addError(e); if (pc.isThrowErrorException()) throw e; else return; } try { if (as == null && (!withinCustom && hasCustom)) ((CustomElementAttributeSetter) element).setCustomDataModelElementAttribute(pc, this, element, attributeName, value); else as.set(pc, element, value); } catch (InvocationTargetException ite) { pc.addError("Unable to set attribute '" + attributeName + "' to '" + value + "' using " + as.getDeclaringClass() + " at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + ite.getTargetException()); log.error(ite.getTargetException()); if (pc.isThrowErrorException()) { Throwable t = ite.getTargetException(); if (t instanceof DataModelException) { throw (DataModelException) t; } throw new DataModelException(pc, t); } } catch (Exception e) { pc.addError("Unable to set attribute '" + attributeName + "' to '" + value + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + e.getMessage()); log.error(e); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); } } /** * Adds PCDATA areas. */ public void addText(XdmParseContext pc, Object element, String text) throws DataModelException, UnsupportedTextException { if (options.ignorePcData) return; if (addText == null) { UnsupportedTextException e = new UnsupportedTextException(pc, element, text); pc.addError(e); if (pc.isThrowErrorException()) throw e; else return; } try { addText.invoke(element, new String[] { text }); } catch (InvocationTargetException ite) { pc.addError("Unable to add text '" + text + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + ite.getMessage()); log.error(ite); if (pc.isThrowErrorException()) { Throwable t = ite.getTargetException(); if (t instanceof DataModelException) { throw (DataModelException) t; } throw new DataModelException(pc, t); } } catch (Exception e) { pc.addError("Unable to add text '" + text + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + e.getMessage()); log.error(e); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); } } protected Object createElement(XdmParseContext pc, String alternateClassName, Object element, String elementName) throws DataModelException, UnsupportedElementException { try { if (alternateClassName != null) { Class cls = null; try { cls = Class.forName(alternateClassName); } catch (ClassNotFoundException e) { pc.addError("Class '" + alternateClassName + "' for element '" + elementName + "' not found at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ". " + e.getMessage()); log.error(e); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); else { NestedCreator nc = (NestedCreator) nestedCreators.get(elementName); if (nc != null) return nc.create(element); } } NestedAltClassCreator nac = (NestedAltClassCreator) nestedAltClassNameCreators.get(elementName); if (nac != null) return nac.create(element, cls); else { // check to make sure that either a storer or creator is available to ensure it's a valid tag if (nestedCreators.get(elementName) != null || nestedStorers.get(elementName) != null) return cls.newInstance(); UnsupportedElementException e = new UnsupportedElementException(this, pc, element, elementName); if (pc != null) { pc.addError(e); if (pc.isThrowErrorException()) throw e; else return null; } else return null; } } else { NestedCreator nc = (NestedCreator) nestedCreators.get(elementName); if (nc != null) return nc.create(element); } } catch (InvocationTargetException ite) { pc.addError("Could not create class '" + alternateClassName + "' for element '" + elementName + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + ite.getMessage()); log.error(ite); if (pc.isThrowErrorException()) { Throwable t = ite.getTargetException(); if (t instanceof DataModelException) { throw (DataModelException) t; } throw new DataModelException(pc, t); } } catch (Exception e) { pc.addError("Could not create class '" + alternateClassName + "' for element '" + elementName + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + e.getMessage()); log.error(e); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); } // if the element is being defined as a sub-element but has an attribute of the same name, it's a convenience attribute setter AttributeSetter as = (AttributeSetter) attributeSetters.get(elementName); if (as != null) return element; else { // see if we're trying to set a named flag as a sub-element for (Iterator i = flagsAttributeAccessors.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); AttributeAccessor accessor = (AttributeAccessor) entry.getValue(); Object returnVal = null; try { returnVal = accessor.get(pc, element); } catch (Exception e) { } if (returnVal instanceof XdmBitmaskedFlagsAttribute) return element; } UnsupportedElementException e = new UnsupportedElementException(this, pc, element, elementName); if (pc != null) { pc.addError(e); if (pc.isThrowErrorException()) throw e; else return null; } else return null; } } /** * Creates a named nested element. */ public Object createElement(XdmParseContext pc, String alternateClassName, Object element, String elementName, boolean withinCustom) throws DataModelException, UnsupportedElementException { //System.out.println("Creating: " + element.getClass().getName() + " " + elementName + " " + alternateClassName + " " + withinCustom); try { if (element instanceof CustomElementCreator && !withinCustom) return ((CustomElementCreator) element).createCustomDataModelElement(pc, this, element, elementName, alternateClassName); else return createElement(pc, alternateClassName, element, elementName); } catch (InvocationTargetException ite) { pc.addError( "Could not create class for element '" + elementName + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + ite.getMessage()); log.error(ite); if (pc.isThrowErrorException()) { Throwable t = ite.getTargetException(); if (t instanceof DataModelException) { throw (DataModelException) t; } throw new DataModelException(pc, t); } else return null; } catch (Exception e) { pc.addError( "Could not create class for element '" + elementName + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + e.getMessage()); log.error(e); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); return null; } } public void storeElement(XdmParseContext pc, Object element, Object child, String elementName, boolean withinCustom) throws DataModelException { if (elementName == null) return; NestedStorer ns = (NestedStorer) nestedStorers.get(elementName); try { if (ns == null && (!withinCustom && element instanceof CustomElementStorer)) ((CustomElementStorer) element).storeCustomDataModelElement(pc, this, element, child, elementName); else if (ns != null) ns.store(element, child); } catch (InvocationTargetException ite) { pc.addError( "Could not store data for for element '" + elementName + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + ite.getMessage()); log.error(ite); if (pc.isThrowErrorException()) { Throwable t = ite.getTargetException(); if (t instanceof DataModelException) { throw (DataModelException) t; } throw new DataModelException(pc, t); } } catch (Exception e) { pc.addError( "Could not store data for for element '" + elementName + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ": " + e.getMessage()); log.error(e); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); } } public void finalizeElementConstruction(XdmParseContext pc, Object element, String elementName) throws DataModelException { if (element instanceof ConstructionFinalizeListener) ((ConstructionFinalizeListener) element).finalizeConstruction(pc, element, elementName); } public boolean assignInstanceValue(final Object element, String key, final Object value, final boolean required) throws IllegalAccessException, InvocationTargetException, DataModelException { key = key.toLowerCase(); Method method = (Method) getAttributeSetterMethods().get(key); if (method != null) { Class[] args = method.getParameterTypes(); if (args.length == 1) { XmlDataModelSchema.AttributeSetter as = (XmlDataModelSchema.AttributeSetter) getAttributeSetters() .get(key); if (as != null) { as.set(null, element, value == null ? null : value.toString()); return true; } else { try { method.invoke(element, new Object[] { value }); return true; } catch (Exception e) { log.error( "Attempting to assign '" + key + "' to a method in '" + element.getClass() + "' but no attribute setter was found and unable to call setXXX(Object).", e); } } } else if (required && log.isErrorEnabled()) log.error("Attempting to assign '" + key + "' to a method in '" + element.getClass() + "' but the method has more than one argument."); } else if (required && log.isErrorEnabled()) log.error("Attempting to assign '" + key + "' to a method in '" + element.getClass() + "' but there is no mutator available."); // if we get to here, we couldn't find the method to assign the value to return false; } public boolean assignMapValue(final Object element, final Map map, String key, final String defaultValue) throws IllegalAccessException, InvocationTargetException, DataModelException { Object value = map.get(key); boolean required = false; if (key.endsWith("!")) { required = true; key = key.substring(0, key.length() - 1); } if (value == null) { if (required) throw new RuntimeException("Map key '" + key + "' is required but not available."); value = defaultValue; } return assignInstanceValue(element, key, value, required); } /** * Given a Map, assign its current values using appropriate accessor methods of the instance object * (using Java reflection). * * @param element The object who's mutator methods should be matched * @param map The Map for which the key is a setXXX(YYY) call [XXX is the key] and values are the YYY parameter * @param keys The names of the keys that should be assigned to the mutators of the instance object. * This may be '*' (for all keys in the map) or a comma-separated list of names. The parameter names * may optionally be followed by an '=' to indicate a default value for the parameter. Parameter * names may optionally be terminated with an '!' to indicate that they are required (an exception * is thrown if the parameter is unavailable. For example, "a,b!,c" would mean that parameter * 'a', 'b' and 'c' should be assigned using setA(), setB() and setC() if available but an * exception should be thrown if 'b' is not available as a request parameter. */ public void assignMapValues(Object element, Map map, String keys) throws IllegalAccessException, DataModelException, InvocationTargetException { if (keys.equals("*")) { for (Iterator i = map.keySet().iterator(); i.hasNext();) { String key = (String) i.next(); assignMapValue(element, map, key, null); } } else { String[] keysArray = TextUtils.getInstance().split(keys, ",", true); for (int i = 0; i < keysArray.length; i++) { String key = keysArray[i]; int defaultValuePos = key.indexOf('='); if (defaultValuePos > 0) assignMapValue(element, map, key.substring(0, defaultValuePos), key.substring(defaultValuePos + 1)); else assignMapValue(element, map, key, null); } } } /** * returns the type of a named nested element. */ public Class getElementType(String elementName) throws DataModelException { Class nt = options.getSubclassedItemClass(elementName, (Class) nestedTypes.get(elementName)); if (nt == null) { String msg = "Class " + bean.getName() + " doesn't support the nested \"" + elementName + "\" element."; throw new DataModelException(null, msg); } return nt; } /** * returns the type of a named attribute. */ public Class getAttributeType(String attributeName) throws DataModelException { Class at = options.getSubclassedItemClass(attributeName, (Class) attributeTypes.get(attributeName)); if (at == null) { String msg = "Class " + bean.getName() + " doesn't support the \"" + attributeName + "\" attribute."; throw new DataModelException(null, msg); } return at; } /** * Does the introspected class ignore PCDATA */ public boolean isIgnoreText() { return options.ignorePcData; } /** * Does the introspected class support PCDATA? */ public boolean supportsCharacters() { return !options.ignorePcData && addText != null; } /** * Does the introspected class ignore PCDATA */ public boolean hasAddTextMethod() { return addText != null; } /** * Return all attribues supported by the introspected class. */ public Set getAttributes() { return attributeSetters.keySet(); } /** * Return all nested elements supported by the introspected class. */ public Map getNestedElements() { return nestedTypes; } /** * Create a proper implementation of AttributeAccessor for the given * attribute type. */ private AttributeAccessor createAttributeAccessor(final Method m, final String attrName, final Class arg) { AttributeAccessor result = new AttributeAccessor() { public Object get(XdmParseContext pc, Object parent) throws InvocationTargetException, IllegalAccessException { return m.invoke(parent, null); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; if (XdmBitmaskedFlagsAttribute.class.isAssignableFrom(arg)) flagsAttributeAccessors.put(attrName, result); return result; } /** * Create a proper implementation of AttributeSetter for the given * attribute type. */ private AttributeSetter createAttributeSetter(final Method m, final String attrName, final Class arg) { if (java.lang.String[].class.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Object[] { TextUtils.getInstance().split(value, ",", true) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.String.class.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new String[] { value }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.Character.class.equals(arg) || java.lang.Character.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Character[] { new Character(value.charAt(0)) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.Byte.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Byte[] { new Byte(value) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.Short.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Short[] { new Short(value) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.Integer.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Integer[] { new Integer(value) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.Long.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Long[] { new Long(value) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.Float.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Float[] { new Float(value) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.lang.Double.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Double[] { new Double(value) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } // boolean gets an extra treatment, because we have a nice method else if (java.lang.Boolean.class.equals(arg) || java.lang.Boolean.TYPE.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new Boolean[] { new Boolean(TextUtils.getInstance().toBoolean(value)) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } // Class doesn't have a String constructor but a decent factory method else if (java.lang.Class.class.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { try { m.invoke(parent, new Class[] { Class.forName(value) }); } catch (ClassNotFoundException ce) { if (pc != null) { DataModelException dme = new DataModelException(pc, ce); pc.addError(dme); if (pc.isThrowErrorException()) throw dme; } else log.error(ce); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (java.io.File.class.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { // resolve relative paths through DataModel m.invoke(parent, new File[] { pc != null ? pc.resolveFile(value) : new File(value) }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (RedirectValueSource.class.isAssignableFrom(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { TextUtils textUtils = TextUtils.getInstance(); ValueSource vs = ValueSources.getInstance().getValueSourceOrStatic(value); if (vs == null) { // better to throw an error here since if there are objects which are based on null/non-null // value of the value source, it is easier to debug pc.addError("Unable to find ValueSource '" + value + "' to wrap in RedirectValueSource at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ". Valid value sources are: " + textUtils.join(ValueSources.getInstance().getAllValueSourceIdentifiers(), ", ")); } try { RedirectValueSource redirectValueSource = (RedirectValueSource) arg.newInstance(); redirectValueSource.setValueSource(vs); m.invoke(parent, new RedirectValueSource[] { redirectValueSource }); } catch (InstantiationException e) { pc.addError("Unable to create RedirectValueSource for '" + value + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ". Valid value sources are: " + textUtils.join(ValueSources.getInstance().getAllValueSourceIdentifiers(), ", ")); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (ValueSource.class.equals(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException { TextUtils textUtils = TextUtils.getInstance(); ValueSource vs = ValueSources.getInstance().getValueSourceOrStatic(value); if (vs == null) { // better to throw an error here since if there are objects which are based on null/non-null // value of the value source, it is easier to debug if (pc != null) pc.addError("Unable to create ValueSource for '" + value + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + ". Valid value sources are: " + textUtils .join(ValueSources.getInstance().getAllValueSourceIdentifiers(), ", ")); else log.error("Unable to create ValueSource for '" + value + ". Valid value sources are: " + textUtils.join(ValueSources.getInstance().getAllValueSourceIdentifiers(), ", ")); } m.invoke(parent, new ValueSource[] { vs }); } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (Command.class.isAssignableFrom(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { try { m.invoke(parent, new Command[] { Commands.getInstance().getCommand(value) }); } catch (CommandNotFoundException e) { if (pc != null) { pc.addError("Unable to create Command for '" + value + "' at " + pc.getLocator().getSystemId() + " line " + pc.getLocator().getLineNumber() + "."); if (pc.isThrowErrorException()) throw new DataModelException(pc, e); } else log.error("Unable to create Command for '" + value + "'", e); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (XdmEnumeratedAttribute.class.isAssignableFrom(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { try { XdmEnumeratedAttribute ea = (XdmEnumeratedAttribute) arg.newInstance(); ea.setValue(pc, parent, attrName, value); m.invoke(parent, new XdmEnumeratedAttribute[] { ea }); } catch (InstantiationException ie) { pc.addError(ie); if (pc.isThrowErrorException()) throw new DataModelException(pc, ie); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (XdmBitmaskedFlagsAttribute.class.isAssignableFrom(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { try { XdmBitmaskedFlagsAttribute bfa; NestedCreator creator = (NestedCreator) nestedCreators.get(attrName); if (creator != null) bfa = (XdmBitmaskedFlagsAttribute) creator.create(parent); else bfa = (XdmBitmaskedFlagsAttribute) arg.newInstance(); bfa.setValue(pc, parent, attrName, value); m.invoke(parent, new XdmBitmaskedFlagsAttribute[] { bfa }); } catch (InstantiationException ie) { pc.addError(ie); if (pc.isThrowErrorException()) throw new DataModelException(pc, ie); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (Locale.class.isAssignableFrom(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { String[] items = TextUtils.getInstance().split(value, ",", true); switch (items.length) { case 1: m.invoke(parent, new Locale[] { new Locale(items[0], "") }); break; case 2: m.invoke(parent, new Locale[] { new Locale(items[1], items[2]) }); break; case 3: m.invoke(parent, new Locale[] { new Locale(items[1], items[2], items[3]) }); break; case 4: if (pc != null) throw new DataModelException(pc, "Too many items in Locale constructor."); else log.error("Too many items in Locale constructor: " + value); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (ResourceBundle.class.isAssignableFrom(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { String[] items = TextUtils.getInstance().split(value, ",", true); switch (items.length) { case 1: m.invoke(parent, new ResourceBundle[] { ResourceBundle.getBundle(items[0]) }); break; case 2: m.invoke(parent, new ResourceBundle[] { ResourceBundle.getBundle(items[0], new Locale(items[1], Locale.US.getCountry())) }); break; case 3: m.invoke(parent, new ResourceBundle[] { ResourceBundle.getBundle(items[0], new Locale(items[1], items[2])) }); break; case 4: m.invoke(parent, new ResourceBundle[] { ResourceBundle.getBundle(items[0], new Locale(items[1], items[2], items[3])) }); default: if (pc != null) throw new DataModelException(pc, "Too many items in ResourceBundle constructor."); else log.error("Too many items in Locale constructor: " + value); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else if (Properties.class.isAssignableFrom(arg)) { return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { // when specifying properties the following are valid // <xxx properties="abc.properties"> <!-- load this property file, throw exception if not found --> // <xxx properties="abc.properties:optional"> <!-- load this property file, no exception if not found --> // <xxx properties="/a/b/abc.properties,/x/y/def.properties"> <!-- load the first property file found, throw exception if none found --> // <xxx properties="/a/b/abc.properties,/x/y/def.properties:optional"> <!-- load the first property file found, no exception if none found --> final TextUtils textUtils = TextUtils.getInstance(); final String[] options = textUtils.split(value, ":", true); final String[] fileNames = textUtils.split(value, ",", true); final Properties properties; switch (options.length) { case 1: properties = PropertiesLoader.loadProperties(fileNames, true, false); m.invoke(parent, new Properties[] { properties }); break; case 2: properties = PropertiesLoader.loadProperties(fileNames, options[1].equals("optional") ? false : true, false); if (properties != null) m.invoke(parent, new Properties[] { properties }); break; default: if (pc != null) throw new DataModelException(pc, "Don't know how to get properties from PropertiesLoader: " + value); else log.error("Don't know how to get properties from PropertiesLoader:" + value); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } else { // worst case. look for a public String constructor and use it try { final Constructor c = arg.getConstructor(new Class[] { java.lang.String.class }); return new AttributeSetter() { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException { try { Object attribute = c.newInstance(new String[] { value }); m.invoke(parent, new Object[] { attribute }); } catch (InstantiationException ie) { if (pc != null) { pc.addError(ie); if (pc.isThrowErrorException()) throw new DataModelException(pc, ie); } else log.error(ie); } } public boolean isInherited() { return !m.getDeclaringClass().equals(bean); } public Class getDeclaringClass() { return m.getDeclaringClass(); } }; } catch (NoSuchMethodException nme) { } } return null; } public class PropertyNames { private String primaryName; private Set aliases = new HashSet(); public PropertyNames(String methodName, String prefix) { int start = prefix.length(); String nameMinusPrefix = methodName.substring(start); String xmlNodeName = TextUtils.getInstance().javaIdentifierToXmlNodeName(nameMinusPrefix); primaryName = xmlNodeName.toLowerCase(); aliases.add(primaryName); if (options.isGenerateAutoAliases() && !xmlNodeName.equals(nameMinusPrefix)) aliases.add(nameMinusPrefix.toLowerCase()); // map all the property names to this object for (Iterator i = aliases.iterator(); i.hasNext();) { String name = (String) i.next(); propertyNames.put(name, this); } Map customAliasesMap = options.getAliases(); String[] allNames = getAllNames(); for (int i = 0; i < allNames.length; i++) { String name = allNames[i]; Set customAliases = (Set) customAliasesMap.get(name); if (customAliases != null) aliases.addAll(customAliases); } options.setPropertyNames(this); } public String getPrimaryName() { return primaryName; } public boolean isPrimaryName(String name) { return primaryName.equals(name); } public Set getAliases() { return aliases; } public String[] getAllNames() { return (String[]) aliases.toArray(new String[aliases.size()]); } /** * Returns a string representation of the object. In general, the * <code>toString</code> method returns a string that * "textually represents" this object. The result should * be a concise but informative representation that is easy for a * person to read. * It is recommended that all subclasses override this method. * <p/> * The <code>toString</code> method for class <code>Object</code> * returns a string consisting of the name of the class of which the * object is an instance, the at-sign character `<code>@</code>', and * the unsigned hexadecimal representation of the hash code of the * object. In other words, this method returns a string equal to the * value of: * <blockquote> * <pre> * getClass().getName() + '@' + Integer.toHexString(hashCode()) * </pre></blockquote> * * @return a string representation of the object. */ public String toString() { String output = primaryName + ": ["; String[] aliases = getAllNames(); for (int i = 0; i < aliases.length; i++) { if (0 != i) output += ", "; output += aliases[i]; } output += "]"; return output; } } private String[] getPropertyNames(String methodName, String prefix) { PropertyNames result = new PropertyNames(methodName, prefix); return result.getAllNames(); } public interface NestedCreator { public Object create(Object parent) throws InvocationTargetException, IllegalAccessException, InstantiationException; public boolean isInherited(); public Class getDeclaringClass(); } public interface NestedAltClassCreator { public Object create(Object parent, Class cls) throws InvocationTargetException, IllegalAccessException, InstantiationException; public boolean isInherited(); public Class getDeclaringClass(); } public interface NestedStorer { public void store(Object parent, Object child) throws InvocationTargetException, IllegalAccessException, InstantiationException; public boolean isInherited(); public Class getDeclaringClass(); } public interface AttributeSetter { public void set(XdmParseContext pc, Object parent, String value) throws InvocationTargetException, IllegalAccessException, DataModelException; public boolean isInherited(); public Class getDeclaringClass(); } public interface AttributeAccessor { public Object get(XdmParseContext pc, Object parent) throws InvocationTargetException, IllegalAccessException, DataModelException; public boolean isInherited(); public Class getDeclaringClass(); } }