Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 686   Methods: 29
NCLOC: 386   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
Concat.java 75% 80.6% 93.1% 80%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 
 55   
 package org.apache.tools.ant.taskdefs;
 56   
 
 57   
 import java.io.BufferedReader;
 58   
 import java.io.BufferedWriter;
 59   
 import java.io.File;
 60   
 import java.io.FileInputStream;
 61   
 import java.io.FileOutputStream;
 62   
 import java.io.FileReader;
 63   
 import java.io.FileWriter;
 64   
 import java.io.IOException;
 65   
 import java.io.InputStream;
 66   
 import java.io.InputStreamReader;
 67   
 import java.io.OutputStream;
 68   
 import java.io.OutputStreamWriter;
 69   
 import java.io.PrintWriter;
 70   
 import java.io.Reader;
 71   
 import java.io.StringReader;
 72   
 import java.io.Writer;
 73   
 import java.util.Enumeration;
 74   
 import java.util.Vector;
 75   
 import org.apache.tools.ant.BuildException;
 76   
 import org.apache.tools.ant.DirectoryScanner;
 77   
 import org.apache.tools.ant.Project;
 78   
 import org.apache.tools.ant.ProjectHelper;
 79   
 import org.apache.tools.ant.Task;
 80   
 import org.apache.tools.ant.filters.util.ChainReaderHelper;
 81   
 import org.apache.tools.ant.types.FileList;
 82   
 import org.apache.tools.ant.types.FileSet;
 83   
 import org.apache.tools.ant.types.FilterChain;
 84   
 import org.apache.tools.ant.types.Path;
 85   
 import org.apache.tools.ant.util.FileUtils;
 86   
 import org.apache.tools.ant.util.StringUtils; // 1.1
 87   
 
 88   
 /**
 89   
  * This class contains the 'concat' task, used to concatenate a series
 90   
  * of files into a single stream. The destination of this stream may
 91   
  * be the system console, or a file. The following is a sample
 92   
  * invocation:
 93   
  *
 94   
  * <pre>
 95   
  * &lt;concat destfile=&quot;${build.dir}/index.xml&quot;
 96   
  *   append=&quot;false&quot;&gt;
 97   
  *
 98   
  *   &lt;fileset dir=&quot;${xml.root.dir}&quot;
 99   
  *     includes=&quot;*.xml&quot; /&gt;
 100   
  *
 101   
  * &lt;/concat&gt;
 102   
  * </pre>
 103   
  *
 104   
  * @author <a href="mailto:derek@activate.net">Derek Slager</a>
 105   
  * @author Peter Reilly
 106   
  */
 107   
 public class Concat extends Task {
 108   
 
 109   
     // Attributes.
 110   
 
 111   
     /**
 112   
      * The destination of the stream. If <code>null</code>, the system
 113   
      * console is used.
 114   
      */
 115   
     private File destinationFile = null;
 116   
 
 117   
     /**
 118   
      * Whether or not the stream should be appended if the destination file 
 119   
      * exists.
 120   
      * Defaults to <code>false</code>.
 121   
      */
 122   
     private boolean append = false;
 123   
 
 124   
     /**
 125   
      * Stores the input file encoding.
 126   
      */
 127   
     private String encoding = null;
 128   
 
 129   
     // Child elements.
 130   
 
 131   
     /**
 132   
      * This buffer stores the text within the 'concat' element.
 133   
      */
 134   
     private StringBuffer textBuffer;
 135   
 
 136   
     /**
 137   
      * Stores a collection of file sets and/or file lists, used to
 138   
      * select multiple files for concatenation.
 139   
      */
 140   
     private Vector sources = new Vector();
 141   
 
 142   
     /** for filtering the concatenated */
 143   
     private Vector        filterChains = null;
 144   
     /** ignore dates on input files */
 145   
     private boolean       forceOverwrite = true;
 146   
     /** String to place at the start of the concatented stream */
 147   
     private TextElement   footer;
 148   
     /** String to place at the end of the concatented stream */
 149   
     private TextElement   header;
 150   
     private Vector        sourceFiles = new Vector();
 151   
 
 152   
     /** 1.1 utilities and copy utilities */
 153   
     private static FileUtils     fileUtils = FileUtils.newFileUtils();
 154   
 
 155   
     // Constructors.
 156   
 
 157   
     /**
 158   
      * Public, no-argument constructor. Required by Ant.
 159   
      */
 160  27
     public Concat() {}
 161   
 
 162   
     // Attribute setters.
 163   
 
 164   
     /**
 165   
      * Sets the destination file, or uses the console if not specified.
 166   
      */
 167  17
     public void setDestfile(File destinationFile) {
 168  17
         this.destinationFile = destinationFile;
 169   
     }
 170   
 
 171   
     /**
 172   
      * Sets the behavior when the destination file exists. If set to
 173   
      * <code>true</code> the stream data will be appended to the
 174   
      * existing file, otherwise the existing file will be
 175   
      * overwritten. Defaults to <code>false</code>.
 176   
      */
 177  2
     public void setAppend(boolean append) {
 178  2
         this.append = append;
 179   
     }
 180   
 
 181   
     /**
 182   
      * Sets the character encoding
 183   
      */
 184  1
     public void setEncoding(String encoding) {
 185  1
         this.encoding = encoding;
 186   
     }
 187   
 
 188   
     /**
 189   
      * Force overwrite existing destination file
 190   
      * @since Ant 1.6
 191   
      */
 192  1
     public void setForce(boolean force) {
 193  1
         this.forceOverwrite = force;
 194   
     }
 195   
 
 196   
     // Nested element creators.
 197   
 
 198   
     /**
 199   
      * Path of files to concatenate.
 200   
      * @since Ant 1.6
 201   
      */
 202  19
      public Path createPath() {
 203  19
         Path path = new Path(getProject());
 204  19
         sources.addElement(path);
 205  19
         return path;
 206   
     }
 207   
 
 208   
     /**
 209   
      * Set of files to concatenate.
 210   
      */
 211  4
     public void addFileset(FileSet set) {
 212  4
         sources.addElement(set);
 213   
     }
 214   
 
 215   
     /**
 216   
      * List of files to concatenate.
 217   
      */
 218  2
     public void addFilelist(FileList list) {
 219  2
         sources.addElement(list);
 220   
     }
 221   
 
 222   
     /**
 223   
      * Adds a FilterChain.
 224   
      * @since Ant 1.6
 225   
      */
 226  3
     public void addFilterChain(FilterChain filterChain) {
 227  3
         if (filterChains == null)
 228  3
             filterChains = new Vector();
 229  3
         filterChains.addElement(filterChain);
 230   
     }
 231   
 
 232   
     /**
 233   
      * This method adds text which appears in the 'concat' element.
 234   
      */
 235  27
     public void addText(String text) {
 236  27
         if (textBuffer == null) {
 237   
             // Initialize to the size of the first text fragment, with
 238   
             // the hopes that it's the only one.
 239  27
             textBuffer = new StringBuffer(text.length());
 240   
         }
 241   
 
 242   
         // Append the fragment -- we defer property replacement until
 243   
         // later just in case we get a partial property in a fragment.
 244  27
         textBuffer.append(text);
 245   
     }
 246   
 
 247   
     /**
 248   
      * Add a header to the concatenated output
 249   
      * @since Ant 1.6
 250   
      */
 251  2
     public void addHeader(TextElement el) {
 252  2
         this.header = el;
 253   
     }
 254   
 
 255   
     /**
 256   
      * Add a footer to the concatenated output
 257   
      * @since Ant 1.6
 258   
      */
 259  1
     public void addFooter(TextElement el) {
 260  1
         this.footer = el;
 261   
     }
 262   
 
 263   
     /**
 264   
      * This method performs the concatenation.
 265   
      */
 266  27
     public void execute() 
 267   
         throws BuildException {
 268   
 
 269   
         // treat empty nested text as no text
 270  27
         sanitizeText();
 271   
 
 272   
         // Sanity check our inputs.
 273  27
         if (sources.size() == 0 && textBuffer == null) {
 274   
             // Nothing to concatenate!
 275  1
             throw new BuildException("At least one file " + 
 276   
                                      "must be provided, or " + 
 277   
                                      "some text.");
 278   
         }
 279   
 
 280   
         // If using filesets, disallow inline text. This is similar to
 281   
         // using GNU 'cat' with file arguments -- stdin is simply
 282   
         // ignored.
 283  26
         if (sources.size() > 0 && textBuffer != null) {
 284  0
             throw new BuildException("Cannot include inline text " + 
 285   
                                      "when using filesets.");
 286   
         }
 287   
 
 288   
         // Iterate thru the sources - paths, filesets and filelists
 289  26
         for (Enumeration e = sources.elements(); e.hasMoreElements();) {
 290  25
             Object o = e.nextElement();
 291  25
             if (o instanceof Path) {
 292  19
                 Path path = (Path) o;
 293  19
                 checkAddFiles(null, path.list());
 294   
 
 295  6
             } else if (o instanceof FileSet) {
 296  4
                 FileSet fileSet = (FileSet) o;
 297  4
                 DirectoryScanner scanner =
 298   
                     fileSet.getDirectoryScanner(getProject());
 299  4
                 checkAddFiles(fileSet.getDir(getProject()),
 300   
                               scanner.getIncludedFiles());
 301   
 
 302  2
             } else if (o instanceof FileList) {
 303  2
                 FileList fileList = (FileList) o;
 304  2
                 checkAddFiles(fileList.getDir(getProject()),
 305   
                               fileList.getFiles(getProject()));
 306   
             }
 307   
         }
 308   
 
 309   
         // check if the files are outofdate
 310  25
         if (destinationFile != null && !forceOverwrite
 311   
             && (sourceFiles.size() > 0) && destinationFile.exists()) {
 312  1
             boolean outofdate = false;
 313  1
             for (int i = 0; i < sourceFiles.size(); ++i) {
 314  1
                 File file = (File) sourceFiles.elementAt(i);
 315  1
                 if (file.lastModified() > destinationFile.lastModified()) {
 316  0
                     outofdate = true;
 317  0
                     break;
 318   
                 }
 319   
             }
 320  1
             if (!outofdate) {
 321  1
                 log(destinationFile + " is up-to-date.", Project.MSG_VERBOSE);
 322  1
                 return; // no need to do anything
 323   
             }
 324   
         }
 325   
 
 326   
         // Do nothing if all the sources are not present
 327   
         // And textBuffer is null
 328  24
         if (textBuffer == null && sourceFiles.size() == 0 
 329   
             && header == null && footer == null) {
 330  2
             log("No existing files and no nested text, doing nothing", 
 331   
                 Project.MSG_INFO);
 332  2
             return;
 333   
         }
 334   
 
 335  22
         cat();
 336   
     }
 337   
 
 338   
     /**
 339   
      * Reset state to default.
 340   
      */
 341  0
     public void reset() {
 342  0
         append = false;
 343  0
         forceOverwrite = true;
 344  0
         destinationFile = null;
 345  0
         encoding = null;
 346  0
         sources.removeAllElements();
 347  0
         sourceFiles.removeAllElements();
 348  0
         filterChains = null;
 349  0
         footer = null;
 350  0
         header = null;
 351   
     }
 352   
 
 353  25
     private void checkAddFiles(File base, String[] filenames) {
 354  25
         for (int i = 0; i < filenames.length; ++i) {
 355  27
             File file = new File(base, filenames[i]);
 356  27
             if (!file.exists()) {
 357  1
                 log("File " + file + " does not exist.", Project.MSG_ERR);
 358  1
                 continue;
 359   
             }
 360  26
             if (destinationFile != null 
 361   
                 && fileUtils.fileNameEquals(destinationFile, file)) {
 362  1
                 throw new BuildException("Input file \"" 
 363   
                                          + file + "\" "
 364   
                                          + "is the same as the output file.");
 365   
             }
 366  25
             sourceFiles.addElement(file);
 367   
         }
 368   
     }
 369   
     
 370   
     /** perform the concatenation */
 371  22
     private void cat() {
 372  22
         OutputStream os = null;
 373  22
         Reader       reader = null;
 374  22
         char[]       buffer = new char[8192];
 375   
 
 376  22
         try {
 377   
 
 378  22
             if (destinationFile == null) {
 379   
                 // Log using WARN so it displays in 'quiet' mode.
 380  8
                 os = new LogOutputStream(this, Project.MSG_WARN);
 381   
             } else {
 382   
                 // ensure that the parent dir of dest file exists
 383  14
                 File parent = fileUtils.getParentFile(destinationFile);
 384  14
                 if (!parent.exists()) {
 385  0
                     parent.mkdirs();
 386   
                 }
 387   
 
 388  14
                 os = new FileOutputStream(destinationFile.getAbsolutePath(),
 389   
                                           append);
 390   
             }
 391   
 
 392  21
             PrintWriter writer = null;
 393  21
             if (encoding == null) {
 394  20
                 writer = new PrintWriter(
 395   
                     new BufferedWriter(
 396   
                         new OutputStreamWriter(os)));
 397   
             } else {
 398  1
                 writer = new PrintWriter(
 399   
                     new BufferedWriter(
 400   
                         new OutputStreamWriter(os, encoding)));
 401   
             }
 402   
 
 403   
 
 404  21
             if (header != null) {
 405  2
                 if (header.getFiltering()) {
 406  1
                     concatenate(
 407   
                         buffer, writer, new StringReader(header.getValue()));
 408   
                 } else {
 409  1
                     writer.print(header.getValue());
 410   
                 }
 411   
             }
 412   
 
 413  21
             if (textBuffer != null) {
 414  11
                 reader = new StringReader(
 415   
                     getProject().replaceProperties(textBuffer.substring(0)));
 416   
             } else {
 417  10
                 reader =  new MultiReader();
 418   
             }
 419   
             
 420  21
             concatenate(buffer, writer, reader);
 421   
 
 422  21
             if (footer != null) {
 423  1
                 if (footer.getFiltering()) {
 424  0
                     concatenate(
 425   
                         buffer, writer, new StringReader(footer.getValue()));
 426   
                 } else {
 427  1
                     writer.print(footer.getValue());
 428   
                 }
 429   
             }
 430   
 
 431  21
             writer.flush();
 432  21
             os.flush();
 433   
 
 434   
         } catch (IOException ioex) {
 435  1
             throw new BuildException("Error while concatenating: "
 436   
                                      + ioex.getMessage(), ioex);
 437   
         } finally {
 438  22
             if (reader != null) {
 439  21
                 try {reader.close();} catch (IOException ignore) {}
 440   
             }
 441  22
             if (os != null) {
 442  21
                 try {os.close();} catch (IOException ignore) {}
 443   
             }
 444   
         }
 445   
     }
 446   
 
 447   
 
 448   
     /** Concatenate a single reader to the writer using buffer */
 449  22
     private void concatenate(char[] buffer, Writer writer, Reader in)
 450   
         throws IOException {
 451  22
         if (filterChains != null) {
 452  3
             ChainReaderHelper helper = new ChainReaderHelper();
 453  3
             helper.setBufferSize(8192);
 454  3
             helper.setPrimaryReader(in);
 455  3
             helper.setFilterChains(filterChains);
 456  3
             helper.setProject(getProject());
 457  3
             in = new BufferedReader(helper.getAssembledReader());
 458   
         }
 459   
         
 460  22
         while (true) {
 461  44
             int nRead = in.read(buffer, 0, buffer.length);
 462  44
             if (nRead == -1) {
 463  22
                 break;
 464   
             }
 465  22
             writer.write(buffer, 0, nRead);
 466   
         }
 467   
         
 468  22
         writer.flush();
 469   
     }
 470   
 
 471   
     /**
 472   
      * Treat empty nested text as no text.
 473   
      *
 474   
      * <p>Depending on the XML parser, addText may have been called
 475   
      * for &quot;ignorable whitespace&quot; as well.</p>
 476   
      */
 477  27
     private void sanitizeText() {
 478  27
         if (textBuffer != null) {
 479  27
             if (textBuffer.substring(0).trim().length() == 0) {
 480  15
                 textBuffer = null;
 481   
             }
 482   
         }
 483   
     }
 484   
 
 485   
     /**
 486   
      * sub element points to a file or contains text
 487   
      */
 488   
     public static class TextElement {
 489   
         private String   value;
 490   
         private boolean  trimLeading = false;
 491   
         private boolean  trim = false;
 492   
         private boolean  filtering = true;
 493   
 
 494   
         /**
 495   
          * whether to filter the text in this element
 496   
          * or not.
 497   
          *
 498   
          * @param filtering true if the text should be filtered.
 499   
          *                  the default value is true.
 500   
          */
 501  2
         public void setFiltering(boolean filtering) {
 502  2
             this.filtering = filtering;
 503   
         }
 504   
         
 505   
         /** return the filtering attribute */
 506  3
         private boolean getFiltering() {
 507  3
             return filtering;
 508   
         }
 509   
         
 510   
         /**
 511   
          * set the text using a file
 512   
          * @param file the file to use
 513   
          * @throws BuildException if the file does not exist, or cannot be
 514   
          *                        read
 515   
          */
 516  1
         public void setFile(File file) {
 517   
             // non-existing files are not allowed
 518  1
             if (!file.exists()) {
 519  0
                 throw new BuildException("File " + file + " does not exist.");
 520   
             }
 521   
 
 522  1
             BufferedReader reader = null;
 523  1
             try {
 524  1
                 reader = new BufferedReader(new FileReader(file));
 525  1
                 value = fileUtils.readFully(reader);
 526   
             } catch (IOException ex) {
 527  0
                 throw new BuildException(ex);
 528   
             } finally {
 529  1
                 if (reader != null) {
 530  1
                     try {reader.close();} catch (Throwable t) {}
 531   
                 }
 532   
             }
 533   
         }
 534   
 
 535   
         /**
 536   
          * set the text using inline
 537   
          */
 538  2
         public void addText(String value) {
 539  2
             if (value.trim().length() == 0) {
 540  0
                 return;
 541   
             }
 542  2
             this.value = value;
 543   
         }
 544   
 
 545   
         /**
 546   
          * s:^\s*:: on each line of input
 547   
          * @param strip if true do the trim
 548   
          */
 549  0
         public void setTrimLeading(boolean strip) {
 550  0
             this.trimLeading = strip;
 551   
         }
 552   
 
 553   
         /**
 554   
          * whether to call text.trim()
 555   
          */
 556  1
         public void setTrim(boolean trim) {
 557  1
             this.trim = trim;
 558   
         }
 559   
 
 560   
         /**
 561   
          * return the text, after possible trimming
 562   
          */
 563  3
         public String getValue() {
 564  3
             if (value == null) {
 565  0
                 value = "";
 566   
             }
 567  3
             if (value.trim().length() == 0) {
 568  0
                 value = "";
 569   
             }
 570  3
             if (trimLeading) {
 571  0
                 char[] current = value.toCharArray();
 572  0
                 StringBuffer b = new StringBuffer(current.length);
 573  0
                 boolean startOfLine = true;
 574  0
                 int pos = 0;
 575  0
                 while (pos < current.length) {
 576  0
                     char ch = current[pos++];
 577  0
                     if (startOfLine) {
 578  0
                         if (ch == ' ' || ch == '\t') {
 579  0
                             continue;
 580   
                         }
 581  0
                         startOfLine = false;
 582   
                     }
 583  0
                     b.append(ch);
 584  0
                     if (ch == '\n' || ch == '\r') {
 585  0
                         startOfLine = true;
 586   
                     }
 587   
                 }
 588  0
                 value = b.toString();
 589   
             }
 590  3
             if (trim) {
 591  1
                 value = value.trim();
 592   
             }
 593  3
             return value;
 594   
         }
 595   
     }
 596   
 
 597   
     /**
 598   
      * This class reads from each of the source files in turn.
 599   
      * The concatentated result can then be filtered as
 600   
      * a single stream.
 601   
      */
 602   
     private class MultiReader extends Reader {
 603   
         private int pos = 0;
 604   
         private Reader reader = null;
 605   
 
 606  230
         private Reader getReader() throws IOException {
 607  230
             if (reader == null) {
 608  24
                 if (encoding == null) {
 609  22
                     reader = new BufferedReader(
 610   
                         new FileReader((File) sourceFiles.elementAt(pos)));
 611   
                 } else {
 612   
                     // invoke the zoo of io readers
 613  2
                     reader = new BufferedReader(
 614   
                         new InputStreamReader(
 615   
                             new FileInputStream(
 616   
                                 (File) sourceFiles.elementAt(pos)),
 617   
                             encoding));
 618   
                 }                
 619   
             }
 620  230
             return reader;
 621   
         }
 622   
 
 623   
         /**
 624   
          * Read a character from the current reader object. Advance
 625   
          * to the next if the reader is finished.
 626   
          * @return the character read, -1 for EOF on the last reader.
 627   
          * @exception IOException - possiblly thrown by the read for a reader
 628   
          *            object.
 629   
          */
 630  197
         public int read() throws IOException {
 631  197
             while (pos < sourceFiles.size()) {
 632  206
                 int ch = getReader().read();
 633  206
                 if (ch == -1) {
 634  12
                     reader.close();
 635  12
                     reader = null;
 636   
                 } else {
 637  194
                     return ch;
 638   
                 }
 639  12
                 pos++; 
 640   
             }
 641  3
             return -1;
 642   
         }
 643   
 
 644   
         /**
 645   
          * Read into the buffer <code>cbuf</code>.
 646   
          * @param cbuf The array to be read into.
 647   
          * @param off The offset.
 648   
          * @param len The length to read.
 649   
          * @exception IOException - possiblely thrown by the reads to the
 650   
          *            reader objects.
 651   
          */
 652  16
         public int read(char cbuf[], int off, int len)
 653   
             throws IOException {
 654  16
             int amountRead = 0;
 655  16
             int iOff = off;
 656  16
             while (pos < sourceFiles.size()) {
 657  24
                 int nRead = getReader().read(cbuf, off, len);
 658  24
                 if (nRead == -1 || nRead == 0) {
 659  12
                     reader.close();
 660  12
                     reader = null;
 661  12
                     pos++;
 662   
                 } else {
 663  12
                     len -= nRead;
 664  12
                     off += nRead;
 665  12
                     amountRead += nRead;
 666  12
                     if (len == 0) {
 667  0
                         return amountRead;
 668   
                     }
 669   
                 }
 670   
             }
 671  16
             if (amountRead == 0) {
 672  8
                 return -1;
 673   
             } else {
 674  8
                 return amountRead;
 675   
             }
 676   
         }
 677   
 
 678  10
         public void close() throws IOException {
 679  10
             if (reader != null) {
 680  0
                 reader.close();
 681   
             }
 682   
         }
 683   
     }
 684   
  }
 685   
 
 686