1 /*
2  * Copyright (c) 2005, The Regents of the University of California, through
3  * Lawrence Berkeley National Laboratory (subject to receipt of any required
4  * approvals from the U.S. Dept. of Energy). All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * (1) Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * (2) Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * (3) Neither the name of the University of California, Lawrence Berkeley
17  * National Laboratory, U.S. Dept. of Energy nor the names of its contributors
18  * may be used to endorse or promote products derived from this software without
19  * specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  * You are under no obligation whatsoever to provide any bug fixes, patches, or
34  * upgrades to the features, functionality or performance of the source code
35  * ("Enhancements") to anyone; however, if you choose to make your Enhancements
36  * available either publicly, or directly to Lawrence Berkeley National
37  * Laboratory, without imposing a separate written license agreement for such
38  * Enhancements, then you hereby grant the following license: a non-exclusive,
39  * royalty-free perpetual license to install, use, modify, prepare derivative
40  * works, incorporate into other computer software, distribute, and sublicense
41  * such enhancements or derivative works thereof, in binary and source code
42  * form.
43  */
44 package nux.xom.sandbox;
45 
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 
49 import nu.xom.Attribute;
50 import nu.xom.Document;
51 import nu.xom.Element;
52 import nu.xom.IllegalAddException;
53 import nu.xom.Node;
54 import nu.xom.NodeFactory;
55 import nu.xom.Nodes;
56 import nu.xom.ParentNode;
57 import nu.xom.WellformednessException;
58 import nu.xom.XMLException;
59 
60 /**
61  * Streams input into one or more independent underlying node factories.
62  *
63  * @author whoschek.AT.lbl.DOT.gov
64  * @author $Author: hoschek3 $
65  * @version $Revision: 1.1 $, $Date: 2006/01/30 02:05:55 $
66  */
67 public final class MulticastNodeFactory extends NodeFactory {
68 
69 	// TODO: how to add additional namespace declarations?
70 
71 	private final NodeFactory[] receivers;
72 	private final RuntimeException[] exceptions;
73 //	private final Document[] docs;
74 	private final ParentNode[] currents;
75 	private final ArrayList[] stacks;
76 	private final boolean[] addAttributesAndNamespaces;
77 	private final boolean[] hasRootElement;
78 	private final boolean failFast;
79 
80 	private final Nodes NONE = new Nodes();
81 
82 	/**
83 	 * Constructs a new instance. In failFast mode fails immediately when at
84 	 * least one child throws an exception. Otherwise continues to stream into
85 	 * the remaining working factories and reports all collected failures on
86 	 * finishMakingDocument().
87 	 *
88 	 * @param receivers
89 	 *            the child factories to push into
90 	 * @param failFast
91 	 *            whether or not to immediately report an exception on the first
92 	 *            child failure
93 	 */
MulticastNodeFactory(NodeFactory[] receivers, boolean failFast)94 	public MulticastNodeFactory(NodeFactory[] receivers, boolean failFast) {
95 		if (receivers == null)
96 			throw new IllegalArgumentException("factories must not be null");
97 		for (int i=0; i < receivers.length; i++) {
98 			if (receivers[i] == null)
99 				throw new IllegalArgumentException("factory must not be null");
100 		}
101 		this.receivers = new NodeFactory[receivers.length];
102 		System.arraycopy(receivers, 0, this.receivers, 0, receivers.length);
103 		this.exceptions = new RuntimeException[receivers.length];
104 //		this.docs = new Document[children.length];
105 		this.currents = new ParentNode[receivers.length];
106 		this.stacks = new ArrayList[receivers.length];
107 		this.addAttributesAndNamespaces = new boolean[receivers.length];
108 		this.hasRootElement = new boolean[receivers.length];
109 		this.failFast = failFast;
110 		reset();
111 	}
112 
reset()113 	private void reset() {
114 		Arrays.fill(exceptions, null);
115 //		Arrays.fill(docs, null);
116 		Arrays.fill(currents, null);
117 		for (int i=0; i < stacks.length; i++) {
118 			if (stacks[i] == null) stacks[i] = new ArrayList();
119 			stacks[i].clear();
120 		}
121 		Arrays.fill(addAttributesAndNamespaces, true);
122 		Arrays.fill(hasRootElement, false);
123 	}
124 
125 	/** Returns the document build for each of the factories. */
getDocuments()126 	public Document[] getDocuments() {
127 		Document[] documents = new Document[currents.length];
128 		for (int i=0; i < currents.length; i++) {
129 			documents[i] = (Document) currents[i];
130 		}
131 		return documents;
132 	}
133 
onException(int i, RuntimeException e)134 	private void onException(int i, RuntimeException e) {
135 		exceptions[i] = e;
136 		if (failFast) throw e;
137 	}
138 
139 	/** {@inheritDoc} */
startMakingDocument()140 	public Document startMakingDocument() {
141 		reset();
142 		for (int i=0; i < receivers.length; i++) {
143 			if (exceptions[i] != null) continue; // ignore failed factory
144 			try {
145 				currents[i] = receivers[i].startMakingDocument();
146 				if (currents[i] == null) throw new NullPointerException(
147 						"startMakingDocument must not return null.");
148 			} catch (RuntimeException e) {
149 				onException(i, e);
150 			}
151 		}
152 		return super.startMakingDocument();
153 	}
154 
155 	/** {@inheritDoc} */
finishMakingDocument(Document doc)156 	public void finishMakingDocument(Document doc) {
157 		for (int i=0; i < receivers.length; i++) {
158 			if (exceptions[i] != null) continue; // ignore failed factory
159 			try {
160 				receivers[i].finishMakingDocument((Document)currents[i]);
161 				if (!hasRootElement[i]) throw new WellformednessException(
162 					"Factory attempted to remove the root element");
163 			} catch (RuntimeException e) {
164 				onException(i, e);
165 			}
166 		}
167 		super.finishMakingDocument(doc);
168 
169 		// report any exceptions collected when not in failFast mode:
170 		for (int i=0; !failFast && i < receivers.length; i++) {
171 			if (exceptions[i] != null) {
172 				throw new MultipleCausesException(exceptions);
173 			}
174 		}
175 	}
176 
177 	/** {@inheritDoc} */
makeRootElement(String qname, String namespaceURI)178 	public Element makeRootElement(String qname, String namespaceURI) {
179 		for (int i=0; i < receivers.length; i++) {
180 			if (exceptions[i] != null) continue; // ignore failed factory
181 			try {
182 				Element elem = receivers[i].makeRootElement(qname, namespaceURI);
183 				if (elem == null) throw new NullPointerException(
184 						"Factory failed to create root element.");
185 				stacks[i].add(elem); // push
186 				((Document)currents[i]).setRootElement(elem);
187 				currents[i] = elem; // recurse down
188 				addAttributesAndNamespaces[i] = true;
189 			} catch (RuntimeException e) {
190 				onException(i, e);
191 			}
192 		}
193 
194 		return new Element(qname, namespaceURI);
195 	}
196 
197 	/** {@inheritDoc} */
startMakingElement(String qname, String namespaceURI)198 	public Element startMakingElement(String qname, String namespaceURI) {
199 		for (int i=0; i < receivers.length; i++) {
200 			if (exceptions[i] != null) continue; // ignore failed factory
201 			try {
202 				Element elem = receivers[i].startMakingElement(qname, namespaceURI);
203 				stacks[i].add(elem); // push even if it's null
204 				if (elem != null) {
205 					currents[i].insertChild(elem, currents[i].getChildCount());
206 					currents[i] = elem; // recurse down
207 				}
208 				addAttributesAndNamespaces[i] = elem != null;
209 			} catch (RuntimeException e) {
210 				onException(i, e);
211 			}
212 		}
213 
214 		return new Element(qname, namespaceURI);
215 	}
216 
217 	/** {@inheritDoc} */
finishMakingElement(Element unused)218 	public Nodes finishMakingElement(Element unused) {
219 		for (int i=0; i < receivers.length; i++) {
220 			if (exceptions[i] != null) continue; // ignore failed factory
221 			try {
222 				Element elem = (Element) stacks[i].remove(stacks[i].size()-1); // pop
223 				if (elem == null) {
224 					continue; // skip element
225 				}
226 				ParentNode parent = elem.getParent();
227 				if (parent == null) throwTamperedWithParent();
228 				currents[i] = parent; // recurse up
229 
230 				Nodes nodes = receivers[i].finishMakingElement(elem);
231 				if (nodes.size()==1 && nodes.get(0)==elem) { // same node? (common case)
232 					if (parent instanceof Document) hasRootElement[i] = true;
233 					continue; // optimization: no need to remove and then readd same element
234 				}
235 
236 				if (parent.getChildCount()-1 < 0) throwTamperedWithParent();
237 				if (parent instanceof Element) { // can't remove root element
238 					parent.removeChild(parent.getChildCount()-1);
239 				}
240 				appendNodes(parent, nodes, i);
241 			} catch (RuntimeException e) {
242 				onException(i, e);
243 			}
244 		}
245 
246 		if (unused.getParent() instanceof Document) {
247 			return new Nodes(unused); // XOM documents must have a root element
248 		}
249 		return NONE;
250 	}
251 
252 	/** {@inheritDoc} */
makeAttribute(String qname, String namespaceURI, String value, Attribute.Type type)253 	public Nodes makeAttribute(String qname, String namespaceURI, String value, Attribute.Type type) {
254 		for (int i=0; i < receivers.length; i++) {
255 			if (exceptions[i] != null) continue; // ignore failed factory
256 			if (!addAttributesAndNamespaces[i]) continue;
257 			try {
258 				Nodes nodes = receivers[i].makeAttribute(
259 						qname, namespaceURI, value, type);
260 				appendNodes(currents[i], nodes, i);
261 			} catch (RuntimeException e) {
262 				onException(i, e);
263 			}
264 		}
265 		return NONE;
266 	}
267 
268 
269 	// makeComment(), makeDocType(), makeProcessingInstruction(), makeText()
270 	// are essentially all the same :-(
271 
272 	/** {@inheritDoc} */
makeComment(String data)273 	public Nodes makeComment(String data) {
274 		for (int i=0; i < receivers.length; i++) {
275 			if (exceptions[i] != null) continue; // ignore failed factory
276 			try {
277 				Nodes nodes = receivers[i].makeText(data);
278 				appendNodes(currents[i], nodes, i);
279 			} catch (RuntimeException e) {
280 				onException(i, e);
281 			}
282 		}
283 		return NONE;
284 	}
285 
286 	/** {@inheritDoc} */
makeDocType(String rootElementName, String publicID, String systemID)287 	public Nodes makeDocType(String rootElementName, String publicID, String systemID) {
288 		for (int i=0; i < receivers.length; i++) {
289 			if (exceptions[i] != null) continue; // ignore failed factory
290 			try {
291 				Nodes nodes = receivers[i].makeDocType(
292 					rootElementName, publicID, systemID);
293 				appendNodes(currents[i], nodes, i);
294 			} catch (RuntimeException e) {
295 				onException(i, e);
296 			}
297 		}
298 		return NONE;
299 	}
300 
301 	/** {@inheritDoc} */
makeProcessingInstruction(String target, String data)302 	public Nodes makeProcessingInstruction(String target, String data) {
303 		for (int i=0; i < receivers.length; i++) {
304 			if (exceptions[i] != null) continue; // ignore failed factory
305 			try {
306 				Nodes nodes = receivers[i].makeProcessingInstruction(target, data);
307 				appendNodes(currents[i], nodes, i);
308 			} catch (RuntimeException e) {
309 				onException(i, e);
310 			}
311 		}
312 		return NONE;
313 	}
314 
315 	/** {@inheritDoc} */
makeText(String text)316 	public Nodes makeText(String text) {
317 		for (int i=0; i < receivers.length; i++) {
318 			if (exceptions[i] != null) continue; // ignore failed factory
319 			try {
320 				Nodes nodes = receivers[i].makeText(text);
321 				appendNodes(currents[i], nodes, i);
322 			} catch (RuntimeException e) {
323 				onException(i, e);
324 			}
325 		}
326 		return NONE;
327 	}
328 
appendNodes(ParentNode parent, Nodes nodes, int k)329 	private void appendNodes(ParentNode parent, Nodes nodes, int k) {
330 		if (parent instanceof Element) {
331 			appendNodesToElement((Element) parent, nodes);
332 		} else {
333 			appendNodesToDocument((Document) parent, nodes, k);
334 		}
335 	}
336 
appendNodesToElement(Element elem, Nodes nodes)337 	private static void appendNodesToElement(Element elem, Nodes nodes) {
338 		if (nodes != null) {
339 			int size = nodes.size();
340 			for (int i=0; i < size; i++) {
341 				Node node = nodes.get(i);
342 				if (node instanceof Attribute) {
343 					elem.addAttribute((Attribute) node);
344 				} else {
345 					elem.insertChild(node, elem.getChildCount());
346 				}
347 			}
348 		}
349 	}
350 
appendNodesToDocument(Document doc, Nodes nodes, int k)351 	private void appendNodesToDocument(Document doc, Nodes nodes, int k) {
352 		if (nodes != null) {
353 			int size = nodes.size();
354 			for (int i=0; i < size; i++) {
355 				Node node = nodes.get(i);
356 				if (node instanceof Element) {
357 					if (hasRootElement[k]) {
358 						throw new IllegalAddException(
359 							"Factory returned multiple root elements");
360 					}
361 					doc.setRootElement((Element) node);
362 					hasRootElement[k] = true;
363 				} else {
364 					int j = doc.getChildCount();
365 					if (!hasRootElement[k]) j--;
366 					doc.insertChild(node, j);
367 				}
368 			}
369 		}
370 	}
371 
throwTamperedWithParent()372 	private static void throwTamperedWithParent() {
373 		throw new XMLException("Factory has tampered with a parent pointer " +
374 				"of ancestor-or-self in finishMakingElement()");
375 	}
376 
377 
378 	///////////////////////////////////////////////////////////////////////////////
379 	// Nested classes:
380 	///////////////////////////////////////////////////////////////////////////////
381 
382 	/**
383 	 * A RuntimeException containing an array of one or more causes
384 	 * (Throwables). Some or all cause elements may be <code>null</code>.
385 	 */
386 	public static final class MultipleCausesException extends RuntimeException {
387 
388 		private final Throwable[] causes;
389 
MultipleCausesException(Throwable[] causes)390 		private MultipleCausesException(Throwable[] causes) {
391 			this.causes = new Throwable[causes.length];
392 			System.arraycopy(causes, 0, this.causes, 0, causes.length);
393 		}
394 
getCauses()395 		public Throwable[] getCauses() {
396 			return causes;
397 		}
398 
toString()399 		public String toString() {
400 			StringBuffer buf = new StringBuffer();
401 			for (int i=0; i < causes.length; i++) {
402 				buf.append("Cause " + i + ": ");
403 				buf.append(causes[i]);
404 				if (i < causes.length-1) buf.append("\n");
405 			}
406 			return buf.toString();
407 		}
408 	}
409 
410 }
411