code: /*
   * This file is part of the Joshua Machine Translation System.
   * 
   * Joshua is free software; you can redistribute it and/or modify it under the terms of the GNU
   * Lesser General Public License as published by the Free Software Foundation; either version 2.1 of
   * the License, or (at your option) any later version.
   * 
   * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
   * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   * 
   * You should have received a copy of the GNU Lesser General Public License along with this library;
   * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   * 02111-1307 USA
   */
  package edu.jhu.thrax.util.io;
  
  import java.io.BufferedReader;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.nio.charset.Charset;
  import java.util.Iterator;
  import java.util.NoSuchElementException;
  import java.util.zip.GZIPInputStream;
  
  /**
   * This class provides an Iterator interface to a BufferedReader. This covers the most common
   * use-cases for reading from files without ugly code to check whether we got a line or not.
   * 
   * @author wren ng thornton <wren@users.sourceforge.net>
   * @version $LastChangedDate: 2009-03-26 15:06:57 -0400 (Thu, 26 Mar 2009) $
   */
  public class LineReader implements Reader<String> {
  
    /*
     * Note: charset name is case-agnostic "UTF-8" is the canonical name "UTF8", "unicode-1-1-utf-8"
     * are aliases Java doesn't distinguish utf8 vs UTF-8 like Perl does
     */
    private static final Charset FILE_ENCODING = Charset.forName("UTF-8");
  
    private BufferedReader reader;
    private String buffer;
    private IOException error;
  
  
    // ===============================================================
    // Constructors and destructors
    // ===============================================================
  
    /**
     * Opens a file for iterating line by line. If the file name ends in ".gz" then we automatically
     * open it with GZIP. File encoding is assumed to be UTF-8.
     * 
     * @param filename the file to be opened
     */
    public LineReader(String filename) throws IOException {
      this(LineReader.getInputStream(filename));
    }
  
  
    /**
     * Wraps an InputStream for iterating line by line. Stream encoding is assumed to be UTF-8.
     */
    public LineReader(InputStream in) {
      this.reader = new BufferedReader(new InputStreamReader(in, FILE_ENCODING));
    }
  
  
    /**
     * Uses a BufferedReader for iterating line by line.
     */
    public LineReader(BufferedReader reader) {
      this.reader = reader;
    }
  
  
    /**
     * Returns an InputStream for a filename, using Joshua's canonical means for interpreting that
     * name (e.g\ detecting gzipped files). This is used by the LineReader constructor that accepts a
     * String argument.
     * 
     * @deprecated This method is provided in order for {@link joshua.decoder.DecoderThread} to open
     *             files in the canonical way for handing off to
     *             {@link joshua.decoder.segment_file.SegmentFileParser}. The
     *             <code>SegmentFileParser</code> interface can't be made more liberal (e.g. to accept
     *             a {@link java.io.Reader}) because {@link javax.xml.parsers.SAXParser} can't parse
     *             that argument and no common {@link java.io.Reader} gives access to the underlying
     *             <code>InputStream</code>. This method is considered a hack which should be removed
     *             once a better solution presents itself.
     */
    @SuppressWarnings("resource")
    @Deprecated
    public static final InputStream getInputStream(String filename) throws IOException {
      FileInputStream fis = new FileInputStream(filename);
      return (filename.endsWith(".gz") ? new GZIPInputStream(fis) : fis);
    }
  
  
    /**
     * This method will close the file handle, and will raise any exceptions that occured during
     * iteration. The method is idempotent, and all calls after the first are no-ops (unless the
     * thread was interrupted or killed). For correctness, you <b>must</b> call this method before the
     * object falls out of scope.
     */
    public void close() throws IOException {
  
      this.buffer = null; // Just in case it's a large string
  
      if (null != this.reader) {
        try {
          // We assume the wrappers will percolate this down.
          this.reader.close();
  
        } catch (IOException e) {
          // We need to trash our cached error for idempotence.
          // Presumably the closing error is the more important
          // one to throw.
          this.error = null;
          throw e;
  
        } finally {
          this.reader = null;
        }
      }
  
      if (null != this.error) {
        IOException e = this.error;
        this.error = null;
        throw e;
      }
    }
  
  
    /**
     * We attempt to avoid leaking file descriptors if you fail to call close before the object falls
     * out of scope. However, the language spec makes <b>no guarantees</b> about timeliness of garbage
     * collection. It is a bug to rely on this method to release the resources. Also, the garbage
     * collector will discard any exceptions that have queued up, without notifying the application in
     * any way.
     * 
     * Having a finalizer means the JVM can't do "fast allocation" of LineReader objects (or
     * subclasses). This isn't too important due to disk latency, but may be worth noting.
     * 
     * @see <a
     *      href="http://java2go.blogspot.com/2007/09/javaone-2007-performance-tips-2-finish.html">Performance
     *      Tips</a>
     * @see <a
     *      href="http://www.javaworld.com/javaworld/jw-06-1998/jw-06-techniques.html?page=1">Techniques</a>
     */
    protected void finalize() throws Throwable {
      try {
        this.close();
      } catch (IOException e) {
        // Do nothing. The GC will discard the exception
        // anyways, but it may cause us to linger on the heap.
      } finally {
        super.finalize();
      }
    }
  
  
  
    // ===============================================================
    // Reader
    // ===============================================================
  
    // Copied from interface documentation.
    /** Determine if the reader is ready to read a line. */
    public boolean ready() throws IOException {
      return this.reader.ready();
    }
  
  
    /**
     * This method is like next() except that it throws the IOException directly. If there are no
     * lines to be read then null is returned.
     */
    public String readLine() throws IOException {
      if (this.hasNext()) {
        String line = this.buffer;
        this.buffer = null;
        return line;
  
      } else {
        if (null != this.error) {
          IOException e = this.error;
          this.error = null;
          throw e;
        }
        return null;
      }
    }
  
  
    // ===============================================================
    // Iterable -- because sometimes Java can be very stupid
    // ===============================================================
  
    /** Return self as an iterator. */
    public Iterator<String> iterator() {
      return this;
    }
  
  
    // ===============================================================
    // Iterator
    // ===============================================================
  
    // Copied from interface documentation.
    /**
     * Returns <code>true</code> if the iteration has more elements. (In other words, returns
     * <code>true</code> if <code>next</code> would return an element rather than throwing an
     * exception.)
     */
    public boolean hasNext() {
      if (null != this.buffer) {
        return true;
  
      } else if (null != this.error) {
        return false;
  
      } else {
        // We're not allowed to throw IOException from within Iterator
        try {
          this.buffer = this.reader.readLine();
        } catch (IOException e) {
          this.buffer = null;
          this.error = e;
          return false;
        }
        return (null != this.buffer);
      }
    }
  
  
    /**
     * Return the next line of the file. If an error is encountered, NoSuchElementException is thrown.
     * The actual IOException encountered will be thrown later, when the LineReader is closed. Also if
     * there is no line to be read then NoSuchElementException is thrown.
     */
    public String next() throws NoSuchElementException {
      if (this.hasNext()) {
        String line = this.buffer;
        this.buffer = null;
        return line;
      } else {
        throw new NoSuchElementException();
      }
    }
  
  
    /** Unsupported. */
    public void remove() throws UnsupportedOperationException {
      throw new UnsupportedOperationException();
    }
  
  
    /**
     * Iterates over all lines, ignoring their contents, and returns the count of lines. If some lines
     * have already been read, this will return the count of remaining lines. Because no lines will
     * remain after calling this method, we implicitly call close.
     * 
     * @return the number of lines read
     */
    public int countLines() throws IOException {
      int lines = 0;
  
      while (this.hasNext()) {
        this.next();
        lines++;
      }
      this.close();
  
      return lines;
    }
  
    // ===============================================================
    // Main
    // ===============================================================
  
    /** Example usage code. */
    public static void main(String[] args) {
      if (1 != args.length) {
        System.out.println("Usage: java LineReader filename");
        System.exit(1);
      }
  
      try {
  
        LineReader in = new LineReader(args[0]);
        try {
          for (String line : in) {
  
            System.out.println(line);
  
          }
        } finally {
          in.close();
        }
  
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  