Source for gnu.xml.pipeline.PipelineFactory

   1: /* PipelineFactory.java --
   2:    Copyright (C) 1999,2000,2001 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package gnu.xml.pipeline;
  39: 
  40: import java.io.File;
  41: import java.io.FileOutputStream;
  42: import java.io.IOException;
  43: import java.io.OutputStream;
  44: import java.io.OutputStreamWriter;
  45: import java.lang.reflect.Constructor;
  46: import java.util.StringTokenizer;
  47: 
  48: import org.xml.sax.*;
  49: import org.xml.sax.ext.*;
  50: 
  51: 
  52: /**
  53:  * This provides static factory methods for creating simple event pipelines.
  54:  * These pipelines are specified by strings, suitable for passing on
  55:  * command lines or embedding in element attributes.  For example, one way
  56:  * to write a pipeline that restores namespace syntax, validates (stopping
  57:  * the pipeline on validity errors) and then writes valid data to standard
  58:  * output is this: <pre>
  59:  *      nsfix | validate | write ( stdout )</pre>
  60:  *
  61:  * <p> In this syntax, the tokens are always separated by whitespace, and each
  62:  * stage of the pipeline may optionally have a parameter (which can be a
  63:  * pipeline) in parentheses.  Interior stages are called filters, and the
  64:  * rightmost end of a pipeline is called a terminus.
  65:  *
  66:  * <p> Stages are usually implemented by a single class, which may not be
  67:  * able to act as both a filter and a terminus; but any terminus can be
  68:  * automatically turned into a filter, through use of a {@link TeeConsumer}.
  69:  * The stage identifiers are either class names, or are one of the following
  70:  * short identifiers built into this class.  (Most of these identifiers are
  71:  * no more than aliases for classes.)  The built-in identifiers include:</p>
  72: 
  73:  <table border="1" cellpadding="3" cellspacing="0">
  74:     <tr bgcolor="#ccccff" class="TableHeadingColor">
  75:         <th align="center" width="5%">Stage</th>
  76:         <th align="center" width="9%">Parameter</th>
  77:         <th align="center" width="1%">Terminus</th>
  78:         <th align="center">Description</th>
  79:     </tr>
  80: 
  81:     <tr valign="top" align="center">
  82:         <td><a href="../dom/Consumer.html">dom</a></td>
  83:         <td><em>none</em></td>
  84:         <td> yes </td>
  85:         <td align="left"> Applications code can access a DOM Document built
  86:         from the input event stream.  When used as a filter, this buffers
  87:         data up to an <em>endDocument</em> call, and then uses a DOM parser
  88:         to report everything that has been recorded (which can easily be
  89:         less than what was reported to it).  </td>
  90:     </tr>
  91:     <tr valign="top" align="center">
  92:         <td><a href="NSFilter.html">nsfix</a></td>
  93:         <td><em>none</em></td>
  94:         <td>no</td>
  95:         <td align="left">This stage ensures that the XML element and attribute
  96:         names in its output use namespace prefixes and declarations correctly.
  97:         That is, so that they match the "Namespace plus LocalName" naming data
  98:         with which each XML element and attribute is already associated.  </td>
  99:     </tr>
 100:     <tr valign="top" align="center">
 101:         <td><a href="EventFilter.html">null</a></td>
 102:         <td><em>none</em></td>
 103:         <td>yes</td>
 104:         <td align="left">This stage ignores all input event data.</td>
 105:     </tr>
 106:     <tr valign="top" align="center">
 107:         <td><a href="CallFilter.html">server</a></td>
 108:         <td><em>required</em><br> server URL </td>
 109:         <td>no</td>
 110:         <td align="left">Sends its input as XML request to a remote server,
 111:         normally a web application server using the HTTP or HTTPS protocols.
 112:         The output of this stage is the parsed response from that server.</td>
 113:     </tr>
 114:     <tr valign="top" align="center">
 115:         <td><a href="TeeConsumer.html">tee</a></td>
 116:         <td><em>required</em><br> first pipeline</td>
 117:         <td>no</td>
 118:         <td align="left">This sends its events down two paths; its parameter
 119:         is a pipeline descriptor for the first path, and the second path
 120:         is the output of this stage.</td>
 121:     </tr>
 122: 
 123:     <tr valign="top" align="center">
 124:         <td><a href="ValidationConsumer.html">validate</a></td>
 125:         <td><em>none</em></td>
 126:         <td>yes</td>
 127:         <td align="left">This checks for validity errors, and reports them
 128:         through its error handler.  The input must include declaration events
 129:         and some lexical events.  </td>
 130:     </tr>
 131:     <tr valign="top" align="center">
 132:         <td><a href="WellFormednessFilter.html">wf</a></td>
 133:         <td><em>none</em></td>
 134:         <td>yes</td>
 135:         <td align="left"> This class provides some basic "well formedness"
 136:         tests on the input event stream, and reports a fatal error if any
 137:         of them fail.  One example: start/end calls for elements must match.
 138:         No SAX parser is permitted to produce malformed output, but other
 139:         components can easily do so.</td>
 140:     </tr>
 141:     <tr valign="top" align="center">
 142:         <td>write</td>
 143:         <td><em>required</em><br> "stdout", "stderr", or filename</td>
 144:         <td>yes</td>
 145:         <td align="left"> Writes its input to the specified output, as pretty
 146:         printed XML text encoded using UTF-8.  Input events must be well
 147:         formed and "namespace fixed", else the output won't be XML (or possibly
 148:         namespace) conformant.  The symbolic names represent
 149:         <em>System.out</em> and <em>System.err</em> respectively; names must
 150:         correspond to files which don't yet exist.</td>
 151:     </tr>
 152:     <tr valign="top" align="center">
 153:         <td>xhtml</td>
 154:         <td><em>required</em><br> "stdout", "stderr", or filename</td>
 155:         <td>yes</td>
 156:         <td align="left"> Like <em>write</em> (above), except that XHTML rules
 157:         are followed.  The XHTML 1.0 Transitional document type is declared,
 158:         and only ASCII characters are written (for interoperability).  Other
 159:         characters are written as entity or character references; the text is
 160:         pretty printed.</td>
 161:     </tr>
 162:     <tr valign="top" align="center">
 163:         <td><a href="XIncludeFilter.html">xinclude</a></td>
 164:         <td><em>none</em></td>
 165:         <td>no</td>
 166:         <td align="left">This stage handles XInclude processing.
 167:         This is like entity inclusion, except that the included content
 168:         is declared in-line rather than in the DTD at the beginning of
 169:         a document.
 170:         </td>
 171:     </tr>
 172:     <tr valign="top" align="center">
 173:         <td><a href="XsltFilter.html">xslt</a></td>
 174:         <td><em>required</em><br> XSLT stylesheet URI</td>
 175:         <td>no</td>
 176:         <td align="left">This stage handles XSLT transformation
 177:         according to a stylesheet.
 178:         The implementation of the transformation may not actually
 179:         stream data, although if such an XSLT engine is in use
 180:         then that can happen.
 181:         </td>
 182:     </tr>
 183: 
 184:  </table>
 185: 
 186:  * <p> Note that {@link EventFilter#bind} can automatically eliminate
 187:  * some filters by setting SAX2 parser features appropriately.  This means
 188:  * that you can routinely put filters like "nsfix", "validate", or "wf" at the
 189:  * front of a pipeline (for components that need inputs conditioned to match
 190:  * that level of correctness), and know that it won't actually be used unless
 191:  * it's absolutely necessary.
 192:  *
 193:  * @author David Brownell
 194:  */
 195: public class PipelineFactory
 196: {
 197:     /**
 198:      * Creates a simple pipeline according to the description string passed in.
 199:      */
 200:     public static EventConsumer createPipeline (String description)
 201:     throws IOException
 202:     {
 203:         return createPipeline (description, null);
 204:     }
 205: 
 206:     /**
 207:      * Extends an existing pipeline by prepending the filter pipeline to the
 208:      * specified consumer.  Some pipelines need more customization than can
 209:      * be done through this simplified syntax.  When they are set up with
 210:      * direct API calls, use this method to merge more complex pipeline
 211:      * segments with easily configured ones.
 212:      */
 213:     public static EventConsumer createPipeline (
 214:         String          description,
 215:         EventConsumer   next
 216:     ) throws IOException
 217:     {
 218:         // tokens are (for now) what's separated by whitespace;
 219:         // very easy to parse, but IDs never have spaces.
 220: 
 221:         StringTokenizer         tokenizer;
 222:         String                  tokens [];
 223: 
 224:         tokenizer = new StringTokenizer (description);
 225:         tokens = new String [tokenizer.countTokens ()];
 226:         for (int i = 0; i < tokens.length; i++)
 227:             tokens [i] = tokenizer.nextToken ();
 228: 
 229:         PipelineFactory         factory = new PipelineFactory ();
 230:         Pipeline                pipeline = factory.parsePipeline (tokens, next);
 231: 
 232:         return pipeline.createPipeline ();
 233:     }
 234: 
 235: 
 236:     private PipelineFactory () { /* NYET */ }
 237: 
 238: 
 239:     /**
 240:      * Extends an existing pipeline by prepending a pre-tokenized filter
 241:      * pipeline to the specified consumer.  Tokens are class names (or the
 242:      * predefined aliases) left and right parenthesis, and the vertical bar.
 243:      */
 244:     public static EventConsumer createPipeline (
 245:         String          tokens [],
 246:         EventConsumer   next
 247:     ) throws IOException
 248:     {
 249:         PipelineFactory         factory = new PipelineFactory ();
 250:         Pipeline                pipeline = factory.parsePipeline (tokens, next);
 251: 
 252:         return pipeline.createPipeline ();
 253:     }
 254: 
 255: 
 256:     private String              tokens [];
 257:     private int                 index;
 258: 
 259:     private Pipeline parsePipeline (String toks [], EventConsumer next)
 260:     {
 261:         tokens = toks;
 262:         index = 0;
 263: 
 264:         Pipeline retval = parsePipeline (next);
 265: 
 266:         if (index != toks.length)
 267:             throw new ArrayIndexOutOfBoundsException (
 268:                     "extra token: " + tokens [index]);
 269:         return retval;
 270:     }
 271: 
 272:     // pipeline  ::= stage | stage '|' pipeline
 273:     private Pipeline parsePipeline (EventConsumer next)
 274:     {
 275:         Pipeline        retval = new Pipeline (parseStage ());
 276: 
 277:         // minimal pipelines:  "stage" and "... | id"
 278:         if (index > (tokens.length - 2)
 279:                 || !"|".equals (tokens [index])
 280:                 ) {
 281:             retval.next = next;
 282:             return retval;
 283:         }
 284:         index++;
 285:         retval.rest = parsePipeline (next);
 286:         return retval;
 287:     }
 288: 
 289:     // stage     ::= id    | id '(' pipeline ')'
 290:     private Stage parseStage ()
 291:     {
 292:         Stage           retval = new Stage (tokens [index++]);
 293: 
 294:         // minimal stages:  "id" and "id ( id )"
 295:         if (index > (tokens.length - 2)
 296:                 || !"(".equals (tokens [index]) /*)*/
 297:                 )
 298:             return retval;
 299: 
 300:         index++;
 301:         retval.param = parsePipeline (null);
 302:         if (index >= tokens.length)
 303:             throw new ArrayIndexOutOfBoundsException (
 304:                     "missing right paren");
 305:         if (/*(*/ !")".equals (tokens [index++]))
 306:             throw new ArrayIndexOutOfBoundsException (
 307:                     "required right paren, not: " + tokens [index - 1]);
 308:         return retval;
 309:     }
 310: 
 311: 
 312:     //
 313:     // these classes obey the conventions for constructors, so they're
 314:     // only built in to this table of shortnames
 315:     //
 316:     //  - filter (one or two types of arglist)
 317:     //     * last constructor is 'next' element
 318:     //     * optional (first) string parameter
 319:     //
 320:     //  - terminus (one or types of arglist)
 321:     //     * optional (only) string parameter
 322:     //
 323:     // terminus stages are transformed into filters if needed, by
 324:     // creating a "tee".  filter stages aren't turned to terminus
 325:     // stages though; either eliminate such stages, or add some
 326:     // terminus explicitly.
 327:     //
 328:     private static final String builtinStages [][] = {
 329:         { "dom",        "gnu.xml.dom.Consumer" },
 330:         { "nsfix",      "gnu.xml.pipeline.NSFilter" },
 331:         { "null",       "gnu.xml.pipeline.EventFilter" },
 332:         { "server",     "gnu.xml.pipeline.CallFilter" },
 333:         { "tee",        "gnu.xml.pipeline.TeeConsumer" },
 334:         { "validate",   "gnu.xml.pipeline.ValidationConsumer" },
 335:         { "wf",         "gnu.xml.pipeline.WellFormednessFilter" },
 336:         { "xinclude",   "gnu.xml.pipeline.XIncludeFilter" },
 337:         { "xslt",       "gnu.xml.pipeline.XsltFilter" },
 338: 
 339: // XXX want:  option for validate, to preload external part of a DTD
 340: 
 341:             //  xhtml, write ... nyet generic-ready
 342:     };
 343: 
 344:     private static class Stage
 345:     {
 346:         String          id;
 347:         Pipeline        param;
 348: 
 349:         Stage (String name)
 350:             {  id = name; }
 351: 
 352:         public String toString ()
 353:         {
 354:             if (param == null)
 355:                 return id;
 356:             return id + " ( " + param + " )";
 357:         }
 358: 
 359:         private void fail (String message)
 360:         throws IOException
 361:         {
 362:             throw new IOException ("in '" + id
 363:                     + "' stage of pipeline, " + message);
 364:         }
 365: 
 366:         EventConsumer createStage (EventConsumer next)
 367:         throws IOException
 368:         {
 369:             String       name = id;
 370: 
 371:             // most builtins are just class aliases
 372:             for (int i = 0; i < builtinStages.length; i++) {
 373:                 if (id.equals (builtinStages [i][0])) {
 374:                     name = builtinStages [i][1];
 375:                     break;
 376:                 }
 377:             }
 378: 
 379:             // Save output as XML or XHTML text
 380:             if ("write".equals (name) || "xhtml".equals (name)) {
 381:                 String          filename;
 382:                 boolean         isXhtml = "xhtml".equals (name);
 383:                 OutputStream    out = null;
 384:                 TextConsumer    consumer;
 385: 
 386:                 if (param == null)
 387:                     fail ("parameter is required");
 388: 
 389:                 filename = param.toString ();
 390:                 if ("stdout".equals (filename))
 391:                     out = System.out;
 392:                 else if ("stderr".equals (filename))
 393:                     out = System.err;
 394:                 else {
 395:                     File f = new File (filename);
 396: 
 397: /*
 398:                     if (!f.isAbsolute ())
 399:                         fail ("require absolute file paths");
 400:  */
 401:                     if (f.exists ())
 402:                         fail ("file already exists: " + f.getName ());
 403: 
 404: // XXX this races against the existence test
 405:                     out = new FileOutputStream (f);
 406:                 }
 407: 
 408:                 if (!isXhtml)
 409:                     consumer = new TextConsumer (out);
 410:                 else
 411:                     consumer = new TextConsumer (
 412:                         new OutputStreamWriter (out, "8859_1"),
 413:                         true);
 414: 
 415:                 consumer.setPrettyPrinting (true);
 416:                 if (next == null)
 417:                     return consumer;
 418:                 return new TeeConsumer (consumer, next);
 419: 
 420:             } else {
 421:                 //
 422:                 // Here go all the builtins that are just aliases for
 423:                 // classes, and all stage IDs that started out as such
 424:                 // class names.  The following logic relies on several
 425:                 // documented conventions for constructor invocation.
 426:                 //
 427:                 String          msg = null;
 428: 
 429:                 try {
 430:                     Class       klass = Class.forName (name);
 431:                     Class       argTypes [] = null;
 432:                     Constructor constructor = null;
 433:                     boolean     filter = false;
 434:                     Object      params [] = null;
 435:                     Object      obj = null;
 436: 
 437:                     // do we need a filter stage?
 438:                     if (next != null) {
 439:                         // "next" consumer is always passed, with
 440:                         // or without the optional string param
 441:                         if (param == null) {
 442:                             argTypes = new Class [1];
 443:                             argTypes [0] = EventConsumer.class;
 444: 
 445:                             params = new Object [1];
 446:                             params [0] = next;
 447: 
 448:                             msg = "no-param filter";
 449:                         } else {
 450:                             argTypes = new Class [2];
 451:                             argTypes [0] = String.class;
 452:                             argTypes [1] = EventConsumer.class;
 453: 
 454:                             params = new Object [2];
 455:                             params [0] = param.toString ();
 456:                             params [1] = next;
 457: 
 458:                             msg = "one-param filter";
 459:                         }
 460: 
 461: 
 462:                         try {
 463:                             constructor = klass.getConstructor (argTypes);
 464:                         } catch (NoSuchMethodException e) {
 465:                             // try creating a filter from a
 466:                             // terminus and a tee
 467:                             filter = true;
 468:                             msg += " built from ";
 469:                         }
 470:                     }
 471: 
 472:                     // build from a terminus stage, with or
 473:                     // without the optional string param
 474:                     if (constructor == null) {
 475:                         String  tmp;
 476: 
 477:                         if (param == null) {
 478:                             argTypes = new Class [0];
 479:                             params = new Object [0];
 480: 
 481:                             tmp = "no-param terminus";
 482:                         } else {
 483:                             argTypes = new Class [1];
 484:                             argTypes [0] = String.class;
 485: 
 486:                             params = new Object [1];
 487:                             params [0] = param.toString ();
 488: 
 489:                             tmp = "one-param terminus";
 490:                         }
 491:                         if (msg == null)
 492:                             msg = tmp;
 493:                         else
 494:                             msg += tmp;
 495:                         constructor = klass.getConstructor (argTypes);
 496:                             // NOT creating terminus by dead-ending
 497:                             // filters ... users should think about
 498:                             // that one, something's likely wrong
 499:                     }
 500: 
 501:                     obj = constructor.newInstance (params);
 502: 
 503:                     // return EventConsumers directly, perhaps after
 504:                     // turning them into a filter
 505:                     if (obj instanceof EventConsumer) {
 506:                         if (filter)
 507:                             return new TeeConsumer ((EventConsumer) obj, next);
 508:                         return (EventConsumer) obj;
 509:                     }
 510: 
 511:                     // if it's not a handler, it's an error
 512:                     // we can wrap handlers in a filter
 513:                     EventFilter         retval = new EventFilter ();
 514:                     boolean             updated = false;
 515: 
 516:                     if (obj instanceof ContentHandler) {
 517:                         retval.setContentHandler ((ContentHandler) obj);
 518:                         updated = true;
 519:                     }
 520:                     if (obj instanceof DTDHandler) {
 521:                         retval.setDTDHandler ((DTDHandler) obj);
 522:                         updated = true;
 523:                     }
 524:                     if (obj instanceof LexicalHandler) {
 525:                         retval.setProperty (
 526:                             EventFilter.PROPERTY_URI + "lexical-handler",
 527:                             obj);
 528:                         updated = true;
 529:                     }
 530:                     if (obj instanceof DeclHandler) {
 531:                         retval.setProperty (
 532:                             EventFilter.PROPERTY_URI + "declaration-handler",
 533:                             obj);
 534:                         updated = true;
 535:                     }
 536: 
 537:                     if (!updated)
 538:                         fail ("class is neither Consumer nor Handler");
 539: 
 540:                     if (filter)
 541:                         return new TeeConsumer (retval, next);
 542:                     return retval;
 543: 
 544:                 } catch (IOException e) {
 545:                     throw e;
 546: 
 547:                 } catch (NoSuchMethodException e) {
 548:                     fail (name + " constructor missing -- " + msg);
 549: 
 550:                 } catch (ClassNotFoundException e) {
 551:                     fail (name + " class not found");
 552: 
 553:                 } catch (Exception e) {
 554:                     // e.printStackTrace ();
 555:                     fail ("stage not available: " + e.getMessage ());
 556:                 }
 557:             }
 558:             // NOTREACHED
 559:             return null;
 560:         }
 561:     }
 562: 
 563:     private static class Pipeline
 564:     {
 565:         Stage           stage;
 566: 
 567:         // rest may be null
 568:         Pipeline        rest;
 569:         EventConsumer   next;
 570: 
 571:         Pipeline (Stage s)
 572:             { stage = s; }
 573: 
 574:         public String toString ()
 575:         {
 576:             if (rest == null && next == null)
 577:                 return stage.toString ();
 578:             if (rest != null)
 579:                 return stage + " | " + rest;
 580:             throw new IllegalArgumentException ("next");
 581:         }
 582: 
 583:         EventConsumer createPipeline ()
 584:         throws IOException
 585:         {
 586:             if (next == null) {
 587:                 if (rest == null)
 588:                     next = stage.createStage (null);
 589:                 else
 590:                     next = stage.createStage (rest.createPipeline ());
 591:             }
 592:             return next;
 593:         }
 594:     }
 595: 
 596: /*
 597:     public static void main (String argv [])
 598:     {
 599:         try {
 600:             // three basic terminus cases
 601:             createPipeline ("null");
 602:             createPipeline ("validate");
 603:             createPipeline ("write ( stdout )");
 604: 
 605:             // four basic filters
 606:             createPipeline ("nsfix | write ( stderr )");
 607:             createPipeline ("wf | null");
 608:             createPipeline ("null | null");
 609:             createPipeline (
 610: "call ( http://www.example.com/services/xml-1a ) | xhtml ( stdout )");
 611: 
 612:             // tee junctions
 613:             createPipeline ("tee ( validate ) | write ( stdout )");
 614:             createPipeline ("tee ( nsfix | write ( stdout ) ) | validate");
 615: 
 616:             // longer pipeline
 617:             createPipeline ("nsfix | tee ( validate ) | write ( stdout )");
 618:             createPipeline (
 619:                 "null | wf | nsfix | tee ( validate ) | write ( stdout )");
 620: 
 621:             // try some parsing error cases
 622:             try {
 623:                 createPipeline ("null (");              // extra token '('
 624:                 System.err.println ("** didn't report error");
 625:             } catch (Exception e) {
 626:                 System.err.println ("== err: " + e.getMessage ()); }
 627: 
 628:             try {
 629:                 createPipeline ("nsfix |");             // extra token '|'
 630:                 System.err.println ("** didn't report error");
 631:             } catch (Exception e) {
 632:                 System.err.println ("== err: " + e.getMessage ()); }
 633: 
 634:             try {
 635:                 createPipeline ("xhtml ( foo");         // missing right paren
 636:                 System.err.println ("** didn't report error");
 637:             } catch (Exception e) {
 638:                 System.err.println ("== err: " + e.getMessage ()); }
 639: 
 640:             try {
 641:                 createPipeline ("xhtml ( foo bar");     // required right paren
 642:                 System.err.println ("** didn't report error");
 643:             } catch (Exception e) {
 644:                 System.err.println ("== err: " + e.getMessage ()); }
 645: 
 646:             try {
 647:                 createPipeline ("tee ( nsfix | validate");// missing right paren
 648:                 System.err.println ("** didn't report error");
 649:             } catch (Exception e) {
 650:                 System.err.println ("== err: " + e.getMessage ()); }
 651: 
 652:             // try some construction error cases
 653: 
 654:             try {
 655:                 createPipeline ("call");                // missing param
 656:                 System.err.println ("** didn't report error");
 657:             } catch (Exception e) {
 658:                 System.err.println ("== err: " + e.getMessage ()); }
 659:             try {
 660:                 createPipeline ("call ( foobar )");     // broken param
 661:                 System.err.println ("** didn't report error");
 662:             } catch (Exception e) {
 663:                 System.err.println ("== err: " + e.getMessage ()); }
 664:             try {
 665:                 createPipeline ("nsfix ( foobar )");    // illegal param
 666:                 System.err.println ("** didn't report error");
 667:             } catch (Exception e) {
 668:                 System.err.println ("== err: " + e.getMessage ()); }
 669:             try {
 670:                 createPipeline ("null ( foobar )");     // illegal param
 671:                 System.err.println ("** didn't report error");
 672:             } catch (Exception e) {
 673:                 System.err.println ("== err: " + e.getMessage ()); }
 674:             try {
 675:                 createPipeline ("wf ( foobar )");       // illegal param
 676:                 System.err.println ("** didn't report error");
 677:             } catch (Exception e) {
 678:                 System.err.println ("== err: " + e.getMessage ()); }
 679:             try {
 680:                 createPipeline ("xhtml ( foobar.html )");
 681:                 new File ("foobar.html").delete ();
 682:                 // now supported
 683:             } catch (Exception e) {
 684:                 System.err.println ("** err: " + e.getMessage ()); }
 685:             try {
 686:                 createPipeline ("xhtml");               // missing param
 687:                 System.err.println ("** didn't report error");
 688:             } catch (Exception e) {
 689:                 System.err.println ("== err: " + e.getMessage ()); }
 690:             try {
 691:                 createPipeline ("write ( stdout ) | null");     // nonterminal
 692:                 System.err.println ("** didn't report error");
 693:             } catch (Exception e) {
 694:                 System.err.println ("== err: " + e.getMessage ()); }
 695:             try {
 696:                 createPipeline ("validate | null");
 697:                 // now supported
 698:             } catch (Exception e) {
 699:                 System.err.println ("** err: " + e.getMessage ()); }
 700:             try {
 701:                 createPipeline ("validate ( foo )");    // illegal param
 702:                 System.err.println ("** didn't report error");
 703:             } catch (Exception e) {
 704:                 System.err.println ("== err: " + e.getMessage ()); }
 705:             try {
 706:                 createPipeline ("tee");                 // missing param
 707:                 System.err.println ("** didn't report error");
 708:             } catch (Exception e) {
 709:                 System.err.println ("== err: " + e.getMessage ()); }
 710:             try {
 711:                     // only builtins so far
 712:                 createPipeline ("com.example.xml.FilterClass");
 713:                 System.err.println ("** didn't report error");
 714:             } catch (Exception e) {
 715:                 System.err.println ("== err: " + e.getMessage ()); }
 716: 
 717:         } catch (Exception e) {
 718:             e.printStackTrace ();
 719:         }
 720:     }
 721: /**/
 722: 
 723: }