package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.codehaus.mojo.jaxb2.schemageneration.XsdGeneratorHelper;
import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.NodeProcessor;
import org.codehaus.plexus.util.IOUtil;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* <p>Namespace resolver for XML documents, which relates XML Namespace Prefixes to XML Namespace URIs.
* Doubles as a JAXB NamespaceContext, if we decide to use JAXB instead of DOM to parse our generated
* schema files.</p>
*
* @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>
* @since 1.4
*/
public class SimpleNamespaceResolver implements NamespaceContext {
// Constants
private static final String DEFAULT_NS = "DEFAULT";
private static final String TARGET_NAMESPACE = "targetNamespace";
private static final String SCHEMA = "schema";
// Internal state
private String sourceFilename;
private String localNamespaceURI;
private Map<String, String> prefix2Uri = new HashMap<String, String>();
private Map<String, String> uri2Prefix = new HashMap<String, String>();
/**
* Creates a new SimpleNamespaceResolver which collects namespace data
* from the provided XML file.
*
* @param xmlFile The XML file from which to collect namespace data, should not be null.
*/
public SimpleNamespaceResolver(final File xmlFile) {
this.sourceFilename = xmlFile.getName();
Reader reader = null;
try {
reader = new FileReader(xmlFile);
initialize(reader);
} catch (FileNotFoundException e) {
throw new IllegalArgumentException("File [" + xmlFile + "] could not be found.");
} finally {
IOUtil.close(reader);
}
}
/**
* {@inheritDoc}
*/
public String getNamespaceURI(final String prefix) {
if (prefix == null) {
// Be compliant with the JAXB contract for NamespaceResolver.
throw new IllegalArgumentException("Cannot handle null prefix argument.");
}
return prefix2Uri.get(XMLConstants.DEFAULT_NS_PREFIX.equals(prefix) ? DEFAULT_NS : prefix);
}
/**
* {@inheritDoc}
*/
public String getPrefix(final String namespaceURI) {
if (namespaceURI == null) {
// Be compliant with the JAXB contract for NamespaceResolver.
throw new IllegalArgumentException("Cannot acquire prefix for null namespaceURI.");
}
return uri2Prefix.get(namespaceURI);
}
/**
* {@inheritDoc}
*/
public Iterator<String> getPrefixes(final String namespaceURI) {
if (namespaceURI == null) {
// Be compliant with the JAXB contract for NamespaceResolver.
throw new IllegalArgumentException("Cannot acquire prefixes for null namespaceURI.");
}
return Collections.singletonList(uri2Prefix.get(namespaceURI)).iterator();
}
/**
* @return A readonly map relating namespace URIs to namespace prefixes.
*/
public Map<String, String> getNamespaceURI2PrefixMap() {
return Collections.unmodifiableMap(uri2Prefix);
}
/**
* @return The namespace URI of the default namespace within the sourceFile of this SimpleNamespaceResolver.
*/
public String getLocalNamespaceURI() {
return localNamespaceURI;
}
/**
* @return The name of the source file used for this SimpleNamespaceResolver.
*/
public String getSourceFilename() {
return sourceFilename;
}
//
// Private helpers
//
/**
* Initializes this SimpleNamespaceResolver to collect namespace data from the provided stream.
*
* @param xmlFileStream A Reader connected to the XML file from which we should read namespace data.
*/
private void initialize(final Reader xmlFileStream) {
// Build a DOM model.
final Document parsedDocument = XsdGeneratorHelper.parseXmlStream(xmlFileStream);
// Process the DOM model.
XsdGeneratorHelper.process(parsedDocument.getFirstChild(), true, new NamespaceAttributeNodeProcessor());
}
private class NamespaceAttributeNodeProcessor
implements NodeProcessor {
/**
* Defines if this visitor should process the provided node.
*
* @param aNode The DOM node to process.
* @return <code>true</code> if the provided Node should be processed by this NodeProcessor.
*/
public boolean accept(final Node aNode) {
// Correct namespace?
if (aNode.getNamespaceURI() != null
&& XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(aNode.getNamespaceURI())) {
return true;
}
// Is this Node the targetNamespace attribute?
if (aNode instanceof Attr) {
final Attr attribute = (Attr) aNode;
final Element parent = attribute.getOwnerElement();
if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(parent.getNamespaceURI())
&& SCHEMA.equalsIgnoreCase(parent.getLocalName())
&& TARGET_NAMESPACE.equals(attribute.getLocalName())) {
SimpleNamespaceResolver.this.localNamespaceURI = attribute.getNodeValue();
}
}
// Ignore processing this Node.
return false;
}
/**
* Processes the provided DOM Node.
*
* @param aNode The DOM Node to process.
*/
public void process(final Node aNode) {
// If we have no namespace, use the DEFAULT_NS as the prefix
final String cacheKey = XMLConstants.XMLNS_ATTRIBUTE.equals(aNode.getNodeName())
? DEFAULT_NS
: aNode.getLocalName();
final String nodeValue = aNode.getNodeValue();
// Cache the namespace in both caches.
final String oldUriValue = prefix2Uri.put(cacheKey, nodeValue);
final String oldPrefixValue = uri2Prefix.put(nodeValue, cacheKey);
// Check sanity; we should not be overwriting values here.
if (oldUriValue != null) {
throw new IllegalStateException(
"Replaced URI [" + oldUriValue + "] with [" + aNode.getNodeValue() + "] for prefix [" + cacheKey
+ "]");
}
if (oldPrefixValue != null) {
throw new IllegalStateException(
"Replaced prefix [" + oldPrefixValue + "] with [" + cacheKey + "] for URI [" + aNode.getNodeValue()
+ "]");
}
}
}
}