Author: cziegeler Date: Fri Dec 31 02:10:27 2004 New Revision: 123778 URL: http://svn.apache.org/viewcvs?view=rev&rev=123778 Log: Add our own configuration builder - not used yet Added: cocoon/trunk/src/core/java/org/apache/cocoon/core/container/ConfigurationBuilder.java (contents, props changed) Modified: cocoon/trunk/src/core/java/org/apache/cocoon/core/container/CocoonServiceManager.java
Modified: cocoon/trunk/src/core/java/org/apache/cocoon/core/container/CocoonServiceManager.java Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/core/java/org/apache/cocoon/core/container/CocoonServiceManager.java?view=diff&rev=123778&p1=cocoon/trunk/src/core/java/org/apache/cocoon/core/container/CocoonServiceManager.java&r1=123777&p2=cocoon/trunk/src/core/java/org/apache/cocoon/core/container/CocoonServiceManager.java&r2=123778 ============================================================================== --- cocoon/trunk/src/core/java/org/apache/cocoon/core/container/CocoonServiceManager.java (original) +++ cocoon/trunk/src/core/java/org/apache/cocoon/core/container/CocoonServiceManager.java Fri Dec 31 02:10:27 2004 @@ -17,7 +17,6 @@ package org.apache.cocoon.core.container; import java.io.IOException; -import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; Added: cocoon/trunk/src/core/java/org/apache/cocoon/core/container/ConfigurationBuilder.java Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/core/java/org/apache/cocoon/core/container/ConfigurationBuilder.java?view=auto&rev=123778 ============================================================================== --- (empty file) +++ cocoon/trunk/src/core/java/org/apache/cocoon/core/container/ConfigurationBuilder.java Fri Dec 31 02:10:27 2004 @@ -0,0 +1,378 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 org.apache.cocoon.core.container; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Iterator; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfiguration; +import org.xml.sax.Attributes; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.NamespaceSupport; + +/** + * A ConfigurationBuilder builds [EMAIL PROTECTED] Configuration}s from XML, + * via a SAX2 compliant parser. + * + * <p> + * The mapping from XML namespaces to [EMAIL PROTECTED] 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>[EMAIL PROTECTED] + * Configuration#getName getName()}.equals("title")</code> and <code>[EMAIL PROTECTED] + * 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> + * + * @version CVS $Revision: 1.33 $ $Date: 2004/04/03 23:55:54 $ + */ +public class ConfigurationBuilder + extends DefaultHandler + implements ErrorHandler { + + private XMLReader parser; + + /** + * Likely number of nested configuration items. If more is + * encountered the lists will grow automatically. + */ + private static final int EXPECTED_DEPTH = 4; + private final ArrayList elements = new ArrayList( EXPECTED_DEPTH ); + private final ArrayList prefixes = new ArrayList( EXPECTED_DEPTH ); + private final ArrayList values = new ArrayList( EXPECTED_DEPTH ); + + /** + * Contains true at index n if space in the configuration with + * depth n is to be preserved. + */ + private final BitSet preserveSpace = new BitSet(); + private Configuration configuration; + private Locator locator; + private final NamespaceSupport namespaceSupport = new NamespaceSupport(); + + /** + * Create a Configuration Builder + */ + public ConfigurationBuilder() { + try { + final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + + saxParserFactory.setNamespaceAware( true ); + + final SAXParser saxParser = saxParserFactory.newSAXParser(); + + this.parser = saxParser.getXMLReader(); + + this.parser.setContentHandler( this ); + this.parser.setErrorHandler( this ); + } catch( final Exception se ) { + throw new Error( "Unable to setup SAX parser" + se ); + } + } + + /** + * 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 ) { + this.clear(); + this.parser.parse( input ); + return this.configuration; + } + } + + /** + * Sets the <code>EntityResolver</code> to + * be used by parser. Useful when dealing with xml + * files that reference external entities. + * + * @param resolver implementation of <code>EntityResolver</code> + */ + public void setEntityResolver( final EntityResolver resolver ) { + this.parser.setEntityResolver( resolver ); + } + + /** + * Clears all data from this configuration handler. + */ + protected void clear() { + this.elements.clear(); + Iterator i = this.prefixes.iterator(); + while( i.hasNext() ) { + ( (ArrayList)i.next() ).clear(); + } + this.prefixes.clear(); + this.values.clear(); + this.locator = null; + } + + /* (non-Javadoc) + * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator) + */ + public void setDocumentLocator( final Locator locator ) { + this.locator = locator; + } + + /* (non-Javadoc) + * @see org.xml.sax.ContentHandler#startDocument() + */ + public void startDocument() + throws SAXException { + this.namespaceSupport.reset(); + super.startDocument(); + } + + /* (non-Javadoc) + * @see org.xml.sax.ContentHandler#endDocument() + */ + public void endDocument() + throws SAXException { + super.endDocument(); + this.namespaceSupport.reset(); + } + + /* (non-Javadoc) + * @see org.xml.sax.ContentHandler#characters(char[], int, int) + */ + public void characters( final char[] ch, int start, int end ) + throws SAXException { + // it is possible to play micro-optimization here by doing + // manual trimming and thus preserve some precious bits + // of memory, but it's really not important enough to justify + // resulting code complexity + final int depth = this.values.size() - 1; + final StringBuffer valueBuffer = (StringBuffer)this.values.get( depth ); + valueBuffer.append( ch, start, end ); + } + + /* (non-Javadoc) + * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String) + */ + public void endElement( final String namespaceURI, + final String localName, + final String rawName ) + throws SAXException { + final int depth = this.elements.size() - 1; + final DefaultConfiguration finishedConfiguration = + (DefaultConfiguration)this.elements.remove( depth ); + final String accumulatedValue = + ( (StringBuffer)this.values.remove( depth ) ).toString(); + final ArrayList prefixes = (ArrayList)this.prefixes.remove( depth ); + + final Iterator i = prefixes.iterator(); + while( i.hasNext() ) { + endPrefixMapping( (String)i.next() ); + } + prefixes.clear(); + + if( finishedConfiguration.getChildren().length == 0 ) { + // leaf node + String finishedValue; + if( this.preserveSpace.get( depth ) ) { + finishedValue = accumulatedValue; + } else if( 0 == accumulatedValue.length() ) { + finishedValue = null; + } else { + finishedValue = accumulatedValue.trim(); + } + finishedConfiguration.setValue( finishedValue ); + } else { + final String trimmedValue = accumulatedValue.trim(); + if( trimmedValue.length() > 0 ) { + throw new SAXException( "Not allowed to define mixed content in the " + + "element " + finishedConfiguration.getName() + " at " + + finishedConfiguration.getLocation() ); + } + } + + if( 0 == depth ) { + this.configuration = finishedConfiguration; + } + this.namespaceSupport.popContext(); + } + + /** + * Create a new <code>DefaultConfiguration</code> with the specified + * local name, namespace, and location. + * + * @param localName a <code>String</code> value + * @param namespaceURI a <code>String</code> value + * @param location a <code>String</code> value + * @return a <code>DefaultConfiguration</code> value + */ + protected DefaultConfiguration createConfiguration( final String localName, + final String namespaceURI, + final String location ) { + String prefix = this.namespaceSupport.getPrefix( namespaceURI ); + if( prefix == null ) { + prefix = ""; + } + return new DefaultConfiguration( localName, location, namespaceURI, prefix ); + } + + + /* (non-Javadoc) + * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) + */ + public void startElement( final String namespaceURI, + final String localName, + final String rawName, + final Attributes attributes ) + throws SAXException { + this.namespaceSupport.pushContext(); + final DefaultConfiguration configuration = + createConfiguration( localName, namespaceURI, getLocationString() ); + // depth of new configuration (not decrementing here, configuration + // is to be added) + final int depth = this.elements.size(); + boolean preserveSpace = false; // top level element trims space by default + + if( depth > 0 ) { + final DefaultConfiguration parent = + (DefaultConfiguration)this.elements.get( depth - 1 ); + parent.addChild( configuration ); + // inherits parent's space preservation policy + preserveSpace = this.preserveSpace.get( depth - 1 ); + } + + this.elements.add( configuration ); + this.values.add( new StringBuffer() ); + + final ArrayList prefixes = new ArrayList(); + AttributesImpl componentAttr = new AttributesImpl(); + + for( int i = 0; i < attributes.getLength(); i++ ) { + if( attributes.getQName( i ).startsWith( "xmlns" ) ) + { + prefixes.add( attributes.getLocalName( i ) ); + this.startPrefixMapping( attributes.getLocalName( i ), + attributes.getValue( i ) ); + } else if( attributes.getQName( i ).equals( "xml:space" ) ) { + preserveSpace = attributes.getValue( i ).equals( "preserve" ); + } else { + componentAttr.addAttribute( attributes.getURI( i ), + attributes.getLocalName( i ), + attributes.getQName( i ), + attributes.getType( i ), + attributes.getValue( i ) ); + } + } + + if( preserveSpace ) { + this.preserveSpace.set( depth ); + } else { + this.preserveSpace.clear( depth ); + } + + this.prefixes.add( prefixes ); + + final int attributesSize = componentAttr.getLength(); + + for( int i = 0; i < attributesSize; i++ ) { + final String name = componentAttr.getQName( i ); + final String value = componentAttr.getValue( i ); + configuration.setAttribute( name, value ); + } + } + + + /* (non-Javadoc) + * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException) + */ + public void error( final SAXParseException exception ) + throws SAXException { + throw exception; + } + + /* (non-Javadoc) + * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException) + */ + public void warning( final SAXParseException exception ) + throws SAXException { + throw exception; + } + + /* (non-Javadoc) + * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException) + */ + public void fatalError( final SAXParseException exception ) + throws SAXException { + throw exception; + } + + /** + * Returns a string showing the current system ID, line number and column number. + * + * @return a <code>String</code> value + */ + private String getLocationString() { + if( null == this.locator ) { + return "Unknown"; + } else { + final int columnNumber = this.locator.getColumnNumber(); + return + this.locator.getSystemId() + ":" + + this.locator.getLineNumber() + + ( columnNumber >= 0 ? ( ":" + columnNumber ) : "" ); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String) + */ + public void startPrefixMapping( String prefix, String uri ) + throws SAXException { + this.namespaceSupport.declarePrefix( prefix, uri ); + super.startPrefixMapping( prefix, uri ); + } + +}