001: /*
002: Copyright (c) 2007, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.binding.generator;
030:
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.HashMap;
034: import java.util.Map;
035:
036: import org.jibx.binding.model.BindingElement;
037: import org.jibx.binding.model.CollectionElement;
038: import org.jibx.binding.model.ContainerElementBase;
039: import org.jibx.binding.model.IClass;
040: import org.jibx.binding.model.IClassLocator;
041: import org.jibx.binding.model.IComponent;
042: import org.jibx.binding.model.MappingElement;
043: import org.jibx.binding.model.ModelVisitor;
044: import org.jibx.binding.model.StructureElement;
045: import org.jibx.binding.model.StructureElementBase;
046: import org.jibx.binding.model.TemplateElementBase;
047: import org.jibx.binding.model.ValidationContext;
048: import org.jibx.binding.model.ValueElement;
049: import org.jibx.runtime.QName;
050: import org.jibx.util.Types;
051:
052: /**
053: * Directory for components included in schema generation. This includes both
054: * <mapping> elements of the bindings and special formats, with the latter
055: * currently limited to enumeration types. The building code works from a
056: * supplied list of bindings, walking the tree structure of the bindings to find
057: * all mappings and processing each mapping directly using the lists of child
058: * components. It creates mapping details and sets flags for the types of access
059: * to each mapping, as well as creating enumeration details and counting usage
060: * to select globals.
061: */
062: public class SchemaDetailDirectory {
063: /** Locator for class information (<code>null</code> if none). */
064: private final IClassLocator m_locator;
065:
066: /** Binding customization information. */
067: private final GlobalCustom m_custom;
068:
069: /** Validation context for bindings. */
070: private final ValidationContext m_context;
071:
072: /** Map from <mapping> definition to mapping detail. */
073: private final Map m_mappingMap;
074:
075: /** Map from class name to enumeration detail. */
076: private final Map m_enumMap;
077:
078: /**
079: * Constructor.
080: *
081: * @param loc locator for class information (<code>null</code> if none)
082: * @param custom binding customization information (used for creating names
083: * as needed)
084: * @param vctx binding validation context
085: */
086: public SchemaDetailDirectory(IClassLocator loc,
087: GlobalCustom custom, ValidationContext vctx) {
088: m_locator = loc;
089: m_custom = custom;
090: m_context = vctx;
091: m_mappingMap = new HashMap();
092: m_enumMap = new HashMap();
093: }
094:
095: /**
096: * Populate the mapping directory from a supplied list of root bindings.
097: * This uses a visitor to analyze the bindings, building the detail
098: * information to be used during the actual generation process.
099: *
100: * @param holders
101: */
102: public void populate(ArrayList holders) {
103: AnalysisVisitor visitor = new AnalysisVisitor(m_context);
104: for (int i = 0; i < holders.size(); i++) {
105: BindingHolder holder = (BindingHolder) holders.get(i);
106: m_context.tourTree(holder.getBinding(), visitor);
107: }
108: }
109:
110: /**
111: * Check if a <structure> element represents a type derivation. If the
112: * element is empty, has no name or property, is required, and is a mapping
113: * reference, then it can be handled as a type derivation.
114: *
115: * @param struct
116: * @return <code>true</code> if a type derivation, <code>false</code> if not
117: */
118: private static boolean isTypeDerivation(StructureElement struct) {
119: return struct.children().size() == 0
120: && !struct.hasName()
121: && !struct.hasProperty()
122: && !struct.isOptional()
123: && (struct.getDeclaredType() != null || struct
124: .getEffectiveMapping() != null);
125: }
126:
127: /**
128: * Check if class is an enumeration type.
129: *
130: * @param clas
131: * @return enumeration type flag
132: */
133: private boolean isEnumeration(IClass clas) {
134: return clas.isSuperclass("java.lang.Enum");
135: }
136:
137: /**
138: * Check if class is a simple value type.
139: *
140: * @param clas
141: * @return simple value type flag
142: */
143: private boolean isSimpleValue(IClass clas) {
144: return Types.isSimpleValue(clas.getName());
145: }
146:
147: /**
148: * Count the usage of an enumeration type. The first time this is called for
149: * a type it just creates the enumeration detail, then if called again for
150: * the same type it flags it as a global.
151: *
152: * @param type
153: */
154: private void countEnumUsage(String type) {
155: SchemaEnumDetail detail = (SchemaEnumDetail) m_enumMap
156: .get(type);
157: if (detail == null) {
158: ClassCustom custom = m_custom.forceClassCustomization(type);
159: detail = new SchemaEnumDetail(custom);
160: m_enumMap.put(type, detail);
161: } else {
162: detail.setGlobal(true);
163: }
164: }
165:
166: /**
167: * Check references to mappings or enumeration types from component children
168: * of binding container element. This allows the starting offset for content
169: * children to be passed in, so that mappings with type extension components
170: * can be handled (with the extension component processed separately, since
171: * it's a special case).
172: *
173: * @param cont container element
174: * @param offset starting offset for content
175: */
176: private void checkReferences(ContainerElementBase cont, int offset) {
177:
178: // first process content components of container
179: ArrayList contents = cont.getContentComponents();
180: for (int i = offset; i < contents.size(); i++) {
181: IComponent comp = (IComponent) contents.get(i);
182: if (comp instanceof ValueElement) {
183:
184: // check value reference to enumeration type
185: IClass type = ((ValueElement) comp).getType();
186: if (isEnumeration(type)) {
187: countEnumUsage(type.getName());
188: }
189:
190: } else if (comp instanceof CollectionElement) {
191:
192: // check for collection item type
193: CollectionElement collect = (CollectionElement) comp;
194: IClass itype = collect.getItemTypeClass();
195: if (isEnumeration(itype)) {
196:
197: // collection items are enumeration type, count directly
198: countEnumUsage(itype.getName());
199:
200: } else if (!isSimpleValue(itype)) {
201:
202: // find implied mapping, if one defined
203: String type = itype.getName();
204: TemplateElementBase ref = collect.getDefinitions()
205: .getSpecificTemplate(type);
206: if (ref instanceof MappingElement) {
207: MappingElement mapref = (MappingElement) ref;
208: SchemaMappingDetail detail = forceMappingDetail(mapref);
209: detail.setElement(true);
210: }
211: }
212:
213: // check for nested type reference(s)
214: checkReferences(collect, 0);
215:
216: } else {
217:
218: // structure, check for nested definition
219: StructureElement struct = (StructureElement) comp;
220: if (struct.children().size() > 0) {
221:
222: // just handle references from nested components
223: checkReferences(struct, 0);
224:
225: } else {
226:
227: // no nested definition, check for mapping reference
228: MappingElement ref = (MappingElement) struct
229: .getEffectiveMapping();
230: if (ref == null) {
231: m_context
232: .addError(
233: "No handling defined for empty structure with no mapping reference",
234: struct);
235: } else {
236:
237: // get the referenced mapping information
238: SchemaMappingDetail detail = forceMappingDetail(ref);
239: if (struct.getName() == null) {
240: if (ref.isAbstract()) {
241:
242: // abstract inline treated as group
243: detail.setGroup(true);
244:
245: } else {
246:
247: // concrete treated as element reference
248: detail.setElement(true);
249:
250: }
251: } else if (!ref.isAbstract()) {
252:
253: // concrete with name should never happen
254: m_context
255: .addError(
256: "No handling defined for name on concrete mapping reference",
257: struct);
258:
259: }
260: }
261: }
262: }
263: }
264: }
265:
266: /**
267: * Create the detail information for a <mapping>. This creates the detail
268: * information and adds it to the map, then analyzes the structure of the
269: * mapping to find references to other mappings and to enumeration types.
270: *
271: * @param map
272: * @return detail
273: */
274: private SchemaMappingDetail addDetail(MappingElement map) {
275:
276: // check structure of mapping definition for schema type extension
277: MappingElement base = null;
278: ArrayList contents = map.getContentComponents();
279: if (contents.size() > 0) {
280:
281: // type extension requires reference as first content component
282: Object content = contents.get(0);
283: if (content instanceof StructureElement) {
284: StructureElement struct = (StructureElement) content;
285: if (isTypeDerivation(struct)) {
286: base = (MappingElement) struct
287: .getEffectiveMapping();
288: }
289: }
290: }
291:
292: // next search recursively for text and/or child element components
293: // this is done at this point (with loop recursion, if needed) so
294: // that the flags can be set when creating the detail instance
295: boolean haschild = false;
296: boolean hastext = false;
297: ArrayList expands = new ArrayList();
298: expands.add(map);
299: for (int i = 0; i < expands.size(); i++) {
300:
301: // check for container with element name or text content
302: ContainerElementBase contain = (ContainerElementBase) expands
303: .get(i);
304: contents = contain.getContentComponents();
305: for (int j = 0; j < contents.size(); j++) {
306: IComponent comp = (IComponent) contents.get(j);
307: if (comp.hasName()) {
308:
309: // component with name means child element
310: haschild = true;
311:
312: } else if (comp instanceof ValueElement) {
313:
314: // value with no name implies text content
315: hastext = true;
316:
317: } else if (comp instanceof CollectionElement) {
318:
319: // collection implies child element (repeating)
320: haschild = true;
321:
322: } else {
323:
324: // structure, check for mapping reference
325: StructureElement struct = (StructureElement) comp;
326: if (struct.children().size() > 0) {
327:
328: // add container structure to expansion list
329: expands.add(comp);
330:
331: } else {
332: MappingElement ref = (MappingElement) struct
333: .getEffectiveMapping();
334: if (ref != null) {
335:
336: // mapping element reference, check if named
337: if (ref.getName() != null) {
338: haschild = true;
339: } else {
340: expands.add(ref);
341: }
342: }
343: }
344: }
345: }
346: }
347:
348: // get the names for this mapping
349: ClassCustom custom = m_custom.forceClassCustomization(map
350: .getClassName());
351: QName tname = map.getTypeQName();
352: if (tname == null) {
353: tname = custom.getTypeQName();
354: }
355: QName oname = null;
356: String name = map.getName();
357: if (name == null) {
358: oname = custom.getElementQName();
359: } else {
360: oname = new QName(map.getNamespace().getUri(), name);
361: }
362:
363: // add new mapping detail based on information found
364: SchemaMappingDetail detail = new SchemaMappingDetail(map,
365: haschild, hastext, base, tname, oname);
366: m_mappingMap.put(map, detail);
367:
368: // check for mapping to element name
369: if (map.getName() == null) {
370:
371: // require base type generation as type
372: if (base != null) {
373: SchemaMappingDetail basedetail = forceMappingDetail(base);
374: basedetail.setType(true);
375: }
376:
377: } else {
378:
379: // force generating an element for this mapping
380: detail.setElement(true);
381: }
382:
383: // error if mapping extension doesn't map to type extension
384: MappingElement extended = map.getExtendsMapping();
385: if (extended != null) {
386: if (base == null
387: || !base.getClassName().equals(
388: extended.getClassName())) {
389: m_context
390: .addError(
391: "'extends' mapping not usable as schema extension base",
392: map);
393: } else {
394:
395: // flag generation as element in substitution group
396: SchemaMappingDetail extdetail = forceMappingDetail(extended);
397: extdetail.setElement(true);
398: detail.setSubstitution(extdetail.getOtherName());
399: detail.setElement(true);
400: }
401: }
402:
403: // process all references from this mapping
404: checkReferences(map, base == null ? 0 : 1);
405: return detail;
406: }
407:
408: /**
409: * Find detail information for a <mapping>. If this is the first time a
410: * particular mapping was requested, a new detail information will be
411: * created for that mapping and returned.
412: *
413: * @param map
414: * @return detail
415: */
416: protected SchemaMappingDetail forceMappingDetail(MappingElement map) {
417: SchemaMappingDetail detail = (SchemaMappingDetail) m_mappingMap
418: .get(map);
419: if (detail == null) {
420: detail = addDetail(map);
421: }
422: return detail;
423: }
424:
425: /**
426: * Get detail information for a <mapping>. If the detail information
427: * does not exist, this throws an exception.
428: *
429: * @param map
430: * @return detail
431: */
432: public SchemaMappingDetail getMappingDetail(MappingElement map) {
433: SchemaMappingDetail detail = (SchemaMappingDetail) m_mappingMap
434: .get(map);
435: if (detail == null) {
436: throw new IllegalStateException("Detail not found");
437: } else {
438: return detail;
439: }
440: }
441:
442: /**
443: * Get detail information for a simple type. If the detail information
444: * does not exist, this throws an exception.
445: *
446: * @param type
447: * @return detail
448: */
449: public SchemaEnumDetail getSimpleDetail(String type) {
450: SchemaEnumDetail detail = (SchemaEnumDetail) m_enumMap
451: .get(type);
452: if (detail == null) {
453: throw new IllegalStateException("Detail not found");
454: } else {
455: return detail;
456: }
457: }
458:
459: /**
460: * Get all complex type details.
461: *
462: * @return collection of {@link SchemaMappingDetail}
463: */
464: public Collection getComplexDetails() {
465: return m_mappingMap.values();
466: }
467:
468: /**
469: * Get all simple type details.
470: *
471: * @return collection of {@link SchemaEnumDetail}
472: */
473: public Collection getSimpleDetails() {
474: return m_enumMap.values();
475: }
476:
477: /**
478: * Model visitor for analyzing the structure of bindings and determining the
479: * appropriate schema components.
480: */
481: public class AnalysisVisitor extends ModelVisitor {
482: /** Validation context running this visitor. */
483: private final ValidationContext m_context;
484:
485: /**
486: * Constructor.
487: *
488: * @param vctx validation context that will run this visitor
489: */
490: public AnalysisVisitor(ValidationContext vctx) {
491: m_context = vctx;
492: }
493:
494: /**
495: * Visit mapping element. This just adds the mapping definition, if not
496: * already added.
497: *
498: * @param node
499: * @return expansion flag
500: */
501: public boolean visit(MappingElement node) {
502:
503: // check for nested mapping
504: if (!(m_context.getParentElement() instanceof BindingElement)) {
505: m_context
506: .addWarning("No schema equivalent for nested type definitions - converting to global type");
507: }
508:
509: // add the definition
510: forceMappingDetail(node);
511: return super .visit(node);
512: }
513:
514: /**
515: * Visit structure or collection element. This just stops the expansion,
516: * since the content of mapping definitions is processed at the time the
517: * mapping is added.
518: *
519: * @param node
520: * @return <code>false</code> to block further expansion
521: */
522: public boolean visit(StructureElementBase node) {
523: return false;
524: }
525: }
526: }
|