001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration2;
019
020import java.io.IOException;
021
022import org.xml.sax.Attributes;
023import org.xml.sax.ContentHandler;
024import org.xml.sax.DTDHandler;
025import org.xml.sax.EntityResolver;
026import org.xml.sax.ErrorHandler;
027import org.xml.sax.InputSource;
028import org.xml.sax.SAXException;
029import org.xml.sax.XMLReader;
030import org.xml.sax.helpers.AttributesImpl;
031
032/**
033 * <p>
034 * A base class for &quot;faked&quot; {@code XMLReader} classes that transform a configuration object in a set of SAX
035 * parsing events.
036 * </p>
037 * <p>
038 * This class provides dummy implementations for most of the methods defined in the {@code XMLReader} interface that are
039 * not used for this special purpose. There will be concrete sub classes that process specific configuration classes.
040 * </p>
041 */
042public abstract class ConfigurationXMLReader implements XMLReader {
043
044    /** Constant for the namespace URI. */
045    protected static final String NS_URI = "";
046
047    /** Constant for the default name of the root element. */
048    private static final String DEFAULT_ROOT_NAME = "config";
049
050    /** An empty attributes object. */
051    private static final Attributes EMPTY_ATTRS = new AttributesImpl();
052
053    /** Stores the content handler. */
054    private ContentHandler contentHandler;
055
056    /** Stores an exception that occurred during parsing. */
057    private SAXException exception;
058
059    /** Stores the name for the root element. */
060    private String rootName;
061
062    /**
063     * Creates a new instance of {@code ConfigurationXMLReader}.
064     */
065    protected ConfigurationXMLReader() {
066        rootName = DEFAULT_ROOT_NAME;
067    }
068
069    /**
070     * Fires a SAX characters event.
071     *
072     * @param text the text
073     */
074    protected void fireCharacters(final String text) {
075        if (getException() == null) {
076            try {
077                final char[] ch = text.toCharArray();
078                getContentHandler().characters(ch, 0, ch.length);
079            } catch (final SAXException ex) {
080                exception = ex;
081            }
082        }
083    }
084
085    /**
086     * Fires a SAX element end event.
087     *
088     * @param name the name of the affected element
089     */
090    protected void fireElementEnd(final String name) {
091        if (getException() == null) {
092            try {
093                getContentHandler().endElement(NS_URI, name, name);
094            } catch (final SAXException ex) {
095                exception = ex;
096            }
097        }
098    }
099
100    /**
101     * Fires a SAX element start event.
102     *
103     * @param name the name of the actual element
104     * @param attribs the attributes of this element (can be <strong>null</strong>)
105     */
106    protected void fireElementStart(final String name, final Attributes attribs) {
107        if (getException() == null) {
108            try {
109                final Attributes at = attribs == null ? EMPTY_ATTRS : attribs;
110                getContentHandler().startElement(NS_URI, name, name, at);
111            } catch (final SAXException ex) {
112                exception = ex;
113            }
114        }
115    }
116
117    /**
118     * Gets the actually set content handler.
119     *
120     * @return the content handler
121     */
122    @Override
123    public ContentHandler getContentHandler() {
124        return contentHandler;
125    }
126
127    /**
128     * Gets the DTD handler. This class does not support DTD handlers, so this method always returns <strong>null</strong>.
129     *
130     * @return the DTD handler
131     */
132    @Override
133    public DTDHandler getDTDHandler() {
134        return null;
135    }
136
137    /**
138     * Gets the entity resolver. This class does not support an entity resolver, so this method always returns
139     * <strong>null</strong>.
140     *
141     * @return the entity resolver
142     */
143    @Override
144    public EntityResolver getEntityResolver() {
145        return null;
146    }
147
148    /**
149     * Gets the error handler. This class does not support an error handler, so this method always returns <strong>null</strong>.
150     *
151     * @return the error handler
152     */
153    @Override
154    public ErrorHandler getErrorHandler() {
155        return null;
156    }
157
158    /**
159     * Gets a reference to an exception that occurred during parsing.
160     *
161     * @return a SAXExcpetion or <strong>null</strong> if none occurred
162     */
163    public SAXException getException() {
164        return exception;
165    }
166
167    /**
168     * Dummy implementation of the interface method.
169     *
170     * @param name the name of the feature
171     * @return always <strong>false</strong> (no features are supported)
172     */
173    @Override
174    public boolean getFeature(final String name) {
175        return false;
176    }
177
178    /**
179     * Gets a reference to the configuration that is parsed by this object.
180     *
181     * @return the parsed configuration
182     */
183    public abstract Configuration getParsedConfiguration();
184
185    /**
186     * Dummy implementation of the interface method. No properties are supported, so this method always returns <strong>null</strong>.
187     *
188     * @param name the name of the requested property
189     * @return the property value
190     */
191    @Override
192    public Object getProperty(final String name) {
193        return null;
194    }
195
196    /**
197     * Gets the name to be used for the root element.
198     *
199     * @return the name for the root element
200     */
201    public String getRootName() {
202        return rootName;
203    }
204
205    /**
206     * Parses the actual configuration object. The passed input source will be ignored.
207     *
208     * @param input the input source (ignored)
209     * @throws IOException if no configuration was specified
210     * @throws SAXException if an error occurs during parsing
211     */
212    @Override
213    public void parse(final InputSource input) throws IOException, SAXException {
214        parseConfiguration();
215    }
216
217    /**
218     * Parses the current configuration object. The passed system ID will be ignored.
219     *
220     * @param systemId the system ID (ignored)
221     * @throws IOException if no configuration was specified
222     * @throws SAXException if an error occurs during parsing
223     */
224    @Override
225    public void parse(final String systemId) throws IOException, SAXException {
226        parseConfiguration();
227    }
228
229    /**
230     * Parses the configuration object and generates SAX events. This is the main processing method.
231     *
232     * @throws IOException if no configuration has been specified
233     * @throws SAXException if an error occurs during parsing
234     */
235    protected void parseConfiguration() throws IOException, SAXException {
236        if (getParsedConfiguration() == null) {
237            throw new IOException("No configuration specified!");
238        }
239
240        if (getContentHandler() != null) {
241            exception = null;
242            getContentHandler().startDocument();
243            processKeys();
244            if (getException() != null) {
245                throw getException();
246            }
247            getContentHandler().endDocument();
248        }
249    }
250
251    /**
252     * Processes all keys stored in the actual configuration. This method is called by {@code parseConfiguration()} to start
253     * the main parsing process. {@code parseConfiguration()} calls the content handler's {@code startDocument()} and
254     * {@code endElement()} methods and cares for exception handling. The remaining actions are left to this method that
255     * must be implemented in a concrete sub class.
256     *
257     * @throws IOException if an IO error occurs
258     * @throws SAXException if a SAX error occurs
259     */
260    protected abstract void processKeys() throws IOException, SAXException;
261
262    /**
263     * Sets the content handler. The object specified here will receive SAX events during parsing.
264     *
265     * @param handler the content handler
266     */
267    @Override
268    public void setContentHandler(final ContentHandler handler) {
269        contentHandler = handler;
270    }
271
272    /**
273     * Sets the DTD handler. The passed value is ignored.
274     *
275     * @param handler the handler to be set
276     */
277    @Override
278    public void setDTDHandler(final DTDHandler handler) {
279    }
280
281    /**
282     * Sets the entity resolver. The passed value is ignored.
283     *
284     * @param resolver the entity resolver
285     */
286    @Override
287    public void setEntityResolver(final EntityResolver resolver) {
288    }
289
290    /**
291     * Sets the error handler. The passed value is ignored.
292     *
293     * @param handler the error handler
294     */
295    @Override
296    public void setErrorHandler(final ErrorHandler handler) {
297    }
298
299    /**
300     * Dummy implementation of the interface method.
301     *
302     * @param name the name of the feature to be set
303     * @param value the value of the feature
304     */
305    @Override
306    public void setFeature(final String name, final boolean value) {
307    }
308
309    /**
310     * Dummy implementation of the interface method. No properties are supported, so a call of this method just has no
311     * effect.
312     *
313     * @param name the property name
314     * @param value the property value
315     */
316    @Override
317    public void setProperty(final String name, final Object value) {
318    }
319
320    /**
321     * Sets the name for the root element.
322     *
323     * @param string the name for the root element.
324     */
325    public void setRootName(final String string) {
326        rootName = string;
327    }
328}