0001: /*
0002: Copyright (c) 2007, Dennis M. Sosnoski
0003: All rights reserved.
0004:
0005: Redistribution and use in source and binary forms, with or without modification,
0006: are permitted provided that the following conditions are met:
0007:
0008: * Redistributions of source code must retain the above copyright notice, this
0009: list of conditions and the following disclaimer.
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: this list of conditions and the following disclaimer in the documentation
0012: and/or other materials provided with the distribution.
0013: * Neither the name of JiBX nor the names of its contributors may be used
0014: to endorse or promote products derived from this software without specific
0015: prior written permission.
0016:
0017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
0018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
0021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
0024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0027: */
0028:
0029: package org.jibx.binding.generator;
0030:
0031: import java.io.File;
0032: import java.io.FileOutputStream;
0033: import java.io.IOException;
0034: import java.util.ArrayList;
0035: import java.util.HashMap;
0036: import java.util.HashSet;
0037: import java.util.Iterator;
0038: import java.util.List;
0039: import java.util.Map;
0040: import java.util.Set;
0041:
0042: import org.jibx.binding.model.BindingElement;
0043: import org.jibx.binding.model.CollectionElement;
0044: import org.jibx.binding.model.IClass;
0045: import org.jibx.binding.model.IncludeElement;
0046: import org.jibx.binding.model.MappingElement;
0047: import org.jibx.binding.model.NestingElementBase;
0048: import org.jibx.binding.model.StructureElement;
0049: import org.jibx.binding.model.StructureElementBase;
0050: import org.jibx.binding.model.ValueElement;
0051: import org.jibx.runtime.BindingDirectory;
0052: import org.jibx.runtime.IBindingFactory;
0053: import org.jibx.runtime.IMarshallable;
0054: import org.jibx.runtime.IMarshallingContext;
0055: import org.jibx.runtime.JiBXException;
0056: import org.jibx.runtime.QName;
0057: import org.jibx.schema.codegen.ReferenceCountMap;
0058: import org.jibx.util.InsertionOrderedMap;
0059: import org.jibx.util.Types;
0060:
0061: /**
0062: * Binding generator implementation.
0063: */
0064: public class BindingGenerator {
0065: /** Binding generation customizations. */
0066: private final GlobalCustom m_global;
0067:
0068: /** Set of class names to be included. */
0069: private final Set m_includeSet;
0070:
0071: /** Set of class names to be ignored. */
0072: private final Set m_ignoreSet;
0073:
0074: /** Set of class names referenced directly as members of other classes. */
0075: private final Set m_directSet;
0076:
0077: /** Set of class names subclassed by other classes in binding. */
0078: private final Set m_super Set;
0079:
0080: /** Map from fully-qualified class name to mapping details. */
0081: private final Map m_mappingDetailsMap;
0082:
0083: /** Map from namespace URI to binding holder. */
0084: private final InsertionOrderedMap m_uriBindingMap;
0085:
0086: /** Target package for binding code generation. */
0087: private String m_targetPackage;
0088:
0089: /**
0090: * Create a generator based on a particular set of customizations.
0091: *
0092: * @param glob
0093: */
0094: public BindingGenerator(GlobalCustom glob) {
0095: m_global = glob;
0096: m_includeSet = new HashSet();
0097: m_ignoreSet = new HashSet();
0098: m_directSet = new HashSet();
0099: m_super Set = new HashSet();
0100: m_mappingDetailsMap = new HashMap();
0101: m_uriBindingMap = new InsertionOrderedMap();
0102: }
0103:
0104: /**
0105: * Check if a class represents a simple value.
0106: * TODO: implement ClassCustom hooks for this purpose
0107: *
0108: * @param type fully qualified class name
0109: * @return <code>true</code> if simple value, <code>false</code> if not
0110: */
0111: public boolean isValueClass(String type) {
0112: ClassCustom clas = m_global.forceClassCustomization(type);
0113: IClass sinfo = clas.getClassInformation().getSuperClass();
0114: if (sinfo != null && "java.lang.Enum".equals(sinfo.getName())) {
0115: return true;
0116: } else {
0117: return false;
0118: }
0119: }
0120:
0121: /**
0122: * Check if a class needs to be included in the binding. This checks all
0123: * members of the class and if necessary superclasses, returning
0124: * <code>true</code> if any member is ultimately found with a simple value.
0125: *
0126: * @param type fully qualified class name
0127: * @return <code>true</code> if class to be included in binding,
0128: * <code>false</code> if it should be skipped
0129: */
0130: public boolean checkInclude(String type) {
0131: if (m_includeSet.contains(type)) {
0132: return true;
0133: } else if (m_ignoreSet.contains(type)) {
0134: return false;
0135: } else {
0136:
0137: // check if any members are to be included
0138: boolean include = false;
0139: ClassCustom clas = m_global.forceClassCustomization(type);
0140: for (Iterator iter = clas.getMembers().iterator(); iter
0141: .hasNext();) {
0142: MemberCustom memb = (MemberCustom) iter.next();
0143: String mtype = memb.isCollection() ? memb.getItemType()
0144: : memb.getWorkingType();
0145: if (Types.isSimpleValue(mtype) || isValueClass(mtype)
0146: || !m_global.isKnownMapping(mtype)
0147: || checkInclude(mtype)) {
0148: include = true;
0149: break;
0150: }
0151: }
0152: if (!include) {
0153:
0154: // force superclass handling if appropriate
0155: if (clas.getMembers().size() > 0 && clas.isUseSuper()) {
0156: String stype = clas.getClassInformation()
0157: .getSuperClass().getName();
0158: if (!"java.lang.Object".equals(stype)
0159: && checkInclude(stype)) {
0160: include = true;
0161: }
0162: }
0163: }
0164: if (include) {
0165: m_includeSet.add(type);
0166: } else {
0167: m_ignoreSet.add(type);
0168: }
0169: return include;
0170: }
0171: }
0172:
0173: /**
0174: * Expand all references from a class. This checks the types of all members
0175: * of the class, counting references and calling itself recursively for any
0176: * types which are not primitives and not java.* or javax.* classes. It also
0177: * expands the superclass, if specified by the class customizations.
0178: *
0179: * @param type fully qualified class name
0180: * @param refmap reference count map
0181: */
0182: public void expandReferences(String type, ReferenceCountMap refmap) {
0183: if (checkInclude(type)) {
0184:
0185: // expand all references from members
0186: ClassCustom clas = m_global.forceClassCustomization(type);
0187: for (Iterator iter = clas.getMembers().iterator(); iter
0188: .hasNext();) {
0189: MemberCustom memb = (MemberCustom) iter.next();
0190: String mtype = memb.isCollection() ? memb.getItemType()
0191: : memb.getWorkingType();
0192: if (!Types.isSimpleValue(mtype) && !isValueClass(mtype)
0193: && !m_global.isKnownMapping(mtype)) {
0194: if ((mtype.startsWith("java."))
0195: || (mtype.startsWith("javax."))) {
0196: throw new IllegalStateException(
0197: "No way to handle type '" + mtype
0198: + '\'');
0199: } else if (checkInclude(mtype)) {
0200: if (refmap.incrementCount(mtype) <= 1) {
0201: expandReferences(mtype, refmap);
0202: }
0203: m_directSet.add(mtype);
0204: }
0205: }
0206: }
0207:
0208: // force superclass handling if appropriate
0209: if (clas.getMembers().size() > 0 && clas.isUseSuper()) {
0210: String stype = clas.getClassInformation()
0211: .getSuperClass().getName();
0212: if (!"java.lang.Object".equals(stype)
0213: && !Types.isSimpleValue(stype)
0214: && !isValueClass(stype)
0215: && !m_global.isKnownMapping(stype)
0216: && checkInclude(stype)) {
0217: if (refmap.incrementCount(stype) <= 1) {
0218: expandReferences(stype, refmap);
0219: }
0220: m_super Set.add(stype);
0221: }
0222: }
0223: }
0224: }
0225:
0226: /**
0227: * Set creation information for structure binding component. This includes
0228: * the declared type, as well as the create type and factory method.
0229: *
0230: * @param memb
0231: * @param struct
0232: */
0233: private void setTypes(MemberCustom memb, StructureElementBase struct) {
0234: String type = memb.getActualType();
0235: if (type != null && !type.equals(memb.getStatedType())) {
0236: struct.setDeclaredType(type);
0237: }
0238: struct.setCreateType(memb.getCreateType());
0239: struct.setFactoryName(memb.getFactoryMethod());
0240: }
0241:
0242: /**
0243: * General object comparison method. Don't know why Sun hasn't seen fit to
0244: * include this somewhere, but at least it's easy to write (over and over
0245: * again).
0246: *
0247: * @param a first object to be compared
0248: * @param b second object to be compared
0249: * @return <code>true</code> if both objects are <code>null</code>, or if
0250: * <code>a.equals(b)</code>; <code>false</code> otherwise
0251: */
0252: public static boolean isEqual(Object a, Object b) {
0253: return (a == null) ? b == null : a.equals(b);
0254: }
0255:
0256: /**
0257: * Check for dependency on another binding.
0258: *
0259: * @param uri namespace for binding of referenced component
0260: * @param hold binding holder
0261: */
0262: private void checkDependency(String uri, BindingHolder hold) {
0263: if (!isEqual(uri, hold.getNamespace())) {
0264: BindingHolder tohold = findBinding(uri);
0265: hold.addReference(tohold);
0266: }
0267: }
0268:
0269: /**
0270: * Define the details of a collection binding. Collection bindings may be
0271: * empty (in the case where the item type is directly mapped), have a
0272: * <value> child element, or have either a mapping reference or direct
0273: * definition <structure> child element. This determines the appropriate
0274: * form based on the item type.
0275: *
0276: * @param itype
0277: * @param iname
0278: * @param coll
0279: * @param hold
0280: */
0281: public void defineCollection(String itype, String iname,
0282: CollectionElement coll, BindingHolder hold) {
0283:
0284: // check item type handling
0285: MappingDetail detail = (MappingDetail) m_mappingDetailsMap
0286: .get(itype);
0287: if (detail != null) {
0288: ClassCustom icust = m_global.getClassCustomization(itype);
0289: if (detail.isUseAbstract() && !detail.isExtended()) {
0290:
0291: // add reference to appropriate binding
0292: QName qname = detail.getTypeQName();
0293: hold.addReference(findBinding(qname.getUri()));
0294:
0295: // add <structure> with map-as for abstract mapping
0296: if (iname == null) {
0297: iname = icust.getElementName();
0298: }
0299: StructureElement struct = new StructureElement();
0300: if (qname == null) {
0301: struct.setMapAsName(itype);
0302: } else {
0303:
0304: // set the type reference
0305: struct.setMapAsQName(qname);
0306:
0307: // check dependency on other binding
0308: checkDependency(qname.getUri(), hold);
0309:
0310: }
0311: struct.setName(iname);
0312: struct.setCreateType(icust.getCreateType());
0313: struct.setFactoryName(icust.getFactoryMethod());
0314: coll.addChild(struct);
0315:
0316: } else {
0317:
0318: // just set item-type for concrete mapping
0319: coll.setItemTypeName(itype);
0320:
0321: }
0322:
0323: } else if (m_global.isKnownMapping(itype)) {
0324:
0325: // just set item-type for known mapping
0326: coll.setItemTypeName(itype);
0327:
0328: } else if (Types.isSimpleValue(itype) || isValueClass(itype)) {
0329:
0330: // add <value> for simple type
0331: ValueElement value = new ValueElement();
0332: value.setName(iname);
0333: value.setDeclaredType(itype);
0334: coll.addChild(value);
0335:
0336: } else {
0337:
0338: // embed structure definition within the collection
0339: StructureElement struct = new StructureElement();
0340: struct.setDeclaredType(itype);
0341: ClassCustom icust = m_global.getClassCustomization(itype);
0342: struct.setCreateType(icust.getCreateType());
0343: struct.setFactoryName(icust.getFactoryMethod());
0344: if (iname == null) {
0345: iname = icust.getElementName();
0346: }
0347: struct.setName(iname);
0348: addMemberBindings(icust, struct, hold);
0349: coll.addChild(struct);
0350:
0351: }
0352: }
0353:
0354: /**
0355: * Add binding details for all members of a class.
0356: *
0357: * @param cust class customization information
0358: * @param parent containing binding component
0359: * @param hold binding holder
0360: */
0361: private void addMemberBindings(ClassCustom cust,
0362: NestingElementBase parent, BindingHolder hold) {
0363:
0364: // generate binding component for each member of class
0365: for (Iterator iter = cust.getMembers().iterator(); iter
0366: .hasNext();) {
0367: MemberCustom memb = (MemberCustom) iter.next();
0368: if (memb.isCollection()) {
0369:
0370: // create collection element for field or property
0371: CollectionElement coll = new CollectionElement();
0372: if (memb.getFieldName() == null) {
0373: coll.setGetName(memb.getGetName());
0374: coll.setSetName(memb.getSetName());
0375: } else {
0376: coll.setFieldName(memb.getFieldName());
0377: }
0378: setTypes(memb, coll);
0379:
0380: // set the element name, if defined
0381: String name = memb.getXmlName();
0382: if (name != null) {
0383: coll.setName(name);
0384: }
0385:
0386: // check for optional value
0387: if (!memb.isRequired()) {
0388: coll.setUsageName("optional");
0389: }
0390:
0391: // check for type defined
0392: String itype = memb.getItemType();
0393: if (itype != null) {
0394:
0395: // set name to be used for items in collection
0396: String iname = memb.getItemName();
0397: if (iname == null) {
0398:
0399: // check for collection name that looks like a plural
0400: if (name != null && name.endsWith("s")) {
0401:
0402: // convert plural ending to singular
0403: if (name.endsWith("ies")) {
0404: iname = name.substring(0,
0405: name.length() - 3)
0406: + "y";
0407: } else {
0408: iname = name.substring(0,
0409: name.length() - 1);
0410: }
0411:
0412: } else {
0413:
0414: // use name based on item type
0415: String simple = itype;
0416: int split = simple.lastIndexOf('.');
0417: if (split >= 0) {
0418: simple = simple.substring(0, split);
0419: }
0420: iname = cust.convertName(simple);
0421:
0422: }
0423: }
0424:
0425: // define the collection details
0426: defineCollection(itype, iname, coll, hold);
0427:
0428: }
0429: parent.addChild(coll);
0430:
0431: } else {
0432:
0433: // check whether value or structure
0434: String wtype = memb.getWorkingType();
0435: if (Types.isSimpleValue(wtype) || isValueClass(wtype)) {
0436:
0437: // add <value> for simple type
0438: ValueElement value = new ValueElement();
0439: if (memb.getFieldName() == null) {
0440: value.setGetName(memb.getGetName());
0441: value.setSetName(memb.getSetName());
0442: } else {
0443: value.setFieldName(memb.getFieldName());
0444: }
0445: value.setName(memb.getXmlName());
0446:
0447: // check for optional value
0448: if (!memb.isRequired()) {
0449: value.setUsageName("optional");
0450: }
0451:
0452: // configure added property values
0453: int style = memb.getStyle();
0454: if (style == NestingBase.ATTRIBUTE_VALUE_STYLE) {
0455: value.setStyleName("attribute");
0456: } else if (style == NestingBase.ELEMENT_VALUE_STYLE) {
0457: value.setStyleName("element");
0458: } else {
0459: value.setStyleName("text");
0460: }
0461: if (memb.getActualType() != null) {
0462: value.setDeclaredType(memb.getActualType());
0463: }
0464: parent.addChild(value);
0465:
0466: } else {
0467:
0468: // create <structure> with name and access information
0469: StructureElement struct = new StructureElement();
0470: if (memb.getFieldName() == null) {
0471: struct.setGetName(memb.getGetName());
0472: struct.setSetName(memb.getSetName());
0473: } else {
0474: struct.setFieldName(memb.getFieldName());
0475: }
0476: setTypes(memb, struct);
0477:
0478: // check for optional value
0479: if (!memb.isRequired()) {
0480: struct.setUsageName("optional");
0481: }
0482:
0483: // set details based on class handling
0484: MappingDetail detail = (MappingDetail) m_mappingDetailsMap
0485: .get(wtype);
0486: if (detail != null) {
0487: if (detail.isUseAbstract()
0488: && !detail.isExtended()) {
0489:
0490: // add 'map-as' and 'name' for abstract mapping
0491: QName qname = detail.getTypeQName();
0492: if (qname == null) {
0493: struct.setMapAsName(wtype);
0494: } else {
0495:
0496: // set type name reference from structure
0497: struct.setMapAsQName(qname);
0498:
0499: // create binding dependency, if needed
0500: checkDependency(qname.getUri(), hold);
0501: }
0502: struct.setName(memb.getXmlName());
0503:
0504: } else {
0505:
0506: // create binding dependency, if needed
0507: checkDependency(detail.getElementQName()
0508: .getUri(), hold);
0509:
0510: }
0511: } else if (!m_global.isKnownMapping(wtype)) {
0512:
0513: // check for type needed
0514: if (memb.getActualType() != null) {
0515: struct
0516: .setDeclaredType(memb
0517: .getActualType());
0518: }
0519: if (memb.getCreateType() != null) {
0520: struct.setCreateType(memb.getCreateType());
0521: }
0522: if (memb.getFactoryMethod() != null) {
0523: struct.setFactoryName(memb
0524: .getFactoryMethod());
0525: }
0526:
0527: // add a name if an optional value
0528: if (!memb.isRequired()) {
0529: struct.setName(memb.getXmlName());
0530: }
0531:
0532: // handle inline structure definition
0533: ClassCustom icust = m_global
0534: .getClassCustomization(memb
0535: .getWorkingType());
0536: addMemberBindings(icust, struct, hold);
0537:
0538: }
0539: parent.addChild(struct);
0540:
0541: }
0542: }
0543: }
0544: }
0545:
0546: /**
0547: * Add the <mapping> definition for a class to a binding. This creates
0548: * either an abstract mapping with a type name, or a concrete mapping with
0549: * an element name, as determined by the passed-in mapping information. If
0550: * the class is a concrete mapping that extends or implements another class
0551: * with an anonymous abstract mapping, the created <mapping> will extend
0552: * that base mapping.
0553: * TODO: type substitution requires extending the binding definition
0554: *
0555: * @param type fully qualified class name
0556: * @param detail mapping details
0557: */
0558: private void addMapping(String type, MappingDetail detail) {
0559:
0560: // create the basic mapping structure
0561: ClassCustom cust = m_global.forceClassCustomization(type);
0562: MappingElement mapping = new MappingElement();
0563: mapping.setClassName(type);
0564: mapping.setCreateType(cust.getCreateType());
0565: mapping.setFactoryName(cust.getFactoryMethod());
0566:
0567: // check type of definition required
0568: BindingHolder hold;
0569: if (detail.isUseAbstract()) {
0570:
0571: // create abstract mapping
0572: mapping.setAbstract(true);
0573: QName qname = detail.getTypeQName();
0574: mapping.setTypeQName(qname);
0575: hold = findBinding(qname.getUri());
0576:
0577: } else {
0578:
0579: // concrete mapping, just need simple name (namespace from binding)
0580: QName qname = detail.getElementQName();
0581: mapping.setName(qname.getName());
0582: hold = findBinding(qname.getUri());
0583:
0584: }
0585:
0586: // check for superclass mapping to be extended (do this first for
0587: // compatibility with schema type extension)
0588: IClass clas = cust.getClassInformation();
0589: StructureElement struct = null;
0590: String ptype = detail.getExtendsType();
0591: if (ptype != null) {
0592:
0593: // create reference to parent mapping
0594: struct = new StructureElement();
0595: MappingDetail pdetail = (MappingDetail) m_mappingDetailsMap
0596: .get(ptype);
0597: if (pdetail.isUseAbstract()) {
0598:
0599: // reference abstract mapped superclass
0600: QName qname = pdetail.getTypeQName();
0601: if (qname == null) {
0602: throw new IllegalStateException(
0603: "Internal error: unimplemented case");
0604: // anonymous
0605: // struct.setMapAsName(ptype);
0606: } else {
0607:
0608: // set extension base type and reference
0609: mapping.setExtendsName(ptype);
0610: struct.setMapAsQName(qname);
0611:
0612: // create binding dependency, if needed
0613: checkDependency(qname.getUri(), hold);
0614: }
0615:
0616: } else {
0617:
0618: // reference concrete mapped superclass
0619: struct.setMapAsName(ptype);
0620: mapping.setExtendsName(ptype);
0621:
0622: }
0623:
0624: } else {
0625:
0626: // not extending a base mapping, check if need to include superclass
0627: IClass parent = clas.getSuperClass();
0628: if (cust.isUseSuper() && parent != null) {
0629: ptype = parent.getName();
0630: ClassCustom scust = m_global
0631: .getClassCustomization(ptype);
0632: if (scust != null && scust.getMembers().size() > 0) {
0633: MappingDetail pdetail = (MappingDetail) m_mappingDetailsMap
0634: .get(ptype);
0635: if (pdetail == null) {
0636:
0637: // handle parent details inline, if needed
0638: struct = new StructureElement();
0639: struct.setDeclaredType(ptype);
0640: addMemberBindings(scust, struct, hold);
0641: if (struct.children().size() == 0) {
0642: struct = null;
0643: }
0644:
0645: } else {
0646: throw new IllegalStateException(
0647: "Internal error: unsupported combination");
0648: }
0649: }
0650: if (struct == null) {
0651:
0652: // check for interface with abstract mapping to extend
0653: if (!detail.isUseAbstract()
0654: && mapping.getExtendsName() == null) {
0655: String[] intfs = parent.getInterfaces();
0656: for (int i = 0; i < intfs.length; i++) {
0657: String intf = intfs[i];
0658: MappingDetail idetail = (MappingDetail) m_mappingDetailsMap
0659: .get(intf);
0660: if (idetail != null
0661: && idetail.isUseAbstract()
0662: && idetail.getTypeQName() == null) {
0663:
0664: // reference abstract mapped interface
0665: struct = new StructureElement();
0666: struct.setMapAsName(intf);
0667: mapping.setExtendsName(intf);
0668: break;
0669: }
0670: }
0671: }
0672:
0673: }
0674: }
0675: }
0676: if (struct != null) {
0677: mapping.addChild(struct);
0678: }
0679:
0680: // add all details of class member handling to binding
0681: addMemberBindings(cust, mapping, hold);
0682: hold.getBinding().addTopChild(mapping);
0683: if (mapping.isAbstract()) {
0684:
0685: // check for both abstract and concrete mapping needed
0686: detail.setAbstractMapping(mapping);
0687: if (detail.isUseConcrete()) {
0688:
0689: // reference abstract mapping from concrete mapping
0690: mapping = new MappingElement();
0691: mapping.setClassName(type);
0692: mapping.setCreateType(cust.getCreateType());
0693: mapping.setFactoryName(cust.getFactoryMethod());
0694: mapping.setName(cust.getElementName());
0695:
0696: // set create type for concrete mapping of abstract class
0697: if (clas.isAbstract()) {
0698: mapping.setAbstract(true);
0699: }
0700:
0701: // define content as structure reference to abstract mapping
0702: struct = new StructureElement();
0703: QName qname = detail.getTypeQName();
0704: struct.setMapAsQName(qname);
0705: mapping.addChild(struct);
0706: hold = findBinding(qname.getUri());
0707: hold.getBinding().addTopChild(mapping);
0708: detail.setConcreteMapping(mapping);
0709: }
0710:
0711: } else {
0712: detail.setConcreteMapping(mapping);
0713: }
0714: }
0715:
0716: /**
0717: * Find the binding to be used for a particular namespace. If this is the
0718: * first time a particular namespace was requested, a new binding will be
0719: * created for that namespace and returned.
0720: *
0721: * @param uri namespace URI (<code>null</code> if no namespace)
0722: * @return binding holder
0723: */
0724: public BindingHolder findBinding(String uri) {
0725: BindingHolder hold = (BindingHolder) m_uriBindingMap.get(uri);
0726: if (hold == null) {
0727: hold = new BindingHolder(uri, m_uriBindingMap.size() + 1);
0728: m_uriBindingMap.put(uri, hold);
0729: BindingElement binding = hold.getBinding();
0730: binding.setForceClasses(m_global.isForceClasses());
0731: binding.setTrackSource(m_global.isTrackSource());
0732: binding.setAddConstructors(m_global.isAddConstructors());
0733: binding.setInBinding(m_global.isInput());
0734: binding.setOutBinding(m_global.isOutput());
0735: }
0736: return hold;
0737: }
0738:
0739: /**
0740: * Add the details for mapping a class.
0741: *
0742: * @param abstr force abstract mapping flag
0743: * @param type fully-qualified class name
0744: * @return mapping details
0745: */
0746: private MappingDetail addMappingDetails(Boolean abstr, String type) {
0747:
0748: // check for existing detail
0749: MappingDetail detail = (MappingDetail) m_mappingDetailsMap
0750: .get(type);
0751: if (detail == null) {
0752:
0753: // check if superclass is base for extension
0754: ClassCustom cust = m_global.getClassCustomization(type);
0755: String stype = null;
0756: IClass sclas = cust.getClassInformation().getSuperClass();
0757: while (sclas != null) {
0758: if (m_directSet.contains(sclas.getName())) {
0759: stype = sclas.getName();
0760: Boolean isabs = Boolean.valueOf(sclas.isAbstract());
0761: MappingDetail sdetail = addMappingDetails(isabs,
0762: stype);
0763: sdetail.setExtended(true);
0764: break;
0765: } else {
0766: sclas = sclas.getSuperClass();
0767: }
0768: }
0769:
0770: // create mapping details as specified
0771: boolean abs = (abstr == null) ? cust.isMapAbstract()
0772: : abstr.booleanValue();
0773: detail = new MappingDetail(cust.getTypeQName(), cust
0774: .getElementQName(), stype);
0775: if (abs || !cust.isConcrete()) {
0776: detail.setUseAbstract(true);
0777: } else {
0778: detail.setUseConcrete(true);
0779: }
0780: m_mappingDetailsMap.put(type, detail);
0781:
0782: // force concrete mapping if extension or base for extension
0783: if (abs && (sclas != null || m_super Set.contains(type))) {
0784: detail.setUseConcrete(true);
0785: }
0786:
0787: // check if package usable as target for binding code
0788: if (m_targetPackage == null
0789: && cust.getClassInformation().isModifiable()) {
0790: m_targetPackage = ((PackageCustom) cust.getParent())
0791: .getName();
0792: }
0793:
0794: } else if (abstr != null) {
0795:
0796: // mapping detail already generated, just make sure of variety
0797: if (abstr.booleanValue()) {
0798: detail.setUseAbstract(true);
0799: } else {
0800: detail.setUseConcrete(true);
0801: }
0802:
0803: }
0804: return detail;
0805: }
0806:
0807: /**
0808: * Find closure of references from a supplied list of classes. References
0809: * counted, and direct references are accumulated for handling. The supplied
0810: * list may include generic classes with type parameters.
0811: *
0812: * @param classes
0813: * @param refmap
0814: */
0815: private void findReferences(List classes, ReferenceCountMap refmap) {
0816: for (int i = 0; i < classes.size(); i++) {
0817: String type = (String) classes.get(i);
0818: int split = type.indexOf('<');
0819: if (split > 0) {
0820: type = type.substring(split + 1, type.length() - 1);
0821: }
0822: expandReferences(type, refmap);
0823: }
0824: }
0825:
0826: /**
0827: * Add mapping details for classes referenced more than once.
0828: *
0829: * @param refmap
0830: */
0831: private void addReferencedMappings(ReferenceCountMap refmap) {
0832: for (Iterator iter = refmap.iterator(); iter.hasNext();) {
0833: String type = (String) iter.next();
0834: if (refmap.getCount(type) > 1
0835: && !m_mappingDetailsMap.containsKey(type)) {
0836: addMappingDetails(null, type);
0837: }
0838: }
0839: }
0840:
0841: /**
0842: * Fix all references between bindings. This needs to be called after the
0843: * generation of binding components is completed, in order to make sure that
0844: * required namespace definitions are included in the output bindings.
0845: */
0846: private void fixBindingReferences() {
0847: ArrayList uris = getNamespaces();
0848: for (int i = 0; i < uris.size(); i++) {
0849: String uri = (String) uris.get(i);
0850: BindingHolder holder = (BindingHolder) m_uriBindingMap
0851: .get(uri);
0852: holder.fixReferences();
0853: }
0854: }
0855:
0856: /**
0857: * Generate the mapping definitions for classes referenced more than once.
0858: *
0859: * @param refmap
0860: */
0861: private void generateReferencedMappings(ReferenceCountMap refmap) {
0862: for (Iterator iter = refmap.iterator(); iter.hasNext();) {
0863: String type = (String) iter.next();
0864: if (refmap.getCount(type) > 1) {
0865: MappingDetail detail = (MappingDetail) m_mappingDetailsMap
0866: .get(type);
0867: if (!detail.isGenerated()) {
0868: addMapping(type, detail);
0869: detail.setGenerated(true);
0870: }
0871: }
0872: }
0873: }
0874:
0875: /**
0876: * Generate mappings for a list of classes. The mapping details must have
0877: * been configured before this method is called.
0878: *
0879: * @param classes
0880: */
0881: private void generateMappings(List classes) {
0882: for (int i = 0; i < classes.size(); i++) {
0883: String type = (String) classes.get(i);
0884: MappingDetail detail = (MappingDetail) m_mappingDetailsMap
0885: .get(type);
0886: if (!detail.isGenerated()) {
0887: addMapping(type, detail);
0888: detail.setGenerated(true);
0889: }
0890: }
0891: }
0892:
0893: /**
0894: * Fix the base classes that are to be used as extension types. This step is
0895: * needed to generate the substitution group structures for classes which
0896: * are both referenced directly and extended by other classes. The method
0897: * must be called after all mapping details have been constucted but before
0898: * the actual binding generation.
0899: */
0900: private void fixBaseClasses() {
0901: for (Iterator iter = m_directSet.iterator(); iter.hasNext();) {
0902: String type = (String) iter.next();
0903: MappingDetail detail = (MappingDetail) m_mappingDetailsMap
0904: .get(type);
0905: if (detail != null && detail.isExtended()) {
0906: detail.setUseConcrete(true);
0907: }
0908: }
0909: }
0910:
0911: /**
0912: * Generate binding(s) for a list of classes. This creates a <mapping>
0913: * definition for each class in the list, and either embeds <structure>
0914: * definitions or creates separate <mapping>s for other classes
0915: * referenced by these classes. If all the classes use the same namespace
0916: * only the binding for that namespace will be created; otherwise, a
0917: * separate binding will be created for each namespace.
0918: *
0919: * @param abstr force abstract mapping flag (determined by class
0920: * customizations if <code>null</code>)
0921: * @param classes class list
0922: */
0923: public void generate(Boolean abstr, List classes) {
0924:
0925: // start by expanding and counting references from supplied classes
0926: ReferenceCountMap refmap = new ReferenceCountMap();
0927: findReferences(classes, refmap);
0928:
0929: // set the classes to be handled with <mapping> definitions
0930: for (int i = 0; i < classes.size(); i++) {
0931: addMappingDetails(abstr, (String) classes.get(i));
0932: }
0933: addReferencedMappings(refmap);
0934: fixBaseClasses();
0935:
0936: // generate the binding(s)
0937: generateMappings(classes);
0938: generateReferencedMappings(refmap);
0939: fixBindingReferences();
0940: }
0941:
0942: /**
0943: * Generate binding(s) for lists of classes. This creates a <mapping>
0944: * definition for each class in the lists, and either embeds <structure>
0945: * definitions or creates separate <mapping>s for other classes
0946: * referenced by these classes. If all the classes use the same namespace
0947: * only the binding for that namespace will be created; otherwise, a
0948: * separate binding will be created for each namespace.
0949: *
0950: * @param qnames list of names for concrete mappings
0951: * @param concrs list of classes to be given concrete mappings
0952: * @param abstrs list of classes to be given abstract mappings
0953: */
0954: public void generateSpecified(ArrayList qnames, List concrs,
0955: List abstrs) {
0956:
0957: // start by expanding and counting references from supplied classes
0958: ReferenceCountMap refmap = new ReferenceCountMap();
0959: findReferences(concrs, refmap);
0960: findReferences(abstrs, refmap);
0961:
0962: // set classes to be handled with mapping definitions
0963: for (int i = 0; i < concrs.size(); i++) {
0964: MappingDetail detail = addMappingDetails(Boolean.FALSE,
0965: (String) concrs.get(i));
0966: detail.setElementQName((QName) qnames.get(i));
0967: }
0968: for (int i = 0; i < abstrs.size(); i++) {
0969: addMappingDetails(Boolean.TRUE, (String) abstrs.get(i));
0970: }
0971: addReferencedMappings(refmap);
0972: fixBaseClasses();
0973:
0974: // generate the binding(s)
0975: generateMappings(concrs);
0976: generateMappings(abstrs);
0977: generateReferencedMappings(refmap);
0978: fixBindingReferences();
0979: }
0980:
0981: /**
0982: * Get the mapping details for a class. This method should only be used
0983: * after the {@link #generate(boolean, ArrayList)} method has
0984: * been called.
0985: *
0986: * @param type fully-qualified class name
0987: * @return mapping details, or <code>null</code> if none
0988: */
0989: public MappingDetail getMappingDetail(String type) {
0990: return (MappingDetail) m_mappingDetailsMap.get(type);
0991: }
0992:
0993: /**
0994: * Get the binding definition for a namespace. This method should only be
0995: * used after the {@link #generate(boolean, ArrayList)} method
0996: * has been called.
0997: *
0998: * @param uri
0999: * @return binding holder, or <code>null</code> if none
1000: */
1001: public BindingHolder getBinding(String uri) {
1002: return (BindingHolder) m_uriBindingMap.get(uri);
1003: }
1004:
1005: /**
1006: * Get the list of binding namespace URIs.
1007: *
1008: * @return namespaces
1009: */
1010: public ArrayList getNamespaces() {
1011: return m_uriBindingMap.keyList();
1012: }
1013:
1014: /**
1015: * Check if a character is an ASCII alpha character.
1016: *
1017: * @param chr
1018: * @return alpha character flag
1019: */
1020: private static boolean isAsciiAlpha(char chr) {
1021: return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z');
1022: }
1023:
1024: /**
1025: * Check if a character is an ASCII numeric character.
1026: *
1027: * @param chr
1028: * @return numeric character flag
1029: */
1030: private static boolean isAsciiNum(char chr) {
1031: return chr >= '0' && chr <= '9';
1032: }
1033:
1034: /**
1035: * Check if a character is an ASCII alpha or numeric character.
1036: *
1037: * @param chr
1038: * @return alpha or numeric character flag
1039: */
1040: private static boolean isAsciiAlphaNum(char chr) {
1041: return isAsciiAlpha(chr) || isAsciiNum(chr);
1042: }
1043:
1044: /**
1045: * Configure the names to be used for writing bindings to files. If only one
1046: * binding has been defined, it just gets the supplied name. If multiple
1047: * bindings have been defined, a single root binding is constructed which
1048: * includes all the other bindings, and that root binding is given the
1049: * supplied names while the other bindings are given unique names within the
1050: * same directory.
1051: *
1052: * @param name file name for root or singleton binding definition
1053: * @return root or singleton binding holder
1054: */
1055: public BindingHolder configureBindings(String name) {
1056: BindingHolder rhold;
1057: BindingElement root;
1058: ArrayList uris = getNamespaces();
1059: if (uris.size() == 1) {
1060:
1061: // single binding, just write it using supplied name
1062: rhold = (BindingHolder) m_uriBindingMap.get(uris.get(0));
1063: root = rhold.getBinding();
1064:
1065: } else {
1066:
1067: // get or create no namespace binding
1068: rhold = findBinding(null);
1069: root = rhold.getBinding();
1070:
1071: // set file names and add to root binding
1072: Set nameset = new HashSet();
1073: for (int i = 0; i < uris.size(); i++) {
1074: String uri = (String) uris.get(i);
1075: BindingHolder holder = (BindingHolder) m_uriBindingMap
1076: .get(uri);
1077: if (holder != rhold) {
1078:
1079: // get last part of namespace URI as file name candidate
1080: String bindname;
1081: String raw = holder.getNamespace();
1082: if (raw == null) {
1083: bindname = "nonamespaceBinding";
1084: } else {
1085: int split = raw.lastIndexOf(':');
1086: raw = raw.substring(split + 1);
1087: split = raw.lastIndexOf('/');
1088: raw = raw.substring(split + 1);
1089: split = raw.lastIndexOf('\\');
1090: raw = raw.substring(split + 1);
1091:
1092: // eliminate any invalid characters in name
1093: StringBuffer buff = new StringBuffer();
1094: int index = 0;
1095: char chr = raw.charAt(0);
1096: if (isAsciiAlpha(chr)) {
1097: buff.append(chr);
1098: index = 1;
1099: } else {
1100: buff.append('_');
1101: }
1102: while (index < raw.length()) {
1103: chr = raw.charAt(index++);
1104: if (isAsciiAlphaNum(chr)) {
1105: buff.append(chr);
1106: }
1107: }
1108: buff.append("Binding");
1109: bindname = buff.toString();
1110: }
1111:
1112: // ensure uniqueness of the name
1113: String uname = bindname.toLowerCase();
1114: int pass = 0;
1115: while (nameset.contains(uname)) {
1116: bindname = bindname + pass;
1117: uname = bindname.toLowerCase();
1118: }
1119: nameset.add(uname);
1120: holder.setFileName(bindname + ".xml");
1121:
1122: // include within the root binding
1123: IncludeElement include = new IncludeElement();
1124: include.setIncludePath(holder.getFileName());
1125: root.addTopChild(include);
1126:
1127: }
1128: }
1129: }
1130:
1131: // set the file name on the singleton or root binding
1132: rhold.setFileName(name);
1133:
1134: // set the binding name based on the file name
1135: int split = name.lastIndexOf('.');
1136: if (split > 0) {
1137: root.setName(name.substring(0, split));
1138: } else {
1139: root.setName(name);
1140: }
1141:
1142: // set the target package for code generation
1143: root.setTargetPackage(m_targetPackage);
1144: return rhold;
1145: }
1146:
1147: /**
1148: * Write the bindings to supplied destination path.
1149: *
1150: * @param dir target directory for writing binding definitions, output to
1151: * console if <code>null</code>
1152: * @throws JiBXException
1153: * @throws IOException
1154: */
1155: public void writeBindings(File dir) throws JiBXException,
1156: IOException {
1157:
1158: // write the bindings
1159: IBindingFactory fact = BindingDirectory
1160: .getFactory(BindingElement.class);
1161: IMarshallingContext ictx = fact.createMarshallingContext();
1162: ictx.setIndent(2);
1163: ArrayList uris = getNamespaces();
1164: for (int i = 0; i < uris.size(); i++) {
1165: String uri = (String) uris.get(i);
1166: BindingHolder holder = (BindingHolder) m_uriBindingMap
1167: .get(uri);
1168: if (dir == null) {
1169: ictx.setOutput(System.out, null);
1170: } else {
1171: File file = new File(dir, holder.getFileName());
1172: ictx.setOutput(new FileOutputStream(file), null);
1173: }
1174: ((IMarshallable) holder.getBinding()).marshal(ictx);
1175: ictx.getXmlWriter().flush();
1176: }
1177: }
1178:
1179: /**
1180: * Run the binding generation using command line parameters.
1181: *
1182: * @param args
1183: * @throws JiBXException
1184: * @throws IOException
1185: */
1186: public static void main(String[] args) throws JiBXException,
1187: IOException {
1188: BindingGeneratorCommandLine parms = new BindingGeneratorCommandLine();
1189: if (args.length > 0 && parms.processArgs(args)) {
1190:
1191: // generate bindings for all namespaces
1192: BindingGenerator gen = new BindingGenerator(parms
1193: .getGlobal());
1194: gen.generate(parms.getAbstract(), parms.getExtraArgs());
1195:
1196: // write the bindings
1197: gen.configureBindings(parms.getBindingName());
1198: gen.writeBindings(parms.getGeneratePath());
1199:
1200: } else {
1201: if (args.length > 0) {
1202: System.err
1203: .println("Terminating due to command line errors");
1204: } else {
1205: parms.printUsage();
1206: }
1207: System.exit(1);
1208: }
1209: }
1210:
1211: /**
1212: * Holder for the details of how a class is going to be mapped. This
1213: * information is needed in order to determine how to reference the mapped
1214: * class when it's used within another mapping definition.
1215: */
1216: public static class MappingDetail {
1217: /** Generate abstract mapping flag. */
1218: private boolean m_useAbstract;
1219:
1220: /** Generate concrete mapping flag. */
1221: private boolean m_useConcrete;
1222:
1223: /** Flag for class extended by other mapped class(es). */
1224: private boolean m_isExtended;
1225:
1226: /** Abstract mapping type name (<code>null</code> if none). */
1227: private final QName m_typeQName;
1228:
1229: /** Concrete mapping element name (<code>null</code> if none). */
1230: private QName m_elementQName;
1231:
1232: /** Concrete mapping type extended by this one. */
1233: private String m_extendsType;
1234:
1235: /** Flag for mapping(s) has been generated. */
1236: private boolean m_isGenerated;
1237:
1238: /** Abstract mapping definition (<code>null</code> if none). */
1239: private MappingElement m_abstractMapping;
1240:
1241: /** Concrete mapping definition (<code>null</code> if none). */
1242: private MappingElement m_concreteMapping;
1243:
1244: /**
1245: * Constructor.
1246: *
1247: * @param aname abstract mapping type name
1248: * @param cname concrete mapping element name
1249: * @param stype superclass for extension (<code>null</code> if not an
1250: * extension mapping)
1251: */
1252: protected MappingDetail(QName aname, QName cname, String stype) {
1253: m_typeQName = aname;
1254: m_elementQName = cname;
1255: m_extendsType = stype;
1256: }
1257:
1258: public MappingElement getAbstractMapping() {
1259: return m_abstractMapping;
1260: }
1261:
1262: public void setAbstractMapping(MappingElement abstractMapping) {
1263: m_abstractMapping = abstractMapping;
1264: }
1265:
1266: public MappingElement getConcreteMapping() {
1267: return m_concreteMapping;
1268: }
1269:
1270: public void setConcreteMapping(MappingElement concreteMapping) {
1271: m_concreteMapping = concreteMapping;
1272: }
1273:
1274: public boolean isExtended() {
1275: return m_isExtended;
1276: }
1277:
1278: public void setExtended(boolean isExtended) {
1279: m_isExtended = isExtended;
1280: }
1281:
1282: public boolean isGenerated() {
1283: return m_isGenerated;
1284: }
1285:
1286: public void setGenerated(boolean isGenerated) {
1287: m_isGenerated = isGenerated;
1288: }
1289:
1290: public boolean isUseAbstract() {
1291: return m_useAbstract;
1292: }
1293:
1294: public void setUseAbstract(boolean useAbstract) {
1295: m_useAbstract = useAbstract;
1296: }
1297:
1298: public boolean isUseConcrete() {
1299: return m_useConcrete;
1300: }
1301:
1302: public void setUseConcrete(boolean useConcrete) {
1303: m_useConcrete = useConcrete;
1304: }
1305:
1306: public QName getElementQName() {
1307: return m_elementQName;
1308: }
1309:
1310: public void setElementQName(QName elementQName) {
1311: m_elementQName = elementQName;
1312: }
1313:
1314: public String getExtendsType() {
1315: return m_extendsType;
1316: }
1317:
1318: public QName getTypeQName() {
1319: return m_typeQName;
1320: }
1321: }
1322: }
|