<commit_before>/*
 * Copyright (C) 2007 The Guava Authors
 *
 * 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.
 */

package com.google.common.io;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.io.FileWriteMode.APPEND;

import com.google.common.annotations.Beta;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.TreeTraverser;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Provides utility methods for working with files.
 *
 * <p>All method parameters must be non-null unless documented otherwise.
 *
 * @author <NAME>
 * @author <NAME>
 * @since 1.0
 */
@Beta
public final class Files {

  /** Maximum loop count when creating temp directories. */
  private static final int TEMP_DIR_ATTEMPTS = 10000;

  private Files() {}

  /**
   * Returns a buffered reader that reads from a file using the given
   * character set.
   *
   * @param file the file to read from
   * @param charset the charset used to decode the input stream; see {@link
   *     Charsets} for helpful predefined constants
   * @return the buffered reader
   */
  public static BufferedReader newReader(File file, Charset charset)
      throws FileNotFoundException {
    checkNotNull(file);
    checkNotNull(charset);
    return new BufferedReader(
        new InputStreamReader(new FileInputStream(file), charset));
  }

  /**
   * Returns a buffered writer that writes to a file using the given
   * character set.
   *
   * @param file the file to write to
   * @param charset the charset used to encode the output stream; see {@link
   *     Charsets} for helpful predefined constants
   * @return the buffered writer
   */
  public static BufferedWriter newWriter(File file, Charset charset)
      throws FileNotFoundException {
    checkNotNull(file);
    checkNotNull(charset);
    return new BufferedWriter(
        new OutputStreamWriter(new FileOutputStream(file), charset));
  }

  /**
   * Returns a new {@link ByteSource} for reading bytes from the given file.
   *
   * @since 14.0
   */
  public static ByteSource asByteSource(File file) {
    return new FileByteSource(file);
  }

  private static final class FileByteSource extends ByteSource {

    private final File file;

    private FileByteSource(File file) {
      this.file = checkNotNull(file);
    }

    @Override
    public FileInputStream openStream() throws IOException {
      return new FileInputStream(file);
    }

    @Override
    public long size() throws IOException {
      if (!file.isFile()) {
        throw new FileNotFoundException(file.toString());
      }
      return file.length();
    }

    @Override
    public byte[] read() throws IOException {
      long size = file.length();
      // some special files may return size 0 but have content
      // read normally to be sure
      if (size == 0) {
        return super.read();
      }

      // can't initialize a large enough array
      // technically, this could probably be Integer.MAX_VALUE - 5
      if (size > Integer.MAX_VALUE) {
        // OOME is what would be thrown if we tried to initialize the array
        throw new OutOfMemoryError("file is too large to fit in a byte array: "
            + size + " bytes");
      }

      // initialize the array to the current size of the file
      byte[] bytes = new byte[(int) size];

      Closer closer = Closer.create();
      try {
        InputStream in = closer.register(openStream());
        int off = 0;
        int read = 0;

        // read until we've read size bytes or reached EOF
        while (off < size
            && ((read = in.read(bytes, off, (int) size - off)) != -1)) {
          off += read;
        }

        byte[] result = bytes;

        if (off < size) {
          // encountered EOF early; truncate the result
          result = Arrays.copyOf(bytes, off);
        } else if (read != -1) {
          // we read size bytes... if the last read didn't return -1, the file got larger
          // so we just read the rest normally and then create a new array
          ByteArrayOutputStream out = new ByteArrayOutputStream();
          ByteStreams.copy(in, out);
          byte[] moreBytes = out.toByteArray();
          result = new byte[bytes.length + moreBytes.length];
          System.arraycopy(bytes, 0, result, 0, bytes.length);
          System.arraycopy(moreBytes, 0, result, bytes.length, moreBytes.length);
        }
        // normally, off should == size and read should == -1
        // in that case, the array is just returned as is
        return result;
      } catch (Throwable e) {
        throw closer.rethrow(e);
      } finally {
        closer.close();
      }
    }

    @Override
    public String toString() {
      return "Files.asByteSource(" + file + ")";
    }
  }

  /**
   * Returns a new {@link ByteSink} for writing bytes to the given file. The
   * given {@code modes} control how the file is opened for writing. When no
   * mode is provided, the file will be truncated before writing. When the
   * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will
   * append to the end of the file without truncating it.
   *
   * @since 14.0
   */
  public static ByteSink asByteSink(File file, FileWriteMode... modes) {
    return new FileByteSink(file, modes);
  }

  private static final class FileByteSink extends ByteSink {

    private final File file;
    private final ImmutableSet<FileWriteMode> modes;

    private FileByteSink(File file, FileWriteMode... modes) {
      this.file = checkNotNull(file);
      this.modes = ImmutableSet.copyOf(modes);
    }<commit_msg>Change FileByteSource.read() to not unnecessarily copy the byte array in the normal case where the file size doesn't change.<commit_after>/*
 * Copyright (C) 2007 The Guava Authors
 *
 * 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.
 */

package com.google.common.io;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.io.FileWriteMode.APPEND;

import com.google.common.annotations.Beta;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.TreeTraverser;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Provides utility methods for working with files.
 *
 * <p>All method parameters must be non-null unless documented otherwise.
 *
 * @author <NAME>
 * @author <NAME>
 * @since 1.0
 */
@Beta
public final class Files {

