001/* parser for pxgraph command line arguments and binary files.
002
003 @Author: Edward A. Lee and Christopher Hylands
004
005 @Version: $Id$
006
007 @Copyright (c) 1997-2014 The Regents of the University of California.
008 All rights reserved.
009
010 Permission is hereby granted, without written agreement and without
011 license or royalty fees, to use, copy, modify, and distribute this
012 software and its documentation for any purpose, provided that the
013 above copyright notice and the following two paragraphs appear in all
014 copies of this software.
015
016 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
017 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
018 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
019 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
020 SUCH DAMAGE.
021
022 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
023 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
024 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
025 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
026 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
027 ENHANCEMENTS, OR MODIFICATIONS.
028
029 PT_COPYRIGHT_VERSION_2
030 COPYRIGHTENDKEY
031 */
032package ptolemy.plot.compat;
033
034import java.io.BufferedInputStream;
035import java.io.DataInputStream;
036import java.io.EOFException;
037import java.io.FileInputStream;
038import java.io.FileNotFoundException;
039import java.io.IOException;
040import java.io.InputStream;
041import java.io.StreamTokenizer;
042import java.io.StringReader;
043import java.net.MalformedURLException;
044import java.net.URL;
045import java.util.Vector;
046
047import ptolemy.plot.CmdLineArgException;
048import ptolemy.plot.Plot;
049import ptolemy.plot.PlotBox;
050
051///////////////////////////////////////////////////////////////////
052//// PxgraphParser
053
054/**
055This class provides backwards compatibility with an older plotting
056program, pxgraph.  It provides two methods, one for parsing command-line
057arguments, and one for reading binary data from a file. In pxgraph,
058the binary files have no format information; all format information
059is provided by command line arguments.
060
061<p>Below we describe the <code>pxgraph</code> arguments.  The text is
062  based on the <code>xgraph</code> Unix man page written by David
063  Harrison (University of California).  To see the command line
064  options, you can type
065  <code>pxgraph -help</code>.</p>
066
067<p>The <code>pxgraph</code> program draws a graph on a display given
068  data read from either data files or from standard input if one of
069  the arguments is a dash. It can display up to 64 independent data
070  sets using different colors and/or line styles for each set. It
071  annotates the graph with a title, axis labels, grid lines or tick
072  marks, grid labels, and a legend. There are options to control the
073  appearance of most components of the graph.</p>
074
075<p>The input format is similar to <code>graph(<i>1G</i>)</code> but differs
076  slightly. The data consists of a number of <I>data</I> <I>sets</I>. Data
077  sets are separated by a blank line. A new data set is also
078  assumed at the start of each input file. A data set consists
079  of an ordered list of points of the form <code><i>directive</i>
080    X Y</code>.</p>
081
082<p>The directive is either <code>draw</code> or <code>move</code> and can
083  be omitted (Note that with binary data files, you must have a directive,
084  the above statement only applies to ascii format data files). If the
085  directive is <code>draw</code>, a line will be drawn
086  between the previous point and the current point (if a line
087  graph is chosen). Specifying a <code>move</code> directive tells
088  xgraph not to draw a line between the points. If the directive
089  is omitted, <code>draw</code> is assumed for all points in a data
090  set except the first point where <code>move</code> is assumed. The
091  <code>move</code> directive is used most often to allow discontinuous
092  data in a data set.</p>
093
094<p>After <code>pxgraph</code> has read the data, it will create a new window
095  to graphically display the data.</p>
096
097<p> Once the window has been opened, all of the data sets will
098  be displayed graphically (subject to the options explained
099  below) with a legend in the upper right corner of the
100  screen. To zoom in on a portion of the graph, depress a
101  mouse button in the window and sweep out a region. <code>pxgraph</code>
102  will then the window will be redrawn with just that portion of
103  the graph. <code>pxgraph</code> also presents four control buttons in
104  the lower left corner of each window: <code>Exit</code>,
105  <code>Print</code>, <code>HTML</code> and <code>About</code>.</p>
106
107<p>The <code>Exit</code> button will exit the process.  You can also
108  type <code>Control-D</code>, <code>Control-C</code> or <code>q</code>
109  to exit.</p>
110
111<p>The <code>Print</code> button brings up a print dialog window.</p>
112
113<p>The <code>About</code> button brings up a message about
114<code>pxgraph</code>.</p>
115
116<p>The <code>HTML</code> button prints an HTML file to stdout that
117  can be used to display the file with applet <code>Plot</code> classes
118  (Experimental).</p>
119
120<p><code>pxgraph</code> accepts a large number of command line options.
121  A list of these options is given below.</p>
122
123<dl>
124  <dt><code>=<i>W</i>x<i>H</i>+<i>X</i>+<i>Y</i></code></dt>
125  <dd>Specifies the initial size and location of the pxgraph
126    window.</dd>
127
128  <dt> <code>-<i>&lt;digit&gt; &lt;name&gt;</i></code></dt>
129  <dd> These options specify the data
130    set name for the corresponding data set. The digit
131    should be in the range 0 to 63. This name will be
132    used in the legend.</dd>
133
134  <dt><code>-bar</code></dt>
135  <dd>Specifies that vertical bars should be drawn from the
136    data points to a base point which can be specified with
137    <code>-brb</code>.
138    Usually, the <code>-nl</code> flag is used with this option.
139    The point itself is located at the center of the bar.</dd>
140
141  <dt><code>-bb</code></dt>
142  <dd>Draw a bounding box around the data region. This is
143    very useful if you prefer to see tick marks rather than
144    grid lines (see <code>-tk</code>).
145    <b>Ignored in the Java version because the plotting area is a different
146      color than the border where the axes are labeled.</b></dd>
147
148  <dt><code>-bd</code> <code><i>&lt;color&gt;</i></code></dt>
149  <dd>This specifies the border color of the <code>pxgraph</code> window.
150    <b>Unsupported in the Java version.</b></dd>
151
152  <dt><code>-bg</code> <code><i>&lt;color&gt;</i></code></dt>
153  <dd>Background color of the area where the labels and legend are rendered.
154    <b>In the Java version, this argument takes hexadecimal color values
155      (<code>fffff</code>), not color names.</b>  Note that the background
156    of the data plotting region is always white because the dataset colors
157    were designed for a white background.</dd>
158
159  <dt><a name="bigendianFlag"><code>-bigendian</code></a></dt>
160  <dd>Data files are in big-endian, or network binary format.
161    See the <code>-binary</code> command line argument documentation
162    below for details about the format.
163    If you are on a little-endian machine, such as a machine
164    with an Intel x86 chip, and you would like to read a binary
165    format file, created on a big-endian machine, such as a Sun SPARC,
166    use the <code>-bigendian</code> flag.</dd>
167
168  <dt><a name="binaryFlag"><code>-binary</code></a></dt>
169  <dd>Data files are in a binary format.
170    The endian-ism of the data depends on which of the two
171    subformats below are chosen.
172    The <code>-binary</code>
173    argument is the primary difference between <code>xgraph</code>
174    and <code>pxgraph</code>.  The
175    <A HREF="http://ptolemy.eecs.berkeley.edu">Ptolemy Project</A> software
176    makes extensive use of <code>-binary</code>.
177    <br>There are two binary formats, both of which use 4 byte floats.
178    <ol>
179      <li>If the first byte of the data file is not a <code>d</code>, then
180        we assume that the file contains 4 byte floats in big-endian ordering
181        with no plot commands.
182      <li>If the first byte of the data file is a <code>d</code>, then
183        we assume that the plot commands are encoded as single characters,
184        and the numeric data is a 4 byte float encoded in the
185        native endian format of the machine that the java interpreter is
186        running on.
187        <br>The commands are encoded as follows:
188        <dl>
189          <dt> <code>d <I>&lt;4byte float&gt; &lt;4byte float&gt;</I></code></dt>
190          <dd> Draw a X, Y point</dd>
191          <dt> <code>e</code></dt>
192          <dd> End of dataset</dd>
193          <dt> <code>n <I>&lt;dataset name&gt;</I>&#92;</code></dt>
194          <dd> New dataset name, ends in <code>&#92;</code></dd>
195          <dt> <code>m <I>&lt;4byte float&gt; &lt;4byte float&gt;</I></code></dt>
196          <dd> Move to a X, Y point.</dd>
197        </dl>
198      </li>
199    </ol>
200    <br>To view a binary plot file under unix, we can use the
201    <code>od</code> command.  Note that the first character is a <code>d</code>
202    followed by eight bytes of data consisting of two floats of four bytes.
203    <pre>
204      cxh@carson 324% od -c data/integrator1.plt
205      0000000   d  \0  \0  \0  \0  \0  \0  \0  \0   d   ? 200  \0  \0   ? 200
206      0000020  \0  \0   d   @  \0  \0  \0   @   , 314 315   d   @   @  \0  \0
207    </pre>
208    <br>For further information about endian-ism, see the
209    <code>-bigendian</code> and <code>-littleendian</code> command
210    line argument documentation.
211  </dd>
212
213  <dt><code>-brb</code> <code><i>&lt;base&gt;</i></code></dt>
214  <dd>This specifies the base for a bar graph. By default,
215    the base is zero.
216    <b>Unsupported in the Java version.</b></dd>
217
218  <dt><code>-brw</code> <code><i>&lt;width&gt;</i></code></dt>
219  <dd>This specifies the width of bars in a bar graph. The
220    amount is specified in the user units. By default,
221    a bar one pixel wide is drawn.</dd>
222
223  <dt><code>-bw</code> <code><i>&lt;size&gt;</i></code></dt>
224  <dd>Border width (in pixels) of the <code>pxgraph</code> window.
225    <b>Unsupported in the Java version.</b></dd>
226
227  <dt><code>-db</code></dt>
228  <dd>Causes xgraph to run in synchronous mode and prints out
229    the values of all known defaults.</dd>
230
231  <dt><code>-fg</code> <code><i>&lt;color&gt;</i></code></dt>
232  <dd>Foreground color. This color is used to draw all text
233    and the normal grid lines in the window.
234    <b>In the Java version, this argument takes hexadecimal color values
235      (<code>fffff</code>), not color names.</b></dd>
236
237  <dt><code>-gw</code></dt>
238  <dd> Width, in pixels, of normal grid lines.
239    <b>Unsupported in the Java version.</b></dd>
240
241  <dt><code>-gs</code></dt>
242  <dd> Line style pattern of normal grid lines.</dd>
243
244  <dt><code>-impulses</code></dt>
245  <dd>Draw a line from any plotted point down to the x axis.
246    (This argument is not present in the X11 <code>pxgraph</code>,
247    but it is similar to <code>-nl -bar</code>).</dd>
248
249  <dt><code>-lf</code> <code><i>&lt;fontname&gt;</i></code></dt>
250  <dd>Label font. All axis labels and grid labels are drawn
251    using this font.
252    <b>Note that the Java version does not use X11 style font specification.</b>
253    In the Java version, fonts may be specified as
254    <menu>
255      <li><code><i>fontname</i></code>, where
256        <code><i>fontname</i></code> is one of <code>helvetica</code>,
257        <code>TimesRoman</code>, <code>Courier</code>,  <code>Dialog</code>,
258        <code>DialogInput</code>, <code>ZapfDingbats</code>.
259
260      <li><code><i>fontname</i>-<i>style</i></code>, where
261        <code><i>style</i></code> is one of
262        <code>PLAIN</code>, <code>ITALIC</code>, <code>BOLD</code>,
263        i.e. <code>helvetica-ITALIC</code>
264      <li><code><i>fontname</i>-<i>size</i></code>, or
265      <li><code><i>fontname</i>-<i>style</i>-<i>size</i></code>, where
266        <code><i>size</i></code> is an integer font size in points.
267    </menu>
268    The default is <code>helvetica-PLAIN-12</code>.
269  </dd>
270
271  <dt><a name="littleendian"><code>-littleendian</code></a></dt>
272  <dd>Data files are in little-endian, or x86 binary format.
273    See the <code>-binary</code> command line argument documentation
274    above for details about the format.
275    If you are on a big-endian machine, such as a Sun Sparc,
276    and you would like to read a binary
277    format file created on a little-endian machine, such as Intel x86
278    machine, then use the <code>-littleendian</code> flag.</dd>
279
280  <dt><code>-lnx</code></dt>
281  <dd>Specifies a logarithmic X axis. Grid labels represent
282    powers of ten.  If <code>-lnx</code> is present, then
283    x values must be greater than zero.</dd>
284
285  <dt><code>-lny</code></dt>
286  <dd>Specifies a logarithmic Y axis. Grid labels represent
287    powers of ten.   If <code>-lny</code> is present, then
288    y values must be greater than zero.</dd>
289
290  <dt><code>-lw</code> <code><i>width</i></code></dt>
291  <dd>Specifies the width of the data lines in pixels. The
292    default is zero.
293    <b>Unsupported in the Java version.</b></dd>
294
295  <dt><code>-lx</code> <code><i>&lt;xl,xh&gt;</i></code></dt>
296  <dd>This option limits the range of the X axis to the
297    specified interval. This (along with <code>-ly</code>) can be used
298    to zoom in on a particularly interesting portion of a
299    larger graph.</dd>
300
301  <dt><code>-ly</code> <code><i>&lt;yl,yh&gt;</i></code></dt>
302  <dd>This option limits the range of the Y axis to the
303    specified interval.</dd>
304
305  <dt><code>-m</code></dt>
306  <dd>Mark each data point with a distinctive marker. There
307    are eight distinctive markers used by xgraph. These
308    markers are assigned uniquely to each different line
309    style on black and white machines and varies with each
310    color on color machines.</dd>
311
312  <dt><code>-M</code></dt>
313  <dd>Similar to <code>-m</code> but markers are assigned uniquely to each
314    eight consecutive data sets (this corresponds to each
315    different line style on color machines).</dd>
316
317  <dt><code>-nl</code></dt>
318  <dd>Turn off drawing lines. When used with <code>-m</code>,
319    <code>-M</code>, <code>-p</code>, or <code>-P</code> this can be used
320    to produce scatter plots. When used with -bar, it can be used to
321    produce standard bar graphs.</dd>
322
323  <dt><code>-o</code> <code><i>output filename</i></code></dt>
324  <dd>The name of the file to place the print output in.  Currently
325    defaults to <code>/tmp/t.ps</code>.  See also the
326    <code>-print</code> option.</dd>
327
328  <dt><code>-p</code></dt>
329  <dd>Marks each data point with a small marker (pixel
330    sized). This is usually used with the -nl option for
331    scatter plots.</dd>
332
333  <dt><code>-P</code></dt>
334  <dd>Similar to <code>-p</code> but marks each pixel with a large dot.</dd>
335
336  <dt><code>-print</code></dt>
337  <dd>Bring up the print dialog immediately upon startup.  Unfortunately,
338    there is no way to automatically print in JDK1.1, the user must hit
339    the <code>Ok</code> button.  See also the <code>-o</code> option.</dd>
340
341  <dt><code>-rv</code></dt>
342  <dd>Reverse video. On black and white displays, this will
343    invert the foreground and background colors. The
344    behaviour on color displays is undefined.</dd>
345
346  <dt><code>-t</code> <code><i>&lt;string&gt;</i></code></dt>
347  <dd>Title of the plot. This string is centered at the top
348    of the graph.</dd>
349
350  <dt><code>-tf</code> <code><i>&lt;fontname&gt;</i></code></dt>
351  <dd>Title font. This is the name of the font to use for
352    the graph title.  See the <code>-lf</code> description above
353    for how to specify fonts.
354    The default is <code>helvetica-BOLD-14</code></dd>
355
356  <dt><code>-tk</code></dt>
357  <dd>This option causes <code>pxgraph</code> to draw tick marks rather
358    than full grid lines. The <code>-bb</code> option is also useful
359    when viewing graphs with tick marks only.</dd>
360
361  <dt><code>-x</code>  <code><i>&lt;unitname&gt;</i></code></dt>
362  <dd>This is the unit name for the X axis. Its default is "X".</dd>
363
364  <dt><code>-y</code> <code><i>&lt;unitname&gt;</i></code></dt>
365  <dd>This is the unit name for the Y axis. Its default is "Y".</dd>
366
367  <dt><code>-zg</code> <code><i>&lt;color&gt;</i></code></dt>
368  <dd>This is the color used to draw the zero grid line.
369    <b>Unsupported in the Java version.</b></dd>
370
371  <dt><code>-zw</code> <code><i>&lt;width&gt;</i></code></dt>
372  <dd>This is the width of the zero grid line in pixels.
373    <b>Unsupported in the Java version.</b></dd>
374</dl>
375
376<b><a name="pxgraphScriptCompatibilityIssues">Compatibility Issues</a></b>
377<p>Various compatibility issues are documented above in <b>bold</b>.
378  Below are some other issues:</p>
379<ol>
380  <li>The original <code>xgraph</code> program allowed many formatting
381    directives inside the file.  This version only supports
382    <code>draw</code> and <code>move</code>.</li>
383  <li>To read from standard input, specify a dash on the command line.
384    Note that mixing reading from standard in and from files is not
385    well supported.</li>
386  <li>This original <code>xgraph</code> program allowed blank lines
387    to separate datasets.  This version does not.  Instead, use the
388    <code>move <i>X</i> <i>Y</i></code> directive.</li>
389  <li>This version does not support X resources.
390  <li>The Java version of <code>pxgraph</code> takes longer to start up
391    than the X11 version.  This is an inherent problem with standalone
392    Java applications.  One guess is that most of the startup time comes
393    from paging in the shared libraries.</li>
394</ol>
395
396<p>
397  For further information about this tool, see the
398  <a href="http://ptolemy.eecs.berkeley.edu/java/ptplot">Java Plot Website</a>.</p>
399
400@author Edward A. Lee and Christopher Hylands
401
402 @version $Id$
403 @since Ptolemy II 0.4
404 @Pt.ProposedRating red (eal)
405 @Pt.AcceptedRating red (cxh)
406 @see PxgraphApplication
407 @see PxgraphApplet
408 */
409public class PxgraphParser {
410    /** Construct a parser to configure the specified plot.
411     *  @param plot The Plot object that is configured.
412     */
413    public PxgraphParser(Plot plot) {
414        _plot = plot;
415    }
416
417    ///////////////////////////////////////////////////////////////////
418    ////                         public methods                    ////
419
420    /** Parse pxgraph style command-line arguments.
421     *  @param args A set of command-line arguments.
422     *  @return The number of arguments read.
423     *  @exception CmdLineArgException If there is a problem parsing
424     *   the command line arguments.
425     *  @exception FileNotFoundException If a file is specified that is not
426     *   found.
427     *  @exception IOException If an error occurs reading an input file.
428     */
429    public int parseArgs(String[] args)
430            throws CmdLineArgException, FileNotFoundException, IOException {
431        return parseArgs(args, null);
432    }
433
434    /** Parse pxgraph style command-line arguments, using the specified
435     *  base URL for any relative URL references.
436     *  @param args A set of command-line arguments.
437     *  @param base A base URL for relative URL references, or null if
438     *   there is none.
439     *  @return The number of arguments read.
440     *  @exception CmdLineArgException If there is a problem parsing
441     *   the command line arguments.
442     *  @exception FileNotFoundException If a file is specified that is not
443     *   found.
444     *  @exception IOException If an error occurs reading an input file.
445     */
446    public int parseArgs(String[] args, URL base)
447            throws CmdLineArgException, FileNotFoundException, IOException {
448        int i = 0;
449        int j;
450        int argumentsRead = 0;
451
452        // If we see both -nl and -bar, assume we do a stem plot.
453        boolean sawbararg = false; // Saw -bar arg.
454        boolean sawnlarg = false; // Saw -nl arg.
455        String savedmarks = "none"; // Save _marks in case we have -P -bar -nl.
456        _binary = false; // Read a binary xgraph file.
457
458        int width = 400;
459        int height = 400;
460
461        String arg;
462        String[] unsupportedOptions = { "-bd", "-brb", "-bw", "-gw", "-lw",
463                "-zg", "-zw" };
464
465        // True if we saw an - arg, which means read from stdin
466        boolean sawDash = false;
467
468        while (args != null && i < args.length
469                && (args[i].startsWith("-") || args[i].startsWith("="))) {
470            arg = args[i++];
471
472            if (arg.startsWith("-")) {
473                // Search for unsupported options that take arguments
474                boolean badarg = false;
475
476                for (j = 0; j < unsupportedOptions.length; j++) {
477                    if (arg.equals(unsupportedOptions[j])) {
478                        System.err.println("Warning: pxgraph: " + arg
479                                + " is not supported");
480                        i++;
481                        badarg = true;
482                    }
483                }
484
485                if (badarg) {
486                    continue;
487                }
488
489                if (arg.equals("-bb")) {
490                    // We ignore -bb because the Java version of pxgraph plot
491                    // region is a different color from the surrounding region.
492                    continue;
493                } else if (arg.equals("-bg")) {
494                    _plot.setBackground(PlotBox.getColorByName(args[i++]));
495                    continue;
496                } else if (arg.equals("-brw")) {
497                    // -brw <width> BarWidth Bars:
498                    // We default the baroffset to 0 here if the value does
499                    // not include a comma.
500                    double[] spec = _parseDoubles(args[i++]);
501
502                    if (spec.length == 1) {
503                        _plot.setBars(spec[0], 0);
504                    } else {
505                        _plot.setBars(spec[0], spec[1]);
506                    }
507
508                    continue;
509                } else if (arg.equals("-lf")) {
510                    // -lf <labelfont>
511                    _plot.setLabelFont(args[i++]);
512                    continue;
513                } else if (arg.equals("-lx")) {
514                    double[] spec = _parseDoubles(args[i++]);
515
516                    if (spec.length == 1) {
517                        throw new CmdLineArgException(
518                                "Failed to parse `" + arg + "'");
519                    } else {
520                        _plot.setXRange(spec[0], spec[1]);
521                    }
522
523                    continue;
524                } else if (arg.equals("-ly")) {
525                    double[] spec = _parseDoubles(args[i++]);
526
527                    if (spec.length == 1) {
528                        throw new CmdLineArgException(
529                                "Failed to parse `" + arg + "'");
530                    } else {
531                        _plot.setYRange(spec[0], spec[1]);
532                    }
533
534                    continue;
535                } else if (arg.equals("-t")) {
536                    // -t <title> TitleText "An X Graph"
537                    String title = args[i++];
538                    _plot.setTitle(title);
539                    continue;
540                } else if (arg.equals("-tf")) {
541                    // -tf <titlefont>
542                    _plot.setTitleFont(args[i++]);
543                    continue;
544                } else if (arg.equals("-x")) {
545                    // -x <unitName> XUnitText XLabel:
546                    _plot.setXLabel(args[i++]);
547                    continue;
548                } else if (arg.equals("-y")) {
549                    // -y <unitName> YUnitText YLabel:
550                    _plot.setYLabel(args[i++]);
551                    continue;
552                } else if (arg.equals("-bar")) {
553                    //-bar BarGraph Bars: on Marks: none Lines: off
554                    // If we saw the -nl arg, then assume impulses
555                    sawbararg = true;
556
557                    if (sawnlarg) {
558                        _plot.setImpulses(true);
559                    } else {
560                        _plot.setBars(true);
561                        _plot.setMarksStyle("none");
562                    }
563
564                    _plot.setConnected(false);
565                    continue;
566                } else if (arg.equals("-binary")) {
567                    _binary = true;
568                    _endian = _NATIVE_ENDIAN;
569                    continue;
570                } else if (arg.equals("-bigendian")) {
571                    _binary = true;
572                    _endian = _BIG_ENDIAN;
573                    continue;
574                } else if (arg.equals("-littleendian")) {
575                    _binary = true;
576                    _endian = _LITTLE_ENDIAN;
577                    continue;
578                } else if (arg.equals("-db")) {
579                    //_debug = 10;
580                    continue;
581                } else if (arg.equals("-debug")) {
582                    // -debug is not in the original X11 pxgraph.
583                    //_debug = (int) Integer.parseInt(args[i++]);
584                    continue;
585                } else if (arg.equals("-fg")) {
586                    _plot.setForeground(PlotBox.getColorByName(args[i++]));
587                    continue;
588                } else if (arg.equals("-help")) {
589                    // -help is not in the original X11 pxgraph.
590                    //_help();
591                    continue;
592                } else if (arg.equals("-impulses")) {
593                    // -impulses is not in the original X11 pxgraph.
594                    _plot.setImpulses(true);
595                    _plot.setConnected(false);
596                    continue;
597                } else if (arg.equals("-lnx")) {
598                    _plot.setXLog(true);
599                    continue;
600                } else if (arg.equals("-lny")) {
601                    _plot.setYLog(true);
602                    continue;
603                } else if (arg.equals("-m")) {
604                    // -m Markers Marks: various
605                    _plot.setMarksStyle("various");
606                    savedmarks = "various";
607                    continue;
608                } else if (arg.equals("-M")) {
609                    // -M StyleMarkers Marks: various
610                    _plot.setMarksStyle("various");
611                    savedmarks = "various";
612                    continue;
613                } else if (arg.equals("-nl")) {
614                    // -nl NoLines Lines: off
615                    // If we saw the -bar arg, then assume impulses
616                    sawnlarg = true;
617
618                    if (sawbararg) {
619                        // Restore the _marks in case we did -P -bar -nl
620                        _plot.setMarksStyle(savedmarks);
621                        _plot.setBars(false);
622                        _plot.setImpulses(true);
623                    }
624
625                    _plot.setConnected(false);
626                    continue;
627                } else if (arg.equals("-o")) {
628                    // -o <output filename>
629                    // _outputFile =  args[i++];
630                    i++;
631                    continue;
632                } else if (arg.equals("-p")) {
633                    // -p PixelMarkers Marks: points
634                    _plot.setMarksStyle("points");
635                    savedmarks = "points";
636                    continue;
637                } else if (arg.equals("-P")) {
638                    // -P LargePixel Marks: dots\n
639                    _plot.setMarksStyle("dots");
640                    savedmarks = "dots";
641                    continue;
642                } else if (arg.equals("-print")) {
643                    // -print is not in the original X11 pxgraph.
644                    continue;
645                } else if (arg.equals("-rv")) {
646                    _plot.setBackground(PlotBox.getColorByName("black"));
647                    _plot.setForeground(PlotBox.getColorByName("white"));
648                    continue;
649                } else if (arg.equals("-test")) {
650                    // -test is not in the original X11 pxgraph.
651                    //_test = true;
652                    continue;
653                } else if (arg.equals("-tk")) {
654                    _plot.setGrid(false);
655                    continue;
656                } else if (arg.equals("-v") || arg.equals("-version")) {
657                    // -version is not in the original X11 pxgraph.
658                    //_version();
659                    continue;
660                } else if (arg.length() > 1 && arg.charAt(0) == '-') {
661                    // Process '-<digit> <datasetname>'
662                    try {
663                        int datasetnumber = Integer.parseInt(arg.substring(1));
664                        if (datasetnumber >= 0) {
665                            _plot.addLegend(datasetnumber, args[i++]);
666                            continue;
667                        }
668                    } catch (NumberFormatException e) {
669                    }
670                }
671            } else {
672                if (arg.startsWith("=")) {
673                    // Process =WxH+X+Y
674                    width = Integer
675                            .parseInt(arg.substring(1, arg.indexOf('x')));
676
677                    int plusIndex = arg.indexOf('+');
678                    int minusIndex = arg.indexOf('-');
679
680                    if (plusIndex != -1 || minusIndex != -1) {
681                        // =WxH+X+Y, =WxH-X+Y, =WxH-X-Y, =WxH+X-Y
682                        if (plusIndex != -1 && minusIndex != -1) {
683                            // =WxH-X+Y or =WxH+X-Y
684                            int index = minusIndex;
685
686                            if (plusIndex < minusIndex) {
687                                index = plusIndex;
688                            }
689
690                            height = Integer.parseInt(
691                                    arg.substring(arg.indexOf('x') + 1, index));
692                        } else {
693                            if (plusIndex != -1) {
694                                // =WxH+X+Y
695                                height = Integer.parseInt(arg.substring(
696                                        arg.indexOf('x') + 1, plusIndex));
697                            } else {
698                                // =WxH-X-Y
699                                height = Integer.parseInt(arg.substring(
700                                        arg.indexOf('x') + 1, minusIndex));
701                            }
702                        }
703                    } else {
704                        if (arg.length() > arg.indexOf('x')) {
705                            // =WxH
706                            height = Integer.parseInt(arg.substring(
707                                    arg.indexOf('x') + 1, arg.length()));
708                        }
709                    }
710
711                    // FIXME: it is unclear what X and Y in =WxH+X+Y mean
712                    // in a non-toplevel window, so we don't process
713                    // those here.  See Pxgraph.java for how to process
714                    // X and Y for a toplevel window.
715                    continue;
716                }
717            }
718
719            if (arg.equals("-")) {
720                sawDash = true;
721            } else {
722                // If we got to here, then we failed to parse the arg
723                throw new CmdLineArgException("Failed to parse `" + arg + "'");
724            }
725        }
726
727        argumentsRead = i++;
728
729        _plot.setSize(width, height);
730
731        InputStream instream;
732
733        if (sawDash) {
734            // FIXME: Reading from both standard in and files is
735            // not well supported.  We always read from standard in first
736            // then from any files.
737            instream = System.in;
738            read(instream);
739        }
740
741        // Findbugs suggests checking for null
742        for (i = argumentsRead; args != null && i < args.length; i++) {
743            // Have a filename.  First attempt to open it as a URL.
744            try {
745                URL inurl = new URL(base, args[i]);
746                instream = inurl.openStream();
747            } catch (MalformedURLException ex) {
748                instream = new FileInputStream(args[i]);
749            }
750
751            read(instream);
752        }
753
754        return argumentsRead;
755    }
756
757    /** Split a string containing pxgraph-compatible command-line arguments
758     *  into an array and call parseArgs() on the array.  This is used
759     *  in the rare circumstance that you want to control the format
760     *  of a plot from an applet HTML file rather than in the plot data
761     *  file.
762     *  @param pxgraphargs The command line arguments.
763     *  @param base A base URL for relative URL references, or null if
764     *   there is none.
765     *  @return The number of arguments read.
766     *  @exception CmdLineArgException If there is a problem parsing
767     *   the command line arguments.
768     *  @exception FileNotFoundException If a file is specified that is not
769     *   found.
770     *  @exception IOException If an error occurs reading an input file.
771     */
772    public int parsePxgraphargs(String pxgraphargs, URL base)
773            throws CmdLineArgException, FileNotFoundException, IOException {
774        // We convert the String to a Stream and then use a StreamTokenizer
775        // to parse the arguments into a Vector and then copy
776        // the vector into an array of Strings.  We use a Vector
777        // so that we can handle an arbitrary number of arguments
778        Vector argvector = new Vector();
779        boolean prependdash = false; // true if we need to add a -
780
781        StringReader pin = new StringReader(pxgraphargs);
782
783        try {
784            StreamTokenizer stoken = new StreamTokenizer(pin);
785
786            // We don't want to parse numbers specially, so we reset
787            // the syntax and then add back what we want.
788            stoken.resetSyntax();
789            stoken.whitespaceChars(0, ' ');
790            stoken.wordChars('(', '~');
791            stoken.quoteChar('"');
792            stoken.quoteChar('\'');
793
794            int c;
795            String partialarg = null;
796            out: while (true) {
797                c = stoken.nextToken();
798
799                //System.out.print(c + " "+stoken.ttype+" "+stoken.sval+" ");
800                switch (stoken.ttype) { // same as value of 'c'
801                case StreamTokenizer.TT_EOF:
802                    break out;
803
804                case StreamTokenizer.TT_WORD:
805
806                    //System.out.println("Word: " + stoken.sval);
807                    if (prependdash) {
808                        prependdash = false;
809
810                        if (partialarg == null) {
811                            argvector.addElement("-" + stoken.sval);
812                        } else {
813                            argvector
814                                    .addElement("-" + partialarg + stoken.sval);
815                        }
816                    } else {
817                        if (partialarg == null) {
818                            argvector.addElement(stoken.sval);
819                        } else {
820                            argvector.addElement(partialarg + stoken.sval);
821                        }
822                    }
823
824                    partialarg = null;
825                    break;
826
827                case '-':
828                    prependdash = true;
829                    break;
830
831                case '#':
832                case '$':
833                case '%':
834                case '&':
835
836                    // The above chars can be part of a URL.  For example
837                    // perl scripts use &.  However, we cannot include
838                    // them in the wordChars() range of chars, since
839                    // the single quote is between them and the rest of the
840                    // chars. So we have to process them by hand.
841                    partialarg = (String) argvector.lastElement() + (char) c;
842                    argvector.removeElementAt(argvector.size() - 1);
843                    break;
844
845                case '"':
846                case '\'':
847
848                    //System.out.println("String: " + stoken.sval);
849                    argvector.addElement(stoken.sval);
850                    break;
851
852                default:
853                    throw new IOException("Failed to parse: '" + (char) c
854                            + "' in `" + pxgraphargs + "'");
855                }
856            }
857        } catch (IOException e) {
858            e.printStackTrace();
859        }
860
861        // Create a array
862        String[] args = new String[argvector.size()];
863
864        for (int i = 0; i < argvector.size(); i++) {
865            args[i] = (String) argvector.elementAt(i);
866        }
867
868        return parseArgs(args, base);
869    }
870
871    /** Read a pxgraph-compatible binary or ASCII encoded file.
872     *  @param inputStream The input stream.
873     *  @exception java.io.IOException If an I/O error occurs.
874     */
875    public void read(InputStream inputStream) throws IOException {
876        DataInputStream in = new DataInputStream(
877                new BufferedInputStream(inputStream));
878
879        if (_binary) {
880            int c;
881            float x = 0;
882            float y = 0;
883            float pointCount = 0;
884            boolean byteSwapped = false;
885            boolean connected = false;
886            byte[] input = new byte[4];
887
888            if (_connected) {
889                connected = true;
890            }
891
892            switch (_endian) {
893            case _NATIVE_ENDIAN:
894
895                try {
896                    if (System.getProperty("os.arch").equals("x86")) {
897                        byteSwapped = true;
898                    }
899                } catch (SecurityException e) {
900                }
901
902                break;
903
904            case _BIG_ENDIAN:
905                break;
906
907            case _LITTLE_ENDIAN:
908                byteSwapped = true;
909                break;
910
911            default:
912                throw new IOException("Internal Error: Don't know about '"
913                        + _endian + "' style of endian");
914            }
915
916            try {
917                // Flag that we are starting a new data set.
918                _firstInSet = true;
919
920                // Flag that we have not seen a DataSet line in this file.
921                _sawFirstDataset = false;
922
923                c = in.readByte();
924
925                if (c != 'd') {
926                    // Assume that the data is one data set, consisting
927                    // of 4 byte floats.  None of the Ptolemy pxgraph
928                    // binary format extensions apply.
929                    // Note that the binary format is bigendian, or network
930                    // order.  Little-endian machines, like x86 will not
931                    // be able to write binary data directly
932                    // (However, they could use Java's mechanisms for
933                    // writing binary files).
934                    // Read 3 more bytes, create the x float.
935                    int bits = c;
936                    bits = bits << 8;
937                    bits += in.readByte();
938                    bits = bits << 8;
939                    bits += in.readByte();
940                    bits = bits << 8;
941                    bits += in.readByte();
942
943                    x = Float.intBitsToFloat(bits);
944                    y = in.readFloat();
945
946                    // _addLegendIfNecessary might increment _currentdataset
947                    connected = _addLegendIfNecessary(connected);
948                    _plot.addPoint(_currentdataset, x, y, connected);
949
950                    if (_connected) {
951                        connected = true;
952                    }
953
954                    while (true) {
955                        x = in.readFloat();
956                        y = in.readFloat();
957                        connected = _addLegendIfNecessary(connected);
958                        _plot.addPoint(_currentdataset, x, y, connected);
959
960                        if (_connected) {
961                            connected = true;
962                        }
963                    }
964                } else {
965                    // Assume that the data is in the pxgraph binary format.
966                    while (true) {
967                        // For speed reasons, the Ptolemy group extended
968                        // pxgraph to read binary format data.
969                        // The format consists of a command character,
970                        // followed by optional arguments
971                        // d <4byte float> <4byte float> - Draw a X, Y point
972                        // e                             - End of a data set
973                        // n <chars> \n           - New set name, ends in \n
974                        // m                             - Move to a point
975                        switch (c) {
976                        case 'd':
977
978                            // Data point.
979                            if (byteSwapped) {
980                                in.readFully(input);
981                                x = Float.intBitsToFloat((input[3] & 0xFF) << 24
982                                        | (input[2] & 0xFF) << 16
983                                        | (input[1] & 0xFF) << 8
984                                        | input[0] & 0xFF);
985                                in.readFully(input);
986                                y = Float.intBitsToFloat((input[3] & 0xFF) << 24
987                                        | (input[2] & 0xFF) << 16
988                                        | (input[1] & 0xFF) << 8
989                                        | input[0] & 0xFF);
990                            } else {
991                                x = in.readFloat();
992                                y = in.readFloat();
993                            }
994
995                            pointCount++;
996                            connected = _addLegendIfNecessary(connected);
997                            _plot.addPoint(_currentdataset, x, y, connected);
998
999                            if (_connected) {
1000                                connected = true;
1001                            }
1002
1003                            break;
1004
1005                        case 'e': // End of set name.
1006                        case 'm': // a disconnected point
1007                            connected = false;
1008                            break;
1009
1010                        case 'n':
1011                            _firstInSet = true;
1012                            _sawFirstDataset = true;
1013
1014                            StringBuffer datasetname = new StringBuffer();
1015                            _currentdataset++;
1016
1017                            // New set name, ends in \n.
1018                            while (c != '\n') {
1019                                datasetname.append(in.readChar());
1020                            }
1021
1022                            _plot.addLegend(_currentdataset,
1023                                    datasetname.toString());
1024                            _plot.setConnected(true);
1025                            break;
1026
1027                        default:
1028                            throw new IOException("Don't understand `"
1029                                    + (char) c + "' character "
1030                                    + "(decimal value = " + c
1031                                    + ") in binary file.  Last point was (" + x
1032                                    + "," + y + ").\nProcessed " + pointCount
1033                                    + " points successfully");
1034                        }
1035
1036                        c = in.readByte();
1037                    }
1038                }
1039            } catch (EOFException e) {
1040            }
1041        } else {
1042            // Read ASCII files.
1043            // NOTE: These are not in xgraph format, but rather in the
1044            // old ptplot format!!
1045            _plot.read(inputStream);
1046        }
1047    }
1048
1049    ///////////////////////////////////////////////////////////////////
1050    ////                         protected members                 ////
1051
1052    /** The current dataset, used for handling multiple files. */
1053    protected int _currentdataset = -1;
1054
1055    /** The plot object to which to apply commands. */
1056    protected Plot _plot;
1057
1058    ///////////////////////////////////////////////////////////////////
1059    ////                         private methods                   ////
1060    // Add a legend if necessary, return the value of the connected flag.
1061    private boolean _addLegendIfNecessary(boolean connected) {
1062        if (!_sawFirstDataset || _currentdataset < 0) {
1063            // We did not set a DataSet line, but
1064            // we did get called with -<digit> args
1065            _sawFirstDataset = true;
1066            _currentdataset++;
1067        }
1068
1069        if (_plot.getLegend(_currentdataset) == null) {
1070            // We did not see a "DataSet" string yet,
1071            // nor did we call addLegend().
1072            _firstInSet = true;
1073            _sawFirstDataset = true;
1074            _plot.addLegend(_currentdataset, "Set " + _currentdataset);
1075        }
1076
1077        if (_firstInSet) {
1078            connected = false;
1079            _firstInSet = false;
1080        }
1081
1082        return connected;
1083    }
1084
1085    // Parse a string with a comma into two doubles.
1086    // If there is no comma, return a single double.
1087    private double[] _parseDoubles(String spec) {
1088        int comma = spec.indexOf(",");
1089
1090        if (comma < 0) {
1091            double[] result = new double[1];
1092            result[0] = Double.parseDouble(spec);
1093            return result;
1094        } else {
1095            double[] result = new double[2];
1096            String spec1 = spec.substring(0, comma);
1097            result[0] = Double.parseDouble(spec1);
1098
1099            String spec2 = spec.substring(comma + 1);
1100            result[1] = Double.parseDouble(spec2);
1101            return result;
1102        }
1103    }
1104
1105    ///////////////////////////////////////////////////////////////////
1106    ////                         private members                   ////
1107    // Check the osarch and use the appropriate endian.
1108    private static final int _NATIVE_ENDIAN = 0;
1109
1110    // Data is in big-endian
1111    private static final int _BIG_ENDIAN = 1;
1112
1113    // Data is in little-endian
1114    private static final int _LITTLE_ENDIAN = 2;
1115
1116    // Flag indicating whether the command specified that the format
1117    private boolean _binary = false;
1118
1119    private boolean _connected = true;
1120
1121    // For debugging, call with -db or -debug.
1122    //private static int _debug = 0;
1123
1124    /** @serial Format to read data in. */
1125    private int _endian = _NATIVE_ENDIAN;
1126
1127    // Is this the first datapoint in a set?
1128    private boolean _firstInSet = true;
1129
1130    // Have we seen a DataSet line in the current data file?
1131    private boolean _sawFirstDataset = false;
1132}