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