Clover coverage report - QedeqKernelSe Coverage Report
Coverage timestamp: Do Jan 11 2007 09:03:50 CET
file stats: LOC: 673   Methods: 32
NCLOC: 368   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
TextInput.java 68,8% 74,9% 87,5% 73,8%
coverage coverage
 1    /* $Id: TextInput.java,v 1.14 2006/10/20 20:23:08 m31 Exp $
 2    *
 3    * This file is part of the project "Hilbert II" - http://www.qedeq.org
 4    *
 5    * Copyright 2000-2006, Michael Meyling <mime@qedeq.org>.
 6    *
 7    * "Hilbert II" is free software; you can redistribute
 8    * it and/or modify it under the terms of the GNU General Public
 9    * License as published by the Free Software Foundation; either
 10    * version 2 of the License, or (at your option) any later version.
 11    *
 12    * This program is distributed in the hope that it will be useful,
 13    * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15    * GNU General Public License for more details.
 16    */
 17   
 18    package org.qedeq.kernel.utility;
 19   
 20    import java.io.File;
 21    import java.io.IOException;
 22    import java.io.InputStream;
 23    import java.net.URL;
 24   
 25   
 26    /**
 27    * This class provides convenient methods for parsing input.
 28    *
 29    * TODO mime 20050426: offer input stream constructor?
 30    * TODO mime 20060330: decorate normal java.io.InputStream or other???
 31    *
 32    * @version $Revision: 1.14 $
 33    * @author Michael Meyling
 34    */
 35    public final class TextInput extends InputStream {
 36   
 37    /** Char marking end of data. */
 38    public static final int EOF = -1;
 39   
 40    /** Char marking end of input line. */
 41    // public final static char CR = '\n'; // TODO mime 20050613: delete if running on all platforms
 42    public static final char CR = '\012';
 43   
 44    /** String for marking current reading position. */
 45    private static final String MARKER = "#####";
 46   
 47    /** Holds the data. */
 48    private final StringBuffer source;
 49   
 50    /** Current line number (starting with 0). */
 51    private int lineNumber = 0;
 52   
 53    /** Current column (starting with 0). */
 54    private int column = 0;
 55   
 56    /** Current reading position (starting with 0). */
 57    private int position = 0;
 58   
 59    /** Address of input, for identifying source. */
 60    private final URL address;
 61   
 62    /** Local address of input, for loading source. */
 63    private final URL localAddress;
 64   
 65   
 66    /**
 67    * Constructor using <code>StringBuffer</code> source.
 68    *
 69    * @param file Data source.
 70    * @throws IOException File reading failed.
 71    * @throws NullPointerException One argument was a null pointer.
 72    */
 73  3161 public TextInput(final File file) throws IOException {
 74  3161 this(file, file.getAbsoluteFile().toURI().toURL());
 75    }
 76   
 77   
 78    /**
 79    * Constructor using <code>StringBuffer</code> source.
 80    *
 81    * @param file Data source.
 82    * @param address For identifying source.
 83    * @throws IOException File reading failed.
 84    * @throws NullPointerException Argument was a null pointer.
 85    */
 86  3161 public TextInput(final File file, final URL address) throws IOException {
 87  3161 if (file == null || address == null) {
 88  0 throw new NullPointerException(
 89    "no null pointer as argument accepted");
 90    }
 91  3161 this.source = new StringBuffer();
 92  3161 IoUtility.loadFile(file, source);
 93  3161 this.address = address;
 94  3161 this.localAddress = file.getAbsoluteFile().toURI().toURL();
 95    }
 96   
 97   
 98    /**
 99    * Constructor using <code>StringBuffer</code> source.
 100    *
 101    * @param source data source
 102    * @throws NullPointerException Argument was a null pointer.
 103    */
 104  83 public TextInput(final StringBuffer source) {
 105  83 if (source == null) {
 106  1 throw new NullPointerException(
 107    "no null pointer as argument accepted");
 108    }
 109  82 this.source = source;
 110  82 this.address = null;
 111  82 this.localAddress = null;
 112    }
 113   
 114    /**
 115    * Constructor using <code>String</code> source.
 116    *
 117    * @param source data source
 118    * @throws NullPointerException Argument was a null pointer.
 119    */
 120  37 public TextInput(final String source) {
 121  37 if (source == null) {
 122  1 throw new NullPointerException(
 123    "no null pointer as argument accepted");
 124    }
 125  36 this.source = new StringBuffer(source);
 126  36 this.address = null;
 127  36 this.localAddress = null;
 128    }
 129   
 130    /**
 131    * Constructor using <code>URL</code> source.
 132    *
 133    * @param url Data source.
 134    * @throws IOException File reading failed.
 135    * @throws NullPointerException One argument was a null pointer.
 136    */
 137  0 public TextInput(final URL url) throws IOException {
 138  0 if (url == null) {
 139  0 throw new NullPointerException(
 140    "no null pointer as argument accepted");
 141    }
 142  0 this.source = new StringBuffer();
 143  0 IoUtility.loadFile(url, source);
 144  0 this.address = url;
 145  0 this.localAddress = url;
 146    }
 147   
 148   
 149    /**
 150    * Reads a single character and increments the reading position
 151    * by one. If no characters are left, <code>-1</code> is returned.
 152    * Otherwise a cast to <code>char</code> gives the character read.
 153    *
 154    * @return Character read, if there are no more chars
 155    * <code>-1</code> is returned.
 156    */
 157  274863642 public final int read() {
 158  274863642 if (position >= source.length()) {
 159  32 return EOF;
 160    }
 161  274863610 if (getChar() == CR) {
 162  7262485 lineNumber++;
 163  7262485 column = 0;
 164    } else {
 165  267601125 column++;
 166    }
 167  274863610 return source.charAt(position++);
 168    }
 169   
 170    /**
 171    * Decrements the reading position by one and reads a single character.
 172    * If no characters are left, <code>-1</code> is returned.
 173    * Otherwise a cast to <code>char</code> gives the character read.
 174    *
 175    * @return Character read, if there are no more chars
 176    * <code>-1</code> is returned.
 177    */
 178  72165 public final int readInverse() {
 179  72165 if (position <= 0) {
 180  1 return -1;
 181    }
 182  72164 final char c = source.charAt(--position);
 183  72164 if (c == CR) {
 184  7 lineNumber--;
 185  7 int pos = source.lastIndexOf("" + CR, position - 1);
 186  7 if (pos < 0) {
 187  2 column = position;
 188    } else {
 189  5 column = position - 1 - pos;
 190    }
 191    } else {
 192  72157 column--;
 193  72157 if (column < 0) {
 194  0 throw new IllegalStateException("column less then 0");
 195    }
 196    }
 197  72164 return c;
 198    }
 199   
 200    /**
 201    * Reads a given amount of characters and increments the reading position
 202    * accordingly.
 203    *
 204    * @param number amount of characters to read
 205    * @return string read
 206    */
 207  2 public final String readString(final int number) {
 208  2 final StringBuffer result = new StringBuffer(number);
 209  2 for (int i = 0; i < number; i++) {
 210  10 final int c = read();
 211  10 if (c != -1) {
 212  10 result.append((char) c);
 213    } else {
 214  0 break;
 215    }
 216    }
 217  2 return result.toString();
 218    }
 219   
 220    /**
 221    * Reads a single character and does not change the reading
 222    * position. If no characters are left, <code>-1</code> is returned.
 223    * Otherwise a cast to <code>char</code> gives the character read.
 224    *
 225    * @return Character read at current position, if there are no more chars
 226    * <code>-1</code> is returned
 227    */
 228  275401200 public final int getChar() {
 229  275401200 if (position >= source.length()) {
 230  374 return -1;
 231    }
 232  275400826 return source.charAt(position);
 233    }
 234   
 235    /**
 236    * Reads a single character and does not change the reading
 237    * position. If offset addition leads out of the source,
 238    * <code>-1</code> is returned. Otherwise a cast to <code>char</code>
 239    * gives the character read.
 240    *
 241    * @param skip Offset from current reading position. Maybe negative.
 242    * @return Character read, if position is out of scope
 243    * <code>-1</code> is returned.
 244    */
 245  173 public final int getChar(final int skip) {
 246  173 if (position + skip < 0 || position + skip >= source.length()) {
 247  20 return -1;
 248    }
 249  153 return source.charAt(position + skip);
 250    }
 251   
 252   
 253    /**
 254    * Skips white space, beginning from reading position.
 255    * Changes reading position to next non white space
 256    * character.
 257    */
 258  1953 public final void skipWhiteSpace() {
 259  1953 while (!isEmpty() && Character.isWhitespace((char) getChar())) {
 260  453 read();
 261    }
 262    }
 263   
 264    /**
 265    * Skips white space, beginning from reading position.
 266    * Changes reading position to next non white space
 267    * character.
 268    */
 269  3 public final void skipWhiteSpaceInverse() {
 270  3 while (getPosition() > 0 && Character.isWhitespace((char) getChar(-1))) {
 271  10 readInverse();
 272    }
 273    }
 274   
 275    /**
 276    * Skip current position back to beginning of an XML tag.
 277    * This is mainly something like <code>&lt;tagName</code>.
 278    *
 279    * @throws IllegalArgumentException No begin of XML tag found.
 280    */
 281  3163 public final void skipBackToBeginOfXmlTag() {
 282  3163 if ('<' == getChar()) {
 283  1 return;
 284    }
 285  3162 boolean quoted = false;
 286  3162 do {
 287  72152 if (-1 == readInverse()) {
 288  0 throw new IllegalArgumentException("begin of xml tag not found");
 289    }
 290  72152 if ('\"' == getChar()) {
 291  5328 quoted = !quoted;
 292    }
 293  72152 } while (quoted || '<' != getChar());
 294    }
 295   
 296    /**
 297    * Skip current position forward to end of an XML tag.
 298    * This is mainly something like <code>&gt;</code>. Quoted data is skipped.
 299    *
 300    * @throws IllegalArgumentException No en of XML tag found.
 301    */
 302  0 public final void skipForwardToEndOfXmlTag() {
 303  0 if ('>' == getChar()) {
 304  0 return;
 305    }
 306  0 boolean quoted = false;
 307  0 while ('>' != getChar()) {
 308  0 if ('\"' == getChar()) {
 309  0 quoted = !quoted;
 310    }
 311  0 if (!quoted) {
 312  0 if (-1 == read()) {
 313  0 throw new IllegalArgumentException("end of xml tag not found");
 314    }
 315    }
 316    }
 317  0 read(); // skip '>'
 318    }
 319   
 320    /**
 321    * Reads tag or attribute name out of XML stream. Whitespace is skipped and
 322    * characters are read till &quot;=&quot; or &quot;&gt;&quot; or whitespace is found.
 323    *
 324    * @return Name of tag or attribute.
 325    * @throws IllegalArgumentException Next non white space character is &quot;=&quot;
 326    * or &quot;&gt;&quot;.
 327    */
 328  704 public final String readNextXmlName() {
 329  704 skipWhiteSpace();
 330  704 if (isEmpty() || '=' == getChar() || '>' == getChar()) {
 331  0 throw new IllegalArgumentException(
 332    "begin of attribute expected");
 333    }
 334  704 StringBuffer buffer = new StringBuffer();
 335  704 while (!isEmpty() && '=' != getChar() && '>' != getChar()
 336    && !Character.isWhitespace((char) getChar())) {
 337  4174 buffer.append((char) read());
 338    }
 339  704 return buffer.toString();
 340    }
 341   
 342    /**
 343    * Reads attribute value out of XML stream. Whitespace is skipped and an &quot;=&quot;
 344    * is expected to follow. Again whitespace is skipped. If no quotation mark follows
 345    * characters are read till whitespace or &quot;&gt;&quot; occurs. Otherwise data is
 346    * read till an ending quotation mark comes.
 347    *
 348    * @return Value read. TODO mime 20050503: do any transformations like &amp;lt; into &lt;?
 349    */
 350  409 public final String readNextAttributeValue() {
 351  409 skipWhiteSpace();
 352  409 if (isEmpty() || '=' != getChar()) {
 353  0 throw new IllegalArgumentException(
 354    "\"=\" expected");
 355    }
 356  409 read(); // read =
 357  409 skipWhiteSpace();
 358  409 if (isEmpty() || '>' == getChar()) {
 359  0 throw new IllegalArgumentException(
 360    "attribute value expected");
 361    }
 362  409 StringBuffer buffer = new StringBuffer();
 363  409 if ('\"' == getChar()) {
 364  409 read(); // read "
 365  409 while (!isEmpty() && '\"' != getChar()) {
 366  5782 buffer.append((char) read());
 367    }
 368  409 if ('\"' != getChar()) {
 369  0 throw new IllegalArgumentException("\" expected");
 370    }
 371  409 read(); // read "
 372    } else {
 373  0 while (!isEmpty() && '>' != getChar()
 374    && !Character.isWhitespace((char) getChar())) {
 375  0 buffer.append((char) read());
 376    }
 377    }
 378  409 return buffer.toString();
 379    }
 380   
 381    /**
 382    * Is there no data left for reading?
 383    *
 384    * @return is all data read?
 385    */
 386  34797 public final boolean isEmpty() {
 387  34797 return position >= source.length();
 388    }
 389   
 390    /**
 391    * Is there no data left for reading after skipping?
 392    *
 393    * @param skip Add this number to current position.
 394    * @return Is data empty at that new position?
 395    */
 396  8 public final boolean isEmpty(final int skip) {
 397  8 return position + skip >= source.length();
 398    }
 399   
 400    /**
 401    * Reads the next string containing only letters or digits,
 402    * leading whitespace is skipped.
 403    * Changes reading position.
 404    *
 405    * @return read string
 406    * @throws IllegalArgumentException if no such characters could
 407    * be found
 408    */
 409  6 public final String readLetterDigitString() {
 410  6 skipWhiteSpace();
 411  6 if (isEmpty() || !Character.isLetterOrDigit((char) getChar())) {
 412  3 read(); // for showing correct position
 413  3 throw new IllegalArgumentException(
 414    "letter or digit expected");
 415    }
 416  3 StringBuffer buffer = new StringBuffer();
 417  3 while (!isEmpty() && Character.isLetterOrDigit((char) getChar())) {
 418  19 buffer.append((char) read());
 419    }
 420  3 return buffer.toString();
 421    }
 422   
 423   
 424    /**
 425    * Reads the next (big) integer, leading whitespace is skipped.
 426    * The first character might be a minus sign, the rest must be
 427    * digits. Leading zero digits are not allowed, also "-0" is not
 428    * accepted. <p>
 429    * Changes reading position.
 430    *
 431    * @return read integer
 432    * @throws IllegalArgumentException if no digits where found or
 433    * the number was to big for an <code>int</code>
 434    */
 435  5 public final String readCounter() {
 436  5 skipWhiteSpace();
 437  5 if (isEmpty()) {
 438  0 throw new IllegalArgumentException("integer expected");
 439    }
 440  5 StringBuffer buffer = new StringBuffer();
 441  5 if (getChar() == '-') {
 442  0 buffer.append(read());
 443    }
 444  5 final int begin = getPosition();
 445  5 if (!Character.isDigit((char) getChar())) {
 446  2 throw new IllegalArgumentException("digit expected");
 447    }
 448  3 while (!isEmpty() && Character.isDigit((char) getChar())) {
 449  13 buffer.append((char) read());
 450    }
 451  3 if (buffer.length() >= 2 && ('0' == buffer.charAt(0)
 452    || '-' == buffer.charAt(0) && '0' == buffer.charAt(1))) {
 453  1 setPosition(begin); // for showing correct position
 454  1 throw new IllegalArgumentException("no leading zeros allowed");
 455    }
 456  2 return buffer.toString();
 457    }
 458   
 459    /**
 460    * Reads the next quoted string, leading whitespace is skipped.
 461    * A correctly quoted string could be created by adding a leading and
 462    * a trailing quote character and doubling each other quote character.
 463    * The resulting string is dequoted.
 464    * Changes reading position.
 465    *
 466    * @return Dequoted string read.
 467    * @throws IllegalArgumentException No correctly quoted string was found.
 468    */
 469  5 public final String readQuoted() {
 470  5 skipWhiteSpace();
 471  5 if (isEmpty() || read() != '\"') {
 472  1 throw new IllegalArgumentException(
 473    "\" expected");
 474    }
 475  4 StringBuffer unquoted = new StringBuffer();
 476  4 char c;
 477  4 do {
 478  14 if (isEmpty()) {
 479  0 throw new IllegalArgumentException(
 480    "ending \" expected");
 481    }
 482  14 c = (char) read();
 483  14 if (c != '\"') {
 484  9 unquoted.append(c);
 485    } else { // c == '\"'
 486  5 if (isEmpty() || getChar() != '\"') {
 487  4 break; // success
 488    }
 489  1 unquoted.append((char) read());
 490    }
 491    } while (true);
 492  4 return unquoted.toString();
 493    }
 494   
 495    /**
 496    * Returns the current line number.
 497    *
 498    * @return Current line number (starting with line 1).
 499    */
 500  272591946 public final int getRow() {
 501  272591946 return lineNumber + 1;
 502    }
 503   
 504    /**
 505    * Returns the current column number.
 506    *
 507    * @return Current column number (starting with line 1).
 508    */
 509  185002 public final int getColumn() {
 510  185002 return column + 1;
 511    }
 512   
 513    /**
 514    * Returns the current line.
 515    *
 516    * @return Current line.
 517    */
 518  2 public final String getLine() {
 519  2 int min = position - 1;
 520  2 while (min >= 0 && source.charAt(min) != CR) {
 521  0 min--;
 522    }
 523  2 int max = position;
 524  2 while (max < source.length()
 525    && source.charAt(max) != CR) {
 526  120 max++;
 527    }
 528  2 if (min + 1 >= max) {
 529  0 return "";
 530    }
 531  2 return source.substring(min + 1, max);
 532    }
 533   
 534    /**
 535    * Returns the current position. Starting with 0.
 536    *
 537    * @return Current position.
 538    */
 539  25961 public final int getPosition() {
 540  25961 return position;
 541    }
 542   
 543    /**
 544    * Returns the highest position number possible. This is equal
 545    * to the length of the source.
 546    *
 547    * @return Maximum position.
 548    */
 549  5 public final int getMaximumPosition() {
 550  5 return source.length();
 551    }
 552   
 553    /**
 554    * Sets the current position (and indirectly the line number).
 555    *
 556    * @param position Set current position to this value.
 557    */
 558  7395 public final void setPosition(final int position) {
 559  7395 if (position >= source.length()) {
 560  445 this.position = source.length();
 561  6950 } else if (this.position != position) {
 562  4718 this.position = 0;
 563  4718 this.lineNumber = 0;
 564  4718 this.column = 0;
 565  4718 for (int i = 0; i < position; i++) { // Q & D
 566  2103816 read();
 567    }
 568    }
 569    }
 570   
 571    /**
 572    * Sets the current line number (and indirectly the position).
 573    *
 574    * @param row Move to this line number.
 575    */
 576  6031 public final void setRow(final int row) {
 577  6031 int r = row;
 578    // check if row is under lower bound
 579  6031 if (r <= 0) {
 580  0 r = 1;
 581    }
 582    // check if already at wanted position
 583  6031 if (getRow() == r) {
 584  443 return;
 585    }
 586    // check if already at end of file
 587  5588 if (getPosition() >= source.length() && getRow() >= r) {
 588  0 return;
 589    }
 590  5588 if (getRow() > r) {
 591    // reset to begin of file
 592  0 this.position = 0;
 593  0 this.lineNumber = 0;
 594  0 this.column = 0;
 595    }
 596  5588 for (int i = 0; getRow() < r; i++) {
 597  272568008 if (EOF == read()) {
 598  0 return;
 599    }
 600    }
 601    }
 602   
 603    /**
 604    * Sets the current column position (and indirectly the position).
 605    * If <code>column</code> is out of range the minimum value (1) or the maximum possible column
 606    * value is taken.
 607    *
 608    * @param column Move to this column. First column has the number one.
 609    */
 610  6027 public final void setColumn(final int column) {
 611  6027 int c = column;
 612    // check if column is out of lower bound
 613  6027 if (c <= 0) {
 614  0 c = 1;
 615    }
 616    // check if already at wanted position
 617  6027 if (getColumn() == c) {
 618  0 return;
 619    }
 620  6027 if (getColumn() > c) {
 621  1 do {
 622  17 this.position--;
 623  17 this.column--;
 624  17 } while (getColumn() > c);
 625  1 return;
 626    }
 627  6026 while (getChar() != CR && getChar() != EOF && getColumn() < c) {
 628  164744 read();
 629    }
 630    }
 631   
 632    /**
 633    * Get address (or something to identify it) of input source.
 634    *
 635    * @return Address of input source.
 636    */
 637  0 public final URL getAddress() {
 638  0 return this.address;
 639    }
 640   
 641    /**
 642    * Get local address (or something to identify it) of input source.
 643    *
 644    * @return Local address of input source.
 645    */
 646  9775 public final URL getLocalAddress() {
 647  9775 return this.localAddress;
 648    }
 649   
 650    /**
 651    * Show reading position.
 652    *
 653    * @return current line with mark at current reading position
 654    */
 655  0 public final String showLinePosition() {
 656  0 final String line = getLine();
 657  0 final StringBuffer buffer = new StringBuffer();
 658  0 final int col = getColumn() - 1;
 659  0 if (col > 0) {
 660  0 if (col < line.length()) {
 661  0 buffer.append(line.substring(0, col));
 662    } else {
 663  0 buffer.append(line);
 664    }
 665    }
 666  0 buffer.append(MARKER);
 667  0 if (col < line.length()) {
 668  0 buffer.append(line.substring(col));
 669    }
 670  0 return buffer.toString();
 671    }
 672   
 673    }