001: /*
002: Copyright (c) 2004-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.model;
030:
031: import java.util.ArrayList;
032:
033: import org.jibx.binding.util.StringArray;
034: import org.jibx.runtime.IMarshallingContext;
035: import org.jibx.runtime.IUnmarshallingContext;
036: import org.jibx.runtime.JiBXException;
037: import org.jibx.runtime.QName;
038:
039: /**
040: * Model component for <b>structure</b> element of binding definition.
041: *
042: * @author Dennis M. Sosnoski
043: */
044: public class StructureElement extends StructureElementBase {
045: /** Enumeration of allowed attribute names */
046: public static final StringArray s_allowedAttributes = new StringArray(
047: new String[] { "map-as" },
048: StructureElementBase.s_allowedAttributes);
049:
050: /** Mapping type name to use for this object. */
051: private String m_mapAsName;
052:
053: /** Mapping qualified type name to use for this object. */
054: private QName m_mapAsQName;
055:
056: /** Flag for structure has a concrete mapping, possibly indeterminant. */
057: private boolean m_hasMappingName;
058:
059: /** Binding to use for this object. */
060: private TemplateElementBase m_effectiveMapping;
061:
062: /**
063: * Default constructor.
064: */
065: public StructureElement() {
066: super (STRUCTURE_ELEMENT);
067: }
068:
069: /**
070: * Get name of mapping type.
071: *
072: * @return mapping type name (or <code>null</code> if none)
073: */
074: public String getMapAsName() {
075: return m_mapAsName;
076: }
077:
078: /**
079: * Set name of mapping type. This method changes the qualified name to
080: * match the mapping type.
081: *
082: * @param name mapping type name (or <code>null</code> if none)
083: */
084: public void setMapAsName(String name) {
085: m_mapAsName = name;
086: m_mapAsQName = (name == null) ? null : new QName(name);
087: }
088:
089: /**
090: * Get qualified name of mapping type.
091: *
092: * @return mapping qualified type name (or <code>null</code> if none)
093: */
094: public QName getMapAsQName() {
095: return m_mapAsQName;
096: }
097:
098: /**
099: * Set qualified name of mapping type. This method changes the mapping name
100: * to match the qualified name.
101: *
102: * @param name mapping qualified type name (or <code>null</code> if none)
103: */
104: public void setMapAsQName(QName name) {
105: m_mapAsQName = name;
106: m_mapAsName = (name == null) ? null : name.toString();
107: }
108:
109: /**
110: * Get actual type mapping. This call is only meaningful after validation.
111: *
112: * @return actual type mapping (or <code>null</code> if none)
113: */
114: public TemplateElementBase getEffectiveMapping() {
115: return m_effectiveMapping;
116: }
117:
118: //
119: // Overrides of base class methods
120:
121: /* (non-Javadoc)
122: * @see org.jibx.binding.model.IComponent#hasName()
123: */
124: public boolean hasName() {
125: if (m_effectiveMapping instanceof MappingElement) {
126: if (((MappingElement) m_effectiveMapping).getName() != null) {
127: return true;
128: }
129: } else if (m_hasMappingName) {
130: return true;
131: }
132: return super .hasName();
133: }
134:
135: /* (non-Javadoc)
136: * @see org.jibx.binding.model.IComponent#getName()
137: */
138: public String getName() {
139: if (m_effectiveMapping instanceof MappingElement) {
140: String name = ((MappingElement) m_effectiveMapping)
141: .getName();
142: if (name != null) {
143: return name;
144: }
145: } else if (m_hasMappingName) {
146: return "#" + getType().getName();
147: }
148: return super .getName();
149: }
150:
151: /* (non-Javadoc)
152: * @see org.jibx.binding.model.IComponent#getUri()
153: */
154: public String getUri() {
155: if (m_effectiveMapping instanceof MappingElement) {
156: String uri = ((MappingElement) m_effectiveMapping).getUri();
157: if (uri != null) {
158: return uri;
159: }
160: }
161: return super .getUri();
162: }
163:
164: /* (non-Javadoc)
165: * @see org.jibx.binding.model.IComponent#hasAttribute()
166: */
167: public boolean hasAttribute() {
168: if (hasName()) {
169: return false;
170: } else if (m_effectiveMapping != null) {
171: return m_effectiveMapping.name() == null
172: && m_effectiveMapping.getAttributeComponents()
173: .size() > 0;
174: } else {
175: return super .hasAttribute();
176: }
177: }
178:
179: /* (non-Javadoc)
180: * @see org.jibx.binding.model.IComponent#hasContent()
181: */
182: public boolean hasContent() {
183: if (hasName()) {
184: return true;
185: } else if (m_effectiveMapping != null) {
186: return m_effectiveMapping.name() != null
187: || m_effectiveMapping.getContentComponents().size() > 0;
188: } else {
189: return super .hasContent();
190: }
191: }
192:
193: /* (non-Javadoc)
194: * @see org.jibx.binding.model.IComponent#getType()
195: */
196: public IClass getType() {
197: if (m_effectiveMapping == null) {
198: return super .getType();
199: } else {
200: return m_effectiveMapping.getHandledClass();
201: }
202: }
203:
204: //
205: // Validation methods
206:
207: /**
208: * JiBX access method to set mapping type name as qualified name.
209: *
210: * @param text mapping name text (<code>null</code> if none)
211: * @param ictx unmarshalling context
212: * @throws JiBXException on deserialization error
213: */
214: private void setQualifiedMapAs(String text,
215: IUnmarshallingContext ictx) throws JiBXException {
216: m_mapAsName = text;
217: m_mapAsQName = QName.deserialize(text, ictx);
218: }
219:
220: /**
221: * JiBX access method to get mapping type name as qualified name.
222: *
223: * @param ictx marshalling context
224: * @return mapping type name text (<code>null</code> if none)
225: * @throws JiBXException on deserialization error
226: */
227: private String getQualifiedMapAs(IMarshallingContext ictx)
228: throws JiBXException {
229: return QName.serialize(m_mapAsQName, ictx);
230: }
231:
232: /**
233: * Make sure all attributes are defined.
234: *
235: * @param uctx unmarshalling context
236: * @exception JiBXException on unmarshalling error
237: */
238: private void preSet(IUnmarshallingContext uctx)
239: throws JiBXException {
240: validateAttributes(uctx, s_allowedAttributes);
241: }
242:
243: /**
244: * Merge namespaces from an implicit context to those defined for a
245: * reference.
246: *
247: * @param defc context supplying namespaces to be merged
248: * @param addc context to be merged into
249: * @param vctx
250: */
251: private void mergeNamespaces(DefinitionContext defc,
252: DefinitionContext addc, ValidationContext vctx) {
253:
254: // check for namespaces present in context
255: ArrayList nss = defc.getNamespaces();
256: if (nss != null) {
257:
258: // merge namespaces with those defined by containing mapping
259: for (int i = 0; i < nss.size(); i++) {
260: NamespaceElement ns = (NamespaceElement) nss.get(i);
261: ValidationProblem prob = addc.addImpliedNamespace(ns,
262: this );
263: if (prob != null) {
264: vctx.addProblem(prob);
265: }
266: }
267: }
268: }
269:
270: /**
271: * Check for conflicts on namespace prefix usage. Abstract mappings may
272: * define namespaces, but the prefixes used by the abstract mappings must
273: * not conflict with those used at the point of reference. This allows the
274: * namespace definitions from the abstract mapping to be promoted to the
275: * containing element.
276: *
277: * @param base
278: * @param vctx
279: */
280: private void checkNamespaceUsage(TemplateElementBase base,
281: ValidationContext vctx) {
282:
283: // check for namespace definitions needing to be handled
284: if (base instanceof MappingElement
285: && ((MappingElement) base).isAbstract()) {
286:
287: // merge namespaces defined within this mapping
288: DefinitionContext addc = vctx.getFormatDefinitions();
289: DefinitionContext defc = base.getDefinitions();
290: if (defc != null) {
291: mergeNamespaces(defc, addc, vctx);
292: }
293: }
294: }
295:
296: /**
297: * Classify child components as contributing attributes, content, or both.
298: * This method is needed to handle on-demand classification during
299: * validation. When a child component is another instance of this class, the
300: * method calls itself on the child component prior to checking the child
301: * component's contribution.
302: *
303: * @param vctx
304: */
305: protected void classifyComponents(ValidationContext vctx) {
306:
307: // check for classification already run
308: if (!isClassified()) {
309:
310: // check if there's a mapping if used without children
311: // TODO: this isn't correct, but validation may not have reached
312: // this element yet so the context may not have been set. Clean
313: // this with parent link in all elements.
314: DefinitionContext dctx = vctx.getDefinitions();
315: if (children().size() == 0) {
316: if (m_mapAsQName == null) {
317:
318: // make sure not just a name, allowed for skipped element
319: if (hasProperty() || getDeclaredType() != null) {
320:
321: // see if this is using implicit marshaller/unmarshaller
322: if ((vctx.isInBinding() && getUnmarshallerName() == null)
323: || (vctx.isOutBinding() && getMarshallerName() == null)) {
324: if (getUsing() == null) {
325:
326: // check for specific type known
327: IClass type = getType();
328: if (!"java.lang.Object".equals(type
329: .getName())) {
330: setMappingReference(vctx, dctx,
331: type);
332: }
333: }
334: }
335: }
336:
337: } else {
338:
339: // find mapping by type name or class name
340: TemplateElementBase base = dctx
341: .getNamedTemplate(m_mapAsQName.toString());
342: if (base == null) {
343: base = dctx.getSpecificTemplate(m_mapAsName);
344: if (base == null) {
345: vctx.addFatal("No mapping with type name "
346: + m_mapAsQName.toString());
347: }
348: }
349: if (base != null) {
350:
351: // make sure type is compatible
352: IClass type = getType();
353: if (type != null) {
354: if (!type.isAssignable(base
355: .getHandledClass())
356: && !base.getHandledClass()
357: .isAssignable(type)) {
358: vctx
359: .addError("Object type "
360: + type.getName()
361: + " is incompatible with binding for class "
362: + base.getClassName());
363: }
364: }
365: m_effectiveMapping = base;
366:
367: // check for namespace conflicts
368: checkNamespaceUsage(base, vctx);
369:
370: // set flag for mapping with name
371: m_hasMappingName = base instanceof MappingElement
372: && !((MappingElement) base)
373: .isAbstract();
374: }
375:
376: }
377:
378: // classify mapping reference as providing content, attributes, or both
379: if (m_effectiveMapping instanceof MappingElement) {
380: MappingElement mapping = (MappingElement) m_effectiveMapping;
381: mapping.classifyComponents(vctx);
382: if (mapping.getName() == null) {
383: ArrayList attribs = EmptyList.INSTANCE;
384: ArrayList contents = EmptyList.INSTANCE;
385: if (mapping.getContentComponents().size() > 0) {
386: contents = new ArrayList();
387: contents.add(m_effectiveMapping);
388: }
389: if (mapping.getAttributeComponents().size() > 0) {
390: attribs = new ArrayList();
391: attribs.add(m_effectiveMapping);
392: }
393: setComponents(attribs, contents);
394: } else {
395: ArrayList contents = new ArrayList();
396: contents.add(m_effectiveMapping);
397: setComponents(EmptyList.INSTANCE, contents);
398: }
399: }
400:
401: } else if (m_mapAsName != null) {
402: vctx
403: .addError("map-as attribute cannot be used with children");
404: }
405:
406: }
407:
408: // pass on call to superclass implementation
409: super .classifyComponents(vctx);
410: }
411:
412: /* (non-Javadoc)
413: * @see org.jibx.binding.model.ElementBase#validate(org.jibx.binding.model.ValidationContext)
414: */
415: public void validate(ValidationContext vctx) {
416:
417: // set up child components and effective mapping
418: classifyComponents(vctx);
419: IClass type = getType();
420: if (type != null) {
421:
422: // check each child component for compatible type
423: ArrayList children = children();
424: if (hasProperty() || getDeclaredType() != null) {
425: checkCompatibleChildren(vctx, type, children);
426: }
427:
428: // check for only set-method supplied
429: if (!vctx.isOutBinding() && getField() == null
430: && getGet() == null && getSet() != null) {
431:
432: // no way to handle both elements and attributes
433: if (hasAttribute() && hasContent()) {
434: vctx
435: .addError("Need way to load existing object instance "
436: + "to support combined attribute and element values");
437: } else {
438: vctx
439: .addWarning("No way to load prior value - "
440: + "new instance will be created on each unmarshalling");
441: }
442: }
443: }
444:
445: // make sure name is defined if nillable
446: if (isNillable() && !hasName()) {
447: vctx.addError("Need element name for nillable='true'");
448: }
449: super .validate(vctx);
450: }
451:
452: /**
453: * Validate mapping reference.
454: *
455: * @param vctx validation context
456: * @param dctx definition context
457: * @param type referenced type
458: */
459: private void setMappingReference(ValidationContext vctx,
460: DefinitionContext dctx, IClass type) {
461:
462: // first try to find mapping by specific type
463: m_effectiveMapping = dctx.getNamedTemplate(type.getName());
464: if (m_effectiveMapping == null) {
465:
466: // see if there's a mapping specific to the reference type
467: String tname = type.getName();
468: TemplateElementBase match = dctx.getSpecificTemplate(tname);
469: if (match != null) {
470: m_effectiveMapping = match;
471: if (match instanceof MappingElement) {
472:
473: // set flag for name defined by mapping
474: MappingElement base = (MappingElement) match;
475: m_hasMappingName = !base.isAbstract();
476: checkNamespaceUsage(base, vctx);
477:
478: // check name usage on non-abstract or base mapping
479: if (super .hasName()) {
480: if (!base.isAbstract()) {
481: vctx
482: .addError("name attribute not allowed on concrete mapping reference");
483: } else if (base.getExtensionTypes().size() > 0) {
484: vctx
485: .addError("name attribute not allowed on reference to mapping with extensions");
486: }
487: }
488: }
489:
490: } else if (!dctx.isCompatibleTemplateType(type)) {
491: vctx.addFatal("No compatible mapping defined for type "
492: + tname);
493: }
494:
495: } else if (m_effectiveMapping instanceof MappingElement) {
496:
497: // set flag for name defined by mapping
498: MappingElement base = (MappingElement) m_effectiveMapping;
499: m_hasMappingName = !base.isAbstract();
500: checkNamespaceUsage(base, vctx);
501:
502: }
503: }
504: }
|