View Javadoc
1   package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.codehaus.mojo.jaxb2.schemageneration.XsdGeneratorHelper;
23  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.NodeProcessor;
24  import org.codehaus.plexus.util.IOUtil;
25  import org.w3c.dom.Attr;
26  import org.w3c.dom.Document;
27  import org.w3c.dom.Element;
28  import org.w3c.dom.Node;
29  
30  import javax.xml.XMLConstants;
31  import javax.xml.namespace.NamespaceContext;
32  import java.io.File;
33  import java.io.FileNotFoundException;
34  import java.io.FileReader;
35  import java.io.Reader;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.Map;
40  
41  /**
42   * <p>Namespace resolver for XML documents, which relates XML Namespace Prefixes to XML Namespace URIs.
43   * Doubles as a JAXB NamespaceContext, if we decide to use JAXB instead of DOM to parse our generated
44   * schema files.</p>
45   *
46   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>
47   * @since 1.4
48   */
49  public class SimpleNamespaceResolver implements NamespaceContext {
50  
51      // Constants
52      private static final String DEFAULT_NS = "DEFAULT";
53      private static final String TARGET_NAMESPACE = "targetNamespace";
54      private static final String SCHEMA = "schema";
55  
56      // Internal state
57      private String sourceFilename;
58      private String localNamespaceURI;
59      private Map<String, String> prefix2Uri = new HashMap<String, String>();
60      private Map<String, String> uri2Prefix = new HashMap<String, String>();
61  
62      /**
63       * Creates a new SimpleNamespaceResolver which collects namespace data
64       * from the provided XML file.
65       *
66       * @param xmlFile The XML file from which to collect namespace data, should not be null.
67       */
68      public SimpleNamespaceResolver(final File xmlFile) {
69          this.sourceFilename = xmlFile.getName();
70  
71          Reader reader = null;
72          try {
73              reader = new FileReader(xmlFile);
74              initialize(reader);
75          } catch (FileNotFoundException e) {
76              throw new IllegalArgumentException("File [" + xmlFile + "] could not be found.");
77          } finally {
78              IOUtil.close(reader);
79          }
80      }
81  
82      /**
83       * {@inheritDoc}
84       */
85      public String getNamespaceURI(final String prefix) {
86          if (prefix == null) {
87              // Be compliant with the JAXB contract for NamespaceResolver.
88              throw new IllegalArgumentException("Cannot handle null prefix argument.");
89          }
90  
91          return prefix2Uri.get(XMLConstants.DEFAULT_NS_PREFIX.equals(prefix) ? DEFAULT_NS : prefix);
92      }
93  
94      /**
95       * {@inheritDoc}
96       */
97      public String getPrefix(final String namespaceURI) {
98          if (namespaceURI == null) {
99              // Be compliant with the JAXB contract for NamespaceResolver.
100             throw new IllegalArgumentException("Cannot acquire prefix for null namespaceURI.");
101         }
102 
103         return uri2Prefix.get(namespaceURI);
104     }
105 
106     /**
107      * {@inheritDoc}
108      */
109     public Iterator<String> getPrefixes(final String namespaceURI) {
110         if (namespaceURI == null) {
111             // Be compliant with the JAXB contract for NamespaceResolver.
112             throw new IllegalArgumentException("Cannot acquire prefixes for null namespaceURI.");
113         }
114 
115         return Collections.singletonList(uri2Prefix.get(namespaceURI)).iterator();
116     }
117 
118     /**
119      * @return A readonly map relating namespace URIs to namespace prefixes.
120      */
121     public Map<String, String> getNamespaceURI2PrefixMap() {
122         return Collections.unmodifiableMap(uri2Prefix);
123     }
124 
125     /**
126      * @return The namespace URI of the default namespace within the sourceFile of this SimpleNamespaceResolver.
127      */
128     public String getLocalNamespaceURI() {
129         return localNamespaceURI;
130     }
131 
132     /**
133      * @return The name of the source file used for this SimpleNamespaceResolver.
134      */
135     public String getSourceFilename() {
136         return sourceFilename;
137     }
138 
139     //
140     // Private helpers
141     //
142 
143     /**
144      * Initializes this SimpleNamespaceResolver to collect namespace data from the provided stream.
145      *
146      * @param xmlFileStream A Reader connected to the XML file from which we should read namespace data.
147      */
148     private void initialize(final Reader xmlFileStream) {
149 
150         // Build a DOM model.
151         final Document parsedDocument = XsdGeneratorHelper.parseXmlStream(xmlFileStream);
152 
153         // Process the DOM model.
154         XsdGeneratorHelper.process(parsedDocument.getFirstChild(), true, new NamespaceAttributeNodeProcessor());
155     }
156 
157     private class NamespaceAttributeNodeProcessor
158             implements NodeProcessor {
159         /**
160          * Defines if this visitor should process the provided node.
161          *
162          * @param aNode The DOM node to process.
163          * @return <code>true</code> if the provided Node should be processed by this NodeProcessor.
164          */
165         public boolean accept(final Node aNode) {
166 
167             // Correct namespace?
168             if (aNode.getNamespaceURI() != null
169                     && XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(aNode.getNamespaceURI())) {
170                 return true;
171             }
172 
173             // Is this Node the targetNamespace attribute?
174             if (aNode instanceof Attr) {
175 
176                 final Attr attribute = (Attr) aNode;
177                 final Element parent = attribute.getOwnerElement();
178                 if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(parent.getNamespaceURI())
179                         && SCHEMA.equalsIgnoreCase(parent.getLocalName())
180                         && TARGET_NAMESPACE.equals(attribute.getLocalName())) {
181 
182                     SimpleNamespaceResolver.this.localNamespaceURI = attribute.getNodeValue();
183                 }
184             }
185 
186             // Ignore processing this Node.
187             return false;
188         }
189 
190         /**
191          * Processes the provided DOM Node.
192          *
193          * @param aNode The DOM Node to process.
194          */
195         public void process(final Node aNode) {
196 
197             // If we have no namespace, use the DEFAULT_NS as the prefix
198             final String cacheKey = XMLConstants.XMLNS_ATTRIBUTE.equals(aNode.getNodeName())
199                     ? DEFAULT_NS
200                     : aNode.getLocalName();
201             final String nodeValue = aNode.getNodeValue();
202 
203             // Cache the namespace in both caches.
204             final String oldUriValue = prefix2Uri.put(cacheKey, nodeValue);
205             final String oldPrefixValue = uri2Prefix.put(nodeValue, cacheKey);
206 
207             // Check sanity; we should not be overwriting values here.
208             if (oldUriValue != null) {
209                 throw new IllegalStateException(
210                         "Replaced URI [" + oldUriValue + "] with [" + aNode.getNodeValue() + "] for prefix [" + cacheKey
211                                 + "]");
212             }
213             if (oldPrefixValue != null) {
214                 throw new IllegalStateException(
215                         "Replaced prefix [" + oldPrefixValue + "] with [" + cacheKey + "] for URI [" + aNode.getNodeValue()
216                                 + "]");
217             }
218         }
219     }
220 }