/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package codetoanalyze.java.infer;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.UTF8StreamJsonParser;
import com.google.common.base.Preconditions;
import com.google.common.io.Closeables;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
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.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.util.Scanner;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import java.util.zip.ZipFile;
import javax.net.ssl.HttpsURLConnection;

public class ResourceLeaks {

  // FileOutputStream tests

  public void fileOutputStreamNotClosed() throws IOException {
    FileOutputStream fis = new FileOutputStream("file.txt");
  }

  public void fileOutputStreamNotClosedAfterWrite() {
    byte[] arr = {1, 2, 3};
    FileOutputStream fis = null;
    try {
      fis = new FileOutputStream("file.txt");
      fis.write(arr);
      fis.close();
    } catch (IOException e) {
    }
  }

  public void fileOutputStreamClosed() throws IOException {
    FileOutputStream fis = new FileOutputStream("file.txt");
    fis.close();
  }

  public void fileOutputStreamOneLeak() throws IOException {
    FileOutputStream fis = new FileOutputStream("file.txt");
    if (fis != null) {
    } else {
    }
  }

  public int fileOutputStreamTwoLeaks1(boolean ok) throws IOException {
    FileOutputStream fis = new FileOutputStream("file.txt");
    if (ok) {
      fis.write(1);
      return 1;
    } else {
      fis.write(2);
      return 2;
    }
  }

  public void fileOutputStreamTwoLeaks2() throws IOException {
    FileOutputStream fis = new FileOutputStream("file.txt");
    if (fis != null) {
    } else {
    }
    fis = new FileOutputStream("x");
  }

  // TwoResources tests

