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-2004 (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 java.io.InputStream;
50  import java.util.Enumeration;
51  import java.util.HashMap;
52  import java.util.Properties;
53  import java.util.StringTokenizer;
54  
55  import org.exolab.castor.net.URIResolver;
56  import org.exolab.castor.net.util.URIResolverImpl;
57  import org.exolab.castor.xml.AttributeSet;
58  import org.exolab.castor.xml.Namespaces;
59  import org.exolab.castor.xml.XMLException;
60  import org.exolab.castor.xml.schema.Annotation;
61  import org.exolab.castor.xml.schema.AttributeDecl;
62  import org.exolab.castor.xml.schema.AttributeGroupDecl;
63  import org.exolab.castor.xml.schema.ComplexType;
64  import org.exolab.castor.xml.schema.ElementDecl;
65  import org.exolab.castor.xml.schema.Form;
66  import org.exolab.castor.xml.schema.SchemaContext;
67  import org.exolab.castor.xml.schema.ModelGroup;
68  import org.exolab.castor.xml.schema.RedefineSchema;
69  import org.exolab.castor.xml.schema.Schema;
70  import org.exolab.castor.xml.schema.SchemaException;
71  import org.exolab.castor.xml.schema.SchemaNames;
72  import org.exolab.castor.xml.schema.ScopableResolver;
73  import org.exolab.castor.xml.schema.SimpleType;
74  import org.exolab.castor.xml.util.AttributeSetImpl;
75  
76  /**
77   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
78   * @version $Revision$ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
79  **/
80  public class SchemaUnmarshaller extends ComponentReader {
81  
82  
83  
84      /**
85       * W3C XML schema namespace.
86       */
87      public static final String XSD_NAMESPACE
88          = "http://www.w3.org/2001/XMLSchema";
89  
90  
91      /**
92       * Unsupported namespace definitions, pointing to older
93       * XML schema specifications.
94       */
95      public static final String[] UNSUPPORTED_NAMESPACES = {
96          "http://www.w3.org/2000/10/XMLSchema",
97          "http://www.w3.org/1999/XMLSchema"
98      };
99  
100       //--------------------/
101      //- Member Variables -/
102     //--------------------/
103 
104     /**
105      * Indicates whether the {@link Schema} processed represents an 
106      * included schema.
107      */
108     private boolean _include = false;
109     
110     /**
111      * The current {@link ComponentReader}.
112     **/
113     private ComponentReader _unmarshaller;
114 
115     /**
116      * Flag to indicate that we are inside an annotation element.
117     **/
118     private int _annotationDepth = 0;
119 
120     /**
121      * The current branch depth.
122     **/
123     private int _depth = 0;
124 
125     boolean skipAll = false;
126 
127     Schema _schema = null;
128 
129     private boolean foundSchemaDef = false;
130 
131 
132     /**
133      * The default namespace URI to be used.
134      */
135     private String _defaultNS = null;
136 
137     /**
138      * The {@link SchemaUnmarshaller} state.
139      */
140     private SchemaUnmarshallerState _state = null;
141    
142     /**
143      * Remapped prefix mappings.
144      */
145     private RemappedPrefixes _prefixMappings = null;
146     
147       //----------------/
148      //- Constructors -/
149     //----------------/
150 
151     /**
152      * Creates a {@link SchemaUnmarshaller} instance.
153      * @param schemaContext A {@link SchemaContext} to be used during schema unmarshalling.
154      * @throws XMLException Indicates that the XML schema cannnot be processed
155      */
156     public SchemaUnmarshaller(final SchemaContext schemaContext)
157     throws XMLException {
158         this(schemaContext, null, null);
159         foundSchemaDef = false;
160     } //-- SchemaUnmarshaller
161 
162     /**
163      * Creates a {@link SchemaUnmarshaller} instance.
164      * @param schemaContext A {@link SchemaContext} to be used during schema unmarshalling.
165      * @param state A {@link SchemaUnmarshallerState} to be used during unmarshalling.
166      * @throws XMLException Indicates that the XML schema cannnot be processed
167      */
168     public SchemaUnmarshaller(
169             final SchemaContext schemaContext,
170             final SchemaUnmarshallerState state)
171     throws XMLException {
172         this(schemaContext, null, null);
173         _state = state;
174         foundSchemaDef = false;
175     } //-- SchemaUnmarshaller
176 
177     /**
178      * Creates a {@link SchemaUnmarshaller} instance.
179      * @param schemaContext A {@link SchemaContext} to be used during schema unmarshalling.
180      * @param include Indicates whether the {@link Schema} to be processed ia an included schema.
181      * @param state A {@link SchemaUnmarshallerState} to be used during unmarshalling.
182      * @param uriResolver {@link URIResolver} to be used during processing.
183      * @throws XMLException Signals a problem in processing the XML schema.
184      * 
185      * Called from IncludeUnmarshaller.
186      * 
187      * @see {@link IncludeUnmarshaller}
188      */
189     public SchemaUnmarshaller(
190             final SchemaContext schemaContext,
191             final boolean include,
192             final SchemaUnmarshallerState state,
193             final URIResolver uriResolver)
194         throws XMLException {
195 
196         this(schemaContext, null, uriResolver);
197         _state = state;
198         _include = include;
199         foundSchemaDef = false;
200     }
201 
202     /**
203      * Creates a {@link SchemaUnmarshaller} instance.
204      * Exists for backward compatibility 
205      * @param schemaContext A {@link SchemaContext} to be used during schema unmarshalling.
206      * @param atts Attribute set to be processed.
207      * @throws XMLException Signals a problem in processing the XML schema.
208      */
209     public SchemaUnmarshaller(
210             final SchemaContext schemaContext,
211             final AttributeSet atts) throws XMLException {
212         this(schemaContext, atts, null);
213     }
214 
215     /**
216      * Creates a {@link SchemaUnmarshaller} instance.
217      * @param schemaContext A {@link SchemaContext} to be used during schema unmarshalling.
218      * @param atts Attribute set to be processed.
219      * @param uriResolver {@link URIResolver} to be used during processing.
220      * @throws XMLException Signals a problem in processing the XML schema.
221      */
222     private SchemaUnmarshaller(
223             final SchemaContext schemaContext,
224             final AttributeSet atts,
225             final URIResolver uriResolver)
226     throws XMLException {
227         super(schemaContext);
228 
229         _schema = new Schema();
230         //--initialize the schema to ensure that the default namespace
231         //--is not set
232         _schema.removeNamespace("");
233         if (getResolver() == null) {
234             setResolver(new ScopableResolver());
235         }
236         if (uriResolver == null) {
237             setURIResolver(new URIResolverImpl());
238         } else {
239             setURIResolver(uriResolver);
240         }
241         foundSchemaDef = true;
242         _state = new SchemaUnmarshallerState();
243         init(atts);
244     } //-- SchemaUnmarshaller
245 
246     /**
247      * Returns the {@link Schema} instance representing the XML schema (file) just 
248      * processed.
249      * @return the {@link Schema} instance obtained from processing an XML schema file.
250      */
251     public Schema getSchema() {
252         return _schema;
253     }
254 
255     /**
256      * Sets the {@link Schema} instance to be processed.
257      * @param schema {@link Schema} instancetp be processed.
258      */
259     public void setSchema(final Schema schema) {
260         _schema = schema;
261     }
262 
263     /**
264      * Returns the Object created by this ComponentReader.
265      * @return the Object created by this ComponentReader
266     **/
267     public Object getObject() {
268         return getSchema();
269     } //-- getObject
270 
271     /**
272      * Returns the name of the element that this ComponentReader
273      * handles.
274      * @return the name of the element that this ComponentReader
275      * handles
276     **/
277     public String elementName() {
278         return SchemaNames.SCHEMA;
279     } //-- elementName
280 
281 
282     /**
283      * Initializes the Schema object with the given attribute list.
284      * @param atts the AttributeList for the schema
285      * @throws XMLException Signals a problem in initializing the {@link Schema} 
286      * object instance
287      */
288     private void init(final AttributeSet atts) throws XMLException {
289         if (atts == null) {
290             return;
291         }
292 
293         String attValue = null;
294 
295         String nsURI = atts.getValue(SchemaNames.TARGET_NS_ATTR);
296         if (nsURI != null &&  nsURI.length() == 0) {
297             throw new SchemaException("empty string is not a legal namespace.");
298         }
299         if ((nsURI != null) && (nsURI.length() > 0)) {
300             if (!_state.cacheIncludedSchemas) {
301                 //if we are including a schema we must take care
302                 //that the namespaces are the same
303                 if ((_include) && (!_schema.getTargetNamespace().equals(nsURI))) {
304                     throw new SchemaException("The target namespace of the included " 
305                             + "components must be the same as the target namespace " 
306                             + "of the including schema");
307                 }
308             }
309                _schema.setTargetNamespace(nsURI);
310         }
311 
312         _schema.setId(atts.getValue(SchemaNames.ID_ATTR));
313         _schema.setVersion(atts.getValue(SchemaNames.VERSION_ATTR));
314 
315         //set the default locator of this schema
316         if (!_include || _state.cacheIncludedSchemas) {
317             _schema.setSchemaLocation(getDocumentLocator().getSystemId());
318         }
319 
320         //-- attributeFormDefault
321         String form = atts.getValue(SchemaNames.ATTR_FORM_DEFAULT_ATTR);
322         if (form != null) {
323             _schema.setAttributeFormDefault(Form.valueOf(form));
324         }
325 
326         //-- elementFormDefault
327         form = atts.getValue(SchemaNames.ELEM_FORM_DEFAULT_ATTR);
328         if (form != null) {
329             _schema.setElementFormDefault(Form.valueOf(form));
330         }
331 
332         //-- @blockDefault
333         attValue = atts.getValue(SchemaNames.BLOCK_DEFAULT_ATTR);
334         if (attValue != null) {
335             _schema.setBlockDefault(attValue);
336         }
337 
338         //-- @finalDefault
339         attValue = atts.getValue(SchemaNames.FINAL_DEFAULT_ATTR);
340         if (attValue != null) {
341             _schema.setFinalDefault(attValue);
342         }
343 
344         //--@version
345         attValue = atts.getValue(SchemaNames.VERSION_ATTR);
346         if (attValue != null) {
347             _schema.setVersion(attValue);
348         }
349 
350     } //-- init
351 
352     /**
353      * Handles namespace attributes.
354      * @param namespaces The name space to handle.
355      * @throws XMLException If there's a problem related to namespace handling.
356      */
357     private void handleNamespaces(final Namespaces namespaces) 
358         throws XMLException {
359 
360         if (namespaces == null) {
361             return;
362         }
363 
364         Enumeration enumeration = namespaces.getLocalNamespaces();
365 
366         while (enumeration.hasMoreElements()) {
367 
368             String ns = (String) enumeration.nextElement();
369             String[] prefixes = namespaces.getNamespacePrefixes(ns);
370 
371             if (prefixes.length == 0) {
372                 //-- this should never happen, but report error just
373                 //-- in case there is a bug in Namespaces class.
374                 String error = "unexpected error processing the following "
375                     + "namespace: '" + ns + "'; the prefix could not be resolved.";
376                 throw new XMLException(error);
377             }
378 
379             boolean hasCollisions = false;
380             for (int pIdx = 0; pIdx < prefixes.length; pIdx++) {
381                 String prefix = prefixes[pIdx];
382                 
383                 //-- Since the Schema Object Model does not yet support
384                 //-- namespace scoping, we need to checking for namespace
385                 //-- prefix collisions...and remap the prefixes
386                 String tmpURI = _schema.getNamespace(prefix);
387                 if ((tmpURI != null) && (foundSchemaDef)) {
388                     if (!tmpURI.equals(ns)) {
389                         if (!hasCollisions) {
390                             hasCollisions = true;
391                             if (_prefixMappings == null) {
392                                 _prefixMappings = new RemappedPrefixes();
393                             } else {
394                                 _prefixMappings = _prefixMappings.newRemappedPrefixes();
395                             }
396                         }
397                         
398                         //-- create a new prefix
399                         if (prefix.length() == 0) {
400                             prefix = "ns";
401                         }
402                             
403                         int count = 1;
404                         String newPrefix = prefix + count;
405                         tmpURI = _schema.getNamespace(newPrefix);
406                         while (tmpURI != null) {
407                             if (tmpURI.equals(ns)) {
408                                 //-- no remapping necessary
409                                 break;
410                             }
411                             ++count;
412                             newPrefix = prefix + count;
413                             tmpURI = _schema.getNamespace(newPrefix);
414                         }
415                         _prefixMappings.addMapping(prefix, newPrefix);
416                         prefix = newPrefix;
417                     } else {
418                         //-- we may need to "reset" a currently mapped prefix
419                         if (_prefixMappings != null) {
420                             if (_prefixMappings.isRemappedPrefix(prefix)) {
421                                 //-- reset mapping in this scope
422                                 _prefixMappings.addMapping(prefix, prefix);
423                             }
424                         }
425                     }
426                 }
427                 //-- end collision handling
428                 
429                 if (prefix.length() == 0) {
430                     _defaultNS = ns;
431                     //register the default namespace with the empty string
432                     _schema.addNamespace("", _defaultNS);
433                 } else {
434                     //-- check for old unsupported schema namespaces
435                     for (int nsIdx = 0; nsIdx < UNSUPPORTED_NAMESPACES.length; nsIdx++) {
436                         if (ns.equals(UNSUPPORTED_NAMESPACES[nsIdx])) {
437                             error("The following namespace \"" + ns 
438                                     + "\" is no longer supported. Please update to " 
439                                     + " the W3C XML Schema Recommendation.");
440                         }
441                     }
442                     _schema.addNamespace(prefix, ns);
443                 }
444             }
445         }
446 
447     } //-- handleNamespaces
448 
449     /** 
450      * Remaps any QName attributes for the given element and attributeSet.
451      * This method is a work around for the lack of namespace scoping 
452      * support in the Schema Object Model
453      * 
454      * @param name Name of the element handled.
455      * @param namespace Namepace of the element processed.
456      * @param atts The attributes of the element processed.
457      */
458     private void handleRemapping(final String name, final String namespace, 
459             final AttributeSetImpl atts) {
460         
461         if (_prefixMappings == null) {
462             return;
463         }
464         
465         //-- increase depth for scoping
466         _prefixMappings.depth++;
467         
468         String[] remapAtts = (String[]) RemappedPrefixes.QNAME_TABLE.get(name);
469         
470         if (remapAtts != null) {
471             for (int i = 0; i < remapAtts.length; i++) {
472                 String value = atts.getValue(remapAtts[i]);
473                 if (value != null) {
474                     value = _prefixMappings.remapQName(value);
475                     atts.setAttribute(remapAtts[i], value);
476                 }
477             }
478         }
479         
480     } //-- handleRemapping
481 
482     /**
483      * Signals the start of an element with the given name.
484      *
485      * @param name the NCName of the element. It is an error
486      * if the name is a QName (ie. contains a prefix).
487      * @param namespace the namespace of the element. This may be null.
488      * Note: A null namespace is not the same as the default namespace unless
489      * the default namespace is also null.
490      * @param atts the AttributeSet containing the attributes associated
491      * with the element.
492      * @param nsDecls the namespace declarations being declared for this
493      * element. This may be null.
494      * @throws XMLException To indicate a problem in processing the current element.
495     **/
496     public void startElement(final String name, 
497             String namespace, final AttributeSet atts,
498             final Namespaces nsDecls) throws XMLException {
499 
500         if (skipAll) {
501             return;
502         }
503         
504         //-- DEBUG
505         //System.out.println("#startElement: " + name + " {" + namespace + "}");
506         //-- /DEBUG
507 
508 
509         //-- process namespaces...unless we are inside an 
510         //-- annotation
511         if (_annotationDepth == 0) {
512             handleNamespaces(nsDecls);
513         }
514 
515         //-- backward compatibility, we'll need to
516         //-- remove this at some point
517         if ((!foundSchemaDef) && (namespace == null)) {
518             if (_defaultNS == null) {
519                 _defaultNS = XSD_NAMESPACE;
520                 namespace = XSD_NAMESPACE;
521                 System.out.println("No namespace declaration has been " +
522                     "found for " + name);
523                 System.out.print("   * assuming default namespace of ");
524                 System.out.println(XSD_NAMESPACE);
525             }
526         }
527         if (namespace == null) {
528             namespace = _defaultNS;
529             //-- end of backward compatibility
530         }
531 
532         //-- keep track of annotations
533         if (name.equals(SchemaNames.ANNOTATION)) {
534             ++_annotationDepth;
535         }
536 
537         //-- check namespace
538         if (!XSD_NAMESPACE.equals(namespace)) {
539             if (_annotationDepth == 0) {
540                 error("'" + name + "' has not been declared in the XML "
541                         + "Schema namespace.");
542             }
543         }
544 
545         //-- handle namespace prefix remapping
546         if (_annotationDepth == 0) { 
547             if (_prefixMappings != null) {
548                 handleRemapping(name, namespace, (AttributeSetImpl) atts);
549             }
550         }
551         
552         //-- Do delagation if necessary
553         if (_unmarshaller != null) {
554             try {
555                 _unmarshaller.startElement(name, namespace, atts, nsDecls);
556             } catch (RuntimeException rtx) {
557                 error(rtx);
558             }
559             ++_depth;
560             return;
561         }
562 
563         if (name.equals(SchemaNames.SCHEMA)) {
564 
565             if (foundSchemaDef) {
566                 illegalElement(name);
567             }
568 
569             foundSchemaDef = true;
570             init(atts);
571             return;
572         }
573 
574         //-- <annotation>
575         if (name.equals(SchemaNames.ANNOTATION)) {
576             _unmarshaller = new AnnotationUnmarshaller(getSchemaContext(), atts);
577         } else if (name.equals(SchemaNames.ATTRIBUTE)) {
578             //--<attribute>
579             _unmarshaller = new AttributeUnmarshaller(getSchemaContext(), _schema, atts);
580         } else if (name.equals(SchemaNames.ATTRIBUTE_GROUP)) {
581             //-- <attributeGroup>
582             _unmarshaller = new AttributeGroupUnmarshaller(getSchemaContext(), _schema, atts);
583         } else if (name.equals(SchemaNames.COMPLEX_TYPE)) {
584             //-- <complexType>
585             _unmarshaller
586                 = new ComplexTypeUnmarshaller(getSchemaContext(), _schema, atts);
587         } else if (name.equals(SchemaNames.ELEMENT)) {
588             //-- <element>
589             _unmarshaller
590                 = new ElementUnmarshaller(getSchemaContext(), _schema, atts);
591         } else if (name.equals(SchemaNames.SIMPLE_TYPE)) {
592             //-- <simpleType>
593             _unmarshaller = new SimpleTypeUnmarshaller(getSchemaContext(), _schema, atts);
594         } else if (name.equals(SchemaNames.GROUP)) {
595             //-- <group>
596              _unmarshaller = new ModelGroupUnmarshaller(getSchemaContext(), _schema, atts);
597         } else if (name.equals(SchemaNames.INCLUDE)) {
598             //-- <include>
599             _unmarshaller
600                 = new IncludeUnmarshaller(getSchemaContext(), _schema, atts, 
601                         getURIResolver(), getDocumentLocator(), _state);
602         } else if (name.equals(SchemaNames.IMPORT)) {
603             //-- <import>
604             _unmarshaller
605                 = new ImportUnmarshaller(getSchemaContext(), _schema, atts, 
606                         getURIResolver(), getDocumentLocator(), _state);
607         } else if (name.equals(SchemaNames.REDEFINE)) {
608             //-- <redefine>
609             _unmarshaller
610             = new RedefineUnmarshaller(getSchemaContext(), _schema, atts, 
611                     getURIResolver(), getDocumentLocator(), _state);
612         } else {
613             //-- we should throw a new Exception here
614             //-- but since we don't support everything
615             //-- yet, simply add an UnknownDef object
616             System.out.print('<');
617             System.out.print(name);
618             System.out.print("> elements are either currently unsupported ");
619             System.out.println("or non-valid schema elements.");
620             _unmarshaller = new UnknownUnmarshaller(getSchemaContext(), name);
621         }
622 
623 //        unmarshaller.setDocumentLocator(getDocumentLocator());
624 
625     } //-- startElement
626 
627     /**
628      * Signals to end of the element with the given name.
629      *
630      * @param name the NCName of the element. It is an error
631      * if the name is a QName (ie. contains a prefix).
632      * @param namespace the namespace of the element.
633      * @throws XMLException To indicate that the current element cannnot be processed successfully.
634     **/
635     public void endElement(String name, String namespace)
636         throws XMLException {
637         if (skipAll) {
638             return;
639         }
640 
641         //-- DEBUG
642         //System.out.println("#endElement: " + name + " {" + namespace + "}");
643         //-- /DEBUG
644 
645         //-- backward compatibility
646         if (namespace == null) {
647             namespace =  _defaultNS;
648         }
649 
650         //-- keep track of annotations
651         if (name.equals(SchemaNames.ANNOTATION)) {
652             --_annotationDepth;
653         }
654         
655         //-- remove namespace remapping, if necessary
656         if (_prefixMappings != null) {
657             if (_prefixMappings.depth == 0) {
658                 _prefixMappings = _prefixMappings.getParent();
659             } else {
660                 --_prefixMappings.depth;
661             }
662         }
663 
664         //-- Do delagation if necessary
665         if ((_unmarshaller != null) && (_depth > 0)) {
666             _unmarshaller.endElement(name, namespace);
667             --_depth;
668             return;
669         }
670 
671         //-- use internal JVM String
672         name = name.intern();
673 
674         if (name == SchemaNames.SCHEMA) {
675             return;
676         }
677 
678         //-- check for name mismatches
679         if ((_unmarshaller != null)) {
680             if (!name.equals(_unmarshaller.elementName())) {
681                 String err = "error: missing end element for ";
682                 err += _unmarshaller.elementName();
683                 throw new SchemaException(err);
684             }
685         } else {
686             String err = "error: missing start element for " + name;
687             throw new SchemaException(err);
688         }
689 
690         //-- call unmarshaller.finish() to perform any necessary cleanup
691         _unmarshaller.finish();
692 
693         //-- <annotation>
694         if (name.equals(SchemaNames.ANNOTATION)) {
695             _schema.addAnnotation((Annotation) _unmarshaller.getObject());
696         } else if (name.equals(SchemaNames.ATTRIBUTE)) {
697             //-- <attribute>
698             _schema.addAttribute((AttributeDecl) _unmarshaller.getObject());
699         } else if (name.equals(SchemaNames.ATTRIBUTE_GROUP)) {
700             //-- <attributeGroup>
701             Object obj = _unmarshaller.getObject();
702             try {
703                 _schema.addAttributeGroup((AttributeGroupDecl) obj);
704             } catch (ClassCastException ex) {
705                 String err = "Top-level AttributeGroups must be defining "
706                     + "AttributeGroups and not referring AttributeGroups.";
707                 error(err);
708             }
709         } else if (name.equals(SchemaNames.COMPLEX_TYPE)) {
710             //-- <complexType>
711             ComplexType complexType = null;
712             complexType = ((ComplexTypeUnmarshaller) _unmarshaller).getComplexType();
713             _schema.addComplexType(complexType);
714             if (complexType.getName() != null) {
715                 getResolver().addResolvable(complexType.getReferenceId(), complexType);
716             } else {
717                 System.out.println("warning: top-level complexType with no name.");
718             }
719         } else if (name.equals(SchemaNames.SIMPLE_TYPE)) {
720             //-- <simpleType>
721             SimpleType simpleType = null;
722             simpleType = ((SimpleTypeUnmarshaller) _unmarshaller).getSimpleType();
723             _schema.addSimpleType(simpleType);
724             getResolver().addResolvable(simpleType.getReferenceId(), simpleType);
725         } else if (name.equals(SchemaNames.ELEMENT)) {
726             //--<element>
727             ElementDecl element = null;
728             element = ((ElementUnmarshaller) _unmarshaller).getElement();
729             _schema.addElementDecl(element);
730         } else if (name.equals(SchemaNames.GROUP)) {
731             //--<group>
732             ModelGroup group = null;
733             group = (((ModelGroupUnmarshaller) _unmarshaller).getGroup());
734             _schema.addModelGroup(group);
735         } else if (name.equals(SchemaNames.REDEFINE)) {
736             //--<redefine>
737             RedefineSchema redefine = null;
738             redefine = (RedefineSchema) (((RedefineUnmarshaller) _unmarshaller).getObject());
739             if ((redefine.getSchemaLocation() == null) && (redefine.hasRedefinition())) {
740                 _schema.removeRedefineSchema(redefine);
741                 String err = "A <redefine> structure with no 'schemaLocation' " 
742                     + "attribute must contain only <annotation> elements";
743                 error(err);
744             }
745         }
746 
747         _unmarshaller = null;
748     } //-- endElement
749 
750     /**
751      * {@inheritDoc}
752      * 
753      * @see org.exolab.castor.xml.schema.reader.ComponentReader#characters(char[], int, int)
754      */
755     public void characters(final char[] ch, final int start, final int length)
756         throws XMLException {
757         //-- Do delagation if necessary
758         if (_unmarshaller != null) {
759             _unmarshaller.characters(ch, start, length);
760         }
761     } //-- characters
762     
763     /**
764      * This class handles remapping of namespace prefixes
765      * for attributes of type QName. This is needed to 
766      * work around a limitation in Castor's Schema Object 
767      * Model, which does not support proper namespace
768      * scoping yet.
769      */
770     static class RemappedPrefixes {
771         
772         public static final String RESOURCE_NAME 
773             = "prefixremap.properties";
774         
775         public static final String RESOURCE_LOCATION =
776             "/org/exolab/castor/xml/schema/reader/";
777         
778         public static final HashMap QNAME_TABLE = new HashMap();
779         private static boolean initialized = false;
780         
781         static {
782             
783             synchronized (QNAME_TABLE) {
784                 
785                 if (!initialized) {
786                     
787                     initialized = true;
788                     
789                     //-- built in mappings
790                     
791                     //-- attribute                    
792                     QNAME_TABLE.put(SchemaNames.ATTRIBUTE, new String [] {
793                         SchemaNames.REF_ATTR, SchemaNames.TYPE_ATTR });
794                         
795                     //-- attributeGroup
796                     QNAME_TABLE.put(SchemaNames.ATTRIBUTE_GROUP, new String [] {
797                         SchemaNames.REF_ATTR });
798                         
799                     //-- element
800                     QNAME_TABLE.put(SchemaNames.ELEMENT, new String [] {
801                         SchemaNames.REF_ATTR, SchemaNames.TYPE_ATTR });
802                         
803                     //-- extension
804                     QNAME_TABLE.put(SchemaNames.EXTENSION, new String [] {
805                         SchemaNames.BASE_ATTR });
806 
807                     //-- group
808                     QNAME_TABLE.put(SchemaNames.GROUP, new String [] {
809                         SchemaNames.REF_ATTR });
810                         
811                     //-- restriction
812                     QNAME_TABLE.put(SchemaNames.RESTRICTION, new String [] {
813                         SchemaNames.BASE_ATTR });
814                         
815                     
816                     //-- custom mappings
817                     String filename = RESOURCE_LOCATION + RESOURCE_NAME;
818                     InputStream is = SchemaUnmarshaller.class.getResourceAsStream(filename);
819                     Properties props = new Properties();
820                     if (is != null) {
821                         try {
822                             props.load(is);
823                         } catch (java.io.IOException iox) {
824                             //-- just use built-in mappings
825                         }
826                     }
827                                         
828                     Enumeration keys = props.propertyNames();
829                     while (keys.hasMoreElements()) {
830                         String name =  (String) keys.nextElement();
831                         StringTokenizer st = new StringTokenizer(props.getProperty(name), ",");
832                         String[] atts = new String[st.countTokens()];
833                         int index = 0;
834                         while (st.hasMoreTokens()) {
835                             atts[index++] = st.nextToken();
836                         }
837                         QNAME_TABLE.put(name, atts);
838                     }
839                     
840                 }
841             }
842         }
843         
844         private HashMap _prefixes = null;
845         
846         private RemappedPrefixes _parent = null;
847         
848         int depth = 0;
849         
850         public boolean isRemappedPrefix(String prefix) {
851             
852             if (prefix == null) prefix = "";
853             
854             if (_prefixes != null) {
855                 if (_prefixes.get(prefix) != null) return true;
856             }
857             
858             if (_parent != null) {
859                 return _parent.isRemappedPrefix(prefix);
860             }
861             return false;
862         }
863         
864         public RemappedPrefixes getParent() {
865             return _parent;
866         }
867         
868         public String getPrefixMapping(final String oldPrefix) {
869             
870             if (_prefixes != null) {
871                 String newPrefix = (String)_prefixes.get(oldPrefix);
872                 if (newPrefix != null)
873                     return newPrefix;
874             }
875             
876             if (_parent != null) {
877                 return _parent.getPrefixMapping(oldPrefix);
878             }
879             
880             return oldPrefix;
881         }
882         
883         public RemappedPrefixes newRemappedPrefixes() {
884             RemappedPrefixes rp = new RemappedPrefixes();
885             rp._parent = this;
886             return rp;
887         }
888         
889         public void addMapping(final String oldPrefix, final String newPrefix) {
890             if (_prefixes == null) {
891                 _prefixes = new HashMap();
892             }
893             _prefixes.put(oldPrefix, newPrefix);
894         }
895 
896         public String remapQName(String value) {
897             if (value == null) {
898                 return null;
899             }
900             
901             //-- non-default namespace
902             int idx = value.indexOf(':');
903             String prefix = "";
904             if (idx >= 0) {
905                 prefix = value.substring(0, idx);
906             } else {
907                 idx = -1;            
908             }
909             String newPrefix = getPrefixMapping(prefix);
910             if (!prefix.equals(newPrefix)) {
911                 if (newPrefix.length() == 0) {
912                     value = value.substring(idx + 1);
913                 } else {
914                     value = newPrefix + ":" + value.substring(idx + 1);
915                 }
916             }
917             
918             return value;
919         } //-- remapValue
920         
921     }
922     
923 } //-- SchemaUnmarshaller
924