1 /*
2 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
3 *
4 * This software is open source.
5 * See the bottom of this file for the licence.
6 */
7
8 package org.dom4j;
9
10 import java.io.IOException;
11 import java.io.ObjectInputStream;
12 import java.io.Serializable;
13 import java.util.List;
14 import java.util.Map;
15
16 import org.dom4j.rule.Pattern;
17 import org.dom4j.tree.AbstractDocument;
18 import org.dom4j.tree.DefaultAttribute;
19 import org.dom4j.tree.DefaultCDATA;
20 import org.dom4j.tree.DefaultComment;
21 import org.dom4j.tree.DefaultDocument;
22 import org.dom4j.tree.DefaultDocumentType;
23 import org.dom4j.tree.DefaultElement;
24 import org.dom4j.tree.DefaultEntity;
25 import org.dom4j.tree.DefaultProcessingInstruction;
26 import org.dom4j.tree.DefaultText;
27 import org.dom4j.tree.QNameCache;
28 import org.dom4j.util.SimpleSingleton;
29 import org.dom4j.util.SingletonStrategy;
30 import org.dom4j.xpath.DefaultXPath;
31 import org.dom4j.xpath.XPathPattern;
32 import org.jaxen.VariableContext;
33
34 /**
35 * <p>
36 * <code>DocumentFactory</code> is a collection of factory methods to allow
37 * easy custom building of DOM4J trees. The default tree that is built uses a
38 * doubly linked tree.
39 * </p>
40 *
41 * <p>
42 * The tree built allows full XPath expressions from anywhere on the tree.
43 * </p>
44 *
45 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
46 */
47 public class DocumentFactory implements Serializable {
48 private static SingletonStrategy singleton = null;
49
50 protected transient QNameCache cache;
51
52 /** Default namespace prefix -> URI mappings for XPath expressions to use */
53 private Map xpathNamespaceURIs;
54
55 private static SingletonStrategy createSingleton() {
56 SingletonStrategy result = null;
57
58 String documentFactoryClassName;
59 try {
60 documentFactoryClassName = System.getProperty("org.dom4j.factory",
61 "org.dom4j.DocumentFactory");
62 } catch (Exception e) {
63 documentFactoryClassName = "org.dom4j.DocumentFactory";
64 }
65
66 try {
67 String singletonClass = System.getProperty(
68 "org.dom4j.DocumentFactory.singleton.strategy",
69 "org.dom4j.util.SimpleSingleton");
70 Class clazz = Class.forName(singletonClass);
71 result = (SingletonStrategy) clazz.newInstance();
72 } catch (Exception e) {
73 result = new SimpleSingleton();
74 }
75
76 result.setSingletonClassName(documentFactoryClassName);
77
78 return result;
79 }
80
81 public DocumentFactory() {
82 init();
83 }
84
85 /**
86 * <p>
87 * Access to singleton implementation of DocumentFactory which is used if no
88 * DocumentFactory is specified when building using the standard builders.
89 * </p>
90 *
91 * @return the default singleon instance
92 */
93 public static synchronized DocumentFactory getInstance() {
94 if (singleton == null) {
95 singleton = createSingleton();
96 }
97 return (DocumentFactory) singleton.instance();
98 }
99
100 // Factory methods
101 public Document createDocument() {
102 DefaultDocument answer = new DefaultDocument();
103 answer.setDocumentFactory(this);
104
105 return answer;
106 }
107
108 /**
109 * DOCUMENT ME!
110 *
111 * @param encoding
112 * DOCUMENT ME!
113 *
114 * @return DOCUMENT ME!
115 *
116 * @since 1.5
117 */
118 public Document createDocument(String encoding) {
119 // to keep the DocumentFactory backwards compatible, we have to do this
120 // in this not so nice way, since subclasses only need to extend the
121 // createDocument() method.
122 Document answer = createDocument();
123
124 if (answer instanceof AbstractDocument) {
125 ((AbstractDocument) answer).setXMLEncoding(encoding);
126 }
127
128 return answer;
129 }
130
131 public Document createDocument(Element rootElement) {
132 Document answer = createDocument();
133 answer.setRootElement(rootElement);
134
135 return answer;
136 }
137
138 public DocumentType createDocType(String name, String publicId,
139 String systemId) {
140 return new DefaultDocumentType(name, publicId, systemId);
141 }
142
143 public Element createElement(QName qname) {
144 return new DefaultElement(qname);
145 }
146
147 public Element createElement(String name) {
148 return createElement(createQName(name));
149 }
150
151 public Element createElement(String qualifiedName, String namespaceURI) {
152 return createElement(createQName(qualifiedName, namespaceURI));
153 }
154
155 public Attribute createAttribute(Element owner, QName qname, String value) {
156 return new DefaultAttribute(qname, value);
157 }
158
159 public Attribute createAttribute(Element owner, String name, String value) {
160 return createAttribute(owner, createQName(name), value);
161 }
162
163 public CDATA createCDATA(String text) {
164 return new DefaultCDATA(text);
165 }
166
167 public Comment createComment(String text) {
168 return new DefaultComment(text);
169 }
170
171 public Text createText(String text) {
172 if (text == null) {
173 String msg = "Adding text to an XML document must not be null";
174 throw new IllegalArgumentException(msg);
175 }
176
177 return new DefaultText(text);
178 }
179
180 public Entity createEntity(String name, String text) {
181 return new DefaultEntity(name, text);
182 }
183
184 public Namespace createNamespace(String prefix, String uri) {
185 return Namespace.get(prefix, uri);
186 }
187
188 public ProcessingInstruction createProcessingInstruction(String target,
189 String data) {
190 return new DefaultProcessingInstruction(target, data);
191 }
192
193 public ProcessingInstruction createProcessingInstruction(String target,
194 Map data) {
195 return new DefaultProcessingInstruction(target, data);
196 }
197
198 public QName createQName(String localName, Namespace namespace) {
199 return cache.get(localName, namespace);
200 }
201
202 public QName createQName(String localName) {
203 return cache.get(localName);
204 }
205
206 public QName createQName(String name, String prefix, String uri) {
207 return cache.get(name, Namespace.get(prefix, uri));
208 }
209
210 public QName createQName(String qualifiedName, String uri) {
211 return cache.get(qualifiedName, uri);
212 }
213
214 /**
215 * <p>
216 * <code>createXPath</code> parses an XPath expression and creates a new
217 * XPath <code>XPath</code> instance.
218 * </p>
219 *
220 * @param xpathExpression
221 * is the XPath expression to create
222 *
223 * @return a new <code>XPath</code> instance
224 *
225 * @throws InvalidXPathException
226 * if the XPath expression is invalid
227 */
228 public XPath createXPath(String xpathExpression)
229 throws InvalidXPathException {
230 DefaultXPath xpath = new DefaultXPath(xpathExpression);
231
232 if (xpathNamespaceURIs != null) {
233 xpath.setNamespaceURIs(xpathNamespaceURIs);
234 }
235
236 return xpath;
237 }
238
239 /**
240 * <p>
241 * <code>createXPath</code> parses an XPath expression and creates a new
242 * XPath <code>XPath</code> instance.
243 * </p>
244 *
245 * @param xpathExpression
246 * is the XPath expression to create
247 * @param variableContext
248 * is the variable context to use when evaluating the XPath
249 *
250 * @return a new <code>XPath</code> instance
251 */
252 public XPath createXPath(String xpathExpression,
253 VariableContext variableContext) {
254 XPath xpath = createXPath(xpathExpression);
255 xpath.setVariableContext(variableContext);
256
257 return xpath;
258 }
259
260 /**
261 * <p>
262 * <code>createXPathFilter</code> parses a NodeFilter from the given XPath
263 * filter expression. XPath filter expressions occur within XPath
264 * expressions such as <code>self::node()[ filterExpression ]</code>
265 * </p>
266 *
267 * @param xpathFilterExpression
268 * is the XPath filter expression to create
269 * @param variableContext
270 * is the variable context to use when evaluating the XPath
271 *
272 * @return a new <code>NodeFilter</code> instance
273 */
274 public NodeFilter createXPathFilter(String xpathFilterExpression,
275 VariableContext variableContext) {
276 XPath answer = createXPath(xpathFilterExpression);
277
278 // DefaultXPath answer = new DefaultXPath( xpathFilterExpression );
279 answer.setVariableContext(variableContext);
280
281 return answer;
282 }
283
284 /**
285 * <p>
286 * <code>createXPathFilter</code> parses a NodeFilter from the given XPath
287 * filter expression. XPath filter expressions occur within XPath
288 * expressions such as <code>self::node()[ filterExpression ]</code>
289 * </p>
290 *
291 * @param xpathFilterExpression
292 * is the XPath filter expression to create
293 *
294 * @return a new <code>NodeFilter</code> instance
295 */
296 public NodeFilter createXPathFilter(String xpathFilterExpression) {
297 return createXPath(xpathFilterExpression);
298
299 // return new DefaultXPath( xpathFilterExpression );
300 }
301
302 /**
303 * <p>
304 * <code>createPattern</code> parses the given XPath expression to create
305 * an XSLT style {@link Pattern}instance which can then be used in an XSLT
306 * processing model.
307 * </p>
308 *
309 * @param xpathPattern
310 * is the XPath pattern expression to create
311 *
312 * @return a new <code>Pattern</code> instance
313 */
314 public Pattern createPattern(String xpathPattern) {
315 return new XPathPattern(xpathPattern);
316 }
317
318 // Properties
319 // -------------------------------------------------------------------------
320
321 /**
322 * Returns a list of all the QName instances currently used by this document
323 * factory
324 *
325 * @return DOCUMENT ME!
326 */
327 public List<QName> getQNames() {
328 return cache.getQNames();
329 }
330
331 /**
332 * DOCUMENT ME!
333 *
334 * @return the Map of namespace URIs that will be used by by XPath
335 * expressions to resolve namespace prefixes into namespace URIs.
336 * The map is keyed by namespace prefix and the value is the
337 * namespace URI. This value could well be null to indicate no
338 * namespace URIs are being mapped.
339 */
340 public Map<String, String> getXPathNamespaceURIs() {
341 return xpathNamespaceURIs;
342 }
343
344 /**
345 * Sets the namespace URIs to be used by XPath expressions created by this
346 * factory or by nodes associated with this factory. The keys are namespace
347 * prefixes and the values are namespace URIs.
348 *
349 * @param namespaceURIs
350 * DOCUMENT ME!
351 */
352 public void setXPathNamespaceURIs(Map<String, String> namespaceURIs) {
353 this.xpathNamespaceURIs = namespaceURIs;
354 }
355
356 // Implementation methods
357 // -------------------------------------------------------------------------
358
359 /**
360 * <p>
361 * <code>createSingleton</code> creates the singleton instance from the
362 * given class name.
363 * </p>
364 *
365 * @param className
366 * is the name of the DocumentFactory class to use
367 *
368 * @return a new singleton instance.
369 */
370 protected static DocumentFactory createSingleton(String className) {
371 // let's try and class load an implementation?
372 try {
373 // I'll use the current class loader
374 // that loaded me to avoid problems in J2EE and web apps
375 Class theClass = Class.forName(className, true,
376 DocumentFactory.class.getClassLoader());
377
378 return (DocumentFactory) theClass.newInstance();
379 } catch (Throwable e) {
380 System.out.println("WARNING: Cannot load DocumentFactory: "
381 + className);
382
383 return new DocumentFactory();
384 }
385 }
386
387 /**
388 * DOCUMENT ME!
389 *
390 * @param qname
391 * DOCUMENT ME!
392 *
393 * @return the cached QName instance if there is one or adds the given qname
394 * to the cache if not
395 */
396 protected QName intern(QName qname) {
397 return cache.intern(qname);
398 }
399
400 /**
401 * Factory method to create the QNameCache. This method should be overloaded
402 * if you wish to use your own derivation of QName.
403 *
404 * @return DOCUMENT ME!
405 */
406 protected QNameCache createQNameCache() {
407 return new QNameCache(this);
408 }
409
410 private void readObject(ObjectInputStream in) throws IOException,
411 ClassNotFoundException {
412 in.defaultReadObject();
413 init();
414 }
415
416 protected void init() {
417 cache = createQNameCache();
418 }
419 }
420
421 /*
422 * Redistribution and use of this software and associated documentation
423 * ("Software"), with or without modification, are permitted provided that the
424 * following conditions are met:
425 *
426 * 1. Redistributions of source code must retain copyright statements and
427 * notices. Redistributions must also contain a copy of this document.
428 *
429 * 2. Redistributions in binary form must reproduce the above copyright notice,
430 * this list of conditions and the following disclaimer in the documentation
431 * and/or other materials provided with the distribution.
432 *
433 * 3. The name "DOM4J" must not be used to endorse or promote products derived
434 * from this Software without prior written permission of MetaStuff, Ltd. For
435 * written permission, please contact dom4j-info@metastuff.com.
436 *
437 * 4. Products derived from this Software may not be called "DOM4J" nor may
438 * "DOM4J" appear in their names without prior written permission of MetaStuff,
439 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
440 *
441 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
442 *
443 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
444 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
445 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
446 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
447 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
448 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
449 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
450 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
451 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
452 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
453 * POSSIBILITY OF SUCH DAMAGE.
454 *
455 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
456 */