code: /*
   *
   * Copyright (c) 2008, All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * Portions of the code may be copied from Google Collections
   * or Apache Commons projects.
   */
  
  package zemberek.core.io;
  
  import static com.google.common.base.Preconditions.checkNotNull;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedReader;
  import java.io.BufferedWriter;
  import java.io.ByteArrayOutputStream;
  import java.io.Closeable;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
  import java.io.PushbackInputStream;
  import java.io.StringWriter;
  import java.io.UnsupportedEncodingException;
  import java.nio.charset.Charset;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.List;
  
  /**
   * this class has IO operations
   */
  public class IOs {
  
    public static final String LINE_SEPARATOR;
    public static final int CHAR_BUFFER_SIZE = 1 << 20;
    private static final int BYTE_BUFFER_SIZE = 1 << 20;
    private static final byte[] bomBytes = new byte[]{(byte) 0xef, (byte) 0xbb, (byte) 0xbf};
  
    static {
      // avoid security issues
      StringWriter buf = new StringWriter(4);
      PrintWriter out = new PrintWriter(buf);
      out.println();
      LINE_SEPARATOR = buf.toString();
    }
  
    private IOs() {
    }
  
    /**
     * Reads a buffered reader as a single string. If there are multi lines, it appends a LINE
     * SEPARATOR.
     *
     * @param reader a reader
     * @return simple string representation of the entire reader. careful with the memory usage.
     * @throws NullPointerException: if reader is null.
     * @throws java.io.IOException if an IO error occurs.
     */
    public static String readAsString(BufferedReader reader) throws IOException {
      try {
        checkNotNull(reader, "reader cannot be null");
        StringBuilder sb = new StringBuilder();
        String s;
        while ((s = reader.readLine()) != null) {
          sb.append(s).append(LINE_SEPARATOR);
        }
        if (sb.length() >= LINE_SEPARATOR.length()) {
          sb.delete(sb.length() - LINE_SEPARATOR.length(), sb.length());
        }
        return sb.toString();
      } finally {
        closeSilently(reader);
      }
    }
  
    /**
     * Reads a reader as a list of strings. each item represents one line in the reader.
     *
     * @param reader a reader
     * @return List of strings.
     * @throws NullPointerException: if reader is null.
     * @throws java.io.IOException if an IO error occurs.
     */
    public static List<String> readAsStringList(BufferedReader reader)
        throws IOException {
      try {
        checkNotNull(reader, "reader cannot be null");
        String s;
        List<String> res = new ArrayList<>();
        while ((s = reader.readLine()) != null) {
          res.add(s);
        }
        return res;
      } finally {
        closeSilently(reader);
      }
    }
  
    /**
     * Reads a reader as a list of strings. each item represents one line in the reader which passes
     * the Filters.
     *
     * @param reader a reader
     * @param trim trims the lines if set
     * @param filters zero or more StringFilter. if there are more than one all filters needs to pass
     * the string.
     * @return List of strings.
     * @throws NullPointerException: if reader is null.
     * @throws java.io.IOException if an IO error occurs.
     */
    public static List<String> readAsStringList(BufferedReader reader, boolean trim,
        Filter... filters)
        throws IOException {
      try {
        checkNotNull(reader, "reader cannot be null");
        String s;
        List<String> res = new ArrayList<>();
        while ((s = reader.readLine()) != null) {
          if (trim) {
            s = s.trim();
          }
          if (filters.length == 0 || StringFilters.canPassAll(s, filters)) {
            res.add(s);
          }
        }
        return res;
      } finally {
        closeSilently(reader);
      }
    }
  
    /**
     * closes the <code>closeables</code> silently, meaning that if the Closeable is null, or if it
     * throws an exception during close() operation it only creates a system error output, does not
     * throw an exception. this is especially useful when you need to close one or more resources in
     * finally blocks. This method should only be called in finalize{} blocks or wherever it really
     * makes sense.
     *
     * @param closeables zero or more closeable.
     */
    public static void closeSilently(Closeable... closeables) {
      // if closeables is null, return silently.
      if (closeables == null) {
        return;
      }
  
      for (Closeable closeable : closeables) {
        try {
          if (closeable != null) {
            closeable.close();
          }
        } catch (IOException e) {
          System.err.println("IO Exception during closing stream (" + closeable + ")." + e);
        }
      }
    }
  
    /**
     * Returns a BufferedReader for the input stream.
     *
     * @param is input stream
     * @return a bufferedReader for the input stream.
     */
    public static BufferedReader getReader(InputStream is) {
      return new BufferedReader(new InputStreamReader(is), CHAR_BUFFER_SIZE);
    }
  
    /**
     * Returns a Buffered reader for the given input stream and charset. if charset is UTF-8 it
     * explicitly checks for UTF-8 BOM information.
     *
     * @param is input stream for the reader.
     * @param charset charset string, if null,empty or has only whitespace, system uses default
     * encoding.
     * @return BufferedReader
     * @throws java.io.IOException: if the given encoding is not supported, or an error occurs if
     * given charset is utf-8 and an IO error occurs during utf-8 BOM detection operation.
     */
    public static BufferedReader getReader(InputStream is, String charset) throws IOException {
      checkNotNull(is, "input stream cannot be null");
      if (!Strings.hasText(charset)) {
        return getReader(is);
      }
      if (charset.trim().equalsIgnoreCase("utf-8")) {
        return new BufferedReader(new InputStreamReader(forceUTF8(is), "utf-8"), CHAR_BUFFER_SIZE);
      } else {
        return new BufferedReader(new InputStreamReader(is, charset), CHAR_BUFFER_SIZE);
      }
  
    }
  
    /**
     * returns a BufferedWriter for the output stream.
     *
     * @param os output stream
     * @return a bufferedReader for the output stream.
     */
    public static BufferedWriter getBufferedWriter(OutputStream os, String encoding) {
      try {
        return new BufferedWriter(new OutputStreamWriter(os, encoding), CHAR_BUFFER_SIZE);
      } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
      }
      return null;
    }
  
    /**
     * returns a PrintWriter for the output stream.
     *
     * @param os output stream
     * @return a bufferedReader for the output stream.
     */
    public static PrintWriter getPrintWriter(OutputStream os) {
      return new PrintWriter(new BufferedWriter(new OutputStreamWriter(os), CHAR_BUFFER_SIZE));
    }
  
    /**
     * returns a BufferedWriter for the output stream.
     *
     * @param os output stream
     * @param charset encoding string.
     * @return a bufferedReader for the output stream.
     * @throws java.io.UnsupportedEncodingException if encoding is not supported.
     */
    public static BufferedWriter getWriter(OutputStream os, String charset)
        throws UnsupportedEncodingException {
      return new BufferedWriter(new OutputStreamWriter(os, charset), CHAR_BUFFER_SIZE);
  
    }
  
    /**
     * returns a PrintWriter for the output stream.
     *
     * @param os output stream
     * @param charset encoding string.
     * @return a PrintWriter for the output stream.
     * @throws java.io.UnsupportedEncodingException if encoding is not supported.
     */
    public static PrintWriter getPrintWriter(OutputStream os, String charset)
        throws UnsupportedEncodingException {
      return new PrintWriter(
          new BufferedWriter(new OutputStreamWriter(os, charset), CHAR_BUFFER_SIZE));
    }
  
    /**
     * returns an IterableLineReader backed by a LineIterator. Can be used directly in enhanced for
     * loops. Please note that if not all the lines are read, reader will not be closed. So, it is
     * suggested to close the IterableLineReader in a finally block using {@link
     * #closeSilently(java.io.Closeable...)}
     *
     * @param is an input stream.
     * @return an IterableLineReader that can be iterated for lines.
     */
    public static IterableLineReader getIterableReader(InputStream is) {
      return new IterableLineReader(getReader(is));
    }
  
    /**
     * returns an IterableLineReader backed by a LineIterator. Can be used directly in enhanced for
     * loops. Please note that if not all the lines are read, reader will not be closed. So, it is
     * suggested to close the IterableLineReader in a finally block using {@link
     * #closeSilently(java.io.Closeable...)}
     *
     * @param is input stream
     * @param charset the charset.
     * @return an IterableLineReader that can be iterated for lines.
     * @throws java.io.IOException if charset is not available, or there is an error ocurred during
     * utf-8 test.
     */
    public static IterableLineReader getIterableReader(InputStream is, String charset)
        throws IOException {
      return new IterableLineReader(getReader(is, charset));
    }
  
    /**
     * Copies oan input stream content to an output stream. Once the copy is finished streams will be
     * closed.
     *
     * @param is input stream
     * @param os output stream
     * @return copied byte count.
     * @throws java.io.IOException if an IO error occurs.
     */
    public static long copy(InputStream is, OutputStream os) throws IOException {
      return copy(is, os, false);
    }
  
    /**
     * Copies oan input stream content to an output stream. Once the copy is finished only the input
     * strean is closed by default. Closing of the output stream depends on the boolean parameter..
     *
     * @param is input stream
     * @param os output stream
     * @param keepOutputOpen if true, output stream will not be closed.
     * @return copied byte count.
     * @throws java.io.IOException if an IO error occurs.
     */
    static long copy(InputStream is, OutputStream os, boolean keepOutputOpen) throws IOException {
      long total = 0;
      try {
        checkNotNull(is, "Input stream cannot be null.");
        checkNotNull(os, "Output stream cannot be null.");
        byte[] buf = new byte[BYTE_BUFFER_SIZE];
        int i;
        while ((i = is.read(buf)) != -1) {
          os.write(buf, 0, i);
          total += i;
        }
      } finally {
        closeSilently(is);
        if (!keepOutputOpen) {
          closeSilently(os);
        }
      }
      return total;
    }
  
    /**
     * compares two input stream contents. Streams will be closed after the operation is ended or
     * interrupted.
     * <p/>
     * copied and modified from Apache Commons-io
     *
     * @param is1 first input stream
     * @param is2 second input stream.
     * @return true if contents of two streams are equal.
     * @throws NullPointerException if one of the stream is null
     * @throws java.io.IOException if an IO exception occurs while reading streams.
     */
    public static boolean contentEquals(InputStream is1, InputStream is2) throws IOException {
      try {
        checkNotNull(is1, "Input stream 1 cannot be null.");
        checkNotNull(is2, "Input stream 2 cannot be null.");
        if (!(is1 instanceof BufferedInputStream)) {
          is1 = new BufferedInputStream(is1, CHAR_BUFFER_SIZE);
        }
        if (!(is2 instanceof BufferedInputStream)) {
          is2 = new BufferedInputStream(is2, CHAR_BUFFER_SIZE);
        }
  
        int ch = is1.read();
        while (-1 != ch) {
          int ch2 = is2.read();
          if (ch != ch2) {
            return false;
          }
          ch = is1.read();
        }
        int ch2 = is2.read();
        return (ch2 == -1);
      } finally {
        closeSilently(is1, is2);
      }
    }
  
    /**
     * Calculates the MD5 of a stream.
     *
     * @param is a non null stream
     * @return MD5 of the stream as byte array.
     * @throws java.io.IOException if an error occurs during read of the stream.
     * @throws NullPointerException if input stream is null
     */
    public static byte[] calculateMD5(InputStream is) throws IOException {
      try {
        checkNotNull(is, "input stream cannot be null.");
        MessageDigest digest;
        digest = MessageDigest.getInstance("MD5");
  
        byte[] buffer = new byte[BYTE_BUFFER_SIZE];
        int read;
  
        while ((read = is.read(buffer)) > 0) {
          digest.update(buffer, 0, read);
        }
        return digest.digest();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("MD5 is not available." + e);
      } finally {
        closeSilently(is);
      }
    }
  
    /**
     * UTF encoded text has a a special information in the beginning of the file called BOM. BOM
     * information is mandatory for all UTF encodings, except UTF-8. Unfortunately, Java assumes UTF-8
     * files does not have the BOM information. But windows systems usually put the UTF-8 BOM data in
     * the begining of the file. This causes errors in Java applications. This method is a workaround
     * for reading UTF-8 encoded files. Checks if the stream has UTF-8 BOM information in the
     * beginning of a stream. if it has the information, it returns a PushbackStream backed by the
     * input stream but three bytes already read. if BOM does not exist, it returns a PushbackStream
     * backed by the input stream.
     *
     * @param is input stream
     * @return if it is an UT8, returns the input stream with three characters already read.
     * @throws java.io.IOException if there is an error during reading of first three bytes
     * @throws NullPointerException if input stream is null
     */
    static InputStream forceUTF8(InputStream is) throws IOException {
      checkNotNull(is, "input stream cannot be null.");
      PushbackInputStream pis = new PushbackInputStream(is, bomBytes.length);
      byte[] bomRead = new byte[bomBytes.length];
      if (pis.read(bomRead, 0, bomBytes.length) == -1) {
        return is;
      }
      if (!Arrays.equals(bomRead, bomBytes)) {
        pis.unread(bomRead);
      }
      return pis;
    }
  
    /**
     * checks if a stream contains UTF8 BOM information at the begininning.
     *
     * @param is inputstream
     * @return if input stream contains UTF-8 bom bytes, return true.
     * @throws java.io.IOException if there is a problem during reading bytes from stream.
     */
    static boolean containsUTF8Bom(InputStream is) throws IOException {
      try {
        checkNotNull(is, "input stream cannot be null.");
        byte[] bomRead = new byte[bomBytes.length];
        return is.read(bomRead, 0, bomBytes.length) != -1 && Arrays.equals(bomRead, bomBytes);
      } finally {
        closeSilently(is);
      }
    }
  
    /**
     * converts an input stream data to byte array. careful with memory usage here. if aim is to
     * transfer bytes from one stream to another, use {@link #copy(java.io.InputStream,
     * java.io.OutputStream)} instead.
     *
     * @param is , an input stream
     * @return a byte array representing the stream data.
     * @throws java.io.IOException if an error occurs during the read or write of the streams.
     * @throws NullPointerException if input stream is null
     */
    public static byte[] readAsByteArray(InputStream is) throws IOException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      try {
        checkNotNull(is, "input stream cannot be null.");
        int b;
        byte[] buffer = new byte[BYTE_BUFFER_SIZE];
        while ((b = is.read(buffer, 0, buffer.length)) != -1) {
          baos.write(buffer, 0, b);
        }
        return baos.toByteArray();
      } finally {
        closeSilently(is, baos);
      }
    }
  
    /**
     * Writes the value of each item in a collection to an <code>OutputStream</code> line by line,
     * using the default character encoding of the platform. A Line separator will be added to each
     * line. If there is a null value, an empty line will be added. The output stream will Not be
     * closed once the operation is finished.
     * <p/>
     * copied and modified from Apache Commons-io
     *
     * @param lines the lines to write, null entries produce blank lines
     * @param output the <code>OutputStream</code> to write to, not null, not closed
     * @throws NullPointerException if the output is null
     * @throws java.io.IOException if an I/O error occurs
     */
    public static void writeLines(Collection<String> lines, OutputStream output) throws IOException {
      if (lines == null) {
        return;
      }
  
      for (String line : lines) {
        if (line != null) {
          output.write(line.getBytes());
        }
        output.write(LINE_SEPARATOR.getBytes());
      }
    }
  
    /**
     * Writes the <code>toString()</code> value of each item in a collection to an
     * <code>OutputStream</code> line by line, using the default character encoding of the platform.
     * if an element is null, nothing is written for it. The stream will Not be closed once the
     * operation is finished.
     * <p/>
     * copied and modified from commons-io
     *
     * @param lines the lines to write, null entries produce blank lines
     * @param output the <code>BufferedWriter</code> to write to, not null, not closed
     * @throws NullPointerException if the output is null
     * @throws java.io.IOException if an I/O error occurs
     */
    public static void writeLines(Collection<String> lines,
        BufferedWriter output) throws IOException {
      writeToStringLines(lines, output);
    }
  
    /**
     * Writes the string to <code>OutputStream</code> The stream will Not be closed once the operation
     * is finished.
     * <p/>
     *
     * @param s String to write.
     * @param output the <code>OutputStream</code> to write to, not null, not closed
     * @throws NullPointerException if the output is null
     * @throws java.io.IOException if an I/O error occurs
     */
    public static void writeString(String s, OutputStream output) throws IOException {
      writeString(s, output, null);
    }
  
    /**
     * Writes the string to <code>OutputStream</code> The stream will Not be closed once the operation
     * is finished.
     * <p/>
     *
     * @param s String to write.
     * @param output the <code>OutputStream</code> to write to, not null, not closed
     * @param encoding character encoding.
     * @throws NullPointerException if the output is null
     * @throws java.io.IOException if an I/O error occurs
     */
    public static void writeString(String s,
        OutputStream output,
        String encoding) throws IOException {
      if (Strings.isNullOrEmpty(s)) {
        return;
      }
      if (encoding == null) {
        encoding = Charset.defaultCharset().name();
      }
      output.write(s.getBytes(encoding));
    }
  
    /**
     * retrieves a classpath resource as stream.
     *
     * @param resource resource name. may or may not contain a / symbol.
     * @return an InputStrean obtained from the resource
     */
    public static InputStream getClassPathResourceAsStream(String resource) {
      if (!resource.startsWith("/")) {
        resource = "/" + resource;
      }
      return IOs.class.getResourceAsStream(resource);
    }
  
    /**
     * Writes the <code>toString()</code> value of each item in a collection to an
     * <code>OutputStream</code> line by line, using the default character encoding of the platform.
     * if an element is null, nothing is written for it. The stream will Not be closed once the
     * operation is finished.
     * <p/>
     * copied and modified from commons-io
     *
     * @param lines the lines to write, null entries produce blank lines
     * @throws NullPointerException if the output is null
     * @throws java.io.IOException if an I/O error occurs
     */
    public static void writeToStringLines(
        Collection<?> lines,
        BufferedWriter writer) throws IOException {
  
      if (lines == null) {
        return;
      }
  
      long i = 0;
      for (Object line : lines) {
        String l = "";
        if (line != null) {
          l = line.toString();
        }
  
        if (!Strings.isNullOrEmpty(l)) {
          writer.write(l);
        } else {
          writer.write(LINE_SEPARATOR);
          continue;
        }
        if (++i < lines.size()) {
          writer.write(LINE_SEPARATOR);
        }
      }
    }
  
    /**
     * returns a LineIterator. it is suggested to close th eiterator in a finally block.
     *
     * @param is input stream to read.
     * @return an IterableLineReader that can be iterated for lines.
     */
    public LineIterator getLineIterator(InputStream is) {
      return new LineIterator(getReader(is));
    }
  
    /**
     * returns a LineIterator. it is suggested to close th eiterator in a finally block.
     *
     * @param is input stream to read.
     * @param charset charset
     * @return an IterableLineReader that can be iterated for lines.
     * @throws java.io.IOException if charset is not available, or there is an error ocurred during
     * utf-8 test.
     */
    public LineIterator getLineIterator(InputStream is, String charset) throws IOException {
      return new LineIterator(getReader(is, charset));
    }
  
  
  }