Coverage Report - org.apache.any23.io.nquads.NQuadsParser
 
Classes in this File Line Coverage Branch Coverage Complexity
NQuadsParser
0%
0/241
0%
0/112
4.586
NQuadsParser$EOS
0%
0/1
N/A
4.586
NQuadsParser$LiteralAttribute
0%
0/4
N/A
4.586
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *  http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 package org.apache.any23.io.nquads;
 19  
 
 20  
 import org.apache.any23.util.ReaderInputStream;
 21  
 import org.openrdf.model.BNode;
 22  
 import org.openrdf.model.Resource;
 23  
 import org.openrdf.model.Statement;
 24  
 import org.openrdf.model.URI;
 25  
 import org.openrdf.model.Value;
 26  
 import org.openrdf.model.datatypes.XMLDatatypeUtil;
 27  
 import org.openrdf.model.impl.URIImpl;
 28  
 import org.openrdf.rio.ParseLocationListener;
 29  
 import org.openrdf.rio.RDFFormat;
 30  
 import org.openrdf.rio.RDFHandler;
 31  
 import org.openrdf.rio.RDFHandlerException;
 32  
 import org.openrdf.rio.RDFParseException;
 33  
 import org.openrdf.rio.helpers.RDFParserBase;
 34  
 import org.openrdf.rio.ntriples.NTriplesUtil;
 35  
 
 36  
 import java.io.BufferedReader;
 37  
 import java.io.IOException;
 38  
 import java.io.InputStream;
 39  
 import java.io.InputStreamReader;
 40  
 import java.io.Reader;
 41  
 
 42  
 /**
 43  
  * <i>N-Quads</i> parser implementation based on the
 44  
  * {@link org.openrdf.rio.RDFParser} interface.
 45  
  * See the format specification <a href="http://sw.deri.org/2008/07/n-quads/">here</a>.
 46  
  *
 47  
  * @author Michele Mostarda (mostarda@fbk.eu)
 48  
  * @see org.openrdf.rio.RDFParser
 49  
  */
 50  
 public class NQuadsParser extends RDFParserBase {
 51  
 
 52  
     /**
 53  
      * Location listener acquired when parsing started.
 54  
      */
 55  
     private ParseLocationListener locationListener;
 56  
 
 57  
     /**
 58  
      * RDF handler acquired when parsing started.
 59  
      */
 60  
     private RDFHandler rdfHandler;
 61  
 
 62  
     /**
 63  
      * Current row, col and marker trackers.
 64  
      */
 65  
     private int row, col, mark;
 66  
 
 67  0
     public NQuadsParser() {}
 68  
 
 69  
     public RDFFormat getRDFFormat() {
 70  0
         return NQuads.FORMAT;
 71  
     }
 72  
 
 73  
     public void parse(Reader reader, String s)
 74  
     throws IOException, RDFParseException, RDFHandlerException {
 75  0
         ReaderInputStream readerInputStream = new ReaderInputStream(reader);
 76  0
         parse(readerInputStream, s);
 77  0
     }
 78  
 
 79  
     public synchronized void parse(InputStream is, String baseURI)
 80  
     throws IOException, RDFParseException, RDFHandlerException {
 81  0
         if(is == null) {
 82  0
             throw new NullPointerException("inputStream cannot be null.");
 83  
         }
 84  0
         if(baseURI == null) {
 85  0
             throw new NullPointerException("baseURI cannot be null.");
 86  
         }
 87  
 
 88  
         try {
 89  0
             row = col = 1;
 90  
 
 91  0
             locationListener = getParseLocationListener();
 92  0
             rdfHandler = getRDFHandler();
 93  
 
 94  0
             setBaseURI(baseURI);
 95  
 
 96  0
             final BufferedReader br = new BufferedReader( new InputStreamReader(is) );
 97  0
             if( rdfHandler != null ) {
 98  0
                 rdfHandler.startRDF();
 99  
             }
 100  0
             while( parseLine(br) ) {
 101  0
                 nextRow();
 102  
             }
 103  
         } finally {
 104  0
             if(rdfHandler != null) {
 105  0
                 rdfHandler.endRDF();
 106  
             }
 107  0
             clear();
 108  0
             clearBNodeIDMap();
 109  0
         }
 110  0
     }
 111  
 
 112  
     /**
 113  
      * Moves to the next row, resets the column.
 114  
      */
 115  
     private void nextRow() {
 116  0
         col = 0;
 117  0
         row++;
 118  0
         if(locationListener != null) {
 119  0
             locationListener.parseLocationUpdate(row, col);
 120  
         }
 121  0
     }
 122  
 
 123  
     /**
 124  
      * Moves to the next column.
 125  
      */
 126  
     private void nextCol() {
 127  0
         col++;
 128  0
         if(locationListener != null) {
 129  0
             locationListener.parseLocationUpdate(row, col);
 130  
         }
 131  0
     }
 132  
 
 133  
     /**
 134  
      * Reads the next char.
 135  
      *
 136  
      * @param br
 137  
      * @return the next read char.
 138  
      * @throws IOException
 139  
      */
 140  
     private char readChar(BufferedReader br) throws IOException {
 141  0
         final int c = br.read();
 142  0
         if(c == -1) {
 143  0
             throw new EOS();
 144  
         }
 145  0
         nextCol();
 146  0
         return (char) c;
 147  
     }
 148  
 
 149  
     /**
 150  
      * Reads an unicode char with pattern <code>\\uABCD</code>.
 151  
      *
 152  
      * @param br input reader.
 153  
      * @return read char.
 154  
      * @throws IOException
 155  
      * @throws RDFParseException
 156  
      */
 157  
     private char readUnicode(BufferedReader br) throws IOException, RDFParseException {
 158  0
         final char[] unicodeSequence = new char[4];
 159  0
         for(int i = 0; i < unicodeSequence.length; i++) {
 160  0
             unicodeSequence[i] = readChar(br);
 161  
         }
 162  0
         final String unicodeCharStr = new String(unicodeSequence);
 163  
         try {
 164  0
             return (char) Integer.parseInt(unicodeCharStr, 16);
 165  0
         } catch (NumberFormatException nfe) {
 166  0
             reportError("Error while converting unicode char '\\u" + unicodeCharStr + "'", row, col);
 167  0
             throw new IllegalStateException();
 168  
         }
 169  
     }
 170  
 
 171  
     /**
 172  
      * Marks the buffered input stream with the current location.
 173  
      *
 174  
      * @param br
 175  
      */
 176  
     private void mark(BufferedReader br) throws IOException {
 177  0
         mark = col;
 178  0
         br.mark(5);
 179  0
     }
 180  
 
 181  
     /**
 182  
      * Resets the buffered input stream and update the new location.
 183  
      *
 184  
      * @param br
 185  
      * @throws IOException
 186  
      */
 187  
     private void reset(BufferedReader br) throws IOException {
 188  0
         col = mark;
 189  0
         br.reset();
 190  0
         if(locationListener != null) {
 191  0
             locationListener.parseLocationUpdate(row, col);
 192  
         }
 193  0
     }
 194  
 
 195  
     /**
 196  
      * Asserts to read a specific char.
 197  
      *
 198  
      * @param br
 199  
      * @param c
 200  
      * @throws IOException
 201  
      */
 202  
     private void assertChar(BufferedReader br, char c) throws IOException {
 203  0
         if( readChar(br) != c) {
 204  0
             throw new IllegalArgumentException(
 205  
                     String.format("Unexpected char at location %s %s, expected '%s'", row, col, c)
 206  
             );
 207  
         }
 208  0
     }
 209  
 
 210  
     /**
 211  
      * Parsers an <i>NQuads</i> line.
 212  
      *
 213  
      * @param br input stream reader containing NQuads.
 214  
      * @return <code>false</code> if the parsing completed, <code>true</code> otherwise.
 215  
      * @throws IOException
 216  
      * @throws RDFParseException
 217  
      * @throws RDFHandlerException
 218  
      */
 219  
     private boolean parseLine(BufferedReader br)
 220  
     throws IOException, RDFParseException, RDFHandlerException {
 221  
 
 222  0
         if(!consumeSpacesAndNotEOS(br)) {
 223  0
             return false;
 224  
         }
 225  
 
 226  
         // Consumes empty line or line comment.
 227  
         try {
 228  0
             if(consumeEmptyLine(br)) return true;
 229  0
             if( consumeComment(br) ) return true;
 230  0
         } catch (EOS eos) {
 231  0
             return false;
 232  0
         }
 233  
 
 234  
         final Resource sub;
 235  
         final URI      pred;
 236  
         final Value    obj;
 237  
         final URI      graph;
 238  
         try {
 239  0
             sub = parseSubject(br);
 240  0
             consumeSpaces(br);
 241  0
             pred = parsePredicate(br);
 242  0
             consumeSpaces(br);
 243  0
             obj = parseObject(br);
 244  0
             consumeSpaces(br);
 245  0
             graph = parseGraph(br);
 246  0
             consumeSpaces(br);
 247  0
             parseDot(br);
 248  0
         } catch (EOS eos) {
 249  0
             reportFatalError("Unexpected end of line.", row, col);
 250  0
             throw new IllegalStateException();
 251  0
         }
 252  
 
 253  0
         notifyStatement(sub, pred, obj, graph);
 254  
 
 255  0
         if(!consumeSpacesAndNotEOS(br)) {
 256  0
             return false;
 257  
         }
 258  0
         return readChar(br) == '\n';
 259  
     }
 260  
 
 261  
     /**
 262  
      * Consumes the line if empty (contains just a carriage return).
 263  
      *
 264  
      * @param br input NQuads stream.
 265  
      * @return <code>true</code> if the line is empty.
 266  
      * @throws IOException if an error occurs while consuming stream.
 267  
      */
 268  
     private boolean consumeEmptyLine(BufferedReader br) throws IOException {
 269  
         char c;
 270  0
         mark(br);
 271  0
         c = readChar(br);
 272  0
         if (c == '\n') {
 273  0
             return true;
 274  
         } else {
 275  0
             reset(br);
 276  0
             return false;
 277  
         }
 278  
     }
 279  
 
 280  
     /**
 281  
      * Consumes all subsequent spaces and returns true, if End Of Stream is reached instead returns false.
 282  
      * @param br input NQuads stream reader.
 283  
      * @return <code>true</code> if there are other chars to be consumed.
 284  
      * @throws IOException if an error occurs while consuming stream.
 285  
      */
 286  
     private boolean consumeSpacesAndNotEOS(BufferedReader br) throws IOException {
 287  
         try {
 288  0
             consumeSpaces(br);
 289  0
             return true;
 290  0
         } catch (EOS eos) {
 291  0
             return false;
 292  
         }
 293  
     }
 294  
 
 295  
     /**
 296  
      * Consumes a comment if any.
 297  
      *
 298  
      * @param br input NQuads stream reader.
 299  
      * @return <code>true</code> if comment has been consumed, false otherwise.
 300  
      * @throws IOException
 301  
      */
 302  
     private boolean consumeComment(BufferedReader br) throws IOException {
 303  
         char c;
 304  0
         mark(br);
 305  0
         c = readChar(br);
 306  0
         if (c == '#') {
 307  0
             mark(br);
 308  0
             while (readChar(br) != '\n');
 309  0
             mark(br);
 310  0
             return true;
 311  
         } else {
 312  0
             reset(br);
 313  0
             return false;
 314  
         }
 315  
     }
 316  
 
 317  
     /**
 318  
      * Notifies the parsed statement to the {@link RDFHandler}.
 319  
      *
 320  
      * @param sub
 321  
      * @param pred
 322  
      * @param obj
 323  
      * @param graph
 324  
      * @throws RDFParseException
 325  
      * @throws RDFHandlerException
 326  
      */
 327  
     private void notifyStatement(Resource sub, URI pred, Value obj, URI graph)
 328  
     throws RDFParseException, RDFHandlerException {
 329  0
         Statement statement = createStatement(sub, pred, obj, graph);
 330  0
         if (rdfHandler != null) {
 331  
             try {
 332  0
                 rdfHandler.handleStatement(statement);
 333  0
             } catch (RDFHandlerException rdfhe) {
 334  0
                 reportFatalError(rdfhe);
 335  0
                 throw rdfhe;
 336  0
             }
 337  
         }
 338  0
     }
 339  
 
 340  
     /**
 341  
      * Consumes spaces until a non space char is detected.
 342  
      *
 343  
      * @param br input stream reader from which consume spaces.
 344  
      * @throws IOException
 345  
      */
 346  
     private void consumeSpaces(BufferedReader br) throws IOException {
 347  
         char c;
 348  
         while(true) {
 349  0
             mark(br);
 350  0
             c = readChar(br);
 351  0
             if(c == ' ' || c == '\r' || c == '\f' || c == '\t') {
 352  0
                 mark(br);
 353  
             } else {
 354  
                 break;
 355  
             }
 356  
         }
 357  0
         reset(br);
 358  0
     }
 359  
 
 360  
     /**
 361  
      * Consumes the dot at the end of NQuads line.
 362  
      *
 363  
      * @param br
 364  
      * @throws IOException
 365  
      */
 366  
     private void parseDot(BufferedReader br) throws IOException {
 367  0
         assertChar(br, '.');
 368  0
     }
 369  
 
 370  
     /**
 371  
      * Parses a URI enclosed within &lt; and &gt; brackets.
 372  
      * @param br
 373  
      * @return the parsed URI.
 374  
      * @throws IOException
 375  
      * @throws RDFParseException
 376  
      */
 377  
     private URI parseURI(BufferedReader br) throws IOException, RDFParseException {
 378  0
         assertChar(br, '<');
 379  
 
 380  0
         StringBuilder sb = new StringBuilder();
 381  
         char c;
 382  
         while(true) {
 383  0
             c = readChar(br);
 384  0
             if(c != '>') {
 385  0
                 sb.append(c);
 386  
             } else {
 387  
                 break;
 388  
             }
 389  
         }
 390  0
         mark(br);
 391  
 
 392  
         try {
 393  
             // TODO - LOW: used to unescape \\uXXXX unicode chars. Unify with #printEscaped().
 394  0
             String uriStr = NTriplesUtil.unescapeString( sb.toString() );
 395  
             URI uri;
 396  0
             if(uriStr.charAt(0) == '#') {
 397  0
                 uri = resolveURI(uriStr);
 398  
             } else {
 399  0
                 uri = createURI(uriStr);
 400  
             }
 401  0
             return uri;
 402  0
         } catch (RDFParseException rdfpe) {
 403  0
             reportFatalError(rdfpe, row, col);
 404  0
             throw rdfpe;
 405  
         }
 406  
     }
 407  
 
 408  
     /**
 409  
      * Parses a BNode.
 410  
      *
 411  
      * @param br the buffered input stream.
 412  
      * @return the generated bnode.
 413  
      * @throws IOException
 414  
      * @throws RDFParseException
 415  
      */
 416  
     private BNode parseBNode(BufferedReader br) throws IOException, RDFParseException {
 417  0
         assertChar(br, '_');
 418  0
         assertChar(br, ':');
 419  
 
 420  
         char c;
 421  0
         StringBuilder sb = new StringBuilder();
 422  
         while(true) {
 423  0
             c = readChar(br);
 424  0
             if(c != ' ' && c != '<') {
 425  0
                 sb.append(c);
 426  0
                 mark(br);
 427  
             } else {
 428  
                 break;
 429  
             }
 430  
         }
 431  0
         reset(br);
 432  
 
 433  
         try {
 434  0
             return createBNode( sb.toString() );
 435  0
         } catch (RDFParseException rdfpe) {
 436  0
             reportFatalError(rdfpe, row, col);
 437  0
             throw rdfpe;
 438  
         }
 439  
     }
 440  
 
 441  
     /**
 442  
      * Parses a literal attribute that can be either the language or the data type.
 443  
      *
 444  
      * @param br
 445  
      * @return the literal attribute.
 446  
      * @throws IOException
 447  
      */
 448  
     private LiteralAttribute parseLiteralAttribute(BufferedReader br) throws IOException {
 449  0
         char c = readChar(br);
 450  0
         if(c != '^' && c != '@') {
 451  0
             reset(br);
 452  0
             return null;
 453  
         }
 454  
 
 455  0
         boolean isLang = true;
 456  0
         if(c == '^') {
 457  0
             isLang = false;
 458  0
             assertChar(br, '^');
 459  
         }
 460  
 
 461  
         // Consuming eventual open URI.
 462  0
         mark(br);
 463  0
         c = readChar(br);
 464  0
         if(c != '<') {
 465  0
             reset(br);
 466  
         }
 467  
 
 468  0
         StringBuilder sb = new StringBuilder();
 469  
         while(true) {
 470  0
             c = readChar(br);
 471  0
             if(c == '>') {
 472  0
                 mark(br);
 473  0
                 continue;
 474  
             }
 475  0
             if(c != ' ' && c != '<') {
 476  0
                 mark(br);
 477  0
                 sb.append(c);
 478  
             } else {
 479  
                 break;
 480  
             }
 481  
         }
 482  0
         reset(br);
 483  0
         return new LiteralAttribute( isLang, sb.toString() );
 484  
     }
 485  
 
 486  
     /**
 487  
      * Validates and normalize the value of a literal on the basis of the datat ype handling policy and
 488  
      * the associated data type.
 489  
      *
 490  
      * @param value
 491  
      * @param datatype
 492  
      * @return the normalized data type. It depends on the data type handling policy and the specified data type.
 493  
      * @throws RDFParseException
 494  
      */
 495  
     private String validateAndNormalizeLiteral(String value, URI datatype) throws RDFParseException {
 496  0
         DatatypeHandling dh = datatypeHandling();
 497  0
         if(dh.equals( DatatypeHandling.IGNORE )) {
 498  0
             return value;
 499  
         }
 500  
 
 501  0
         if ( dh.equals(DatatypeHandling.VERIFY) ) {
 502  0
             if( ! XMLDatatypeUtil.isBuiltInDatatype(datatype)){
 503  0
                 return value;
 504  
             }
 505  0
             if( ! XMLDatatypeUtil.isValidValue(value, datatype) ) {
 506  0
                 throw new RDFParseException(
 507  
                         String.format("Illegal literal value '%s' with datatype %s", value, datatype.stringValue() ),
 508  
                         row, col
 509  
                 );
 510  
             }
 511  0
             return value;
 512  0
         } else if( dh.equals(DatatypeHandling.NORMALIZE) ) {
 513  0
             return XMLDatatypeUtil.normalize(value, datatype);
 514  
         } else {
 515  0
             throw new IllegalArgumentException( String.format("Unsupported datatype handling: %s", dh) );
 516  
         }
 517  
     }
 518  
 
 519  
     /**
 520  
      * Prints the escaped version of the given char c.
 521  
      *
 522  
      * @param c escaped char.
 523  
      * @param sb output string builder.
 524  
      */
 525  
     private void printEscaped(char c, StringBuilder sb) {
 526  0
         if(c == 'b') {
 527  0
             sb.append('\b');
 528  0
             return;
 529  
         }
 530  0
         if(c == 'f') {
 531  0
             sb.append('\f');
 532  0
             return;
 533  
         }
 534  0
         if(c == 'n') {
 535  0
             sb.append('\n');
 536  0
             return;
 537  
         }
 538  0
         if(c == 'r') {
 539  0
             sb.append('\r');
 540  0
             return;
 541  
         }
 542  0
         if(c == 't') {
 543  0
             sb.append('\t');
 544  0
             return;
 545  
         }
 546  0
     }
 547  
 
 548  
     /**
 549  
      * Parses a literal.
 550  
      *
 551  
      * @param br
 552  
      * @return the parsed literal.
 553  
      * @throws IOException
 554  
      * @throws RDFParseException
 555  
      */
 556  
     private Value parseLiteral(BufferedReader br) throws IOException, RDFParseException {
 557  0
         assertChar(br, '"');
 558  
 
 559  
         char c;
 560  0
         boolean escaped = false;
 561  0
         StringBuilder sb = new StringBuilder();
 562  
         while(true) {
 563  0
             c = readChar(br);
 564  0
             if( c == '\\' ) {
 565  0
                 if(escaped) {
 566  0
                     escaped = false;
 567  0
                     sb.append(c);
 568  
                 } else {
 569  0
                     escaped = true;
 570  
                 }
 571  0
                 continue;
 572  0
             } else if(c == '"' && !escaped) {
 573  0
                 break;
 574  
             }
 575  0
             if(escaped) {
 576  0
                 if(c == 'u') {
 577  0
                     char unicodeChar = readUnicode(br);
 578  0
                     sb.append(unicodeChar);
 579  0
                 } else {
 580  0
                     printEscaped(c, sb);
 581  
                 }
 582  0
                 escaped = false;
 583  
             } else {
 584  0
                 sb.append(c);
 585  
             }
 586  
         }
 587  0
         mark(br);
 588  
 
 589  0
         LiteralAttribute lt = parseLiteralAttribute(br);
 590  
 
 591  0
         final String value = sb.toString();
 592  0
         if(lt == null) {
 593  0
             return createLiteral(value, null, null);
 594  0
         }else if(lt.isLang) {
 595  0
             return createLiteral(
 596  
                     value,
 597  
                     lt.value,
 598  
                     null
 599  
             );
 600  
         } else {
 601  0
             URI literalType = null;
 602  
             try {
 603  0
                 literalType = new URIImpl(lt.value);
 604  0
             } catch (Exception e) {
 605  0
                 reportError( String.format("Error while parsing literal type '%s'", lt.value), row, col );
 606  0
             }
 607  0
             return createLiteral(
 608  
                     validateAndNormalizeLiteral(value, literalType),
 609  
                     null,
 610  
                     literalType
 611  
             );
 612  
         }
 613  
     }
 614  
 
 615  
     /**
 616  
      * Parses the subject sequence.
 617  
      *
 618  
      * @param br
 619  
      * @return the corresponding URI object.
 620  
      * @throws IOException
 621  
      * @throws RDFParseException
 622  
      */
 623  
     private Resource parseSubject(BufferedReader br) throws IOException, RDFParseException {
 624  0
         mark(br);
 625  0
         char c = readChar(br);
 626  0
         reset(br);
 627  0
         if( c == '<' ) {
 628  0
             return parseURI(br);
 629  
         } else {
 630  0
             return parseBNode(br);
 631  
         }
 632  
     }
 633  
 
 634  
     /**
 635  
      * Parses the predicate URI.
 636  
      *
 637  
      * @param br
 638  
      * @return the corresponding URI object.
 639  
      * @throws IOException
 640  
      * @throws RDFParseException
 641  
      */
 642  
     private URI parsePredicate(BufferedReader br) throws IOException, RDFParseException {
 643  0
         return parseURI(br);
 644  
     }
 645  
 
 646  
     /**
 647  
      * Parses the the object sequence.
 648  
      *
 649  
      * @param br
 650  
      * @return the corresponding URI object.
 651  
      * @throws IOException
 652  
      * @throws RDFParseException
 653  
      */
 654  
     private Value parseObject(BufferedReader br) throws IOException, RDFParseException {
 655  0
         mark(br);
 656  0
         char c = readChar(br);
 657  0
         reset(br);
 658  0
         if( c == '<' ) {
 659  0
             return parseURI(br);
 660  0
         } else if( c == '_') {
 661  0
             return parseBNode(br);
 662  
         } else {
 663  0
             return parseLiteral(br);
 664  
         }
 665  
     }
 666  
 
 667  
     /**
 668  
      * Represents a literal with its attribute value that can be either a language or a data type.
 669  
      */
 670  
     class LiteralAttribute {
 671  
         final boolean isLang;
 672  
         final String value;
 673  
 
 674  0
         LiteralAttribute(boolean lang, String value) {
 675  0
             isLang = lang;
 676  0
             this.value = value;
 677  0
         }
 678  
     }
 679  
 
 680  
     /**
 681  
      * Parses the graph URI.
 682  
      *
 683  
      * @param br
 684  
      * @return the corresponding URI object.
 685  
      * @throws IOException
 686  
      * @throws RDFParseException
 687  
      */
 688  
     private URI parseGraph(BufferedReader br) throws IOException, RDFParseException {
 689  0
         return parseURI(br);
 690  
     }
 691  
 
 692  
     /**
 693  
      * Defines the End Of Stream exception.
 694  
      */
 695  0
     class EOS extends IOException {}
 696  
 
 697  
 }