View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Intalio, Inc.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Intalio, Inc. Exolab is a registered
23   *    trademark of Intalio, Inc.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 1999-2003 (C) Intalio Inc. All Rights Reserved.
42   *
43   * $Id$
44   */
45  
46  package org.exolab.castor.xml.schema.reader;
47  
48  //-- imported classes and packages
49  import org.exolab.castor.xml.AttributeSet;
50  import org.exolab.castor.xml.Namespaces;
51  import org.exolab.castor.xml.XMLException;
52  import org.exolab.castor.xml.schema.Annotation;
53  import org.exolab.castor.xml.schema.ElementDecl;
54  import org.exolab.castor.xml.schema.Form;
55  import org.exolab.castor.xml.schema.IdentityConstraint;
56  import org.exolab.castor.xml.schema.Schema;
57  import org.exolab.castor.xml.schema.SchemaContext;
58  import org.exolab.castor.xml.schema.SchemaException;
59  import org.exolab.castor.xml.schema.SchemaNames;
60  import org.exolab.castor.xml.schema.XMLType;
61  
62  /**
63   * A class for Unmarshalling element definitions
64   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
65   *
66   * @version $Revision$ $Date: 2003-03-03 02:57:21 -0700 (Mon, 03 Mar 2003) $
67   */
68  public class ElementUnmarshaller extends ComponentReader {
69  
70      /**
71       * The value of the maximum occurance wild card
72      **/
73      private static final String MAX_OCCURS_WILDCARD = "unbounded";
74  
75        //--------------------/
76       //- Member Variables -/
77      //--------------------/
78  
79      /**
80       * The current ComponentReader
81      **/
82      private ComponentReader unmarshaller;
83  
84      /**
85       * The current branch depth
86      **/
87      private int depth = 0;
88  
89      /**
90       * The element reference for the element definition we are "unmarshalling".
91      **/
92      private ElementDecl _element = null;
93  
94  
95      private CharacterUnmarshaller charUnmarshaller = null;
96  
97      private Schema _schema = null;
98  
99      private boolean foundAnnotation         = false;
100     private boolean foundIdentityConstraint = false;
101     private boolean foundSimpleType         = false;
102     private boolean foundComplexType        = false;
103     private boolean foundTypeReference      = false;
104 
105       //----------------/
106      //- Constructors -/
107     //----------------/
108 
109     /**
110      * Creates a new ElementUnmarshaller.
111      * @param schemaContext the {@link SchemaContext} to get some configuration settings from
112      * @param schema the Schema to which the Element belongs
113      * @param atts the AttributeList
114     **/
115     public ElementUnmarshaller(
116             final SchemaContext schemaContext,
117             final Schema schema,
118             final AttributeSet atts)
119     throws XMLException {
120         super(schemaContext);
121 
122         this._schema = schema;
123 
124         _element = new ElementDecl(schema);
125         
126         String attValue = null;
127         
128         //-- @ref
129         attValue = atts.getValue(SchemaNames.REF_ATTR);
130         if (attValue != null) {
131             _element.setReferenceName(attValue);
132             //-- report error if name attr exists also
133             if (atts.getValue(SchemaNames.NAME_ATTR) != null) {
134                 error("The attributes 'ref' and 'name' appearing on " +
135                     "element declarations are mutually exclusive.");
136             }
137             validateRefAtts(atts);
138         }
139         //-- @name
140         else {
141             _element.setName(atts.getValue(SchemaNames.NAME_ATTR));
142         }
143 
144         //-- @abstract
145         attValue = atts.getValue(SchemaNames.ABSTRACT);
146         if (attValue != null) {
147             _element.setAbstract((new Boolean(attValue)).booleanValue());
148         }
149         
150         //-- @block
151         _element.setBlock(atts.getValue(SchemaNames.BLOCK_ATTR));
152         
153         //-- @default
154         attValue = atts.getValue(SchemaNames.DEFAULT_ATTR);
155         if (attValue != null) {
156             if (_element.getFixedValue() != null)
157                 error("'default' and 'fixed' must not both be present.");
158             _element.setDefaultValue(attValue);
159         }
160 
161         //-- @final
162         _element.setFinal(atts.getValue(SchemaNames.FINAL_ATTR));
163 
164         //-- @abstract
165         final boolean isAbstract = new Boolean(atts.getValue(SchemaNames.ABSTRACT)).booleanValue();
166         if (isAbstract) {
167             _element.setAbstract(isAbstract);
168         }
169 
170         //-- @fixed
171         attValue = atts.getValue(SchemaNames.FIXED_ATTR);
172         if (attValue != null) {
173             if (_element.getDefaultValue() != null)
174                 throw new IllegalArgumentException("'default' and 'fixed' must not both be present.");
175             _element.setFixedValue(attValue);
176         }
177         
178         //-- @form
179         attValue = atts.getValue(SchemaNames.FORM);
180         if (attValue != null) {
181             _element.setForm(Form.valueOf(attValue));
182         }
183         
184         //-- @id
185         _element.setId(atts.getValue(SchemaNames.ID_ATTR));
186         
187         //-- @substitutionGroup
188         attValue = atts.getValue(SchemaNames.SUBSTITUTION_GROUP_ATTR);
189         if (attValue != null) {
190             _element.setSubstitutionGroup(attValue);
191         }
192         
193         //-- @type
194         attValue = atts.getValue(SchemaNames.TYPE_ATTR);
195         if (attValue != null) {
196             foundTypeReference = true;
197             _element.setTypeReference(attValue);
198         }
199         
200 
201         //-- @nillable
202         attValue = atts.getValue(SchemaNames.NILLABLE_ATTR);
203         if (attValue != null) {
204             if (attValue.equals("true") || attValue.equals("1")) { 
205                 _element.setNillable(true);
206             }
207             else if (!attValue.equals("false") && !attValue.equals("0")) {
208                 String err = "Invalid value for the 'nillable' attribute of "+
209                     "an element definition: " + attValue;
210                 throw new IllegalArgumentException(err);
211             }
212         }
213 
214         /*
215          * @minOccurs
216          * if minOccurs is present the value is the int value of
217          * of the attribute, otherwise minOccurs is 1.
218          */
219         attValue = atts.getValue(SchemaNames.MIN_OCCURS_ATTR);
220         int minOccurs = 1;
221         if (attValue != null) {
222             minOccurs = toInt(attValue);
223             _element.setMinOccurs(minOccurs);
224         }
225 
226         /*
227          * @maxOccurs
228          * If maxOccurs is present, the value is either unbounded
229          * or the int value of the attribute, otherwise maxOccurs
230          * equals the minOccurs value.
231          */
232         attValue = atts.getValue(SchemaNames.MAX_OCCURS_ATTR);
233         if (attValue != null) {
234             if (MAX_OCCURS_WILDCARD.equals(attValue)) attValue = "-1";
235             int maxOccurs = toInt(attValue);
236             _element.setMaxOccurs(maxOccurs);
237         } else if (minOccurs > 1)
238             _element.setMaxOccurs(minOccurs);
239 
240         charUnmarshaller = new CharacterUnmarshaller(getSchemaContext());
241     } //-- ElementUnmarshaller
242 
243       //-----------/
244      //- Methods -/
245     //-----------/
246 
247     /**
248      * Returns the name of the element that this ComponentReader
249      * handles
250      * @return the name of the element that this ComponentReader
251      * handles
252     **/
253     public String elementName() {
254         return SchemaNames.ELEMENT;
255     } //-- elementName
256 
257     /**
258      *
259     **/
260     public ElementDecl getElement() {
261         return _element;
262     } //-- getElement
263 
264     /**
265      * Returns the Object created by this ComponentReader
266      * @return the Object created by this ComponentReader
267     **/
268     public Object getObject() {
269         return _element;
270     } //-- getObject
271 
272     /**
273      * Signals the start of an element with the given name.
274      *
275      * @param name the NCName of the element. It is an error
276      * if the name is a QName (ie. contains a prefix).
277      * @param namespace the namespace of the element. This may be null.
278      * Note: A null namespace is not the same as the default namespace unless
279      * the default namespace is also null.
280      * @param atts the AttributeSet containing the attributes associated
281      * with the element.
282      * @param nsDecls the namespace declarations being declared for this 
283      * element. This may be null.
284     **/
285     public void startElement(String name, String namespace, AttributeSet atts,
286         Namespaces nsDecls)
287         throws XMLException
288     {
289 
290         //-- Do delagation if necessary
291         if (unmarshaller != null) {
292             unmarshaller.startElement(name, namespace, atts, nsDecls);
293             ++depth;
294             return;
295         }
296 
297         if (SchemaNames.ANNOTATION.equals(name)) {
298             if (foundSimpleType || foundIdentityConstraint ||foundComplexType)
299                 error("An annotation may only appear as the first child "+
300                     "of an element definition.");
301 
302 
303             if (foundAnnotation)
304                 error("Only one (1) 'annotation' is allowed as a child of "+
305                     "element definitions.");
306 
307             foundAnnotation = true;
308             unmarshaller = new AnnotationUnmarshaller(getSchemaContext(), atts);
309         }
310         else if (SchemaNames.COMPLEX_TYPE.equals(name)) {
311 
312             if (foundComplexType)
313                 error("Only one (1) 'complexType' may appear in an "+
314                     "element definition.");
315             if (foundSimpleType)
316                 error("Both 'simpleType' and 'complexType' cannot appear "+
317                     "in the same element definition.");
318             if (foundTypeReference)
319                 error("Both 'type' attribute and 'complexType' element "+
320                     "cannot appear in the same element definition.");
321                 
322 
323             if (foundIdentityConstraint)
324                 error("A 'complexType' must appear before 'key', "+
325                     "'keyref' and 'unique' elements.");
326 
327             foundComplexType = true;
328             unmarshaller
329                 = new ComplexTypeUnmarshaller(getSchemaContext(), _schema, atts);
330         }
331         else if (SchemaNames.SIMPLE_TYPE.equals(name)) {
332 
333             if (foundSimpleType)
334                 error("Only one (1) 'simpleType' may appear in an "+
335                     "element definition.");
336             if (foundComplexType)
337                 error("Both 'simpleType' and 'complexType' cannot appear "+
338                     "in the same element definition.");
339             if (foundTypeReference)
340                 error("Both 'type' attribute and 'simpleType' element "+
341                     "cannot appear in the same element definition.");
342 
343             if (foundIdentityConstraint)
344                 error("A 'simpleType' must appear before 'key', "+
345                     "'keyref' and 'unique' elements.");
346 
347             foundSimpleType = true;
348             unmarshaller = new SimpleTypeUnmarshaller(getSchemaContext(), _schema, atts);
349         }
350         else if (SchemaNames.KEY.equals(name) || 
351                  SchemaNames.KEYREF.equals(name) ||
352                  SchemaNames.UNIQUE.equals(name)) 
353         {
354             foundIdentityConstraint = true;
355             unmarshaller = new IdentityConstraintUnmarshaller(getSchemaContext(), name, atts);
356         }
357         else illegalElement(name);
358 
359     } //-- startElement
360 
361     /**
362      * Signals to end of the element with the given name.
363      *
364      * @param name the NCName of the element. It is an error
365      * if the name is a QName (ie. contains a prefix).
366      * @param namespace the namespace of the element.
367     **/
368     public void endElement(String name, String namespace)
369         throws XMLException
370     {
371 
372         //-- Do delagation if necessary
373         if ((unmarshaller != null) && (depth > 0)) {
374             unmarshaller.endElement(name, namespace);
375             --depth;
376             return;
377         }
378 
379         //-- check for name mismatches
380         if ((unmarshaller != null) && (charUnmarshaller != unmarshaller)) {
381             if (!name.equals(unmarshaller.elementName())) {
382                 String err = "missing end element for ";
383                 err += unmarshaller.elementName();
384                 throw new SchemaException(err);
385             }
386         }
387 
388         //-- call finish for any necessary cleanup
389         unmarshaller.finish();
390 
391         if (SchemaNames.ANNOTATION.equals(name)) {
392             Annotation ann = (Annotation)unmarshaller.getObject();
393             _element.addAnnotation(ann);
394         }
395         else if (SchemaNames.COMPLEX_TYPE.equals(name)) {
396 
397             XMLType xmlType
398                 = ((ComplexTypeUnmarshaller)unmarshaller).getComplexType();
399 
400             _element.setType(xmlType);
401 
402         }
403         else if (SchemaNames.SIMPLE_TYPE.equals(name)) {
404             XMLType xmlType
405                 = ((SimpleTypeUnmarshaller)unmarshaller).getSimpleType();
406             _element.setType(xmlType);
407         }
408         else if (SchemaNames.KEY.equals(name) || 
409                  SchemaNames.KEYREF.equals(name) ||
410                  SchemaNames.UNIQUE.equals(name)) 
411         {
412             IdentityConstraint constraint 
413                 = (IdentityConstraint) unmarshaller.getObject();
414             _element.addIdentityConstraint(constraint);
415         }
416 
417         unmarshaller = null;
418 
419     } //-- endElement
420 
421     public void characters(char[] ch, int start, int length)
422         throws XMLException
423     {
424         //-- Do delagation if necessary
425         if (unmarshaller != null) {
426             unmarshaller.characters(ch, start, length);
427         }
428     } //-- characters
429 
430     /**
431      * Makes sure only minOccurs, maxOccurs, id, and ref occur
432      * for element references.
433      *
434      * @param atts the AttributeSet to process
435      */
436     private static void validateRefAtts(AttributeSet atts) 
437         throws XMLException
438     {
439         
440         StringBuffer errors = null;
441         
442         for (int i = 0; i < atts.getSize(); i++) {
443             String name = atts.getName(i);
444             if (SchemaNames.REF_ATTR.equals(name)) 
445                 continue;
446             else if (SchemaNames.MAX_OCCURS_ATTR.equals(name)) 
447                 continue;
448             else if (SchemaNames.MIN_OCCURS_ATTR.equals(name)) 
449                 continue;
450             else if (SchemaNames.ID_ATTR.equals(name)) 
451                 continue;
452             else {
453                 //-- check namespace
454                 String namespace = atts.getNamespace(i);
455                 
456                 //-- If we have no namespace (ie no prefix) or we
457                 //-- have the XSD Namespace then throw error
458                 if ((namespace == null) || 
459                     (namespace.length() == 0) ||
460                      namespace.equals(SchemaUnmarshaller.XSD_NAMESPACE))
461                 {
462                     //-- unprefixed attribute...assume XML Schema namespace
463                     String error = "The attribute '" + name + 
464                         "' must not appear on an element reference.";
465                     if (errors == null)
466                         errors = new StringBuffer(error);
467                     else
468                         errors.append(error);
469                         
470                     errors.append(System.getProperty("line.separator"));
471                     
472                     
473                 }
474                 //-- otherwise we have a namespaced attribute from a different
475                 //-- namespace..this is valid...continue
476             }
477         }
478         
479         if (errors != null)
480             throw new XMLException(errors.toString());
481             
482     } //-- validateRefAtts
483 
484 } //-- ElementUnmarshaller