/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* Licensed 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.
*/
package com.alibaba.antx.util.configuration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* A DefaultConfigurationBuilder builds <code>Configuration</code>s from XML,
* via a SAX2 compliant parser.
* <p>
* XML namespace support is optional, and disabled by default to preserve
* backwards-compatibility. To enable it, pass the
* {@link #DefaultConfigurationBuilder(boolean)} constructor the flag
* <code>true</code>, or pass a namespace-enabled <code>XMLReader</code> to the
* {@link #DefaultConfigurationBuilder(XMLReader)} constructor.
* </p>
* <p>
* The mapping from XML namespaces to {@link Configuration} namespaces is pretty
* straightforward, with one caveat: attribute namespaces are (deliberately) not
* supported. Enabling namespace processing has the following effects:
* </p>
* <ul>
* <li>Attributes starting with <code>xmlns:</code> are interpreted as declaring
* a prefix:namespaceURI mapping, and won't result in the creation of
* <code>xmlns</code>-prefixed attributes in the <code>Configuration</code>.</li>
* <li>Prefixed XML elements, like
* <tt><doc:title xmlns:doc="http://foo.com">,</tt> will result in a
* <code>Configuration</code> with <code>{@link Configuration#getName
* getName()}.equals("title")</code> and <code>{@link Configuration#getNamespace
* getNamespace()}.equals("http://foo.com")</code>.</li>
* </ul>
* <p>
* Whitespace handling. Since mixed content is not allowed in the
* configurations, whitespace is completely discarded in non-leaf nodes. For the
* leaf nodes the default behavior is to trim the space surrounding the value.
* This can be changed by specifying <code>xml:space</code> attribute with value
* of <code>preserve</code> in that case the whitespace is left intact.
* </p>
*
* @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
*/
public class DefaultConfigurationBuilder {
private SAXConfigurationHandler m_handler;
private XMLReader m_parser;
/**
* Create a Configuration Builder with a default XMLReader that ignores
* namespaces. In order to enable namespaces, use either the constructor
* that has a boolean or that allows you to pass in your own
* namespace-enabled XMLReader.
*/
public DefaultConfigurationBuilder() {
this(false);
}
/**
* Create a Configuration Builder, specifying a flag that determines
* namespace support.
*
* @param enableNamespaces If <code>true</code>, a namespace-aware
* <code>SAXParser</code> is used. If <code>false</code>, the
* default JAXP <code>SAXParser</code> (without namespace
* support) is used.
* @since 4.1
*/
public DefaultConfigurationBuilder(final boolean enableNamespaces) {
//yaya the bugs with some compilers and final variables ..
try {
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
if (enableNamespaces) {
saxParserFactory.setNamespaceAware(true);
}
final SAXParser saxParser = saxParserFactory.newSAXParser();
this.setParser(saxParser.getXMLReader());
} catch (final Exception se) {
throw new Error("Unable to setup SAX parser" + se);
}
}
/**
* Create a Configuration Builder with your own XMLReader.
*
* @param parser an <code>XMLReader</code>
*/
public DefaultConfigurationBuilder(XMLReader parser) {
this.setParser(parser);
}
/** Internally sets up the XMLReader */
private void setParser(XMLReader parser) {
m_parser = parser;
m_handler = getHandler();
m_parser.setContentHandler(m_handler);
m_parser.setErrorHandler(m_handler);
}
/**
* Get a SAXConfigurationHandler for your configuration reading.
*
* @return a <code>SAXConfigurationHandler</code>
*/
protected SAXConfigurationHandler getHandler() {
try {
if (m_parser.getFeature("http://xml.org/sax/features/namespaces")) {
return new NamespacedSAXConfigurationHandler();
}
} catch (Exception e) {
// ignore error and fall through to the non-namespaced version
}
return new SAXConfigurationHandler();
}
/**
* Build a configuration object from a file using a filename.
*
* @param filename name of the file
* @return a <code>Configuration</code> object
* @throws SAXException if a parsing error occurs
* @throws IOException if an I/O error occurs
* @throws ConfigurationException if an error occurs
*/
public Configuration buildFromFile(final String filename) throws SAXException, IOException, ConfigurationException {
return buildFromFile(new File(filename));
}
/**
* Build a configuration object from a file using a File object.
*
* @param file a <code>File</code> object
* @return a <code>Configuration</code> object
* @throws SAXException if a parsing error occurs
* @throws IOException if an I/O error occurs
* @throws ConfigurationException if an error occurs
*/
public Configuration buildFromFile(final File file) throws SAXException, IOException, ConfigurationException {
return buildFromURL(file.toURI().toURL());
}
/**
* Build a configuration object from a file using a File object.
*
* @param file a <code>File</code> object
* @return a <code>Configuration</code> object
* @throws SAXException if a parsing error occurs
* @throws IOException if an I/O error occurs
* @throws ConfigurationException if an error occurs
*/
public Configuration buildFromURL(final URL url) throws SAXException, IOException, ConfigurationException {
synchronized (this) {
m_handler.clear();
m_parser.parse(url.toString());
return m_handler.getConfiguration();
}
}
/**
* Build a configuration object using an InputStream.
*
* @param inputStream an <code>InputStream</code> value
* @return a <code>Configuration</code> object
* @throws SAXException if a parsing error occurs
* @throws IOException if an I/O error occurs
* @throws ConfigurationException if an error occurs
*/
public Configuration build(final InputStream inputStream) throws SAXException, IOException, ConfigurationException {
return build(new InputSource(inputStream));
}
/**
* Build a configuration object using an URI
*
* @param uri a <code>String</code> value
* @return a <code>Configuration</code> object
* @throws SAXException if a parsing error occurs
* @throws IOException if an I/O error occurs
* @throws ConfigurationException if an error occurs
*/
public Configuration build(final String uri) throws SAXException, IOException, ConfigurationException {
return build(new InputSource(uri));
}
/**
* Build a configuration object using an XML InputSource object
*
* @param input an <code>InputSource</code> value
* @return a <code>Configuration</code> object
* @throws SAXException if a parsing error occurs
* @throws IOException if an I/O error occurs
* @throws ConfigurationException if an error occurs
*/
public Configuration build(final InputSource input) throws SAXException, IOException, ConfigurationException {
synchronized (this) {
m_handler.clear();
m_parser.parse(input);
return m_handler.getConfiguration();
}
}
}