001: /*
002: Copyright (c) 2004-2006, 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.model;
030:
031: import java.util.ArrayList;
032: import java.util.HashMap;
033:
034: import org.jibx.binding.def.NamespaceDefinition;
035: import org.jibx.runtime.JiBXException;
036:
037: /**
038: * Definition context information. This is used to track definitions of items
039: * that can be referenced by other items. The contexts are nested, so that names
040: * not found in a context may be defined by a containing context. The access
041: * methods take this into account, automatically delegating to the containing
042: * context (if defined) when a lookup fails.
043: *
044: * @author Dennis M. Sosnoski
045: * @version 1.0
046: */
047: public class DefinitionContext {
048: /** Link to containing definition context. */
049: private final DefinitionContext m_outerContext;
050:
051: /** Namespace used by default at this level for attributes. */
052: private NamespaceElement m_attributeDefault;
053:
054: /** Namespace used by default at this level for elements. */
055: private NamespaceElement m_elementDefault;
056:
057: /** Namespaces defined at level (lazy create). */
058: private ArrayList m_namespaces;
059:
060: /** Mapping from prefix to namespace definition (lazy create). */
061: private HashMap m_prefixMap;
062:
063: /** Mapping from URI to namespace definition (lazy create). */
064: private HashMap m_uriMap;
065:
066: /** Map from element names to mappings defined at level (lazy create). */
067: private HashMap m_mappingMap;
068:
069: /** Class hierarchy context for format definitions (lazy create). */
070: private ClassHierarchyContext m_formatContext;
071:
072: /** Class hierarchy context for template definitions (lazy create). */
073: private ClassHierarchyContext m_templateContext;
074:
075: /** Named binding components (lazy create). */
076: private HashMap m_namedStructureMap;
077:
078: /**
079: * Constructor.
080: *
081: * @param outer containing definition context (<code>null</code> if
082: * at root of tree)
083: */
084: protected DefinitionContext(DefinitionContext outer) {
085: m_outerContext = outer;
086: }
087:
088: /**
089: * Copy a context for use by an included binding. The duplicated context has
090: * the same containing context as the original, and shared reference
091: * structures for formats and templates, but only a static copy of the
092: * namespace definitions.
093: *
094: * @return context copy for included binding
095: */
096: /*package*/DefinitionContext getIncludeCopy() {
097: DefinitionContext dupl = new DefinitionContext(m_outerContext);
098: if (m_mappingMap == null) {
099: m_mappingMap = new HashMap();
100: }
101: dupl.m_mappingMap = m_mappingMap;
102: if (m_formatContext == null) {
103: m_formatContext = new ClassHierarchyContext(
104: getContainingFormatContext());
105: }
106: dupl.m_formatContext = m_formatContext;
107: if (m_templateContext == null) {
108: m_templateContext = new ClassHierarchyContext(
109: getContainingTemplateContext());
110: }
111: dupl.m_templateContext = m_templateContext;
112: return dupl;
113: }
114:
115: /**
116: * Inject namespaces from this context into another context. This is
117: * intended for includes, where the included binding inherits the
118: * namespace declarations of the containing binding.
119: *
120: * @param to
121: */
122: /*package*/void injectNamespaces(DefinitionContext to) {
123: to.m_attributeDefault = m_attributeDefault;
124: to.m_elementDefault = m_elementDefault;
125: if (m_namespaces != null) {
126: to.m_namespaces = new ArrayList(m_namespaces);
127: to.m_prefixMap = new HashMap(m_prefixMap);
128: to.m_uriMap = new HashMap(m_uriMap);
129: }
130: }
131:
132: /**
133: * Get containing context.
134: *
135: * @return containing context information (<code>null</code> if at root of
136: * tree)
137: */
138: public DefinitionContext getContaining() {
139: return m_outerContext;
140: }
141:
142: /**
143: * Get containing format context.
144: *
145: * @return innermost containing context for format definitions
146: * (<code>null</code> none defined)
147: */
148: private ClassHierarchyContext getContainingFormatContext() {
149: if (m_outerContext == null) {
150: return null;
151: } else {
152: return m_outerContext.getFormatContext();
153: }
154: }
155:
156: /**
157: * Get current format context.
158: *
159: * @return innermost context for format definitions (<code>null</code> none
160: * defined)
161: */
162: private ClassHierarchyContext getFormatContext() {
163: if (m_formatContext == null) {
164: return getContainingFormatContext();
165: } else {
166: return m_formatContext;
167: }
168: }
169:
170: /**
171: * Get containing template context.
172: *
173: * @return innermost containing context for template definitions
174: * (<code>null</code> none defined)
175: */
176: private ClassHierarchyContext getContainingTemplateContext() {
177: if (m_outerContext == null) {
178: return null;
179: } else {
180: return m_outerContext.getTemplateContext();
181: }
182: }
183:
184: /**
185: * Get current template context.
186: *
187: * @return innermost context for template definitions (<code>null</code> none
188: * defined)
189: */
190: private ClassHierarchyContext getTemplateContext() {
191: if (m_templateContext == null) {
192: return getContainingTemplateContext();
193: } else {
194: return m_templateContext;
195: }
196: }
197:
198: /**
199: * Add namespace to set defined at this level.
200: *
201: * @param def namespace definition element to be added
202: * @return problem information, or <code>null</code> if no problem
203: */
204: /* public ValidationProblem addNamespace(NamespaceElement def) {
205:
206: // initialize structures if first namespace definition
207: if (m_namespaces == null) {
208: m_namespaces = new ArrayList();
209: m_prefixMap = new HashMap();
210: m_uriMap = new HashMap();
211: }
212:
213: // check for conflict as default for attributes
214: if (def.isAttributeDefault()) {
215: if (m_attributeDefault == null) {
216: m_attributeDefault = def;
217: } else {
218: return new ValidationProblem
219: ("Conflicting attribute namespaces", def);
220: }
221: }
222:
223: // check for conflict as default for elements
224: if (def.isElementDefault()) {
225: if (m_elementDefault == null) {
226: m_elementDefault = def;
227: } else {
228: return new ValidationProblem
229: ("Conflicting element namespaces", def);
230: }
231: }
232:
233: // check for conflict on prefix
234: String prefix = def.getPrefix();
235: if (m_prefixMap.get(prefix) != null) {
236: return new ValidationProblem("Namespace prefix conflict", def);
237: }
238:
239: // check for duplicate definition of same URI
240: String uri = def.getUri();
241: Object prior = m_uriMap.get(uri);
242: if (prior != null && ((NamespaceElement)prior).getPrefix() != null) {
243: // TODO: is this needed? multiple prefixes should be allowed
244: return null;
245: }
246:
247: // add only if successful in all tests
248: m_namespaces.add(def);
249: m_prefixMap.put(prefix, def);
250: m_uriMap.put(uri, def);
251: return null;
252: } */
253:
254: /**
255: * Get namespace for prefix.
256: *
257: * @param prefix
258: * @return namespace definition in this context, <code>null</code> if none
259: */
260: public NamespaceElement getNamespaceForPrefix(String prefix) {
261: if (m_prefixMap == null) {
262: return null;
263: } else {
264: return (NamespaceElement) m_prefixMap.get(prefix);
265: }
266: }
267:
268: /**
269: * Check for namespace using the same prefix. This also intializes the
270: * namespace structures for this context the first time the method is
271: * called.
272: *
273: * @param def
274: * @return namespace definition using same prefix, <code>null</code> if none
275: */
276: private NamespaceElement checkDuplicatePrefix(NamespaceElement def) {
277:
278: // create structures if not already done
279: if (m_namespaces == null) {
280: m_namespaces = new ArrayList();
281: m_prefixMap = new HashMap();
282: m_uriMap = new HashMap();
283: }
284:
285: // check for conflict (or duplicate) on prefix
286: String prefix = def.getPrefix();
287: return (NamespaceElement) m_prefixMap.get(prefix);
288: }
289:
290: /**
291: * Add namespace to internal tables.
292: *
293: * @param def
294: */
295: private void internalAddNamespace(NamespaceElement def) {
296: String uri = def.getUri();
297: String prefix = def.getPrefix();
298: m_namespaces.add(def);
299: m_prefixMap.put(prefix, def);
300: m_uriMap.put(uri, def);
301: }
302:
303: /**
304: * Add namespace to set defined at this level.
305: *
306: * @param def namespace definition element to be added (duplicates ignored)
307: * @return problem information, or <code>null</code> if no problem
308: */
309: public ValidationProblem addNamespace(NamespaceElement def) {
310: String uri = def.getUri();
311: NamespaceElement dup = checkDuplicatePrefix(def);
312: if (dup == null) {
313:
314: // check for no definition of same URI with prefix
315: NamespaceElement prior = (NamespaceElement) m_uriMap
316: .get(uri);
317: if (prior == null || prior.getPrefix() == null) {
318:
319: // check for conflict as default for attributes
320: if (def.isAttributeDefault()) {
321: if (m_attributeDefault == null) {
322: m_attributeDefault = def;
323: } else {
324: return new ValidationProblem(
325: "Conflicting attribute namespaces", def);
326: }
327: }
328:
329: // check for conflict as default for elements
330: if (def.isElementDefault()) {
331: if (m_elementDefault == null) {
332: m_elementDefault = def;
333: } else {
334: return new ValidationProblem(
335: "Conflicting element namespaces", def);
336: }
337: }
338:
339: // no conflicts, add it
340: internalAddNamespace(def);
341: }
342:
343: } else if (!uri.equals(dup.getUri())) {
344: String prefix = def.getPrefix();
345: if (prefix == null) {
346: return new ValidationProblem(
347: "Default namespace conflict", def);
348: } else {
349: return new ValidationProblem(
350: "Namespace prefix conflict for prefix '"
351: + prefix + '\'', def);
352: }
353: }
354: return null;
355: }
356:
357: /**
358: * Add namespace declaration to set defined at this level. This method
359: * treats all namespaces as though they were declared with default="none".
360: *
361: * @param def namespace definition to be added (duplicates ignored)
362: * @param ref binding element referencing the namespace
363: * @return problem information, or <code>null</code> if no problem
364: */
365: public ValidationProblem addImpliedNamespace(NamespaceElement def,
366: ElementBase ref) {
367: String uri = def.getUri();
368: NamespaceElement dup = checkDuplicatePrefix(def);
369: DefinitionContext ctx = this ;
370: String prefix = def.getPrefix();
371: while (dup == null && (ctx = ctx.getContaining()) != null) {
372: dup = getNamespaceForPrefix(prefix);
373: }
374: if (dup == null) {
375:
376: // check for no definition of same URI with prefix
377: NamespaceElement prior = (NamespaceElement) m_uriMap
378: .get(uri);
379: if (prior == null || prior.getPrefix() == null) {
380:
381: // no conflicts, add it
382: internalAddNamespace(def);
383: }
384:
385: } else if (!uri.equals(dup.getUri())) {
386: if (prefix == null) {
387: return new ValidationProblem(
388: "Default namespace conflict on mapping reference",
389: ref);
390: } else {
391: return new ValidationProblem(
392: "Namespace conflict on mapping reference, for prefix '"
393: + prefix + '\'', ref);
394: }
395: }
396: return null;
397: }
398:
399: /**
400: * Get namespace definition for element name.
401: * TODO: handle multiple prefixes for namespace, proper screening
402: *
403: * @param name attribute group defining name
404: * @return namespace definition, or <code>null</code> if none that matches
405: */
406: public NamespaceElement getElementNamespace(NameAttributes name) {
407: String uri = name.getUri();
408: String prefix = name.getPrefix();
409: NamespaceElement ns = null;
410: if (uri != null) {
411: if (m_uriMap != null) {
412: ns = (NamespaceElement) m_uriMap.get(uri);
413: if (ns != null && prefix != null) {
414: if (!prefix.equals(ns.getPrefix())) {
415: ns = null;
416: }
417: }
418: }
419: } else if (prefix != null) {
420: if (m_prefixMap != null) {
421: ns = (NamespaceElement) m_prefixMap.get(prefix);
422: }
423: } else {
424: ns = m_elementDefault;
425: }
426: if (ns == null && m_outerContext != null) {
427: ns = m_outerContext.getElementNamespace(name);
428: }
429: return ns;
430: }
431:
432: /**
433: * Get namespace definition for attribute name.
434: * TODO: handle multiple prefixes for namespace, proper screening
435: *
436: * @param name attribute group defining name
437: * @return namespace definition, or <code>null</code> if none that matches
438: */
439: public NamespaceElement getAttributeNamespace(NameAttributes name) {
440: String uri = name.getUri();
441: String prefix = name.getPrefix();
442: NamespaceElement ns = null;
443: if (uri != null) {
444: if (m_uriMap != null) {
445: ns = (NamespaceElement) m_uriMap.get(uri);
446: if (ns != null && prefix != null) {
447: if (!prefix.equals(ns.getPrefix())) {
448: ns = null;
449: }
450: }
451: }
452: } else if (prefix != null) {
453: if (m_prefixMap != null) {
454: ns = (NamespaceElement) m_prefixMap.get(prefix);
455: }
456: } else {
457: ns = m_attributeDefault;
458: }
459: if (ns == null && m_outerContext != null) {
460: ns = m_outerContext.getAttributeNamespace(name);
461: }
462: return ns;
463: }
464:
465: /**
466: * Add format to set defined at this level.
467: *
468: * @param def format definition element to be added
469: * @param vctx validation context in use
470: */
471: public void addFormat(FormatElement def, ValidationContext vctx) {
472: if (m_formatContext == null) {
473: m_formatContext = new ClassHierarchyContext(
474: getContainingFormatContext());
475: }
476: if (def.isDefaultFormat()) {
477: IClass clas = def.getType();
478: m_formatContext.addTypedComponent(clas, def, vctx);
479: }
480: String label = def.getLabel();
481: if (label != null) {
482: m_formatContext.addNamedComponent(label, def, vctx);
483: }
484: }
485:
486: /**
487: * Get specific format definition for type. Finds with an exact match
488: * on the class name, checking the containing definitions if a format
489: * is not found at this level.
490: *
491: * @param type fully qualified class name to be converted
492: * @return conversion definition for class, or <code>null</code> if not
493: * found
494: */
495: public FormatElement getSpecificFormat(String type) {
496: ClassHierarchyContext ctx = getFormatContext();
497: if (ctx == null) {
498: return null;
499: } else {
500: return (FormatElement) ctx.getSpecificComponent(type);
501: }
502: }
503:
504: /**
505: * Get named format definition. Finds the format with the supplied
506: * name, checking the containing definitions if the format is not found
507: * at this level.
508: *
509: * @param name conversion name to be found
510: * @return conversion definition with specified name, or <code>null</code>
511: * if no conversion with that name
512: */
513: public FormatElement getNamedFormat(String name) {
514: ClassHierarchyContext ctx = getFormatContext();
515: if (ctx == null) {
516: return null;
517: } else {
518: return (FormatElement) ctx.getNamedComponent(name);
519: }
520: }
521:
522: /**
523: * Get best format definition for class. Finds the format based on the
524: * inheritance hierarchy for the supplied class. If a specific format for
525: * the actual class is not found (either in this or a containing level) this
526: * returns the most specific superclass format.
527: *
528: * @param clas information for target conversion class
529: * @return conversion definition for class, or <code>null</code> if no
530: * compatible conversion defined
531: */
532: public FormatElement getBestFormat(IClass clas) {
533: ClassHierarchyContext ctx = getFormatContext();
534: if (ctx == null) {
535: return null;
536: } else {
537: return (FormatElement) ctx.getMostSpecificComponent(clas);
538: }
539: }
540:
541: /**
542: * Add mapped name to set defined at this level.
543: *
544: * @param name mapped name
545: * @param def mapping definition
546: * @param vctx validation context
547: */
548: public void addMappedName(NameAttributes name, MappingElement def,
549: ValidationContext vctx) {
550: if (m_mappingMap == null) {
551: m_mappingMap = new HashMap();
552: }
553: if (m_mappingMap.containsKey(name)) {
554: if (vctx.isInBinding()) {
555: vctx
556: .addError("Duplicate mapping name not allowed for unmarshalling");
557: }
558: } else {
559: m_mappingMap.put(name, def);
560: }
561: }
562:
563: /**
564: * Add template or mapping to set defined at this level.
565: *
566: * @param def template definition element to be added
567: * @param vctx validation context in use
568: */
569: public void addTemplate(TemplateElementBase def,
570: ValidationContext vctx) {
571: if (m_templateContext == null) {
572: m_templateContext = new ClassHierarchyContext(
573: getContainingTemplateContext());
574: }
575: if (def.isDefaultTemplate()) {
576: IClass clas = def.getHandledClass();
577: m_templateContext.addTypedComponent(clas, def, vctx);
578: }
579: if (def instanceof TemplateElement) {
580: TemplateElement tdef = (TemplateElement) def;
581: if (tdef.getLabel() != null) {
582: m_templateContext.addNamedComponent(tdef.getLabel(),
583: def, vctx);
584: }
585: } else {
586: // TODO: Remove for 2.0
587: MappingElement mdef = (MappingElement) def;
588: if (mdef.getTypeName() != null) {
589: m_templateContext.addNamedComponent(mdef.getTypeName(),
590: def, vctx);
591: }
592: }
593: }
594:
595: /**
596: * Get specific template definition for type. Finds with an exact match
597: * on the class name, checking the containing definitions if a template
598: * is not found at this level.
599: *
600: * @param type fully qualified class name to be converted
601: * @return template definition for type, or <code>null</code> if not
602: * found
603: */
604: public TemplateElementBase getSpecificTemplate(String type) {
605: ClassHierarchyContext ctx = getTemplateContext();
606: if (ctx == null) {
607: return null;
608: } else {
609: return (TemplateElementBase) ctx.getSpecificComponent(type);
610: }
611: }
612:
613: /**
614: * Get named template definition. Finds the template with the supplied
615: * name, checking the containing definitions if the template is not found
616: * at this level.
617: * TODO: Make this specific to TemplateElement in 2.0
618: *
619: * @param name conversion name to be found
620: * @return template definition for class, or <code>null</code> if no
621: * template with that name
622: */
623: public TemplateElementBase getNamedTemplate(String name) {
624: ClassHierarchyContext ctx = getTemplateContext();
625: if (ctx == null) {
626: return null;
627: } else {
628: return (TemplateElementBase) ctx.getNamedComponent(name);
629: }
630: }
631:
632: /**
633: * Checks if a class is compatible with one or more templates. This checks
634: * based on the inheritance hierarchy for the supplied class, looks for the
635: * class or interface itself as well as any subclasses or implementations.
636: *
637: * @param clas information for target class
638: * @return <code>true</code> if compatible type, <code>false</code> if not
639: */
640: public boolean isCompatibleTemplateType(IClass clas) {
641: ClassHierarchyContext chctx = getTemplateContext();
642: if (chctx == null) {
643: return false;
644: } else {
645: return chctx.isCompatibleType(clas);
646: }
647: }
648:
649: /**
650: * Add named structure to set defined in this context. For named structures
651: * only the definition context associated with the binding element should be
652: * used. This is a kludge, but will go away in 2.0.
653: *
654: * @param def structure definition
655: * @return problem information, or <code>null</code> if no problem
656: */
657: public ValidationProblem addNamedStructure(ContainerElementBase def) {
658:
659: // create structure if not already done
660: if (m_namedStructureMap == null) {
661: m_namedStructureMap = new HashMap();
662: }
663:
664: // check for conflict on label before adding to definitions
665: String label = def.getLabel();
666: if (m_namedStructureMap.get(label) == null) {
667: m_namedStructureMap.put(label, def);
668: return null;
669: } else {
670: return new ValidationProblem("Duplicate label \"" + label
671: + '"', def);
672: }
673: }
674:
675: /**
676: * Get labeled structure definition within this context. For named
677: * structures only the definition context associated with the binding
678: * element should be used. This is a kludge, but will go away in 2.0.
679: *
680: * @param label structure definition label
681: * @return structure definition with specified label, or <code>null</code>
682: * if not defined
683: */
684: public ContainerElementBase getNamedStructure(String label) {
685: if (m_namedStructureMap == null) {
686: return null;
687: } else {
688: return (ContainerElementBase) m_namedStructureMap
689: .get(label);
690: }
691: }
692:
693: /**
694: * Get the namespaces defined in this context
695: *
696: * @return namespace definitions (may be <code>null</code> if none)
697: */
698: public ArrayList getNamespaces() {
699: return m_namespaces;
700: }
701: }
|