/* * Copyright (c) 2007-2008, Dennis M. Sosnoski All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of * JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jibx.binding.generator; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.jibx.binding.model.BindingDirectory; import org.jibx.binding.model.BindingHolder; import org.jibx.binding.model.CollectionElement; import org.jibx.binding.model.ElementBase; import org.jibx.binding.model.IClass; import org.jibx.binding.model.MappingElement; import org.jibx.binding.model.NestingElementBase; import org.jibx.binding.model.StructureElement; import org.jibx.binding.model.StructureElementBase; import org.jibx.binding.model.ValueElement; import org.jibx.runtime.JiBXException; import org.jibx.runtime.QName; import org.jibx.runtime.Utility; import org.jibx.schema.codegen.ReferenceCountMap; import org.jibx.util.Types; /** * Binding generator implementation. * * @author Dennis M. Sosnoski */ public class BindingGenerator { /** Binding generation customizations. */ private final GlobalCustom m_global; /** Set of class names to be included. */ private final Set m_includeSet; /** Set of class names to be ignored. */ private final Set m_ignoreSet; /** Set of class names to be handled directly. */ private final Set m_directSet; /** Set of class names subclassed by other classes in binding. */ private final Set m_superSet; /** Map from fully-qualified class name to mapping details. */ private final Map m_mappingDetailsMap; /** Map from namespace URI to {@link UniqueNameSet} for type names. */ private final Map m_typeNamesMap; /** Map from namespace URI to {@link UniqueNameSet} for element names. */ private final Map m_elementNamesMap; /** Target package for binding code generation. */ private String m_targetPackage; /** Directory for bindings being built. */ private BindingDirectory m_directory; /** * Create a generator based on a particular set of customizations. * * @param glob */ public BindingGenerator(GlobalCustom glob) { m_global = glob; m_includeSet = new HashSet(); m_ignoreSet = new HashSet(); m_directSet = new HashSet(); m_superSet = new HashSet(); m_mappingDetailsMap = new HashMap(); m_typeNamesMap = new HashMap(); m_elementNamesMap = new HashMap(); m_directory = new BindingDirectory(glob.isForceClasses(), glob.isTrackSource(), glob.isAddConstructors(), glob.isInput(), glob.isOutput()); } /** * Check if a class represents a simple value. TODO: implement ClassCustom hooks for this purpose * * @param type fully qualified class name * @return true if simple value, false if not */ public boolean isValueClass(String type) { ClassCustom clas = m_global.forceClassCustomization(type); IClass sinfo = clas.getClassInformation().getSuperClass(); if (sinfo != null && "java.lang.Enum".equals(sinfo.getName())) { return true; } else { return false; } } /** * Check if a class needs to be included in the binding. This checks all members of the class and if necessary * superclasses, returning true if any member is ultimately found with a simple value. * * @param type fully qualified class name * @return true if class to be included in binding, false if it should be skipped */ public boolean checkInclude(String type) { if (m_includeSet.contains(type)) { return true; } else if (m_ignoreSet.contains(type)) { return false; } else { // check if any members are to be included boolean include = false; ClassCustom clas = m_global.forceClassCustomization(type); for (Iterator iter = clas.getMembers().iterator(); iter.hasNext();) { MemberCustom memb = (MemberCustom)iter.next(); String mtype = memb.isCollection() ? memb.getItemType() : memb.getWorkingType(); if (Types.isSimpleValue(mtype) || isValueClass(mtype) || !m_global.isKnownMapping(mtype) || checkInclude(mtype)) { include = true; break; } } if (!include) { // force superclass handling if appropriate if (clas.getMembers().size() > 0 && clas.isUseSuper()) { String stype = clas.getClassInformation().getSuperClass().getName(); if (!"java.lang.Object".equals(stype) && checkInclude(stype)) { include = true; } } } if (include) { m_includeSet.add(type); } else { m_ignoreSet.add(type); } return include; } } /** * Expand all references from a class. This checks the types of all members of the class, counting references and * calling itself recursively for any types which are not primitives and not java.* or javax.* classes. It also * expands the superclass, if specified by the class customizations. * * @param type fully qualified class name * @param refmap reference count map */ public void expandReferences(String type, ReferenceCountMap refmap) { if (checkInclude(type)) { // expand all references from members ClassCustom clas = m_global.forceClassCustomization(type); for (Iterator iter = clas.getMembers().iterator(); iter.hasNext();) { MemberCustom memb = (MemberCustom)iter.next(); String mtype = memb.isCollection() ? memb.getItemType() : memb.getWorkingType(); if (!Types.isSimpleValue(mtype) && !isValueClass(mtype) && !m_global.isKnownMapping(mtype)) { if ((mtype.startsWith("java.")) || (mtype.startsWith("javax."))) { throw new IllegalStateException("No way to handle type '" + mtype + '\''); } else if (checkInclude(mtype)) { if (refmap.incrementCount(mtype) <= 1) { expandReferences(mtype, refmap); } m_directSet.add(mtype); } } } // force superclass handling if appropriate if (clas.getMembers().size() > 0 && clas.isUseSuper()) { String stype = clas.getClassInformation().getSuperClass().getName(); if (!"java.lang.Object".equals(stype) && !Types.isSimpleValue(stype) && !isValueClass(stype) && !m_global.isKnownMapping(stype) && checkInclude(stype)) { if (refmap.incrementCount(stype) <= 1) { expandReferences(stype, refmap); } m_superSet.add(stype); } } } } /** * Set creation information for structure binding component. This includes the declared type, as well as the create * type and factory method. * * @param memb * @param struct */ private void setTypes(MemberCustom memb, StructureElementBase struct) { String type = memb.getActualType(); if (type != null && !type.equals(memb.getStatedType())) { struct.setDeclaredType(type); } struct.setCreateType(memb.getCreateType()); struct.setFactoryName(memb.getFactoryMethod()); } /** * Define the details of a collection binding. Collection bindings may be empty (in the case where the item type is * directly mapped), have a <value> child element, or have either a mapping reference or direct definition * <structure> child element. This determines the appropriate form based on the item type. * * @param itype item type * @param iname item name * @param coll * @param hold */ public void defineCollection(String itype, String iname, CollectionElement coll, BindingHolder hold) { // check item type handling BindingMappingDetail detail = (BindingMappingDetail)m_mappingDetailsMap.get(itype); ClassCustom icust = m_global.getClassCustomization(itype); if (detail != null) { if (detail.isUseAbstract() && !detail.isExtended()) { // add reference to appropriate binding QName qname = detail.getTypeQName(); hold.addReference(m_directory.findBinding(qname.getUri())); // add with map-as for abstract mapping if (iname == null) { iname = icust.getElementName(); } StructureElement struct = new StructureElement(); if (qname == null) { struct.setMapAsName(itype); } else { // set the type reference struct.setMapAsQName(qname); // check dependency on other binding m_directory.addDependency(qname.getUri(), hold); } struct.setName(iname); struct.setCreateType(icust.getCreateType()); struct.setFactoryName(icust.getFactoryMethod()); coll.addChild(struct); } else { // just set item-type for concrete mapping coll.setItemTypeName(itype); } } else if (m_global.isKnownMapping(itype)) { // just set item-type for known mapping coll.setItemTypeName(itype); } else if (Types.isSimpleValue(itype) || isValueClass(itype)) { // add for simple type, or enum type with optional value method ValueElement value = new ValueElement(); value.setName(iname); value.setDeclaredType(itype); coll.addChild(value); if (icust != null) { value.setDefaultText(icust.getEnumValueMethod()); } } else { // embed structure definition within the collection StructureElement struct = new StructureElement(); struct.setDeclaredType(itype); if (iname == null) { iname = icust.getElementName(); } struct.setName(iname); fillStructure(icust, null, null, struct, hold); coll.addChild(struct); } } /** * Add binding details for the actual members of a class, excluding any members which have been handled separately. * * @param cust class customization information * @param exmethmap map from property method names to be excluded to the corresponding property customizations * @param inmethmap map from property method names included in binding to the corresponding property customizations * (populated by this method, null if not needed) * @param parent containing binding component * @param hold binding holder */ private void addMemberBindings(ClassCustom cust, Map exmethmap, Map inmethmap, NestingElementBase parent, BindingHolder hold) { // generate binding component for each member of class for (Iterator iter = cust.getMembers().iterator(); iter.hasNext();) { // skip this member if excluded property MemberCustom memb = (MemberCustom)iter.next(); String gmeth = memb.getGetName(); String smeth = memb.getSetName(); if (memb.isProperty() && !memb.isPrivate()) { MemberCustom match = (MemberCustom)(gmeth != null ? exmethmap.get(gmeth) : exmethmap.get(smeth)); if (match == null) { // add methods for included property to map implemented for this binding if (inmethmap != null) { if (gmeth != null) { inmethmap.put(gmeth, memb); } if (smeth != null) { inmethmap.put(smeth, memb); } } } else { continue; } } // check for repeated item if (memb.isCollection()) { // create collection element for field or property CollectionElement coll = new CollectionElement(); if (memb.getFieldName() == null) { coll.setGetName(gmeth); coll.setSetName(memb.getSetName()); } else { coll.setFieldName(memb.getFieldName()); } setTypes(memb, coll); // set the element name, if defined String name = memb.getXmlName(); if (cust.isWrapCollections() && name != null) { coll.setName(name); } // check for optional value if (!memb.isRequired()) { coll.setUsageName("optional"); } // check for type defined String itype = memb.getItemType(); if (itype != null) { // set name to be used for items in collection String iname = memb.getItemName(); if (iname == null) { // use name based on item type String simple = itype; int split = simple.lastIndexOf('.'); if (split >= 0) { simple = simple.substring(0, split); } iname = cust.convertName(simple); } // define the collection details defineCollection(itype, iname, coll, hold); // check for special case of two unwrapped collections with same item-type and no children ArrayList siblings = parent.children(); if (siblings.size() > 0 && coll.children().size() == 0 && coll.getName() == null) { ElementBase sibling = (ElementBase)siblings.get(siblings.size()-1); if (sibling.type() == ElementBase.COLLECTION_ELEMENT) { CollectionElement lastcoll = (CollectionElement)sibling; if (lastcoll.children().size() == 0 && lastcoll.getName() == null && Utility.safeEquals(lastcoll.getItemTypeName(), coll.getItemTypeName())) { throw new IllegalStateException("Need to use wrapper element for collection member '" + memb.getBaseName() + "' of class " + cust.getName()); } } } } parent.addChild(coll); } else { // check whether value or structure String wtype = memb.getWorkingType(); if (Types.isSimpleValue(wtype) || isValueClass(wtype)) { // add for simple type or enum ValueElement value = new ValueElement(); if (memb.getFieldName() == null) { value.setGetName(gmeth); value.setSetName(memb.getSetName()); } else { value.setFieldName(memb.getFieldName()); } value.setName(memb.getXmlName()); // pass on optional value method for enum ClassCustom icust = m_global.getClassCustomization(wtype); if (icust != null) { value.setEnumValueName(icust.getEnumValueMethod()); } // check for optional value if (!memb.isRequired()) { value.setUsageName("optional"); } // configure added property values int style = memb.getStyle(); if (style == NestingBase.ATTRIBUTE_VALUE_STYLE) { value.setStyleName("attribute"); } else if (style == NestingBase.ELEMENT_VALUE_STYLE) { value.setStyleName("element"); } else { value.setStyleName("text"); } if (memb.getActualType() != null) { value.setDeclaredType(memb.getActualType()); } parent.addChild(value); } else { // create with name and access information StructureElement struct = new StructureElement(); if (memb.getFieldName() == null) { struct.setGetName(gmeth); struct.setSetName(memb.getSetName()); } else { struct.setFieldName(memb.getFieldName()); } setTypes(memb, struct); // check for optional value if (!memb.isRequired()) { struct.setUsageName("optional"); } // set details based on class handling ClassCustom mcust = m_global.getClassCustomization(memb.getWorkingType()); fillStructure(mcust, memb, null, struct, hold); parent.addChild(struct); } } } } /** * Add binding details for the full representation of a class. This includes superclasses, if configured, and makes * use of any appropriate <mapping> definitions as part of the binding. * * @param cust class customization information * @param memb member customization information (null if implicit reference, rather than member) * @param inmethmap map from property method names included in binding to the corresponding property customizations * (populated by this method, null if not needed) * @param struct structure element referencing the class * @param hold binding holder */ private void fillStructure(ClassCustom cust, MemberCustom memb, Map inmethmap, StructureElement struct, BindingHolder hold) { // check for an actual definition String type = cust.getName(); BindingMappingDetail detail = (BindingMappingDetail)m_mappingDetailsMap.get(type); if (detail != null) { // use existing definition for type if (detail.isUseAbstract() && !detail.isExtended()) { QName qname = detail.getTypeQName(); m_directory.addDependency(qname.getUri(), hold); struct.setMapAsQName(qname); if (memb != null) { struct.setName(memb.getXmlName()); } } else { struct.setMapAsName(cust.getName()); } if (inmethmap != null) { inmethmap.putAll(detail.getAccessMethodMap()); } } else if (!m_global.isKnownMapping(type)) { // add member reference information if (memb != null) { // check for type settings needed if (memb.getActualType() != null) { struct.setDeclaredType(memb.getActualType()); } if (memb.getCreateType() != null) { struct.setCreateType(memb.getCreateType()); } if (memb.getFactoryMethod() != null) { struct.setFactoryName(memb.getFactoryMethod()); } // add a name if an optional value if (!memb.isRequired()) { struct.setName(memb.getXmlName()); } } // check if need to use superclass in binding IClass clas = cust.getClassInformation(); IClass sclas = clas.getSuperClass(); String stype = sclas.getName(); Map exmethmap = Collections.EMPTY_MAP; if (checkInclude(stype)) { // check if any added content from this class ClassCustom scust = m_global.getClassCustomization(stype); exmethmap = new HashMap(); if (cust.getMembers().size() > 0) { // add child element for superclass, followed by content from this class StructureElement sstruct = new StructureElement(); sstruct.setDeclaredType(stype); fillStructure(scust, null, exmethmap, sstruct, hold); struct.addChild(sstruct); } else { // just replace class reference with superclass reference in existing struct.setDeclaredType(stype); fillStructure(scust, null, exmethmap, struct, hold); } } else { // check for interface with abstract mapping to reference String[] intfs = sclas.getInterfaces(); for (int i = 0; i < intfs.length; i++) { String intf = intfs[i]; BindingMappingDetail idetail = (BindingMappingDetail)m_mappingDetailsMap.get(intf); if (idetail != null && idetail.isUseAbstract() && idetail.getTypeQName() == null) { // reference abstract mapped interface struct = new StructureElement(); struct.setMapAsName(intf); exmethmap = idetail.getAccessMethodMap(); break; } } } // fill in content details from this class if (inmethmap != null) { inmethmap.putAll(exmethmap); } if (cust.getMembers().size() > 0) { addMemberBindings(cust, exmethmap, inmethmap, struct, hold); } } } /** * Add the <mapping> definition for a class to a binding. This creates either an abstract mapping with a type * name, or a concrete mapping with an element name, as determined by the passed-in mapping information. If the * class is a concrete mapping that extends or implements another class with an anonymous abstract mapping, the * created <mapping> will extend that base mapping. TODO: type substitution requires extending the binding * definition * * @param type fully qualified class name * @param detail mapping details */ private void addMapping(String type, BindingMappingDetail detail) { // create the basic mapping structure(s) ClassCustom cust = m_global.forceClassCustomization(type); MappingElement mainmapping = null; QName qname = null; MappingElement mapcon = null; IClass clas = cust.getClassInformation(); if (detail.isUseConcrete()) { // create concrete mapping mapcon = createMapping(type, cust); qname = detail.getElementQName(); mapcon.setName(qname.getName()); detail.setConcreteMapping(mapcon); // make "concrete" mapping abstract if no class creation possible if (clas.isAbstract() || clas.isInterface()) { mapcon.setAbstract(true); } // fill details directly to this mapping unless abstract also created mainmapping = mapcon; } MappingElement mapabs = null; if (detail.isUseAbstract()) { // create abstract mapping mapabs = createMapping(type, cust); mapabs.setAbstract(true); qname = detail.getTypeQName(); mapabs.setTypeQName(qname); detail.setAbstractMapping(mapabs); // use abstract mapping for details (concrete will reference this one) mainmapping = mapabs; } if (mainmapping != null) { // check for superclass mapping to be extended (do this first for // compatibility with schema type extension) BindingHolder hold = m_directory.findBinding(qname.getUri()); StructureElement struct = null; String ptype = detail.getExtendsType(); Map exmembmap = Collections.EMPTY_MAP; Map inmembmap = new HashMap(); if (ptype == null) { // not extending a base mapping, check if need to include superclass IClass parent = clas.getSuperClass(); if (cust.isUseSuper() && parent != null) { ptype = parent.getName(); if (checkInclude(ptype)) { struct = new StructureElement(); struct.setDeclaredType(ptype); ClassCustom scust = m_global.getClassCustomization(ptype); exmembmap = new HashMap(); fillStructure(scust, null, exmembmap, struct, hold); } } } else { // create reference to parent mapping struct = new StructureElement(); BindingMappingDetail pdetail = (BindingMappingDetail)m_mappingDetailsMap.get(ptype); if (!pdetail.isGenerated()) { addMapping(ptype, pdetail); } exmembmap = pdetail.getAccessMethodMap(); if (pdetail.isUseAbstract()) { // reference abstract mapped superclass QName tname = pdetail.getTypeQName(); if (tname == null) { throw new IllegalStateException("Internal error: unimplemented case of superclass " + ptype + " to be extended by subclass " + type + ", without an abstract "); } else { m_directory.addDependency(tname.getUri(), hold); struct.setMapAsQName(tname); } } else { // reference concrete mapped superclass struct.setMapAsName(ptype); } // set extension for child concrete mapping if (mapcon != null) { mapcon.setExtendsName(ptype); } } // add extension reference structure to mapping if (struct != null) { mainmapping.addChild(struct); } // add all details of class member handling to binding inmembmap.putAll(exmembmap); addMemberBindings(cust, exmembmap, inmembmap, mainmapping, hold); hold.addMapping(mainmapping); if (mapabs != null && mapcon != null) { // define content as structure reference to abstract mapping struct = new StructureElement(); QName tname = detail.getTypeQName(); struct.setMapAsQName(tname); mapcon.addChild(struct); hold.addMapping(mapcon); } // set the member property map for mapping detail.setAccessMethodMap(inmembmap); detail.setGenerated(true); } else { throw new IllegalStateException("Internal error: mapping detail for " + type + " with neither abstract nor concrete mapping"); } } /** * Create and initialize a <mapping> element. * * @param type * @param cust * @return mapping */ private MappingElement createMapping(String type, ClassCustom cust) { MappingElement mapabs; mapabs = new MappingElement(); mapabs.setClassName(type); mapabs.setCreateType(cust.getCreateType()); mapabs.setFactoryName(cust.getFactoryMethod()); return mapabs; } /** * Add the details for mapping a class. * TODO: should add checks for unique particle attribution rule - need to check for duplicate element names without * a required element in between, and if found alter the names after the first; note that duplicates may be from the * base type, or from a group; also need to make sure attribute names are unique * * @param abstr force abstract mapping flag * @param ename element name for concrete mapping (null if unspecified) * @param type fully-qualified class name * @return mapping details */ private BindingMappingDetail addMappingDetails(Boolean abstr, QName ename, String type) { // check for existing detail BindingMappingDetail detail = (BindingMappingDetail)m_mappingDetailsMap.get(type); if (detail == null) { // provide warning when interface used with field access ClassCustom cust = m_global.getClassCustomization(type); IClass sclas = cust.getClassInformation(); if (sclas.isInterface() && !cust.isPropertyAccess()) { System.out.println("Warning: generating mapping for interface " + type + " without checking properties - consider setting property-access='true'"); } // check if a superclass is base for extension String stype = null; Boolean isabs = null; while ((sclas = sclas.getSuperClass()) != null) { if (m_directSet.contains(sclas.getName())) { stype = sclas.getName(); isabs = Boolean.valueOf(sclas.isAbstract()); break; } } // TODO: really should check for multiple superclasses/interfaces to // be handled (and generate a warning, since they can't be handled) if (stype == null) { // check if an interface is base for extension sclas = cust.getClassInformation(); loop: while (sclas != null) { String[] intfs = sclas.getInterfaces(); for (int i = 0; i < intfs.length; i++) { String itype = intfs[i]; while (true) { if (m_directSet.contains(itype)) { stype = itype; isabs = Boolean.TRUE; break loop; } else { ClassCustom icust = m_global.getClassCustomization(itype); if (icust == null) { break; } else { // check for interface (should only ever be one) of interface String[] sintfs = icust.getClassInformation().getInterfaces(); if (sintfs.length > 0) { itype = sintfs[0]; } else { break; } } } } } sclas = sclas.getSuperClass(); } } if (stype != null) { BindingMappingDetail sdetail = addMappingDetails(isabs, null, stype); sdetail.setExtended(true); } // fix names if necessary to avoid conflicts QName tname = fixTypeName(cust.getTypeQName()); if (isQNameUsed(ename, m_elementNamesMap)) { throw new IllegalStateException("Internal error - attempting to create mapping with element name " + ename + " already used"); } else if (ename == null) { ename = cust.getElementQName(); } ename = fixElementName(ename); // create mapping details as specified boolean abs = (abstr == null) ? cust.isMapAbstract() : abstr.booleanValue(); detail = new BindingMappingDetail(type, tname, ename, stype); if (abs || !cust.isConcrete()) { detail.setUseAbstract(true); } else { detail.setUseConcrete(true); } m_mappingDetailsMap.put(type, detail); // force concrete mapping if extension or base for extension if (abs && (sclas != null || m_superSet.contains(type))) { detail.setUseConcrete(true); } // check if package usable as target for binding code if (m_targetPackage == null && cust.getClassInformation().isModifiable()) { m_targetPackage = ((PackageCustom)cust.getParent()).getName(); } } else if (abstr != null) { // mapping detail already generated, just make sure of variety if (abstr.booleanValue()) { detail.setUseAbstract(true); } else { detail.setUseConcrete(true); } } return detail; } /** * Check if a qualified name is already defined within a category of names. * * @param qname requested qualified name (null allowed, always returns false) * @param map namespace URI to {@link UniqueNameSet} map for category * @return true if used, false if not */ private boolean isQNameUsed(QName qname, Map map) { if (qname != null) { UniqueNameSet nameset = (UniqueNameSet)map.get(qname.getUri()); if (nameset != null) { return nameset.contains(qname.getName()); } } return false; } /** * Fix local name to be unique within the appropriate namespace for a category of names. * * @param qname requested qualified name (null allowed, always returns null) * @param map namespace URI to {@link UniqueNameSet} map for category * @return unique version of qualified name */ private QName fixQName(QName qname, Map map) { if (qname != null) { String uri = qname.getUri(); UniqueNameSet nameset = (UniqueNameSet)map.get(uri); if (nameset == null) { nameset = new UniqueNameSet(); map.put(uri, nameset); } String base = qname.getName(); String name = nameset.add(base); if (name.equals(base)) { return qname; } else { return new QName(uri, name); } } else { return null; } } /** * Fix element local name to be unique within the appropriate namespace. * * @param qname requested qualified name (null allowed, always returns null) * @return unique version of qualified name */ private QName fixElementName(QName qname) { return fixQName(qname, m_elementNamesMap); } /** * Fix type local name to be unique within the appropriate namespace. * * @param qname requested qualified name (null allowed, always returns null) * @return unique version of qualified name */ private QName fixTypeName(QName qname) { return fixQName(qname, m_typeNamesMap); } /** * Find closure of references from a supplied list of classes. References counted, and direct references are * accumulated for handling. The supplied list may include generic classes with type parameters. * * @param classes * @param refmap */ private void findReferences(List classes, ReferenceCountMap refmap) { for (int i = 0; i < classes.size(); i++) { String type = (String)classes.get(i); int split = type.indexOf('<'); if (split > 0) { type = type.substring(split + 1, type.length() - 1); } expandReferences(type, refmap); } } /** * Flag classes referenced more than once to be handled with <mapping> definitions. * * @param refmap */ private void flagMultipleReferences(ReferenceCountMap refmap) { for (Iterator iter = refmap.iterator(); iter.hasNext();) { String type = (String)iter.next(); if (refmap.getCount(type) > 1) { m_directSet.add(type); } } } /** * Add mapping details for classes referenced more than once. * * @param refmap */ private void addReferencedMappings(ReferenceCountMap refmap) { for (Iterator iter = refmap.iterator(); iter.hasNext();) { String type = (String)iter.next(); if (refmap.getCount(type) > 1 && !m_mappingDetailsMap.containsKey(type)) { addMappingDetails(null, null, type); } } } /** * Generate the mapping definitions for classes referenced more than once. * * @param refmap */ private void generateReferencedMappings(ReferenceCountMap refmap) { for (Iterator iter = refmap.iterator(); iter.hasNext();) { String type = (String)iter.next(); if (refmap.getCount(type) > 1) { BindingMappingDetail detail = (BindingMappingDetail)m_mappingDetailsMap.get(type); if (!detail.isGenerated()) { addMapping(type, detail); } } } } /** * Generate mappings for a list of classes. The mapping details must have been configured before this method is * called. * * @param classes */ private void generateMappings(List classes) { for (int i = 0; i < classes.size(); i++) { String type = (String)classes.get(i); BindingMappingDetail detail = (BindingMappingDetail)m_mappingDetailsMap.get(type); if (!detail.isGenerated()) { addMapping(type, detail); } } } /** * Fix the base classes that are to be used as extension types. This step is needed to generate the substitution * group structures for classes which are both referenced directly and extended by other classes. The method must be * called after all mapping details have been constucted but before the actual binding generation. */ private void fixBaseClasses() { for (Iterator iter = m_directSet.iterator(); iter.hasNext();) { String type = (String)iter.next(); BindingMappingDetail detail = (BindingMappingDetail)m_mappingDetailsMap.get(type); if (detail != null && detail.isExtended()) { detail.setUseConcrete(true); } } } /** * Generate binding(s) for a list of classes. This creates a <mapping> definition for each class in the list, and * either embeds <structure> definitions or creates separate <mapping>s for other classes referenced by these * classes. If all the classes use the same namespace only the binding for that namespace will be created; * otherwise, a separate binding will be created for each namespace. * * @param abstr force abstract mapping flag (determined by class customizations if null) * @param classes class list */ public void generate(Boolean abstr, List classes) { // start by expanding and counting references from supplied classes ReferenceCountMap refmap = new ReferenceCountMap(); m_directSet.addAll(classes); findReferences(classes, refmap); flagMultipleReferences(refmap); // set the classes to be handled with definitions for (int i = 0; i < classes.size(); i++) { addMappingDetails(abstr, null, (String)classes.get(i)); } addReferencedMappings(refmap); fixBaseClasses(); // generate the binding(s) generateMappings(classes); generateReferencedMappings(refmap); } /** * Generate binding(s) for lists of classes. This creates a <mapping> definition for each class in the lists, and * either embeds <structure> definitions or creates separate <mapping>s for other classes referenced by these * classes. If all the classes use the same namespace only the binding for that namespace will be created; * otherwise, a separate binding will be created for each namespace. * * @param qnames list of names for concrete mappings * @param concrs list of classes to be given concrete mappings * @param abstrs list of classes to be given abstract mappings */ public void generateSpecified(ArrayList qnames, List concrs, List abstrs) { // start by expanding and counting references from supplied classes ReferenceCountMap refmap = new ReferenceCountMap(); m_directSet.addAll(concrs); m_directSet.addAll(abstrs); findReferences(concrs, refmap); findReferences(abstrs, refmap); flagMultipleReferences(refmap); // set classes to be handled with mapping definitions for (int i = 0; i < concrs.size(); i++) { BindingMappingDetail detail = addMappingDetails(Boolean.FALSE, (QName)qnames.get(i), (String)concrs.get(i)); if (detail.getElementQName() == null) { detail.setElementQName(fixElementName((QName)qnames.get(i))); } } for (int i = 0; i < abstrs.size(); i++) { addMappingDetails(Boolean.TRUE, null, (String)abstrs.get(i)); } addReferencedMappings(refmap); fixBaseClasses(); // generate the binding(s) generateMappings(concrs); generateMappings(abstrs); generateReferencedMappings(refmap); } /** * Get the mapping details for a class. This method should only be used after the * {@link #generate(Boolean, List)} method has been called. * * @param type fully-qualified class name * @return mapping details, or null if none */ public BindingMappingDetail getMappingDetail(String type) { return (BindingMappingDetail)m_mappingDetailsMap.get(type); } /** * Find the binding to be used for a particular namespace. If this is the first time a particular namespace was * requested, a new binding will be created for that namespace and returned. This method just delegates to the * {@link org.jibx.binding.model.BindingDirectory} implementation. * * @param uri namespace URI (null if no namespace) * @return binding holder */ public BindingHolder findBinding(String uri) { return m_directory.findBinding(uri); } /** * Get the binding definition for a namespace. This method should only be used after the * {@link #generate(Boolean, List)} method has been called. It delegates to the * {@link org.jibx.binding.model.BindingDirectory} implementation. * * @param uri * @return binding holder, or null if none */ public BindingHolder getBinding(String uri) { return m_directory.getBinding(uri); } /** * Get the list of binding namespace URIs. * * @return namespaces */ public ArrayList getNamespaces() { return m_directory.getNamespaces(); } /** * Complete the generated bindings and write them as files. * * @param name * @param adduris * @param dir * @return root binding holder * @throws JiBXException on error in generated bindings * @throws IOException on error writing bindings */ public BindingHolder finish(String name, String[] adduris, File dir) throws JiBXException, IOException { m_directory.finish(); BindingHolder root = m_directory.configureFiles(name, adduris, m_targetPackage); m_directory.writeBindings(dir); return root; } /** * Run the binding generation using command line parameters. * * @param args * @throws JiBXException * @throws IOException */ public static void main(String[] args) throws JiBXException, IOException { BindingGeneratorCommandLine parms = new BindingGeneratorCommandLine(); if (args.length > 0 && parms.processArgs(args)) { // generate bindings for all namespaces BindingGenerator gen = new BindingGenerator(parms.getGlobal()); gen.generate(parms.getAbstract(), parms.getExtraArgs()); gen.finish(parms.getBindingName(), new String[0], parms.getGeneratePath()); } else { if (args.length > 0) { System.err.println("Terminating due to command line errors"); } else { parms.printUsage(); } System.exit(1); } } }