Source Code Cross Referenced for XMLConfiguration.java in  » Library » Apache-commons-configuration-1.4-src » org » apache » commons » configuration » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Library » Apache commons configuration 1.4 src » org.apache.commons.configuration 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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:         *     http://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:
018:        package org.apache.commons.configuration;
019:
020:        import java.io.File;
021:        import java.io.InputStream;
022:        import java.io.Reader;
023:        import java.io.Writer;
024:        import java.net.URL;
025:        import java.util.ArrayList;
026:        import java.util.Collection;
027:        import java.util.Iterator;
028:        import java.util.List;
029:
030:        import javax.xml.parsers.DocumentBuilder;
031:        import javax.xml.parsers.DocumentBuilderFactory;
032:        import javax.xml.parsers.ParserConfigurationException;
033:        import javax.xml.transform.OutputKeys;
034:        import javax.xml.transform.Result;
035:        import javax.xml.transform.Source;
036:        import javax.xml.transform.Transformer;
037:        import javax.xml.transform.TransformerException;
038:        import javax.xml.transform.TransformerFactory;
039:        import javax.xml.transform.TransformerFactoryConfigurationError;
040:        import javax.xml.transform.dom.DOMSource;
041:        import javax.xml.transform.stream.StreamResult;
042:
043:        import org.apache.commons.collections.iterators.SingletonIterator;
044:        import org.w3c.dom.Attr;
045:        import org.w3c.dom.CDATASection;
046:        import org.w3c.dom.DOMException;
047:        import org.w3c.dom.Document;
048:        import org.w3c.dom.Element;
049:        import org.w3c.dom.NamedNodeMap;
050:        import org.w3c.dom.NodeList;
051:        import org.w3c.dom.Text;
052:        import org.xml.sax.InputSource;
053:        import org.xml.sax.SAXException;
054:        import org.xml.sax.SAXParseException;
055:        import org.xml.sax.helpers.DefaultHandler;
056:
057:        /**
058:         * <p>A specialized hierarchical configuration class that is able to parse XML
059:         * documents.</p>
060:         *
061:         * <p>The parsed document will be stored keeping its structure. The class also
062:         * tries to preserve as much information from the loaded XML document as
063:         * possible, including comments and processing instructions. These will be
064:         * contained in documents created by the <code>save()</code> methods, too.</p>
065:         *
066:         * <p>Like other file based configuration classes this class maintains the name
067:         * and path to the loaded configuration file. These properties can be altered
068:         * using several setter methods, but they are not modified by <code>save()</code>
069:         * and <code>load()</code> methods. If XML documents contain relative paths to
070:         * other documents (e.g. to a DTD), these references are resolved based on the
071:         * path set for this configuration.</p>
072:         *
073:         * <p>By inheriting from <code>{@link AbstractConfiguration}</code> this class
074:         * provides some extended functionaly, e.g. interpolation of property values.
075:         * Like in <code>{@link PropertiesConfiguration}</code> property values can
076:         * contain delimiter characters (the comma ',' per default) and are then splitted
077:         * into multiple values. This works for XML attributes and text content of
078:         * elements as well. The delimiter can be escaped by a backslash. As an example
079:         * consider the following XML fragment:</p>
080:         *
081:         * <p>
082:         * <pre>
083:         * &lt;config&gt;
084:         *   &lt;array&gt;10,20,30,40&lt;/array&gt;
085:         *   &lt;scalar&gt;3\,1415&lt;/scalar&gt;
086:         *   &lt;cite text="To be or not to be\, this is the question!"/&gt;
087:         * &lt;/config&gt;
088:         * </pre>
089:         * </p>
090:         * <p>Here the content of the <code>array</code> element will be splitted at
091:         * the commas, so the <code>array</code> key will be assigned 4 values. In the
092:         * <code>scalar</code> property and the <code>text</code> attribute of the
093:         * <code>cite</code> element the comma is escaped, so that no splitting is
094:         * performed.</p>
095:         *
096:         * <p><code>XMLConfiguration</code> implements the <code>{@link FileConfiguration}</code>
097:         * interface and thus provides full support for loading XML documents from
098:         * different sources like files, URLs, or streams. A full description of these
099:         * features can be found in the documentation of
100:         * <code>{@link AbstractFileConfiguration}</code>.</p>
101:         *
102:         * @since commons-configuration 1.0
103:         *
104:         * @author J&ouml;rg Schaible
105:         * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger </a>
106:         * @version $Revision: 513498 $, $Date: 2007-03-01 22:15:07 +0100 (Do, 01 Mrz 2007) $
107:         */
108:        public class XMLConfiguration extends
109:                AbstractHierarchicalFileConfiguration {
110:            /**
111:             * The serial version UID.
112:             */
113:            private static final long serialVersionUID = 2453781111653383552L;
114:
115:            /** Constant for the default root element name. */
116:            private static final String DEFAULT_ROOT_NAME = "configuration";
117:
118:            /** The document from this configuration's data source. */
119:            private Document document;
120:
121:            /** Stores the name of the root element. */
122:            private String rootElementName;
123:
124:            /** Stores the public ID from the DOCTYPE.*/
125:            private String publicID;
126:
127:            /** Stores the system ID from the DOCTYPE.*/
128:            private String systemID;
129:
130:            /** Stores the document builder that should be used for loading.*/
131:            private DocumentBuilder documentBuilder;
132:
133:            /** Stores a flag whether DTD validation should be performed.*/
134:            private boolean validating;
135:
136:            /**
137:             * Creates a new instance of <code>XMLConfiguration</code>.
138:             */
139:            public XMLConfiguration() {
140:                super ();
141:            }
142:
143:            /**
144:             * Creates a new instance of <code>XMLConfiguration</code> and copies the
145:             * content of the passed in configuration into this object. Note that only
146:             * the data of the passed in configuration will be copied. If, for instance,
147:             * the other configuration is a <code>XMLConfiguration</code>, too,
148:             * things like comments or processing instructions will be lost.
149:             *
150:             * @param c the configuration to copy
151:             * @since 1.4
152:             */
153:            public XMLConfiguration(HierarchicalConfiguration c) {
154:                super (c);
155:                clearReferences(getRootNode());
156:            }
157:
158:            /**
159:             * Creates a new instance of <code>XMLConfiguration</code>. The
160:             * configuration is loaded from the specified file
161:             *
162:             * @param fileName the name of the file to load
163:             * @throws ConfigurationException if the file cannot be loaded
164:             */
165:            public XMLConfiguration(String fileName)
166:                    throws ConfigurationException {
167:                super (fileName);
168:            }
169:
170:            /**
171:             * Creates a new instance of <code>XMLConfiguration</code>.
172:             * The configuration is loaded from the specified file.
173:             *
174:             * @param file the file
175:             * @throws ConfigurationException if an error occurs while loading the file
176:             */
177:            public XMLConfiguration(File file) throws ConfigurationException {
178:                super (file);
179:            }
180:
181:            /**
182:             * Creates a new instance of <code>XMLConfiguration</code>.
183:             * The configuration is loaded from the specified URL.
184:             *
185:             * @param url the URL
186:             * @throws ConfigurationException if loading causes an error
187:             */
188:            public XMLConfiguration(URL url) throws ConfigurationException {
189:                super (url);
190:            }
191:
192:            /**
193:             * Returns the name of the root element. If this configuration was loaded
194:             * from a XML document, the name of this document's root element is
195:             * returned. Otherwise it is possible to set a name for the root element
196:             * that will be used when this configuration is stored.
197:             *
198:             * @return the name of the root element
199:             */
200:            public String getRootElementName() {
201:                if (getDocument() == null) {
202:                    return (rootElementName == null) ? DEFAULT_ROOT_NAME
203:                            : rootElementName;
204:                } else {
205:                    return getDocument().getDocumentElement().getNodeName();
206:                }
207:            }
208:
209:            /**
210:             * Sets the name of the root element. This name is used when this
211:             * configuration object is stored in an XML file. Note that setting the name
212:             * of the root element works only if this configuration has been newly
213:             * created. If the configuration was loaded from an XML file, the name
214:             * cannot be changed and an <code>UnsupportedOperationException</code>
215:             * exception is thrown. Whether this configuration has been loaded from an
216:             * XML document or not can be found out using the <code>getDocument()</code>
217:             * method.
218:             *
219:             * @param name the name of the root element
220:             */
221:            public void setRootElementName(String name) {
222:                if (getDocument() != null) {
223:                    throw new UnsupportedOperationException(
224:                            "The name of the root element "
225:                                    + "cannot be changed when loaded from an XML document!");
226:                }
227:                rootElementName = name;
228:            }
229:
230:            /**
231:             * Returns the <code>DocumentBuilder</code> object that is used for
232:             * loading documents. If no specific builder has been set, this method
233:             * returns <b>null</b>.
234:             *
235:             * @return the <code>DocumentBuilder</code> for loading new documents
236:             * @since 1.2
237:             */
238:            public DocumentBuilder getDocumentBuilder() {
239:                return documentBuilder;
240:            }
241:
242:            /**
243:             * Sets the <code>DocumentBuilder</code> object to be used for loading
244:             * documents. This method makes it possible to specify the exact document
245:             * builder. So an application can create a builder, configure it for its
246:             * special needs, and then pass it to this method.
247:             *
248:             * @param documentBuilder the document builder to be used; if undefined, a
249:             * default builder will be used
250:             * @since 1.2
251:             */
252:            public void setDocumentBuilder(DocumentBuilder documentBuilder) {
253:                this .documentBuilder = documentBuilder;
254:            }
255:
256:            /**
257:             * Returns the public ID of the DOCTYPE declaration from the loaded XML
258:             * document. This is <b>null</b> if no document has been loaded yet or if
259:             * the document does not contain a DOCTYPE declaration with a public ID.
260:             *
261:             * @return the public ID
262:             * @since 1.3
263:             */
264:            public String getPublicID() {
265:                return publicID;
266:            }
267:
268:            /**
269:             * Sets the public ID of the DOCTYPE declaration. When this configuration is
270:             * saved, a DOCTYPE declaration will be constructed that contains this
271:             * public ID.
272:             *
273:             * @param publicID the public ID
274:             * @since 1.3
275:             */
276:            public void setPublicID(String publicID) {
277:                this .publicID = publicID;
278:            }
279:
280:            /**
281:             * Returns the system ID of the DOCTYPE declaration from the loaded XML
282:             * document. This is <b>null</b> if no document has been loaded yet or if
283:             * the document does not contain a DOCTYPE declaration with a system ID.
284:             *
285:             * @return the system ID
286:             * @since 1.3
287:             */
288:            public String getSystemID() {
289:                return systemID;
290:            }
291:
292:            /**
293:             * Sets the system ID of the DOCTYPE declaration. When this configuration is
294:             * saved, a DOCTYPE declaration will be constructed that contains this
295:             * system ID.
296:             *
297:             * @param systemID the system ID
298:             * @since 1.3
299:             */
300:            public void setSystemID(String systemID) {
301:                this .systemID = systemID;
302:            }
303:
304:            /**
305:             * Returns the value of the validating flag.
306:             *
307:             * @return the validating flag
308:             * @since 1.2
309:             */
310:            public boolean isValidating() {
311:                return validating;
312:            }
313:
314:            /**
315:             * Sets the value of the validating flag. This flag determines whether
316:             * DTD validation should be performed when loading XML documents. This
317:             * flag is evaluated only if no custom <code>DocumentBuilder</code> was set.
318:             *
319:             * @param validating the validating flag
320:             * @since 1.2
321:             */
322:            public void setValidating(boolean validating) {
323:                this .validating = validating;
324:            }
325:
326:            /**
327:             * Returns the XML document this configuration was loaded from. The return
328:             * value is <b>null</b> if this configuration was not loaded from a XML
329:             * document.
330:             *
331:             * @return the XML document this configuration was loaded from
332:             */
333:            public Document getDocument() {
334:                return document;
335:            }
336:
337:            /**
338:             * Removes all properties from this configuration. If this configuration
339:             * was loaded from a file, the associated DOM document is also cleared.
340:             */
341:            public void clear() {
342:                super .clear();
343:                document = null;
344:            }
345:
346:            /**
347:             * Initializes this configuration from an XML document.
348:             *
349:             * @param document the document to be parsed
350:             * @param elemRefs a flag whether references to the XML elements should be set
351:             */
352:            public void initProperties(Document document, boolean elemRefs) {
353:                if (document.getDoctype() != null) {
354:                    setPublicID(document.getDoctype().getPublicId());
355:                    setSystemID(document.getDoctype().getSystemId());
356:                }
357:                constructHierarchy(getRoot(), document.getDocumentElement(),
358:                        elemRefs);
359:            }
360:
361:            /**
362:             * Helper method for building the internal storage hierarchy. The XML
363:             * elements are transformed into node objects.
364:             *
365:             * @param node the actual node
366:             * @param element the actual XML element
367:             * @param elemRefs a flag whether references to the XML elements should be set
368:             */
369:            private void constructHierarchy(Node node, Element element,
370:                    boolean elemRefs) {
371:                processAttributes(node, element, elemRefs);
372:                StringBuffer buffer = new StringBuffer();
373:                NodeList list = element.getChildNodes();
374:                for (int i = 0; i < list.getLength(); i++) {
375:                    org.w3c.dom.Node w3cNode = list.item(i);
376:                    if (w3cNode instanceof  Element) {
377:                        Element child = (Element) w3cNode;
378:                        Node childNode = new XMLNode(child.getTagName(),
379:                                elemRefs ? child : null);
380:                        constructHierarchy(childNode, child, elemRefs);
381:                        node.addChild(childNode);
382:                        handleDelimiters(node, childNode);
383:                    } else if (w3cNode instanceof  Text) {
384:                        Text data = (Text) w3cNode;
385:                        buffer.append(data.getData());
386:                    }
387:                }
388:                String text = buffer.toString().trim();
389:                if (text.length() > 0 || !node.hasChildren()) {
390:                    node.setValue(text);
391:                }
392:            }
393:
394:            /**
395:             * Helper method for constructing node objects for the attributes of the
396:             * given XML element.
397:             *
398:             * @param node the actual node
399:             * @param element the actual XML element
400:             * @param elemRefs a flag whether references to the XML elements should be set
401:             */
402:            private void processAttributes(Node node, Element element,
403:                    boolean elemRefs) {
404:                NamedNodeMap attributes = element.getAttributes();
405:                for (int i = 0; i < attributes.getLength(); ++i) {
406:                    org.w3c.dom.Node w3cNode = attributes.item(i);
407:                    if (w3cNode instanceof  Attr) {
408:                        Attr attr = (Attr) w3cNode;
409:                        Iterator it;
410:                        if (isDelimiterParsingDisabled()) {
411:                            it = new SingletonIterator(attr.getValue());
412:                        } else {
413:                            it = PropertyConverter.split(attr.getValue(),
414:                                    getListDelimiter()).iterator();
415:                        }
416:                        while (it.hasNext()) {
417:                            Node child = new XMLNode(attr.getName(),
418:                                    elemRefs ? element : null);
419:                            child.setValue(it.next());
420:                            node.addAttribute(child);
421:                        }
422:                    }
423:                }
424:            }
425:
426:            /**
427:             * Deals with elements whose value is a list. In this case multiple child
428:             * elements must be added.
429:             *
430:             * @param parent the parent element
431:             * @param child the child element
432:             */
433:            private void handleDelimiters(Node parent, Node child) {
434:                if (child.getValue() != null) {
435:                    List values;
436:                    if (isDelimiterParsingDisabled()) {
437:                        values = new ArrayList();
438:                        values.add(child.getValue().toString());
439:                    } else {
440:                        values = PropertyConverter.split(child.getValue()
441:                                .toString(), getListDelimiter());
442:                    }
443:
444:                    if (values.size() > 1) {
445:                        // remove the original child
446:                        parent.remove(child);
447:                        // add multiple new children
448:                        for (Iterator it = values.iterator(); it.hasNext();) {
449:                            Node c = new XMLNode(child.getName(), null);
450:                            c.setValue(it.next());
451:                            parent.addChild(c);
452:                        }
453:                    } else if (values.size() == 1) {
454:                        // we will have to replace the value because it might
455:                        // contain escaped delimiters
456:                        child.setValue(values.get(0));
457:                    }
458:                }
459:            }
460:
461:            /**
462:             * Creates the <code>DocumentBuilder</code> to be used for loading files.
463:             * This implementation checks whether a specific
464:             * <code>DocumentBuilder</code> has been set. If this is the case, this
465:             * one is used. Otherwise a default builder is created. Depending on the
466:             * value of the validating flag this builder will be a validating or a non
467:             * validating <code>DocumentBuilder</code>.
468:             *
469:             * @return the <code>DocumentBuilder</code> for loading configuration
470:             * files
471:             * @throws ParserConfigurationException if an error occurs
472:             * @since 1.2
473:             */
474:            protected DocumentBuilder createDocumentBuilder()
475:                    throws ParserConfigurationException {
476:                if (getDocumentBuilder() != null) {
477:                    return getDocumentBuilder();
478:                } else {
479:                    DocumentBuilderFactory factory = DocumentBuilderFactory
480:                            .newInstance();
481:                    factory.setValidating(isValidating());
482:                    DocumentBuilder result = factory.newDocumentBuilder();
483:
484:                    if (isValidating()) {
485:                        // register an error handler which detects validation errors
486:                        result.setErrorHandler(new DefaultHandler() {
487:                            public void error(SAXParseException ex)
488:                                    throws SAXException {
489:                                throw ex;
490:                            }
491:                        });
492:                    }
493:                    return result;
494:                }
495:            }
496:
497:            /**
498:             * Creates a DOM document from the internal tree of configuration nodes.
499:             *
500:             * @return the new document
501:             * @throws ConfigurationException if an error occurs
502:             */
503:            protected Document createDocument() throws ConfigurationException {
504:                try {
505:                    if (document == null) {
506:                        DocumentBuilder builder = DocumentBuilderFactory
507:                                .newInstance().newDocumentBuilder();
508:                        Document newDocument = builder.newDocument();
509:                        Element rootElem = newDocument
510:                                .createElement(getRootElementName());
511:                        newDocument.appendChild(rootElem);
512:                        document = newDocument;
513:                    }
514:
515:                    XMLBuilderVisitor builder = new XMLBuilderVisitor(document,
516:                            getListDelimiter());
517:                    builder.processDocument(getRoot());
518:                    return document;
519:                } /* try */
520:                catch (DOMException domEx) {
521:                    throw new ConfigurationException(domEx);
522:                } catch (ParserConfigurationException pex) {
523:                    throw new ConfigurationException(pex);
524:                }
525:            }
526:
527:            /**
528:             * Creates a new node object. This implementation returns an instance of the
529:             * <code>XMLNode</code> class.
530:             *
531:             * @param name the node's name
532:             * @return the new node
533:             */
534:            protected Node createNode(String name) {
535:                return new XMLNode(name, null);
536:            }
537:
538:            /**
539:             * Loads the configuration from the given input stream.
540:             *
541:             * @param in the input stream
542:             * @throws ConfigurationException if an error occurs
543:             */
544:            public void load(InputStream in) throws ConfigurationException {
545:                load(new InputSource(in));
546:            }
547:
548:            /**
549:             * Load the configuration from the given reader.
550:             * Note that the <code>clear()</code> method is not called, so
551:             * the properties contained in the loaded file will be added to the
552:             * actual set of properties.
553:             *
554:             * @param in An InputStream.
555:             *
556:             * @throws ConfigurationException if an error occurs
557:             */
558:            public void load(Reader in) throws ConfigurationException {
559:                load(new InputSource(in));
560:            }
561:
562:            /**
563:             * Loads a configuration file from the specified input source.
564:             * @param source the input source
565:             * @throws ConfigurationException if an error occurs
566:             */
567:            private void load(InputSource source) throws ConfigurationException {
568:                try {
569:                    URL sourceURL = getDelegate().getURL();
570:                    if (sourceURL != null) {
571:                        source.setSystemId(sourceURL.toString());
572:                    }
573:
574:                    DocumentBuilder builder = createDocumentBuilder();
575:                    Document newDocument = builder.parse(source);
576:                    Document oldDocument = document;
577:                    document = null;
578:                    initProperties(newDocument, oldDocument == null);
579:                    document = (oldDocument == null) ? newDocument
580:                            : oldDocument;
581:                } catch (Exception e) {
582:                    throw new ConfigurationException(e.getMessage(), e);
583:                }
584:            }
585:
586:            /**
587:             * Saves the configuration to the specified writer.
588:             *
589:             * @param writer the writer used to save the configuration
590:             * @throws ConfigurationException if an error occurs
591:             */
592:            public void save(Writer writer) throws ConfigurationException {
593:                try {
594:                    Transformer transformer = createTransformer();
595:                    Source source = new DOMSource(createDocument());
596:                    Result result = new StreamResult(writer);
597:                    transformer.transform(source, result);
598:                } catch (TransformerException e) {
599:                    throw new ConfigurationException(e.getMessage(), e);
600:                } catch (TransformerFactoryConfigurationError err) {
601:                    throw new ConfigurationException(err.getMessage(), err);
602:                }
603:            }
604:
605:            /**
606:             * Creates and initializes the transformer used for save operations. This
607:             * base implementation initializes all of the default settings like
608:             * indention mode and the DOCTYPE. Derived classes may overload this method
609:             * if they have specific needs.
610:             *
611:             * @return the transformer to use for a save operation
612:             * @throws TransformerException if an error occurs
613:             * @since 1.3
614:             */
615:            protected Transformer createTransformer()
616:                    throws TransformerException {
617:                Transformer transformer = TransformerFactory.newInstance()
618:                        .newTransformer();
619:
620:                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
621:                if (getEncoding() != null) {
622:                    transformer.setOutputProperty(OutputKeys.ENCODING,
623:                            getEncoding());
624:                }
625:                if (getPublicID() != null) {
626:                    transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
627:                            getPublicID());
628:                }
629:                if (getSystemID() != null) {
630:                    transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
631:                            getSystemID());
632:                }
633:
634:                return transformer;
635:            }
636:
637:            /**
638:             * Creates a copy of this object. The new configuration object will contain
639:             * the same properties as the original, but it will lose any connection to a
640:             * source document (if one exists). This is to avoid race conditions if both
641:             * the original and the copy are modified and then saved.
642:             *
643:             * @return the copy
644:             */
645:            public Object clone() {
646:                XMLConfiguration copy = (XMLConfiguration) super .clone();
647:
648:                // clear document related properties
649:                copy.document = null;
650:                copy.setDelegate(copy.createDelegate());
651:                // clear all references in the nodes, too
652:                clearReferences(copy.getRootNode());
653:
654:                return copy;
655:            }
656:
657:            /**
658:             * Creates the file configuration delegate for this object. This implementation
659:             * will return an instance of a class derived from <code>FileConfigurationDelegate</code>
660:             * that deals with some specialities of <code>XMLConfiguration</code>.
661:             * @return the delegate for this object
662:             */
663:            protected FileConfigurationDelegate createDelegate() {
664:                return new XMLFileConfigurationDelegate();
665:            }
666:
667:            /**
668:             * A specialized <code>Node</code> class that is connected with an XML
669:             * element. Changes on a node are also performed on the associated element.
670:             */
671:            class XMLNode extends Node {
672:                /**
673:                 * The serial version UID.
674:                 */
675:                private static final long serialVersionUID = -4133988932174596562L;
676:
677:                /**
678:                 * Creates a new instance of <code>XMLNode</code> and initializes it
679:                 * with a name and the corresponding XML element.
680:                 *
681:                 * @param name the node's name
682:                 * @param elem the XML element
683:                 */
684:                public XMLNode(String name, Element elem) {
685:                    super (name);
686:                    setReference(elem);
687:                }
688:
689:                /**
690:                 * Sets the value of this node. If this node is associated with an XML
691:                 * element, this element will be updated, too.
692:                 *
693:                 * @param value the node's new value
694:                 */
695:                public void setValue(Object value) {
696:                    super .setValue(value);
697:
698:                    if (getReference() != null && document != null) {
699:                        if (isAttribute()) {
700:                            updateAttribute();
701:                        } else {
702:                            updateElement(value);
703:                        }
704:                    }
705:                }
706:
707:                /**
708:                 * Updates the associated XML elements when a node is removed.
709:                 */
710:                protected void removeReference() {
711:                    if (getReference() != null) {
712:                        Element element = (Element) getReference();
713:                        if (isAttribute()) {
714:                            updateAttribute();
715:                        } else {
716:                            org.w3c.dom.Node parentElem = element
717:                                    .getParentNode();
718:                            if (parentElem != null) {
719:                                parentElem.removeChild(element);
720:                            }
721:                        }
722:                    }
723:                }
724:
725:                /**
726:                 * Updates the node's value if it represents an element node.
727:                 *
728:                 * @param value the new value
729:                 */
730:                private void updateElement(Object value) {
731:                    Text txtNode = findTextNodeForUpdate();
732:                    if (value == null) {
733:                        // remove text
734:                        if (txtNode != null) {
735:                            ((Element) getReference()).removeChild(txtNode);
736:                        }
737:                    } else {
738:                        if (txtNode == null) {
739:                            txtNode = document.createTextNode(PropertyConverter
740:                                    .escapeDelimiters(value.toString(),
741:                                            getListDelimiter()));
742:                            if (((Element) getReference()).getFirstChild() != null) {
743:                                ((Element) getReference()).insertBefore(
744:                                        txtNode, ((Element) getReference())
745:                                                .getFirstChild());
746:                            } else {
747:                                ((Element) getReference()).appendChild(txtNode);
748:                            }
749:                        } else {
750:                            txtNode.setNodeValue(PropertyConverter
751:                                    .escapeDelimiters(value.toString(),
752:                                            getListDelimiter()));
753:                        }
754:                    }
755:                }
756:
757:                /**
758:                 * Updates the node's value if it represents an attribute.
759:                 *
760:                 */
761:                private void updateAttribute() {
762:                    XMLBuilderVisitor.updateAttribute(getParent(), getName(),
763:                            getListDelimiter());
764:                }
765:
766:                /**
767:                 * Returns the only text node of this element for update. This method is
768:                 * called when the element's text changes. Then all text nodes except
769:                 * for the first are removed. A reference to the first is returned or
770:                 * <b>null </b> if there is no text node at all.
771:                 *
772:                 * @return the first and only text node
773:                 */
774:                private Text findTextNodeForUpdate() {
775:                    Text result = null;
776:                    Element elem = (Element) getReference();
777:                    // Find all Text nodes
778:                    NodeList children = elem.getChildNodes();
779:                    Collection textNodes = new ArrayList();
780:                    for (int i = 0; i < children.getLength(); i++) {
781:                        org.w3c.dom.Node nd = children.item(i);
782:                        if (nd instanceof  Text) {
783:                            if (result == null) {
784:                                result = (Text) nd;
785:                            } else {
786:                                textNodes.add(nd);
787:                            }
788:                        }
789:                    }
790:
791:                    // We don't want CDATAs
792:                    if (result instanceof  CDATASection) {
793:                        textNodes.add(result);
794:                        result = null;
795:                    }
796:
797:                    // Remove all but the first Text node
798:                    for (Iterator it = textNodes.iterator(); it.hasNext();) {
799:                        elem.removeChild((org.w3c.dom.Node) it.next());
800:                    }
801:                    return result;
802:                }
803:            }
804:
805:            /**
806:             * A concrete <code>BuilderVisitor</code> that can construct XML
807:             * documents.
808:             */
809:            static class XMLBuilderVisitor extends BuilderVisitor {
810:                /** Stores the document to be constructed. */
811:                private Document document;
812:
813:                /** Stores the list delimiter.*/
814:                private char listDelimiter = AbstractConfiguration
815:                        .getDefaultListDelimiter();
816:
817:                /**
818:                 * Creates a new instance of <code>XMLBuilderVisitor</code>
819:                 *
820:                 * @param doc the document to be created
821:                 * @param listDelimiter the delimiter for attribute properties with multiple values
822:                 */
823:                public XMLBuilderVisitor(Document doc, char listDelimiter) {
824:                    document = doc;
825:                    this .listDelimiter = listDelimiter;
826:                }
827:
828:                /**
829:                 * Processes the node hierarchy and adds new nodes to the document.
830:                 *
831:                 * @param rootNode the root node
832:                 */
833:                public void processDocument(Node rootNode) {
834:                    rootNode.visit(this , null);
835:                }
836:
837:                /**
838:                 * Inserts a new node. This implementation ensures that the correct
839:                 * XML element is created and inserted between the given siblings.
840:                 *
841:                 * @param newNode the node to insert
842:                 * @param parent the parent node
843:                 * @param sibling1 the first sibling
844:                 * @param sibling2 the second sibling
845:                 * @return the new node
846:                 */
847:                protected Object insert(Node newNode, Node parent,
848:                        Node sibling1, Node sibling2) {
849:                    if (newNode.isAttribute()) {
850:                        updateAttribute(parent, getElement(parent), newNode
851:                                .getName(), listDelimiter);
852:                        return null;
853:                    }
854:
855:                    else {
856:                        Element elem = document
857:                                .createElement(newNode.getName());
858:                        if (newNode.getValue() != null) {
859:                            elem.appendChild(document
860:                                    .createTextNode(PropertyConverter
861:                                            .escapeDelimiters(newNode
862:                                                    .getValue().toString(),
863:                                                    listDelimiter)));
864:                        }
865:                        if (sibling2 == null) {
866:                            getElement(parent).appendChild(elem);
867:                        } else if (sibling1 != null) {
868:                            getElement(parent).insertBefore(elem,
869:                                    getElement(sibling1).getNextSibling());
870:                        } else {
871:                            getElement(parent).insertBefore(elem,
872:                                    getElement(parent).getFirstChild());
873:                        }
874:                        return elem;
875:                    }
876:                }
877:
878:                /**
879:                 * Helper method for updating the value of the specified node's
880:                 * attribute with the given name.
881:                 *
882:                 * @param node the affected node
883:                 * @param elem the element that is associated with this node
884:                 * @param name the name of the affected attribute
885:                 * @param listDelimiter the delimiter vor attributes with multiple values
886:                 */
887:                private static void updateAttribute(Node node, Element elem,
888:                        String name, char listDelimiter) {
889:                    if (node != null && elem != null) {
890:                        List attrs = node.getAttributes(name);
891:                        StringBuffer buf = new StringBuffer();
892:                        for (Iterator it = attrs.iterator(); it.hasNext();) {
893:                            Node attr = (Node) it.next();
894:                            if (attr.getValue() != null) {
895:                                if (buf.length() > 0) {
896:                                    buf.append(listDelimiter);
897:                                }
898:                                buf.append(PropertyConverter.escapeDelimiters(
899:                                        attr.getValue().toString(),
900:                                        getDefaultListDelimiter()));
901:                            }
902:                            attr.setReference(elem);
903:                        }
904:
905:                        if (buf.length() < 1) {
906:                            elem.removeAttribute(name);
907:                        } else {
908:                            elem.setAttribute(name, buf.toString());
909:                        }
910:                    }
911:                }
912:
913:                /**
914:                 * Updates the value of the specified attribute of the given node.
915:                 * Because there can be multiple child nodes representing this attribute
916:                 * the new value is determined by iterating over all those child nodes.
917:                 *
918:                 * @param node the affected node
919:                 * @param name the name of the attribute
920:                 * @param listDelimiter the delimiter vor attributes with multiple values
921:                 */
922:                static void updateAttribute(Node node, String name,
923:                        char listDelimiter) {
924:                    if (node != null) {
925:                        updateAttribute(node, (Element) node.getReference(),
926:                                name, listDelimiter);
927:                    }
928:                }
929:
930:                /**
931:                 * Helper method for accessing the element of the specified node.
932:                 *
933:                 * @param node the node
934:                 * @return the element of this node
935:                 */
936:                private Element getElement(Node node) {
937:                    // special treatement for root node of the hierarchy
938:                    return (node.getName() != null) ? (Element) node
939:                            .getReference() : document.getDocumentElement();
940:                }
941:            }
942:
943:            /**
944:             * A special implementation of the <code>FileConfiguration</code> interface that is
945:             * used internally to implement the <code>FileConfiguration</code> methods
946:             * for <code>XMLConfiguration</code>, too.
947:             */
948:            private class XMLFileConfigurationDelegate extends
949:                    FileConfigurationDelegate {
950:                public void load(InputStream in) throws ConfigurationException {
951:                    XMLConfiguration.this.load(in);
952:                }
953:            }
954:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.