001: /*
002: * Copyright 2004-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.compass.core.util.config;
018:
019: import java.util.ArrayList;
020: import java.util.BitSet;
021: import java.util.Iterator;
022:
023: import org.xml.sax.Attributes;
024: import org.xml.sax.Locator;
025: import org.xml.sax.SAXException;
026: import org.xml.sax.SAXParseException;
027: import org.xml.sax.helpers.AttributesImpl;
028: import org.xml.sax.helpers.NamespaceSupport;
029:
030: /**
031: * A SAXConfigurationHandler helps build Configurations out of sax events,
032: * including namespace information.
033: */
034: public class NamespacedSAXConfigurationHandler extends
035: SAXConfigurationHandler {
036:
037: /**
038: * Likely number of nested configuration items. If more is encountered the
039: * lists will grow automatically.
040: */
041: private static final int EXPECTED_DEPTH = 5;
042:
043: private final ArrayList m_elements = new ArrayList(EXPECTED_DEPTH);
044:
045: private final ArrayList m_prefixes = new ArrayList(EXPECTED_DEPTH);
046:
047: private final ArrayList m_values = new ArrayList(EXPECTED_DEPTH);
048:
049: /**
050: * Contains true at index n if space in the configuration with depth n is to
051: * be preserved.
052: */
053: private final BitSet m_preserveSpace = new BitSet();
054:
055: private ConfigurationHelper m_configuration;
056:
057: private Locator m_locator;
058:
059: private NamespaceSupport m_namespaceSupport = new NamespaceSupport();
060:
061: /**
062: * Get the configuration object that was built.
063: *
064: * @return a <code>Configuration</code> object
065: */
066: public ConfigurationHelper getConfiguration() {
067: return m_configuration;
068: }
069:
070: /**
071: * Clears all data from this configuration handler.
072: */
073: public void clear() {
074: m_elements.clear();
075: Iterator i = m_prefixes.iterator();
076: while (i.hasNext()) {
077: ((ArrayList) i.next()).clear();
078: }
079: m_prefixes.clear();
080: m_values.clear();
081: m_locator = null;
082: }
083:
084: /**
085: * Set the document <code>Locator</code> to use.
086: */
087: public void setDocumentLocator(final Locator locator) {
088: m_locator = locator;
089: }
090:
091: /**
092: * Handling hook for starting the document parsing.
093: */
094: public void startDocument() throws SAXException {
095: m_namespaceSupport.reset();
096: super .startDocument();
097: }
098:
099: /**
100: * Handling hook for ending the document parsing.
101: */
102: public void endDocument() throws SAXException {
103: super .endDocument();
104: m_namespaceSupport.reset();
105: }
106:
107: /**
108: * Handling hook for character data.
109: */
110: public void characters(final char[] ch, int start, int end)
111: throws SAXException {
112: // it is possible to play micro-optimization here by doing
113: // manual trimming and thus preserve some precious bits
114: // of memory, but it's really not important enough to justify
115: // resulting code complexity
116: final int depth = m_values.size() - 1;
117: final StringBuffer valueBuffer = (StringBuffer) m_values
118: .get(depth);
119: valueBuffer.append(ch, start, end);
120: }
121:
122: /**
123: * Handling hook for finishing parsing of an element.
124: */
125: public void endElement(final String namespaceURI,
126: final String localName, final String rawName)
127: throws SAXException {
128: final int depth = m_elements.size() - 1;
129: final XmlConfigurationHelper finishedConfiguration = (XmlConfigurationHelper) m_elements
130: .remove(depth);
131: final String accumulatedValue = ((StringBuffer) m_values
132: .remove(depth)).toString();
133: final ArrayList prefixes = (ArrayList) m_prefixes.remove(depth);
134: final Iterator i = prefixes.iterator();
135: while (i.hasNext()) {
136: endPrefixMapping((String) i.next());
137: }
138: prefixes.clear();
139: if (finishedConfiguration.getChildren().length == 0) {
140: // leaf node
141: String finishedValue;
142: if (m_preserveSpace.get(depth)) {
143: finishedValue = accumulatedValue;
144: } else if (0 == accumulatedValue.length()) {
145: finishedValue = null;
146: } else {
147: finishedValue = accumulatedValue.trim();
148: }
149: finishedConfiguration.setValue(finishedValue);
150: } else {
151: final String trimmedValue = accumulatedValue.trim();
152: if (trimmedValue.length() > 0) {
153: throw new SAXException(
154: "Not allowed to define mixed content in the "
155: + "element "
156: + finishedConfiguration.getName()
157: + " at "
158: + finishedConfiguration.getLocation());
159: }
160: }
161: if (0 == depth) {
162: m_configuration = finishedConfiguration;
163: }
164: m_namespaceSupport.popContext();
165: }
166:
167: /**
168: * Create a new <code>DefaultConfiguration</code> with the specified local
169: * name, namespace, and location.
170: */
171: protected XmlConfigurationHelper createConfiguration(
172: final String localName, final String namespaceURI,
173: final String location) {
174: String prefix = m_namespaceSupport.getPrefix(namespaceURI);
175: if (prefix == null) {
176: prefix = "";
177: }
178: return new XmlConfigurationHelper(localName, location,
179: namespaceURI, prefix);
180: }
181:
182: /**
183: * Handling hook for starting parsing of an element.
184: */
185: public void startElement(final String namespaceURI,
186: final String localName, final String rawName,
187: final Attributes attributes) throws SAXException {
188: m_namespaceSupport.pushContext();
189: final XmlConfigurationHelper configuration = createConfiguration(
190: localName, namespaceURI, getLocationString());
191: // depth of new configuration (not decrementing here, configuration
192: // is to be added)
193: final int depth = m_elements.size();
194: boolean preserveSpace = false; // top level element trims space by
195: // default
196: if (depth > 0) {
197: final XmlConfigurationHelper parent = (XmlConfigurationHelper) m_elements
198: .get(depth - 1);
199: parent.addChild(configuration);
200: // inherits parent's space preservation policy
201: preserveSpace = m_preserveSpace.get(depth - 1);
202: }
203: m_elements.add(configuration);
204: m_values.add(new StringBuffer());
205: final ArrayList prefixes = new ArrayList();
206: AttributesImpl componentAttr = new AttributesImpl();
207: for (int i = 0; i < attributes.getLength(); i++) {
208: if (attributes.getQName(i).startsWith("xmlns")) {
209: prefixes.add(attributes.getLocalName(i));
210: this .startPrefixMapping(attributes.getLocalName(i),
211: attributes.getValue(i));
212: } else if (attributes.getQName(i).equals("xml:space")) {
213: preserveSpace = attributes.getValue(i).equals(
214: "preserve");
215: } else {
216: componentAttr.addAttribute(attributes.getURI(i),
217: attributes.getLocalName(i), attributes
218: .getQName(i), attributes.getType(i),
219: attributes.getValue(i));
220: }
221: }
222: if (preserveSpace) {
223: m_preserveSpace.set(depth);
224: } else {
225: m_preserveSpace.clear(depth);
226: }
227: m_prefixes.add(prefixes);
228: final int attributesSize = componentAttr.getLength();
229: for (int i = 0; i < attributesSize; i++) {
230: final String name = componentAttr.getQName(i);
231: final String value = componentAttr.getValue(i);
232: configuration.setAttribute(name, value);
233: }
234: }
235:
236: /**
237: * This just throws an exception on a parse error.
238: */
239: public void error(final SAXParseException exception)
240: throws SAXException {
241: throw exception;
242: }
243:
244: /**
245: * This just throws an exception on a parse error.
246: */
247: public void warning(final SAXParseException exception)
248: throws SAXException {
249: throw exception;
250: }
251:
252: /**
253: * This just throws an exception on a parse error.
254: */
255: public void fatalError(final SAXParseException exception)
256: throws SAXException {
257: throw exception;
258: }
259:
260: /**
261: * Returns a string showing the current system ID, line number and column
262: * number.
263: */
264: protected String getLocationString() {
265: if (null == m_locator) {
266: return "Unknown";
267: } else {
268: final int columnNumber = m_locator.getColumnNumber();
269: return m_locator.getSystemId() + ":"
270: + m_locator.getLineNumber()
271: + (columnNumber >= 0 ? (":" + columnNumber) : "");
272: }
273: }
274:
275: /**
276: * Handling hook for starting prefix mapping.
277: */
278: public void startPrefixMapping(String prefix, String uri)
279: throws SAXException {
280: m_namespaceSupport.declarePrefix(prefix, uri);
281: super.startPrefixMapping(prefix, uri);
282: }
283: }
|