  /** Maximum loop count when creating temp directories. */
  private static final int TEMP_DIR_ATTEMPTS = 10000;

  private Files() {}

  /**
   * Returns a buffered reader that reads from a file using the given
   * character set.
   *
   * @param file the file to read from
   * @param charset the charset used to decode the input stream; see {@link
   *     Charsets} for helpful predefined constants
   * @return the buffered reader
   */
  public static BufferedReader newReader(File file, Charset charset)
      throws FileNotFoundException {
    checkNotNull(file);
    checkNotNull(charset);
    return new BufferedReader(
        new InputStreamReader(new FileInputStream(file), charset));
  }

  /**
   * Returns a buffered writer that writes to a file using the given
   * character set.
   *
   * @param file the file to write to
   * @param charset the charset used to encode the output stream; see {@link
   *     Charsets} for helpful predefined constants
   * @return the buffered writer
   */
  public static BufferedWriter newWriter(File file, Charset charset)
      throws FileNotFoundException {
    checkNotNull(file);
    checkNotNull(charset);
    return new BufferedWriter(
        new OutputStreamWriter(new FileOutputStream(file), charset));
  }

  /**
   * Returns a new {@link ByteSource} for reading bytes from the given file.
   *
   * @since 14.0
   */
  public static ByteSource asByteSource(File file) {
    return new FileByteSource(file);
  }

  private static final class FileByteSource extends ByteSource {

    private final File file;

    private FileByteSource(File file) {
      this.file = checkNotNull(file);
    }

    @Override
    public FileInputStream openStream() throws IOException {
      return new FileInputStream(file);
    }

    @Override
    public long size() throws IOException {
      if (!file.isFile()) {
        throw new FileNotFoundException(file.toString());
      }
      return file.length();
    }

    @Override
    public byte[] read() throws IOException {
      long size = file.length();
      // some special files may return size 0 but have content
      // read normally to be sure
      if (size == 0) {
        return super.read();
      }

      // can't initialize a large enough array
      // technically, this could probably be Integer.MAX_VALUE - 5
      if (size > Integer.MAX_VALUE) {
        // OOME is what would be thrown if we tried to initialize the array
        throw new OutOfMemoryError("file is too large to fit in a byte array: "
            + size + " bytes");
      }

      // initialize the array to the current size of the file
      byte[] bytes = new byte[(int) size];

      Closer closer = Closer.create();
      try {
        InputStream in = closer.register(openStream());
        int off = 0;
        int read = 0;

        // read until we've read size bytes or reached EOF
        while (off < size
            && ((read = in.read(bytes, off, (int) size - off)) != -1)) {
          off += read;
        }

        if (off < size) {
          // encountered EOF early; truncate the result
          return Arrays.copyOf(bytes, off);
        }

        // otherwise, exactly size bytes were read

        int b = in.read(); // check for EOF
        if (b == -1) {
          // EOF; the file did not change size, so return the original array
          return bytes;
        }

        // the file got larger, so read the rest normally
        InternalByteArrayOutputStream out
            = new InternalByteArrayOutputStream();
        out.write(b); // write the byte we read when testing for EOF
        ByteStreams.copy(in, out);

        byte[] result = new byte[bytes.length + out.size()];
        System.arraycopy(bytes, 0, result, 0, bytes.length);
        out.writeTo(result, bytes.length);
        return result;
      } catch (Throwable e) {
        throw closer.rethrow(e);
      } finally {
        closer.close();
      }
    }

    @Override
    public String toString() {
      return "Files.asByteSource(" + file + ")";
    }
  }

  /**
   * BAOS subclass for direct access to its internal buffer.
   */
  private static final class InternalByteArrayOutputStream
      extends ByteArrayOutputStream {
    /**
     * Writes the contents of the internal buffer to the given array starting
     * at the given offset. Assumes the array has space to hold count bytes.
     */
    void writeTo(byte[] b, int off) {
      System.arraycopy(buf, 0, b, off, count);
    }
  }

  /**
   * Returns a new {@link ByteSink} for writing bytes to the given file. The
   * given {@code modes} control how the file is opened for writing. When no
   * mode is provided, the file will be truncated before writing. When the
   * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will
   * append to the end of the file without truncating it.
   *
   * @since 14.0
   */
  public static ByteSink asByteSink(File file, FileWriteMode... modes) {
    return new FileByteSink(file, modes);
  }

  private static final class FileByteSink extends ByteSink {

    private final File file;
    private final ImmutableSet<FileWriteMode> modes;

    private FileByteSink(File file, FileWriteMode... modes) {
      this.file = checkNotNull(file);
      this.modes = ImmutableSet.copyOf(modes);
    }
{'fraction_non_alphanumeric': 0.09176004372779448, 'fraction_numerical': 0.004372779447936594, 'mean_word_length': 2.924128686327078, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2388796', 'n_tokens_mistral': 4090, 'n_tokens_neox': 3960, 'n_words': 1455}