0001: /*
0002: Copyright (c) 2004-2005, 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;
0030:
0031: import java.io.FileNotFoundException;
0032: import java.io.FileOutputStream;
0033: import java.io.PrintStream;
0034: import java.util.ArrayList;
0035: import java.util.Arrays;
0036: import java.util.HashMap;
0037: import java.util.HashSet;
0038: import java.util.Iterator;
0039:
0040: import org.apache.bcel.classfile.Field;
0041: import org.apache.bcel.classfile.JavaClass;
0042: import org.jibx.binding.classes.ClassCache;
0043: import org.jibx.binding.classes.ClassFile;
0044: import org.jibx.binding.classes.ClassItem;
0045: import org.jibx.binding.model.BindingElement;
0046: import org.jibx.binding.model.CollectionElement;
0047: import org.jibx.binding.model.ContainerElementBase;
0048: import org.jibx.binding.model.MappingElement;
0049: import org.jibx.binding.model.NamespaceElement;
0050: import org.jibx.binding.model.StructureElement;
0051: import org.jibx.binding.model.StructureElementBase;
0052: import org.jibx.binding.model.ValueElement;
0053: import org.jibx.binding.util.ObjectStack;
0054: import org.jibx.runtime.BindingDirectory;
0055: import org.jibx.runtime.IBindingFactory;
0056: import org.jibx.runtime.IMarshallingContext;
0057: import org.jibx.runtime.JiBXException;
0058:
0059: /**
0060: * Binding generator. This loads the specified input classes and processes them
0061: * to generate a default binding definition.
0062: *
0063: * @author Dennis M. Sosnoski
0064: * @version 1.0
0065: */
0066:
0067: public class BindingGenerator {
0068: /** Generator version. */
0069: private static String CURRENT_VERSION = "0.2";
0070:
0071: /** Set of objects treated as primitives. */
0072: private static HashSet s_objectPrimitiveSet = new HashSet();
0073:
0074: static {
0075: s_objectPrimitiveSet.add("java.lang.Boolean");
0076: s_objectPrimitiveSet.add("java.lang.Byte");
0077: s_objectPrimitiveSet.add("java.lang.Char");
0078: s_objectPrimitiveSet.add("java.lang.Double");
0079: s_objectPrimitiveSet.add("java.lang.Float");
0080: s_objectPrimitiveSet.add("java.lang.Integer");
0081: s_objectPrimitiveSet.add("java.lang.Long");
0082: s_objectPrimitiveSet.add("java.lang.Short");
0083: s_objectPrimitiveSet.add("java.math.BigDecimal");
0084: s_objectPrimitiveSet.add("java.math.BigInteger");
0085: //#!j2me{
0086: s_objectPrimitiveSet.add("java.sql.Date");
0087: s_objectPrimitiveSet.add("java.sql.Time");
0088: s_objectPrimitiveSet.add("java.sql.Timestamp");
0089: //#j2me}
0090: s_objectPrimitiveSet.add("java.util.Date");
0091: }
0092:
0093: /** Show verbose output flag. */
0094: private boolean m_verbose;
0095:
0096: /** Use camel case for XML names flag. */
0097: private boolean m_mixedCase;
0098:
0099: /** Namespace URI for elements. */
0100: private String m_namespaceUri;
0101:
0102: /** Class names to mapped element names map. */
0103: private HashMap m_mappedNames;
0104:
0105: /** Class names to properties list map. */
0106: private HashMap m_beanNames;
0107:
0108: /** Class names to deserializers map for typesafe enumerations. */
0109: private HashMap m_enumerationNames;
0110:
0111: /** Stack of structure definitions in progress (used to detect cycles). */
0112: private ObjectStack m_structureStack;
0113:
0114: /** Class names bound as nested structures. */
0115: private HashSet m_structureNames;
0116:
0117: /** Class names to be treated like interfaces (not mapped directly). */
0118: private HashSet m_ignoreNames;
0119:
0120: /**
0121: * Default constructor. This just initializes all options disabled.
0122: */
0123: public BindingGenerator() {
0124: m_mappedNames = new HashMap();
0125: m_structureStack = new ObjectStack();
0126: m_structureNames = new HashSet();
0127: m_ignoreNames = new HashSet();
0128: }
0129:
0130: /**
0131: * Constructor with settings specified.
0132: *
0133: * @param verbose report binding details and results
0134: * @param mixed use camel case in element names
0135: * @param uri namespace URI for element bindings
0136: */
0137: public BindingGenerator(boolean verbose, boolean mixed, String uri) {
0138: this ();
0139: m_verbose = verbose;
0140: m_mixedCase = mixed;
0141: m_namespaceUri = uri;
0142: }
0143:
0144: /**
0145: * Set control flag for verbose processing reports.
0146: *
0147: * @param verbose report verbose information in processing bindings flag
0148: */
0149: public void setVerbose(boolean verbose) {
0150: m_verbose = verbose;
0151: }
0152:
0153: /**
0154: * Set control flag for camel case element naming.
0155: *
0156: * @param camel use camel case element naming flag
0157: */
0158: public void setCamelCase(boolean camel) {
0159: m_mixedCase = camel;
0160: }
0161:
0162: /**
0163: * Indent to proper depth for current item.
0164: *
0165: * @param pw output print stream to be indented
0166: */
0167: private void nestingIndent(PrintStream pw) {
0168: for (int i = 0; i < m_structureStack.size(); i++) {
0169: pw.print(' ');
0170: }
0171: }
0172:
0173: /**
0174: * Convert class or unprefixed field name to element or attribute name.
0175: *
0176: * @param base class or simple field name to be converted
0177: * @return element or attribute name
0178: */
0179: private String convertName(String base) {
0180: StringBuffer name = new StringBuffer();
0181: name.append(Character.toLowerCase(base.charAt(0)));
0182: if (m_mixedCase) {
0183: name.append(base.substring(1));
0184: } else {
0185: boolean ignore = true;
0186: for (int i = 1; i < base.length(); i++) {
0187: char chr = base.charAt(i);
0188: if (Character.isUpperCase(chr)) {
0189: chr = Character.toLowerCase(chr);
0190: if (ignore) {
0191: int next = i + 1;
0192: ignore = next >= base.length()
0193: || Character.isUpperCase(base
0194: .charAt(next));
0195: }
0196: if (ignore) {
0197: name.append(chr);
0198: } else {
0199: name.append('-');
0200: name.append(chr);
0201: ignore = true;
0202: }
0203: } else {
0204: name.append(chr);
0205: ignore = false;
0206: }
0207: }
0208: }
0209: return name.toString();
0210: }
0211:
0212: /**
0213: * Generate structure element name from class name using set conversions.
0214: *
0215: * @param cname class name to be converted
0216: * @return element name for instances of class
0217: */
0218: public String elementName(String cname) {
0219: int split = cname.lastIndexOf('.');
0220: if (split >= 0) {
0221: cname = cname.substring(split + 1);
0222: }
0223: while ((split = cname.indexOf('$')) >= 0) {
0224: cname = cname.substring(0, split)
0225: + cname.substring(split + 1);
0226: }
0227: return convertName(cname);
0228: }
0229:
0230: /**
0231: * Generate structure element name from class name using set conversions.
0232: *
0233: * @param fname field name to be converted
0234: * @return element name for instances of class
0235: */
0236: private String valueName(String fname) {
0237: String base = fname;
0238: if (base.startsWith("m_")) {
0239: base = base.substring(2);
0240: } else if (base.startsWith("_")) {
0241: base = base.substring(1);
0242: } else if (base.startsWith("f") && base.length() > 1) {
0243: if (Character.isUpperCase(base.charAt(1))) {
0244: base = base.substring(1);
0245: }
0246: }
0247: return convertName(base);
0248: }
0249:
0250: /**
0251: * Construct the list of child binding components that define the binding
0252: * structure for fields of a particular class. This binds all
0253: * non-final/non-static/non-transient fields of the class, if necessary
0254: * creating nested structure elements for unmapped classes referenced by the
0255: * fields.
0256: *
0257: * @param cf class information
0258: * @param contain binding structure container element
0259: * @throws JiBXException on error in binding generation
0260: */
0261: private void defineFields(ClassFile cf, ContainerElementBase contain)
0262: throws JiBXException {
0263:
0264: // process all non-final/non-static/non-transient fields of class
0265: JavaClass clas = cf.getRawClass();
0266: Field[] fields = clas.getFields();
0267: for (int i = 0; i < fields.length; i++) {
0268: Field field = fields[i];
0269: if (!field.isFinal() && !field.isStatic()
0270: && !field.isTransient()) {
0271:
0272: // find type of handling needed for field type
0273: boolean simple = false;
0274: boolean object = true;
0275: boolean attribute = true;
0276: String fname = field.getName();
0277: String tname = field.getType().toString();
0278: if (ClassItem.isPrimitive(tname)) {
0279: simple = true;
0280: object = false;
0281: } else if (s_objectPrimitiveSet.contains(tname)) {
0282: simple = true;
0283: } else if (tname.equals("byte[]")) {
0284: simple = true;
0285: attribute = false;
0286: } else if (tname.startsWith("java.")) {
0287:
0288: // check for standard library classes we can handle
0289: ClassFile pcf = ClassCache.getClassFile(tname);
0290: ClassItem init = pcf
0291: .getInitializerMethod("(Ljava/lang/String;)");
0292: if (init != null) {
0293: simple = pcf.getMethod("toString",
0294: "()Ljava/lang/String;") != null;
0295: attribute = false;
0296: }
0297:
0298: }
0299:
0300: // check type of handling to use for value
0301: if (m_enumerationNames.get(tname) != null) {
0302:
0303: // define a value using deserializer method
0304: String mname = (String) m_enumerationNames
0305: .get(tname);
0306: int split = mname.lastIndexOf('.');
0307: ClassFile dcf = ClassCache.getClassFile(mname
0308: .substring(0, split));
0309: ClassItem dser = dcf.getStaticMethod(mname
0310: .substring(split + 1),
0311: "(Ljava/lang/String;)");
0312: if (dser == null
0313: || !tname.equals(dser.getTypeName())) {
0314: throw new JiBXException(
0315: "Deserializer method not "
0316: + "found for enumeration class "
0317: + tname);
0318: }
0319: ValueElement value = new ValueElement();
0320: value.setFieldName(fname);
0321: value.setName(valueName(fname));
0322: value.setUsageName("optional");
0323: value.setStyleName("element");
0324: value.setDeserializerName(mname);
0325: contain.addChild(value);
0326:
0327: } else if (m_mappedNames.get(tname) != null) {
0328:
0329: // use mapping definition for class
0330: StructureElement structure = new StructureElement();
0331: structure.setUsageName("optional");
0332: structure.setFieldName(fname);
0333: if (((String) m_mappedNames.get(tname)).length() == 0) {
0334:
0335: // add a name for reference to abstract mapping
0336: structure.setName(elementName(tname));
0337: }
0338: contain.addChild(structure);
0339: if (m_verbose) {
0340: nestingIndent(System.out);
0341: System.out
0342: .println("referenced existing binding for "
0343: + tname);
0344: }
0345:
0346: } else if (simple) {
0347:
0348: // define a simple value
0349: ValueElement value = new ValueElement();
0350: value.setFieldName(fname);
0351: value.setName(valueName(fname));
0352: if (object) {
0353: value.setUsageName("optional");
0354: }
0355: if (!attribute) {
0356: value.setStyleName("element");
0357: }
0358: contain.addChild(value);
0359:
0360: } else if (tname.endsWith("[]")) {
0361:
0362: // array, check if item type has a mapping
0363: String bname = tname.substring(0,
0364: tname.length() - 2);
0365: if (m_mappedNames.get(bname) == null) {
0366:
0367: // no mapping, use collection with inline structure
0368: // TODO: fill it in
0369: throw new JiBXException("Base element type "
0370: + bname + " must be mapped");
0371:
0372: } else {
0373:
0374: // mapping for type, use direct collection
0375: // TODO: convert to simple collection
0376: StructureElement structure = new StructureElement();
0377: structure.setUsageName("optional");
0378: structure.setFieldName(fname);
0379: structure
0380: .setMarshallerName("org.jibx.extras.TypedArrayMapper");
0381: structure
0382: .setUnmarshallerName("org.jibx.extras.TypedArrayMapper");
0383: contain.addChild(structure);
0384: }
0385:
0386: } else {
0387:
0388: // no defined handling, check for collection vs. structure
0389: ClassFile pcf = ClassCache.getClassFile(tname);
0390: StructureElementBase element;
0391: if (pcf.isImplements("Ljava/util/List;")) {
0392:
0393: // create a collection for list subclass
0394: System.err
0395: .println("Warning: field "
0396: + fname
0397: + " requires mapped implementation of item classes");
0398: element = new CollectionElement();
0399: element
0400: .setComment(" add details of collection items "
0401: + "to complete binding definition ");
0402: element.setFieldName(fname);
0403:
0404: // specify factory method if just typed as interface
0405: if ("java.util.List".equals(tname)) {
0406: element
0407: .setFactoryName("org.jibx.runtime.Utility.arrayListFactory");
0408: }
0409:
0410: } else if (pcf.isInterface()
0411: || m_ignoreNames.contains(tname)) {
0412:
0413: // create mapping reference with warning for interface
0414: nestingIndent(System.err);
0415: System.err
0416: .println("Warning: reference to interface "
0417: + "or abstract class "
0418: + tname
0419: + " requires mapped implementation");
0420: element = new StructureElement();
0421: element.setFieldName(fname);
0422:
0423: } else {
0424:
0425: // handle other types of structures directly
0426: element = createStructure(pcf, fname);
0427: }
0428:
0429: // finish with common handling
0430: element.setUsageName("optional");
0431: contain.addChild(element);
0432:
0433: }
0434: }
0435: }
0436: }
0437:
0438: /**
0439: * Construct the list of child binding components that define the binding
0440: * structure corresponding to properties of a particular class. This binds
0441: * the specified properties of the class, using get/set methods, if
0442: * necessary creating nested structure elements for unmapped classes
0443: * referenced by the properties.
0444: *
0445: * @param cf class information
0446: * @param props list of properties specified for class
0447: * @param internal allow private get/set methods flag
0448: * @param contain binding structure container element
0449: * @throws JiBXException on error in binding generation
0450: */
0451: private void defineProperties(ClassFile cf, ArrayList props,
0452: boolean internal, ContainerElementBase contain)
0453: throws JiBXException {
0454:
0455: // process all properties of class
0456: for (int i = 0; i < props.size(); i++) {
0457: String pname = (String) props.get(i);
0458: String base = Character.toUpperCase(pname.charAt(0))
0459: + pname.substring(1);
0460: String gname = "get" + base;
0461: ClassItem gmeth = cf.getMethod(gname, "()");
0462: if (gmeth == null) {
0463: throw new JiBXException("No method " + gname
0464: + "() found for property " + base
0465: + " in class " + cf.getName());
0466: } else {
0467: String tname = gmeth.getTypeName();
0468: String sname = "set" + base;
0469: String sig = "(" + gmeth.getSignature().substring(2)
0470: + ")V";
0471: ClassItem smeth = cf.getMethod(sname, sig);
0472: if (smeth == null) {
0473: throw new JiBXException("No method " + sname + "("
0474: + tname + ") found for property " + base
0475: + " in class " + cf.getName());
0476: } else {
0477:
0478: // find type of handling needed for field type
0479: boolean simple = false;
0480: boolean object = true;
0481: boolean attribute = true;
0482: if (ClassItem.isPrimitive(tname)) {
0483: simple = true;
0484: object = false;
0485: } else if (s_objectPrimitiveSet.contains(tname)) {
0486: simple = true;
0487: } else if (tname.equals("byte[]")) {
0488: simple = true;
0489: attribute = false;
0490: } else if (tname.startsWith("java.")) {
0491:
0492: // check for standard library classes we can handle
0493: ClassFile pcf = ClassCache.getClassFile(tname);
0494: if (pcf.getInitializerMethod("()") != null) {
0495: simple = pcf.getMethod("toString",
0496: "()Ljava/lang/String;") != null;
0497: attribute = false;
0498: }
0499:
0500: }
0501: if (simple) {
0502:
0503: // define a simple value
0504: ValueElement value = new ValueElement();
0505: value.setGetName(gname);
0506: value.setSetName(sname);
0507: value.setName(valueName(pname));
0508: if (object) {
0509: value.setUsageName("optional");
0510: }
0511: if (!attribute) {
0512: value.setStyleName("element");
0513: }
0514: contain.addChild(value);
0515:
0516: } else if (m_enumerationNames.get(tname) != null) {
0517:
0518: // define a value using deserializer method
0519: String mname = (String) m_enumerationNames
0520: .get(tname);
0521: int split = mname.lastIndexOf('.');
0522: ClassFile dcf = ClassCache.getClassFile(mname
0523: .substring(0, split));
0524: ClassItem dser = dcf.getStaticMethod(mname
0525: .substring(split + 1),
0526: "(Ljava/lang/String;)");
0527: if (dser == null
0528: || !tname.equals(dser.getTypeName())) {
0529: throw new JiBXException(
0530: "Deserializer method not "
0531: + "found for enumeration class "
0532: + tname);
0533: }
0534: ValueElement value = new ValueElement();
0535: value.setGetName(gname);
0536: value.setSetName(sname);
0537: value.setName(valueName(pname));
0538: value.setUsageName("optional");
0539: value.setStyleName("element");
0540: value.setDeserializerName(mname);
0541: contain.addChild(value);
0542:
0543: } else if (m_mappedNames.get(tname) != null) {
0544:
0545: // use mapping definition for class
0546: StructureElement structure = new StructureElement();
0547: structure.setUsageName("optional");
0548: structure.setGetName(gname);
0549: structure.setSetName(sname);
0550: if (((String) m_mappedNames.get(tname))
0551: .length() == 0) {
0552:
0553: // add a name for reference to abstract mapping
0554: structure.setName(elementName(tname));
0555: }
0556: contain.addChild(structure);
0557: if (m_verbose) {
0558: nestingIndent(System.out);
0559: System.out
0560: .println("referenced existing binding for "
0561: + tname);
0562: }
0563:
0564: } else if (tname.endsWith("[]")) {
0565:
0566: // array, only supported for mapped base type
0567: String bname = tname.substring(0, tname
0568: .length() - 2);
0569: if (m_mappedNames.get(bname) == null) {
0570: throw new JiBXException(
0571: "Base element type " + bname
0572: + " must be mapped");
0573: } else {
0574: StructureElement structure = new StructureElement();
0575: structure.setUsageName("optional");
0576: structure.setGetName(gname);
0577: structure.setSetName(sname);
0578: structure
0579: .setMarshallerName("org.jibx.extras.TypedArrayMapper");
0580: structure
0581: .setUnmarshallerName("org.jibx.extras.TypedArrayMapper");
0582: contain.addChild(structure);
0583: }
0584:
0585: } else {
0586:
0587: // no defined handling, check collection vs. structure
0588: ClassFile pcf = ClassCache.getClassFile(tname);
0589: StructureElementBase element;
0590: if (pcf.isImplements("Ljava/util/List;")) {
0591:
0592: // create a collection for list subclass
0593: System.err
0594: .println("Warning: property "
0595: + pname
0596: + " requires mapped implementation of item "
0597: + "classes");
0598: element = new CollectionElement();
0599: element
0600: .setComment(" add details of collection "
0601: + "items to complete binding definition ");
0602: element.setGetName(gname);
0603: element.setSetName(sname);
0604:
0605: // specify factory method if just typed as interface
0606: if ("java.util.List".equals(tname)) {
0607: element
0608: .setFactoryName("org.jibx.runtime."
0609: + "Utility.arrayListFactory");
0610: }
0611:
0612: } else if (pcf.isInterface()
0613: || m_ignoreNames.contains(tname)) {
0614:
0615: // mapping reference with warning for interface
0616: nestingIndent(System.err);
0617: System.err
0618: .println("Warning: reference to "
0619: + "interface or abstract class "
0620: + tname
0621: + " requires mapped implementation");
0622: element = new StructureElement();
0623: element.setGetName(gname);
0624: element.setSetName(sname);
0625:
0626: } else {
0627:
0628: // handle other types of structures directly
0629: element = createStructure(pcf, pname);
0630: }
0631:
0632: // finish with common handling
0633: element.setUsageName("optional");
0634: contain.addChild(element);
0635:
0636: }
0637: }
0638: }
0639: }
0640: }
0641:
0642: /**
0643: * Construct the list of child binding components that define the binding
0644: * structure corresponding to a particular class. This binds all
0645: * non-final/non-static/non-transient fields of the class, if necessary
0646: * creating nested structure elements for unmapped classes referenced by the
0647: * fields.
0648: *
0649: * @param cf class information
0650: * @param contain binding structure container element
0651: * @throws JiBXException on error in binding generation
0652: */
0653: private void defineStructure(ClassFile cf,
0654: ContainerElementBase contain) throws JiBXException {
0655:
0656: // process basic fields or property list
0657: Object props = m_beanNames.get(cf.getName());
0658: if (props == null) {
0659: defineFields(cf, contain);
0660: } else {
0661: defineProperties(cf, (ArrayList) props, true, contain);
0662: }
0663:
0664: // check if superclass may have data for binding
0665: ClassFile sf = cf.getSuperFile();
0666: String sname = sf.getName();
0667: if (!"java.lang.Object".equals(sname)
0668: && !m_ignoreNames.contains(sname)) {
0669: if (m_mappedNames.get(sname) != null) {
0670: StructureElement structure = new StructureElement();
0671: structure.setMapAsName(sname);
0672: structure.setName(elementName(sname));
0673: contain.addChild(structure);
0674: } else if (m_beanNames.get(sname) != null) {
0675: defineProperties(sf,
0676: (ArrayList) m_beanNames.get(sname), false,
0677: contain);
0678: } else if (sf.getRawClass().getFields().length > 0) {
0679: nestingIndent(System.err);
0680: System.err
0681: .println("Warning: fields from base class "
0682: + sname
0683: + " of class "
0684: + cf.getName()
0685: + " not handled by generated "
0686: + "binding; use mapping or specify property list");
0687: contain
0688: .setComment(" missing information for base class "
0689: + sname + " ");
0690: }
0691: }
0692: }
0693:
0694: /**
0695: * Create the structure element for a particular class. This maps all
0696: * non-final/non-static/non-transient fields of the class, if necessary
0697: * creating nested structures.
0698: *
0699: * @param cf class information
0700: * @param fname name of field supplying reference
0701: * @throws JiBXException on error in binding generation
0702: */
0703: private StructureElement createStructure(ClassFile cf, String fname)
0704: throws JiBXException {
0705: JavaClass clas = cf.getRawClass();
0706: if (m_verbose) {
0707: nestingIndent(System.out);
0708: System.out
0709: .println("creating nested structure definition for "
0710: + clas.getClassName());
0711: }
0712: String cname = clas.getClassName();
0713: for (int i = 0; i < m_structureStack.size(); i++) {
0714: if (cname.equals(m_structureStack.peek(i))) {
0715: StringBuffer buff = new StringBuffer(
0716: "Error: recursive use of ");
0717: buff.append(cname);
0718: buff.append(" requires <mapping>:\n ");
0719: while (i >= 0) {
0720: buff.append(m_structureStack.peek(i--));
0721: buff.append(" -> ");
0722: }
0723: buff.append(cname);
0724: throw new JiBXException(buff.toString());
0725: }
0726: }
0727: if (cname.startsWith("java.")) {
0728: nestingIndent(System.err);
0729: System.err
0730: .println("Warning: trying to create structure for "
0731: + cname);
0732: } else if (m_structureNames.contains(cname)) {
0733: nestingIndent(System.err);
0734: System.err.println("Warning: repeated usage of class "
0735: + cname + "; consider adding to mapping list");
0736: } else {
0737: m_structureNames.add(cname);
0738: }
0739: m_structureStack.push(cname);
0740: StructureElement element = new StructureElement();
0741: element.setFieldName(fname);
0742: element.setName(valueName(fname));
0743: defineStructure(cf, element);
0744: if (element.children().isEmpty()) {
0745: throw new JiBXException("No content found for class "
0746: + cname);
0747: }
0748: m_structureStack.pop();
0749: if (m_verbose) {
0750: nestingIndent(System.out);
0751: System.out
0752: .println("completed nested structure definition for "
0753: + clas.getClassName());
0754: }
0755: return element;
0756: }
0757:
0758: /**
0759: * Create the mapping element for a particular class. This maps all
0760: * non-final/non-static/non-transient fields of the class, if necessary
0761: * creating nested structures.
0762: *
0763: * @param cf class information
0764: * @param abstr force abstract mapping flag
0765: * @throws JiBXException on error in binding generation
0766: */
0767: private MappingElement createMapping(ClassFile cf, boolean abstr)
0768: throws JiBXException {
0769: JavaClass clas = cf.getRawClass();
0770: if (m_verbose) {
0771: System.out.println("\nBuilding mapping definition for "
0772: + clas.getClassName());
0773: }
0774: MappingElement element = new MappingElement();
0775: element.setAbstract(abstr || clas.isAbstract()
0776: || clas.isInterface());
0777: String name = clas.getClassName();
0778: element.setClassName(name);
0779: if (abstr) {
0780: element.setAbstract(true);
0781: } else {
0782: element.setName((String) m_mappedNames.get(name));
0783: }
0784: m_structureStack.push(name);
0785: defineStructure(cf, element);
0786: m_structureStack.pop();
0787: return element;
0788: }
0789:
0790: private static boolean isMappable(String cname) {
0791: if ("java.lang.String".equals(cname)) {
0792: return false;
0793: } else if ("java.lang.Object".equals(cname)) {
0794: return false;
0795: } else if (ClassItem.isPrimitive(cname)) {
0796: return false;
0797: } else {
0798: return !s_objectPrimitiveSet.contains(cname);
0799: }
0800: }
0801:
0802: /**
0803: * Get the set of data classes passed to or returned by a list of methods
0804: * within a class. The classes returned exclude primitive types, wrappers,
0805: * <code>java.lang.String</code>, and <code>java.lang.Object</code>.
0806: * Exception classes thrown by the methods are also optionally accumulated.
0807: *
0808: * @param cname target class name
0809: * @param mnames method names to be checked
0810: * @param dataset set for accumulation of data classes (optional, data
0811: * classes not recorded if <code>null</code>)
0812: * @param exceptset set for accumulation of exception classes (optional,
0813: * data classes not recorded if <code>null</code>)
0814: * @throws JiBXException on error in loading class information
0815: */
0816: public static void findClassesUsed(String cname, ArrayList mnames,
0817: HashSet dataset, HashSet exceptset) throws JiBXException {
0818: ClassFile cf = ClassCache.getClassFile(cname);
0819: if (cf != null) {
0820: for (int i = 0; i < mnames.size(); i++) {
0821: String mname = (String) mnames.get(i);
0822: ClassItem mitem = cf.getMethod(mname, "");
0823: if (mitem == null) {
0824: System.err.println("Method " + mname
0825: + " not found in class " + cname);
0826: } else {
0827: if (dataset != null) {
0828: String type = mitem.getTypeName();
0829: if (type != null && isMappable(type)) {
0830: dataset.add(type);
0831: }
0832: String[] args = mitem.getArgumentTypes();
0833: for (int j = 0; j < args.length; j++) {
0834: type = args[j];
0835: if (isMappable(type)) {
0836: dataset.add(args[j]);
0837: }
0838: }
0839: }
0840: if (exceptset != null) {
0841: String[] excepts = mitem.getExceptions();
0842: for (int j = 0; j < excepts.length; j++) {
0843: exceptset.add(excepts[j]);
0844: }
0845: }
0846: }
0847: }
0848: }
0849: }
0850:
0851: /**
0852: * Generate a set of bindings using supplied classpaths and class names.
0853: *
0854: * @param names list of class names to be included in binding
0855: * @param abstracts set of classes to be handled with abstract mappings in
0856: * binding
0857: * @param customs map of customized class names to marshaller/unmarshaller
0858: * class names
0859: * @param beans map of class names to supplied lists of properties
0860: * @param enums map of typesafe enumeration classes to deserializer methods
0861: * @param ignores list of non-interface classes to be treated as interfaces
0862: * (no mapping, but mapped subclasses are used at runtime)
0863: * @exception JiBXException if error in generating the binding definition
0864: */
0865: public BindingElement generate(ArrayList names, HashSet abstracts,
0866: HashMap customs, HashMap beans, HashMap enums,
0867: ArrayList ignores) throws JiBXException {
0868:
0869: // print current version information
0870: System.out.println("Running binding generator version "
0871: + CURRENT_VERSION);
0872:
0873: // add all classes with mappings to tracking map
0874: m_mappedNames.clear();
0875: for (int i = 0; i < names.size(); i++) {
0876:
0877: // make sure class can potentially be handled automatically
0878: boolean drop = false;
0879: String name = (String) names.get(i);
0880: ClassFile cf = ClassCache.getClassFile(name);
0881: if (cf.isImplements("Ljava/util/List;")
0882: || cf.isImplements("Ljava/util/Map;")) {
0883: System.err
0884: .println("Warning: referenced class "
0885: + name
0886: + " is a collection class that cannot be mapped "
0887: + "automatically; dropped from mapped list in binding");
0888: drop = true;
0889: } else if (cf.isInterface()) {
0890: System.err.println("Warning: interface " + name
0891: + " is being handled as abstract mapping");
0892: abstracts.add(name);
0893: drop = true;
0894: } else if (cf.isAbstract()) {
0895: System.err
0896: .println("Warning: mapping abstract class "
0897: + name
0898: + "; make sure actual subclasses are mapped as extending "
0899: + "this abstract mapping");
0900: abstracts.add(name);
0901: drop = true;
0902: }
0903: if (drop) {
0904: names.remove(i--);
0905: } else {
0906: m_mappedNames.put(name, elementName(name));
0907: }
0908: }
0909: for (Iterator iter = abstracts.iterator(); iter.hasNext();) {
0910: m_mappedNames.put((String) iter.next(), "");
0911: }
0912: for (Iterator iter = customs.keySet().iterator(); iter
0913: .hasNext();) {
0914: String name = (String) iter.next();
0915: m_mappedNames.put(name, elementName(name));
0916: }
0917:
0918: // create set for ignores
0919: m_ignoreNames.clear();
0920: m_ignoreNames.addAll(ignores);
0921:
0922: // create binding with optional namespace
0923: BindingElement binding = new BindingElement();
0924: binding.setStyleName("attribute");
0925: if (m_namespaceUri != null) {
0926: NamespaceElement namespace = new NamespaceElement();
0927: namespace
0928: .setComment(" namespace for all elements of binding ");
0929: namespace.setDefaultName("elements");
0930: namespace.setUri(m_namespaceUri);
0931: binding.addTopChild(namespace);
0932: }
0933:
0934: // add mapping for each specified class
0935: m_structureStack.clear();
0936: m_structureNames.clear();
0937: m_beanNames = beans;
0938: m_enumerationNames = enums;
0939: for (int i = 0; i < names.size(); i++) {
0940: String cname = (String) names.get(i);
0941: if (!abstracts.contains(cname)) {
0942: ClassFile cf = ClassCache.getClassFile(cname);
0943: MappingElement mapping = createMapping(cf, false);
0944: mapping.setComment(" generated mapping for class "
0945: + cname);
0946: binding.addTopChild(mapping);
0947: }
0948: }
0949: for (Iterator iter = abstracts.iterator(); iter.hasNext();) {
0950: String cname = (String) iter.next();
0951: ClassFile cf = ClassCache.getClassFile(cname);
0952: MappingElement mapping = createMapping(cf, true);
0953: mapping.setComment(" generate abstract mapping for class "
0954: + cname);
0955: binding.addTopChild(mapping);
0956: }
0957:
0958: // finish with custom mapping definitions
0959: for (Iterator iter = customs.keySet().iterator(); iter
0960: .hasNext();) {
0961: String cname = (String) iter.next();
0962: String mname = (String) customs.get(cname);
0963: MappingElement mapping = new MappingElement();
0964: mapping.setComment(" specified mapping for class " + cname);
0965: mapping.setClassName(cname);
0966: mapping.setName((String) m_mappedNames.get(cname));
0967: mapping.setMarshallerName(mname);
0968: mapping.setUnmarshallerName(mname);
0969: binding.addTopChild(mapping);
0970: }
0971:
0972: // list classes handled if verbose
0973: if (m_verbose) {
0974: for (int i = 0; i < names.size(); i++) {
0975: m_structureNames.add(names.get(i));
0976: }
0977: m_structureNames.addAll(customs.keySet());
0978: String[] classes = (String[]) m_structureNames
0979: .toArray(new String[m_structureNames.size()]);
0980: Arrays.sort(classes);
0981: System.out.println("\nClasses included in binding:");
0982: for (int i = 0; i < classes.length; i++) {
0983: System.out.println(" " + classes[i]);
0984: }
0985: }
0986: return binding;
0987: }
0988:
0989: /**
0990: * Main method for running compiler as application.
0991: *
0992: * @param args command line arguments
0993: */
0994: public static void main(String[] args) {
0995: if (args.length > 0) {
0996: try {
0997:
0998: // check for various flags set
0999: boolean mixed = false;
1000: boolean verbose = false;
1001: String uri = null;
1002: String fname = "binding.xml";
1003: ArrayList paths = new ArrayList();
1004: HashMap enums = new HashMap();
1005: HashSet abstracts = new HashSet();
1006: ArrayList ignores = new ArrayList();
1007: HashMap customs = new HashMap();
1008: HashMap beans = new HashMap();
1009: int offset = 0;
1010: for (; offset < args.length; offset++) {
1011: String arg = args[offset];
1012: if ("-v".equalsIgnoreCase(arg)) {
1013: verbose = true;
1014: } else if ("-b".equalsIgnoreCase(arg)) {
1015: String plist = args[++offset];
1016: int split = plist.indexOf("=");
1017: String bname = plist.substring(0, split);
1018: ArrayList props = new ArrayList();
1019: int base = ++split;
1020: while ((split = plist.indexOf(',', split)) >= 0) {
1021: props.add(plist.substring(base, split));
1022: base = ++split;
1023: }
1024: props.add(plist.substring(base));
1025: beans.put(bname, props);
1026: } else if ("-c".equalsIgnoreCase(arg)) {
1027: String custom = args[++offset];
1028: int split = custom.indexOf('=');
1029: if (split > 0) {
1030: customs.put(custom.substring(0, split),
1031: custom.substring(split + 1));
1032: } else {
1033: System.err
1034: .println("Unable to interpret customization "
1035: + custom);
1036: }
1037: } else if ("-e".equalsIgnoreCase(arg)) {
1038: String cname;
1039: String mname;
1040: String enumf = args[++offset];
1041: int split = enumf.indexOf('=');
1042: if (split > 0) {
1043: cname = enumf.substring(0, split);
1044: mname = enumf.substring(split + 1);
1045: if (mname.indexOf('.') < 0) {
1046: mname = cname + '.' + mname;
1047: }
1048: } else {
1049: cname = enumf;
1050: split = cname.lastIndexOf('.');
1051: if (split >= 0) {
1052: mname = cname + '.' + "get"
1053: + cname.substring(split + 1);
1054: } else {
1055: mname = cname + '.' + "get" + cname;
1056: }
1057: }
1058: enums.put(cname, mname);
1059: } else if ("-i".equalsIgnoreCase(arg)) {
1060: ignores.add(args[++offset]);
1061: } else if ("-m".equalsIgnoreCase(arg)) {
1062: mixed = true;
1063: } else if ("-f".equalsIgnoreCase(arg)) {
1064: fname = args[++offset];
1065: } else if ("-n".equalsIgnoreCase(arg)) {
1066: uri = args[++offset];
1067: } else if ("-p".equalsIgnoreCase(arg)) {
1068: paths.add(args[++offset]);
1069: } else if ("-a".equalsIgnoreCase(arg)) {
1070: abstracts.add(args[++offset]);
1071: } else {
1072: break;
1073: }
1074: }
1075:
1076: // set up path and binding lists
1077: String[] vmpaths = Utility.getClassPaths();
1078: for (int i = 0; i < vmpaths.length; i++) {
1079: paths.add(vmpaths[i]);
1080: }
1081: ArrayList names = new ArrayList();
1082: for (int i = offset; i < args.length; i++) {
1083: if (!abstracts.contains(args[i])) {
1084: names.add(args[i]);
1085: }
1086: }
1087:
1088: // report on the configuration
1089: if (verbose) {
1090: System.out.println("Using paths:");
1091: for (int i = 0; i < paths.size(); i++) {
1092: System.out.println(" " + paths.get(i));
1093: }
1094: System.out.println("Using class names:");
1095: for (int i = 0; i < names.size(); i++) {
1096: System.out.println(" " + names.get(i));
1097: }
1098: if (abstracts.size() > 0) {
1099: System.out
1100: .println("Using abstract class names:");
1101: for (Iterator iter = abstracts.iterator(); iter
1102: .hasNext();) {
1103: System.out.println(" " + iter.next());
1104: }
1105: }
1106: if (customs.size() > 0) {
1107: System.out
1108: .println("Using custom marshaller/unmarshallers:");
1109: Iterator iter = customs.keySet().iterator();
1110: while (iter.hasNext()) {
1111: String key = (String) iter.next();
1112: System.out.println(" " + customs.get(key)
1113: + " for " + key);
1114: }
1115: }
1116: if (beans.size() > 0) {
1117: System.out
1118: .println("Using bean property lists:");
1119: Iterator iter = beans.keySet().iterator();
1120: while (iter.hasNext()) {
1121: String key = (String) iter.next();
1122: System.out.print(" " + key
1123: + " with properties:");
1124: ArrayList props = (ArrayList) beans
1125: .get(key);
1126: for (int i = 0; i < props.size(); i++) {
1127: System.out.print(i > 0 ? ", " : " ");
1128: System.out.print(props.get(i));
1129: }
1130: }
1131: }
1132: if (enums.size() > 0) {
1133: System.out
1134: .println("Using enumeration classes:");
1135: Iterator iter = enums.keySet().iterator();
1136: while (iter.hasNext()) {
1137: String key = (String) iter.next();
1138: System.out.println(" " + key
1139: + " with deserializer "
1140: + enums.get(key));
1141: }
1142: }
1143: }
1144:
1145: // set paths to be used for loading referenced classes
1146: String[] parray = (String[]) paths
1147: .toArray(new String[paths.size()]);
1148: ClassCache.setPaths(parray);
1149: ClassFile.setPaths(parray);
1150:
1151: // generate the binding
1152: BindingGenerator generate = new BindingGenerator(
1153: verbose, mixed, uri);
1154: BindingElement binding = generate.generate(names,
1155: abstracts, customs, beans, enums, ignores);
1156:
1157: // marshal binding out to file
1158: IBindingFactory bfact = BindingDirectory
1159: .getFactory(BindingElement.class);
1160: IMarshallingContext mctx = bfact
1161: .createMarshallingContext();
1162: mctx.setIndent(2);
1163: mctx.marshalDocument(binding, "UTF-8", null,
1164: new FileOutputStream(fname));
1165:
1166: } catch (JiBXException ex) {
1167: ex.printStackTrace(System.out);
1168: System.exit(1);
1169: } catch (FileNotFoundException e) {
1170: e.printStackTrace(System.out);
1171: System.exit(2);
1172: }
1173:
1174: } else {
1175: System.out
1176: .println("\nUsage: java org.jibx.binding.BindingGenerator [options] "
1177: + "class1 class2 ...\nwhere options are:\n -a abstract class\n"
1178: + " -b bean property class,\n -c custom mapped class,\n"
1179: + " -e enumeration class,\n -f binding file name,\n"
1180: + " -i ignore class,\n -m use mixed case XML names (instead "
1181: + "of hyphenated),\n -n namespace URI,\n -p class "
1182: + "loading path component,\n -v turns on verbose output\n"
1183: + "The class# files are different classes to be included in "
1184: + "binding (references from\nthese classes will also be "
1185: + "included).\n");
1186: System.exit(1);
1187: }
1188: }
1189: }
|