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