/* * Copyright (c) 2007, 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.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.jibx.binding.model.BindingElement; import org.jibx.binding.model.BindingHolder; import org.jibx.binding.model.CollectionElement; import org.jibx.binding.model.ContainerElementBase; import org.jibx.binding.model.IClass; import org.jibx.binding.model.IClassLocator; import org.jibx.binding.model.IComponent; import org.jibx.binding.model.MappingElement; import org.jibx.binding.model.ModelVisitor; import org.jibx.binding.model.StructureElement; import org.jibx.binding.model.StructureElementBase; import org.jibx.binding.model.TemplateElementBase; import org.jibx.binding.model.ValidationContext; import org.jibx.binding.model.ValueElement; import org.jibx.runtime.QName; import org.jibx.util.Types; /** * Directory for components included in schema generation. This includes both <mapping> elements of the bindings and * special formats, with the latter currently limited to enumeration types. The building code works from a supplied list * of bindings, walking the tree structure of the bindings to find all mappings and processing each mapping directly * using the lists of child components. It creates mapping details and sets flags for the types of access to each * mapping, as well as creating enumeration details and counting usage to select globals. * * @author Dennis M. Sosnoski */ public class SchemaDetailDirectory { /** Locator for class information (null if none). */ private final IClassLocator m_locator; /** Binding customization information. */ private final GlobalCustom m_custom; /** Validation context for bindings. */ private final ValidationContext m_context; /** Map from <mapping> definition to mapping detail. */ private final Map m_mappingMap; /** Map from class name to enumeration detail. */ private final Map m_enumMap; /** * Constructor. * * @param loc locator for class information (null if none) * @param custom binding customization information (used for creating names as needed) * @param vctx binding validation context */ public SchemaDetailDirectory(IClassLocator loc, GlobalCustom custom, ValidationContext vctx) { m_locator = loc; m_custom = custom; m_context = vctx; m_mappingMap = new HashMap(); m_enumMap = new HashMap(); } /** * Populate the mapping directory from a supplied list of root bindings. This uses a visitor to analyze the * bindings, building the detail information to be used during the actual generation process. * * @param holders */ public void populate(ArrayList holders) { AnalysisVisitor visitor = new AnalysisVisitor(m_context); for (int i = 0; i < holders.size(); i++) { BindingHolder holder = (BindingHolder)holders.get(i); m_context.tourTree(holder.getBinding(), visitor); } } /** * Check if a <structure> element represents a type derivation. If the element is empty, has no name or property, * is required, and is a mapping reference, then it can be handled as a type derivation. * * @param struct * @return true if a type derivation, false if not */ private static boolean isTypeDerivation(StructureElement struct) { return struct.children().size() == 0 && !struct.hasName() && !struct.hasProperty() && !struct.isOptional() && (struct.getDeclaredType() != null || struct.getEffectiveMapping() != null); } /** * Check if class is an enumeration type. * * @param clas * @return enumeration type flag */ private boolean isEnumeration(IClass clas) { return clas.isSuperclass("java.lang.Enum"); } /** * Check if class is a simple value type. * * @param clas * @return simple value type flag */ private boolean isSimpleValue(IClass clas) { return Types.isSimpleValue(clas.getName()); } /** * Count the usage of an enumeration type. The first time this is called for a type it just creates the enumeration * detail, then if called again for the same type it flags it as a global. * * @param type */ private void countEnumUsage(String type) { SchemaEnumDetail detail = (SchemaEnumDetail)m_enumMap.get(type); if (detail == null) { ClassCustom custom = m_custom.forceClassCustomization(type); detail = new SchemaEnumDetail(custom); m_enumMap.put(type, detail); } else { detail.setGlobal(true); } } /** * Check references to mappings or enumeration types from component children of binding container element. This * allows the starting offset for content children to be passed in, so that mappings with type extension components * can be handled (with the extension component processed separately, since it's a special case). * * @param cont container element * @param offset starting offset for content */ private void checkReferences(ContainerElementBase cont, int offset) { // process all child components of container ArrayList childs = cont.children(); for (int i = offset; i < childs.size(); i++) { Object child = childs.get(i); if (child instanceof ValueElement) { // check value reference to enumeration type IClass type = ((ValueElement)child).getType(); if (isEnumeration(type)) { countEnumUsage(type.getName()); } } else if (child instanceof CollectionElement) { // check for collection item type CollectionElement collect = (CollectionElement)child; IClass itype = collect.getItemTypeClass(); if (isEnumeration(itype)) { // collection items are enumeration type, count directly countEnumUsage(itype.getName()); } else if (!isSimpleValue(itype)) { // find implied mapping, if one defined String type = itype.getName(); TemplateElementBase ref = collect.getDefinitions().getSpecificTemplate(type); if (ref instanceof MappingElement) { MappingElement mapref = (MappingElement)ref; SchemaMappingDetail detail = forceMappingDetail(mapref); detail.setElement(true); } } // check for nested type reference(s) checkReferences(collect, 0); } else if (child instanceof StructureElement) { // structure, check for nested definition StructureElement struct = (StructureElement)child; if (struct.children().size() > 0) { // just handle references from nested components checkReferences(struct, 0); } else { // no nested definition, check for mapping reference MappingElement ref = (MappingElement)struct.getEffectiveMapping(); if (ref == null) { m_context.addError("No handling defined for empty structure with no mapping reference", struct); } else { // get the referenced mapping information SchemaMappingDetail detail = forceMappingDetail(ref); if (struct.getName() == null) { if (ref.isAbstract()) { // abstract inline treated as group detail.setGroup(true); } else { // concrete treated as element reference detail.setElement(true); } } else if (!ref.isAbstract()) { // concrete with name should never happen m_context.addError("No handling defined for name on concrete mapping reference", struct); } } } } } } /** * Create the detail information for a <mapping>. This creates the detail information and adds it to the map, * then analyzes the structure of the mapping to find references to other mappings and to enumeration types. * * @param map * @return detail */ private SchemaMappingDetail addDetail(MappingElement map) { // check structure of mapping definition for schema type extension MappingElement base = null; ArrayList contents = map.getContentComponents(); if (contents.size() > 0) { // type extension requires reference as first content component Object content = contents.get(0); if (content instanceof StructureElement) { StructureElement struct = (StructureElement)content; if (isTypeDerivation(struct)) { base = (MappingElement)struct.getEffectiveMapping(); } } } // next search recursively for text and/or child element components this is done at this point (with loop // recursion, if needed) so that the flags can be set when creating the detail instance boolean haschild = false; boolean hastext = false; ArrayList expands = new ArrayList(); expands.add(map); for (int i = 0; i < expands.size(); i++) { // check for container with element name or text content ContainerElementBase contain = (ContainerElementBase)expands.get(i); contents = contain.getContentComponents(); for (int j = 0; j < contents.size(); j++) { IComponent comp = (IComponent)contents.get(j); if (comp.hasName()) { // component with name means child element haschild = true; } else if (comp instanceof ValueElement) { // value with no name implies text content hastext = true; } else if (comp instanceof CollectionElement) { // collection implies child element (repeating) haschild = true; } else { // structure, check for mapping reference StructureElement struct = (StructureElement)comp; if (struct.children().size() > 0) { // add container structure to expansion list expands.add(comp); } else { MappingElement ref = (MappingElement)struct.getEffectiveMapping(); if (ref != null) { // mapping element reference, check if named if (ref.getName() != null) { haschild = true; } else { expands.add(ref); } } } } } } // get the names for this mapping ClassCustom custom = m_custom.forceClassCustomization(map.getClassName()); QName tname = map.getTypeQName(); if (tname == null) { tname = custom.getTypeQName(); } QName oname = null; String name = map.getName(); if (name == null) { oname = custom.getElementQName(); } else { oname = new QName(map.getNamespace().getUri(), name); } // add new mapping detail based on information found SchemaMappingDetail detail = new SchemaMappingDetail(map, haschild, hastext, base, tname, oname); m_mappingMap.put(map, detail); // check for mapping to element name if (map.getName() == null) { // require base type generation as type if (base != null) { SchemaMappingDetail basedetail = forceMappingDetail(base); basedetail.setType(true); } } else { // force generating an element for this mapping detail.setElement(true); } // error if mapping extension doesn't map to type extension MappingElement extended = map.getExtendsMapping(); if (extended != null) { // recursively check for extension type match boolean isext = false; MappingElement ancest = base; while (ancest != null) { if (ancest.getClassName().equals(extended.getClassName())) { isext = true; break; } else { ancest = forceMappingDetail(ancest).getExtensionBase(); } } if (isext) { // flag generation as element in substitution group SchemaMappingDetail extdetail = forceMappingDetail(extended); extdetail.setElement(true); detail.setSubstitution(extdetail.getOtherName()); detail.setElement(true); } else { m_context.addError("'extends' mapping not usable as schema extension base", map); } } // process all references from this mapping checkReferences(map, base == null ? 0 : 1); return detail; } /** * Find detail information for a <mapping>. If this is the first time a particular mapping was requested, a new * detail information will be created for that mapping and returned. * * @param map * @return detail */ protected SchemaMappingDetail forceMappingDetail(MappingElement map) { SchemaMappingDetail detail = (SchemaMappingDetail)m_mappingMap.get(map); if (detail == null) { detail = addDetail(map); } return detail; } /** * Get detail information for a <mapping>. If the detail information does not exist, this throws an exception. * * @param map * @return detail */ public SchemaMappingDetail getMappingDetail(MappingElement map) { SchemaMappingDetail detail = (SchemaMappingDetail)m_mappingMap.get(map); if (detail == null) { throw new IllegalStateException("Detail not found"); } else { return detail; } } /** * Get detail information for a simple type. If the detail information does not exist, this throws an exception. * * @param type * @return detail */ public SchemaEnumDetail getSimpleDetail(String type) { SchemaEnumDetail detail = (SchemaEnumDetail)m_enumMap.get(type); if (detail == null) { throw new IllegalStateException("Detail not found"); } else { return detail; } } /** * Get all complex type details. * * @return collection of {@link SchemaMappingDetail} */ public Collection getComplexDetails() { return m_mappingMap.values(); } /** * Get all simple type details. * * @return collection of {@link SchemaEnumDetail} */ public Collection getSimpleDetails() { return m_enumMap.values(); } /** * Model visitor for analyzing the structure of bindings and determining the appropriate schema components. */ public class AnalysisVisitor extends ModelVisitor { /** Validation context running this visitor. */ private final ValidationContext m_context; /** * Constructor. * * @param vctx validation context that will run this visitor */ public AnalysisVisitor(ValidationContext vctx) { m_context = vctx; } /** * Visit mapping element. This just adds the mapping definition, if not already added. * * @param node * @return expansion flag */ public boolean visit(MappingElement node) { // check for nested mapping if (!(m_context.getParentElement() instanceof BindingElement)) { m_context.addWarning("No schema equivalent for nested type definitions - converting to global type"); } // add the definition forceMappingDetail(node); return super.visit(node); } /** * Visit structure or collection element. This just stops the expansion, since the content of mapping * definitions is processed at the time the mapping is added. * * @param node * @return false to block further expansion */ public boolean visit(StructureElementBase node) { return false; } } }