  public static void twoResources() throws IOException {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
      fis = new FileInputStream(new File("infile.txt"));
      fos = new FileOutputStream(new File("outfile.txt"));
      fos.write(fis.read());
    } finally {
      if (fis != null) fis.close();
      if (fos != null) fos.close();
    }
  }

  public static void twoResourcesHeliosFix() throws IOException {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
      fis = new FileInputStream(new File("whatever.txt"));
      try {
        fos = new FileOutputStream(new File("everwhat.txt"));
        fos.write(fis.read());
      } finally {
        if (fos != null) fos.close();
      }
    } finally {
      if (fis != null) fis.close();
    }
  }

  public static void twoResourcesCommonFix() throws IOException {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
      fis = new FileInputStream(new File("infile.txt"));
      fos = new FileOutputStream(new File("outfile.txt"));
      fos.write(fis.read());
    } finally {
      try {
        if (fis != null) fis.close();
      } catch (Exception e) {
      }
      try {
        if (fos != null) fos.close();
      } catch (Exception e) {
      }
    }
  }

  public static void twoResourcesServerSocket() throws IOException {
    ServerSocket a = null;
    ServerSocket b = null;
    try {
      a = new ServerSocket();
      b = new ServerSocket();
    } finally {
      if (a != null) a.close();
      if (b != null) b.close();
    }
  }

  public static void twoResourcesRandomAccessFile() throws IOException {
    RandomAccessFile a = null;
    RandomAccessFile b = null;
    try {
      a = new RandomAccessFile("", "rw");
      b = new RandomAccessFile("", "rw");
    } finally {
      if (a != null) a.close();
      if (b != null) b.close();
    }
  }

  public static void twoResourcesRandomAccessFileCommonFix() throws IOException {
    RandomAccessFile a = null;
    RandomAccessFile b = null;
    try {
      a = new RandomAccessFile("", "rw");
      b = new RandomAccessFile("", "rw");
    } finally {
      try {
        if (a != null) a.close();
      } catch (Exception e) {
      }
      try {
        if (b != null) b.close();
      } catch (Exception e) {
      }
    }
  }

  // NestedResource tests

  // BufferedInputStream does not throw exception, and its close
  // closes the FileInputStream as well
  public void nestedGood() throws IOException {
    BufferedInputStream b = new BufferedInputStream(new FileInputStream("file.txt"));
    b.close();
  }

  // GZipInputStream can throw IO Exception
  // in which case the new FileInputStream will be dangling
  public void nestedBad1() throws IOException {
    GZIPInputStream g = new GZIPInputStream(new FileInputStream("file.txt"));
    g.close();
  }

  public void nestedBad2() throws IOException {
    GZIPOutputStream g = new GZIPOutputStream(new FileOutputStream("file.txt"));
    g.close();
  }

  /* Fixed versions of this are below with ObjectInputStream tests */
  public void objectInputStreamClosedNestedBad() throws IOException {
    ObjectInputStream oin = null;
    try {
      oin = new ObjectInputStream(new FileInputStream("file.txt"));
      int a = oin.available();
    } catch (IOException e) {
    } finally {
      if (oin != null) oin.close();
    }
  }

  /* Fixed versions of this are below with ObjectInputStream tests */
  public void objectOutputStreamClosedNestedBad() throws IOException {
    ObjectOutputStream oin = null;
    try {
      oin = new ObjectOutputStream(new FileOutputStream("file.txt"));
      oin.write(3);
    } catch (IOException e) {
    } finally {
      if (oin != null) oin.close();
    }
  }

  // ZipFile tests      (Jarfile Tests also test Zipfiles)

  public static void zipFileLeakExceptionalBranch() throws IOException {
    ZipFile j = null;
    try {
      j = new ZipFile("");
    } catch (IOException e) {
      FileOutputStream fis = new FileOutputStream("file.txt");
      // The purpose of this is to cause a leak, from when ZipFile constructor throws
    } finally {
      if (j != null) j.close();
    }
  }

  public static void zipFileNoLeak() throws IOException {
    ZipFile j = null;
    try {
      j = new ZipFile("");
    } finally {
      if (j != null) j.close();
    }
  }

  // JarFile tests

  public boolean jarFileClosed() {
    JarFile jarFile = null;
    try {
      jarFile = new JarFile("");
    } catch (IOException e) {
    } finally {
      try {
        if (jarFile != null) {
          jarFile.close();
        }
      } catch (IOException e) {
      }
    }
    return false;
  }

  public boolean jarFileNotClosed() {
    JarFile jarFile = null;
    try {
      jarFile = new JarFile("");
    } catch (IOException e) {
    }
    return false;
  }

  // FileInputStream tests

  public void fileInputStreamNotClosedAfterRead() {
    FileInputStream fis;
    try {
      fis = new FileInputStream("file.txt");
      fis.read();
      fis.close();
    } catch (IOException e) {
    }
  }

  public void fileInputStreamClosed() throws IOException {
    FileInputStream fis = null;
    try {
      fis = new FileInputStream("file.txt");
      fis.available();
    } catch (IOException e) {
    } finally {
      if (fis != null) fis.close();
    }
  }

  // PipedInputStream tests

  public void pipedInputStreamNotClosedAfterRead(PipedOutputStream pout) {
    PipedInputStream pin;
    try {
      pin = new PipedInputStream(pout);
      int data = pin.read();
      pin.close();
    } catch (IOException e) {
    }
  }

  public void pipedInputStreamClosed(PipedOutputStream pout) throws IOException {
    PipedInputStream pin = null;
    try {
      pin = new PipedInputStream(pout);
      int data = pin.read();
    } catch (IOException e) {
    } finally {
      pin.close();
    }
  }

  // PipedOutputStream tests

  public void pipedOutputStreamNotClosedAfterWrite() {
    byte[] arr = {1, 2, 3, 4, 5};
    PipedOutputStream pout;
    try {
      pout = new PipedOutputStream();
      pout.write(arr);
      pout.close();
    } catch (IOException e) {
    }
  }

  public void pipedOutputStreamClosed(PipedInputStream pin) throws IOException {
    PipedOutputStream pout = null;
    try {
      pout = new PipedOutputStream(pin);
      pout.flush();
    } catch (IOException e) {
    } finally {
      pout.close();
    }
  }

  // ObjectOutputStream tests

  public void objectOutputStreamNotClosedAfterWrite() {
    byte[] arr = {1, 2, 3, 4, 5};
    ObjectOutputStream oout;
    try {
      oout = new ObjectOutputStream(new FileOutputStream("file.txt"));
      oout.write(arr);
      oout.close();
    } catch (IOException e) {
    }
  }

  public void objectOutputStreamClosed() throws IOException {
    ObjectOutputStream oout = null;
    FileOutputStream fis = new FileOutputStream("file.txt");
    try {
      oout = new ObjectOutputStream(fis);
      oout.flush();
    } catch (IOException e) {
    } finally {
      fis.close();
    }
  }

  // ObjectInputStream tests

  public void objectInputStreamNotClosedAfterRead() {
    ObjectInputStream oin;
    try {
      oin = new ObjectInputStream(new FileInputStream("file.txt"));
      oin.read();
      oin.close();
    } catch (IOException e) {
    }
  }

  public void objectInputStreamClosed() throws IOException {
    ObjectInputStream oin = null;
    FileInputStream fis = new FileInputStream("file.txt");
    try {
      oin = new ObjectInputStream(fis);
      int a = oin.available();
    } catch (IOException e) {
    } finally {
      if (oin != null) {
        oin.close();
      } else {
        fis.close();
      }
    }
  }

  public void objectInputStreamClosed2() throws IOException {
    ObjectInputStream oin = null;
    FileInputStream fis = new FileInputStream("file.txt");
    try {
      oin = new ObjectInputStream(fis);
      int a = oin.available();
    } catch (IOException e) {
    } finally {
      fis.close();
    }
  }

  // JarInputStream tests

  public static void jarInputStreamNoLeak() throws IOException {
    FileInputStream fos = new FileInputStream("");
    try {
      JarInputStream g = new JarInputStream(fos);
      g.close();
    } catch (IOException e) {
      fos.close();
    }
  }

  public static void jarInputStreamLeak() throws IOException {
    FileInputStream fos = new FileInputStream("");
    try {
      JarInputStream g = new JarInputStream(fos); //  Testing exceptional condition in constructor
      g.close();
    } catch (IOException e) {
      // fos.close();
    }
  }

  public static void nestedBadJarInputStream(File file) throws IOException {
    JarInputStream g = new JarInputStream(new FileInputStream(file));
    g.close();
  }

  // JarOutputStream tests

  public static void jarOutputStreamNoLeak() throws IOException {
    FileOutputStream fos = new FileOutputStream("");
    try {
      JarOutputStream g = new JarOutputStream(fos);
      g.close();
    } catch (IOException e) {
      fos.close();
    }
  }

  public static void jarOutputStreamLeak() throws IOException {
    FileOutputStream fos = new FileOutputStream("");
    try {
      JarOutputStream g = new JarOutputStream(fos); //  Testing exceptional condition in constructor
      g.close();
    } catch (IOException e) {
      // fos.close();
    }
  }

  public static void nestedBadJarOutputStream() throws IOException {
    JarOutputStream g = new JarOutputStream(new FileOutputStream("file.txt"));
    g.close();
  }

  // Socket tests

  public void socketNotClosed() {
    Socket socket = new Socket();
  }

  public void socketClosed() throws IOException {
    Socket socket = new Socket();
    socket.close();
  }

  // Socket InputStream tests

  public int socketInputStreamNotClosed(Socket socket) throws IOException {
    InputStream stream = socket.getInputStream();
    return stream.read();
  }

  public void socketInputStreamClosed() throws IOException {
    Socket socket = new Socket();
    InputStream stream = socket.getInputStream();
    try {
      stream.close();
    } catch (Exception e) {
    }
    socket.close();
  }

  // Socket OutputStream tests

  public void socketOutputStreamNotClosed(Socket socket) throws IOException {
    OutputStream stream = socket.getOutputStream();
    stream.write(10);
  }

  public void socketOutputStreamClosed() throws IOException {
    Socket socket = new Socket();
    OutputStream stream = socket.getOutputStream();
    try {
      stream.close();
    } catch (Exception e) {
    }
    socket.close();
  }

  // ServerSocket tests

  public void serverSocketNotClosed() throws IOException {
    ServerSocket listener = new ServerSocket(9090);
    while (true) {
      Socket socket = listener.accept();
      try {
        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        out.println("");
      } finally {
        socket.close();
      }
      listener.close();
    }
  }

  public void serverSocketClosed() throws IOException {
    ServerSocket socket = new ServerSocket();
    socket.close();
  }

  public void serverSocketWithSocketClosed() throws IOException {
    ServerSocket listener = new ServerSocket(9090);
    try {
      while (true) {
        Socket socket = listener.accept();
        try {
          PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
          out.println("");
        } finally {
          socket.close();
        }
      }
    } finally {
      listener.close();
    }
  }

  // HttpURLConnection

  public void openHttpURLConnectionDisconnected() throws IOException {
    String content = "TEXT";
    DataOutputStream outputStream = null;
    HttpURLConnection connection = null;
    URL address = new URL("http://www.facebook.com");
    connection = (HttpURLConnection) address.openConnection();
    try {
      outputStream = new DataOutputStream(connection.getOutputStream());
      outputStream.writeBytes(content);
      outputStream.flush();

    } finally {
      connection.disconnect();
    }
  }

  public void openHttpURLConnectionNotDisconnected() throws IOException {
    String content = "TEXT";
    DataOutputStream outputStream = null;
    HttpURLConnection connection = null;
    URL address = new URL("http://www.facebook.com");
    connection = (HttpURLConnection) address.openConnection();

    outputStream = new DataOutputStream(connection.getOutputStream());
    outputStream.writeBytes(content);
  }

  public void openHttpsURLConnectionNotDisconnected() throws IOException {
    HttpsURLConnection connection = null;
    URL address = new URL("https://www.facebook.com");
    connection = (HttpsURLConnection) address.openConnection();
  }

  public void openHttpsURLConnectionDisconnected() throws IOException {
    HttpsURLConnection connection = null;
    URL address = new URL("https://www.facebook.com");
    connection = (HttpsURLConnection) address.openConnection();
    connection.disconnect();
  }

  public void closedWithCloseables() throws IOException {
    FileInputStream fs = new FileInputStream("file.txt");
    try {
      fs.read();
    } finally {
      Closeables.close(fs, false);
    }
  }

  public void closedQuietlyWithCloseables() throws IOException {
    FileInputStream fs = new FileInputStream("file.txt");
    try {
      fs.read();
    } finally {
      Closeables.closeQuietly(fs);
    }
  }

  public void closeNullWithCloseables() throws IOException {
    FileInputStream fs = null;
    try {
      fs = new FileInputStream("file.txt");
    } finally {
      Closeables.close(fs, true);
    }
  }

  public void closeNullQuietlyWithCloseables() throws IOException {
    FileInputStream fs = null;
    try {
      fs = new FileInputStream("file.txt");
    } finally {
      Closeables.closeQuietly(fs);
    }
  }

  private static void myClose(Closeable closeable, boolean swallowIOException) throws IOException {
    if (closeable == null) {
      return;
    }
    try {
      closeable.close();
    } catch (IOException e) {
      if (!swallowIOException) {
        throw e;
      }
    }
  }

  public void closeWithCloseablesNestedAlloc() throws IOException {
    BufferedInputStream b = null;
    try {
      b = new BufferedInputStream(new FileInputStream("file.txt"));
    } finally {
      myClose(b, false);
    }
  }

  // JsonParser tests

  public void parseFromStringAndNotClose(JsonFactory factory) throws IOException {
    UTF8StreamJsonParser parser = null;
    try {
      parser = (UTF8StreamJsonParser) factory.createParser(new File("[]"));
      Object o = parser.readValueAs(Object.class);
      ignore(o);
    } catch (Exception e) {
    } finally {
    }
  }

  public void parseFromInputStreamAndClose(JsonFactory factory) throws IOException {
    JsonParser parser = null;
    FileInputStream in = null;
    try {
      in = new FileInputStream("");
      parser = factory.createParser(in);
      Object o = parser.readValueAs(Object.class);
      ignore(o);
    } catch (Exception e) {
    } finally {
      if (in != null) in.close();
    }
    // parser does not own a resources which is closed externally
  }

  public void parseFromInputStreamAndLeak(JsonFactory factory) throws IOException {
    JsonParser parser = null;
    FileInputStream in = null;
    try {
      in = new FileInputStream("");
      parser = factory.createParser(in);
      Object o = parser.readValueAs(Object.class);
      ignore(o);
    } catch (Exception e) {
    } finally {
      if (parser != null) parser.close();
    }
    // parser does not own a resource which is leaked
  }

  private void ignore(Object o) {}

  // Installation.java examples. Even the fix was a fp for a while
  // for several reasons, so this test is just to make sure it remains
  // banished forever

  private String readInstallationFileGood(File installation) throws IOException {
    RandomAccessFile f = new RandomAccessFile(installation, "r");
    try {
      byte[] bytes = new byte[(int) f.length()];
      f.readFully(bytes);
      return new String(bytes);
    } finally {
      f.close();
    }
  }

  private String readInstallationFileBad(File installation) throws IOException {
    RandomAccessFile f = new RandomAccessFile(installation, "r");
    byte[] bytes = new byte[(int) f.length()];
    f.readFully(bytes);
    f.close();
    return new String(bytes);
  }

  private int readConfigCloseStream(String mTurnConfigUrl) {
    try {
      URL url = new URL(mTurnConfigUrl);
      URLConnection connection = url.openConnection();
      InputStream stream = connection.getInputStream();
      try {
        return stream.read();
      } finally {
        stream.close();
      }
    } catch (Exception e) {
    }
    return 0;
  }

  private int readConfigNotCloseStream(String mTurnConfigUrl) {
    try {
      URL url = new URL(mTurnConfigUrl);
      URLConnection connection = url.openConnection();
      InputStream stream = connection.getInputStream();
      return stream.read();
    } catch (Exception e) {
    }
    return 0;
  }

  private void readConfigNotClosedOK(String mTurnConfigUrl) {
    try {
      URL url = new URL(mTurnConfigUrl);
      URLConnection connection = url.openConnection();
      ignore(connection);
    } catch (Exception e) {
    }
  }

  // TypedArray

  public void themeObtainTypedArrayAndRecycle(Resources.Theme theme) {
    TypedArray array = theme.obtainStyledAttributes(new int[] {});
    ignore(array);
    array.recycle();
  }

  public void themeObtainTypedArrayAndLeak(Resources.Theme theme) {
    TypedArray array = theme.obtainStyledAttributes(new int[] {});
    ignore(array);
  }

  public void activityObtainTypedArrayAndRecycle(Activity activity) {
    TypedArray array = activity.obtainStyledAttributes(new int[] {});
    ignore(array);
    array.recycle();
  }

  public void activityObtainTypedArrayAndLeak(Activity activity) {
    TypedArray array = activity.obtainStyledAttributes(new int[] {});
    ignore(array);
  }

  public void contextObtainTypedArrayAndRecycle(Context context) {
    TypedArray array = context.obtainStyledAttributes(new int[] {});
    ignore(array);
    array.recycle();
  }

  public void contextObtainTypedArrayAndLeak(Context context) {
    TypedArray array = context.obtainStyledAttributes(new int[] {});
    ignore(array);
  }

  // FileChannel

  void copyFileLeak(File src, File dst) throws IOException {
    FileChannel inChannel = new FileInputStream(src).getChannel();
    FileChannel outChannel = new FileOutputStream(dst).getChannel();
    try {
      inChannel.transferTo(0, inChannel.size(), outChannel);
    } finally {
      if (inChannel != null) inChannel.close();
      if (outChannel != null) outChannel.close();
    }
  }

  void copyFileClose(File src, File dst) throws IOException {
    FileChannel inChannel = new FileInputStream(src).getChannel();
    try {
      ignore(inChannel);
    } finally {
      inChannel.close();
    }
  }

  protected long checkNotNullCauseNoLeak(URL mUrl) throws IOException {
    URL url = new URL("http://www.facebook.com");
    HttpURLConnection serverConnection =
        (HttpURLConnection) Preconditions.checkNotNull(url.openConnection());
    try {
      ignore(serverConnection);
    } catch (NumberFormatException nfe) {
    } finally {
      serverConnection.disconnect();
    }
    return 0;
  }

  void scannerNotClosed() throws IOException {
    Scanner scanner = new Scanner(new FileInputStream("file.txt"));
  }

  void scannerClosed() throws IOException {
    Scanner scanner = new Scanner(new FileInputStream("file.txt"));
    scanner.close();
  }

  void processDestroyed() {
    Process process = null;
    try {
      process = Runtime.getRuntime().exec("");
    } catch (IOException e) {
    } finally {
      process.destroy();
    }
  }

  void processForciblyDestroyed() throws IOException {
    Process process = null;
    try {
      process = Runtime.getRuntime().exec("");
    } finally {
      ignore(process.destroyForcibly());
    }
  }

  class Container {
    FileInputStream inputStream;
  }

  native Container load(FileInputStream inputStream);

  public Container resourceReturnedIndirectly() {
    FileInputStream inputStream;
    Container container = null;
    try {
      inputStream = new FileInputStream("pif.txt");
      container = load(inputStream);
    } catch (FileNotFoundException e) {
      return null;
    }
    return container;
  }

  native void unknownClose(Closeable c);

  public void resourceClosedBySkippedMethod() {
    FileInputStream inputStream = null;
    try {
      inputStream = new FileInputStream("pif.txt");
    } catch (FileNotFoundException e) {
      return;
    } finally {
      unknownClose(inputStream);
    }
  }

  public int tryWithResource() {
    try (FileInputStream inputStream = new FileInputStream("paf.txt")) {
      return inputStream.read();
    } catch (IOException e) {
      return 0;
    }
  }

  public InputStreamReader withCharset(URLConnection urlConnection) {
    InputStreamReader reader = null;
    try {
      reader = new InputStreamReader(urlConnection.getInputStream(), "iso-8859-1");
    } catch (Exception e) {
      return null;
    } finally {
      if (reader != null) {
        try {
          reader.close();
        } catch (IOException e) {
          // do nothing
        }
      }
    }
    return reader;
  }

  public void withZipFile() throws IOException {
    ZipFile f = new ZipFile("hi");
    InputStream s = f.getInputStream(f.getEntry("there"));
    if (s != null) s.toString();
    f.close();
  }

  public void deflaterLeak() {
    Deflater comp = new Deflater();
  }

  public void deflaternoLeak() {
    Deflater comp = new Deflater();
    comp.end();
  }

  public void inflaterLeak() {
    Inflater decomp = new Inflater();
  }

  public void inflaterNoLeak() {
    Inflater decomp = new Inflater();
    decomp.end();
  }

  void NoResourceLeakWarningAfterCheckState(File f, int x) throws IOException {
    InputStream stream = new FileInputStream(f);
    Preconditions.checkState(x > 0);
    stream.close();
  }
}