/*
 * Copyright 2017 The Error Prone 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.errorprone.bugpatterns;

import static com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH;

import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import java.util.Enumeration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** {@link JdkObsolete}Test */
@RunWith(JUnit4.class)
public class JdkObsoleteTest {
  private final CompilationTestHelper testHelper =
      CompilationTestHelper.newInstance(JdkObsolete.class, getClass());

  @Test
  public void positive() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.nio.file.Path;",
            "class Test {",
            "  {",
            "    // BUG: Diagnostic contains:",
            "    new java.util.LinkedList<>();",
            "    // BUG: Diagnostic contains:",
            "    new java.util.Stack<>();",
            "    // BUG: Diagnostic contains:",
            "    new java.util.Vector<>();",
            "    // BUG: Diagnostic contains:",
            "    new java.util.Hashtable<>();",
            "    // BUG: Diagnostic contains:",
            "    new StringBuffer();",
            "    // BUG: Diagnostic contains:",
            "    new java.util.Hashtable<Object, Object>() {};",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void stringBuffer_appendReplacement() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.util.regex.Matcher;",
            "class Test {",
            "  void f(Matcher m) {",
            "    StringBuffer sb = new StringBuffer();",
            "    m.appendReplacement(sb, null);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void stringBuffer_appendTail() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.util.regex.Matcher;",
            "class Test {",
            "  void f(Matcher m) {",
            "    StringBuffer sb = new StringBuffer();",
            "    m.appendTail(sb);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void positiveExtends() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.nio.file.Path;",
            "class Test {",
            "  // BUG: Diagnostic contains:",
            "  abstract class A implements java.util.Enumeration<Object> {}",
            "  // BUG: Diagnostic contains:",
            "  abstract class B implements java.util.SortedSet<Object> {}",
            "  // BUG: Diagnostic contains:",
            "  abstract class C implements java.util.SortedMap<Object, Object> {}",
            "  // BUG: Diagnostic contains:",
            "  abstract class D extends java.util.Dictionary<Object, Object> {}",
            "}")
        .doTest();
  }

  @Test
  public void refactoring() {
    BugCheckerRefactoringTestHelper.newInstance(JdkObsolete.class, getClass())
        .addInputLines(
            "in/Test.java", //
            "import java.util.*;",
            "class Test {",
            "  Deque<Object> d = new LinkedList<>();",
            "  List<Object> l = new LinkedList<>();",
            "  {",
            "    l = new LinkedList<>();",
            "  }",
            "  LinkedList<Object> ll = new LinkedList<>();",
            "  List<Object> lll = new LinkedList<Object>() {{",
            "    add(null); // yikes",
            "  }};",
            "}")
        .addOutputLines(
            "out/Test.java", //
            "import java.util.*;",
            "class Test {",
            "  Deque<Object> d = new ArrayDeque<>();",
            "  List<Object> l = new ArrayList<>();",
            "  {",
            "    l = new ArrayList<>();",
            "  }",
            "  LinkedList<Object> ll = new LinkedList<>();",
            "  List<Object> lll = new LinkedList<Object>() {{",
            "    add(null); // yikes",
            "  }};",
            "}")
        .doTest(TEXT_MATCH);
  }

  @Test
  public void stringBufferRefactoringTest() {
    BugCheckerRefactoringTestHelper.newInstance(JdkObsolete.class, getClass())
        .addInputLines(
            "in/Test.java", //
            "class Test {",
            "  String f() {",
            "    StringBuffer sb = new StringBuffer();",
            "    return sb.append(42).toString();",
            "  }",
            "}")
        .addOutputLines(
            "out/Test.java", //
            "class Test {",
            "  String f() {",
            "    StringBuilder sb = new StringBuilder();",
            "    return sb.append(42).toString();",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void stringBufferRefactoringTest_usingVar() {
    BugCheckerRefactoringTestHelper.newInstance(JdkObsolete.class, getClass())
        .addInputLines(
            "in/Test.java", //
            "class Test {",
            "  String f() {",
            "    var sb = new StringBuffer();",
            "    return sb.append(42).toString();",
            "  }",
            "}")
        .addOutputLines(
            "out/Test.java", //
            "class Test {",
            "  String f() {",
            "    var sb = new StringBuilder();",
            "    return sb.append(42).toString();",
            "  }",
            "}")
        .doTest();
  }

  /** A test input. */
  public interface Lib {
    Enumeration<Integer> foos();
  }

  @Test
  public void obsoleteOverride() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import " + Lib.class.getCanonicalName() + ";",
            "import java.util.Enumeration;",
            "class Test implements Lib {",
            "  public Enumeration<Integer> foos() {",
            "    return new Enumeration<Integer>() {",
            "      @Override public boolean hasMoreElements() { return false; }",
            "      @Override public Integer nextElement() { return null; }",
            "    };",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void additionalRefactorings() {
    BugCheckerRefactoringTestHelper.newInstance(JdkObsolete.class, getClass())
        .addInputLines(
            "in/Test.java", //
            "import java.util.*;",
            "import java.util.function.*;",
            "class Test {",
            "  Supplier<Deque<Object>> a = () -> new LinkedList<>();",
            "  Supplier<Deque<Object>> b = () -> {",
            "    return new LinkedList<>();",
            "  };",
            "  Supplier<Deque<Object>> c = LinkedList::new;",
            "  Deque<Object> f() {",
            "    return new LinkedList<>();",
            "  }",
            "  void g(Deque<Object> x) {}",
            "  {",
            "    g(new LinkedList<>());",
            "  }",
            "  {",
            "    List<LinkedList<String>> xs = new ArrayList<>();",
            "    List<List<String>> ys = new ArrayList<>();",
            "    xs.add(new LinkedList<>());",
            "    ys.add(new LinkedList<>());",
            "  }",
            "}")
        .addOutputLines(
            "out/Test.java", //
            "import java.util.*;",
            "import java.util.function.*;",
            "class Test {",
            "  Supplier<Deque<Object>> a = () -> new ArrayDeque<>();",
            "  Supplier<Deque<Object>> b = () -> {",
            "    return new ArrayDeque<>();",
            "  };",
            "  Supplier<Deque<Object>> c = ArrayDeque::new;",
            "  Deque<Object> f() {",
            "    return new ArrayDeque<>();",
            "  }",
            "  void g(Deque<Object> x) {}",
            "  {",
            "    g(new ArrayDeque<>());",
            "  }",
            "  {",
            "    List<LinkedList<String>> xs = new ArrayList<>();",
            "    List<List<String>> ys = new ArrayList<>();",
            "    xs.add(new LinkedList<>());",
            "    ys.add(new ArrayList<>());",
            "  }",
            "}")
        .doTest(TEXT_MATCH);
  }

  @Test
  public void obsoleteMocking() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import static org.mockito.Mockito.when;",
            "import " + Lib.class.getCanonicalName() + ";",
            "import java.util.Enumeration;",
            "class Test {",
            "  void test(Lib lib) {",
            "    when(lib.foos())",
            "        .thenReturn(",
            "            new Enumeration<Integer>() {",
            "              public boolean hasMoreElements() {",
            "                return false;",
            "              }",
            "              public Integer nextElement() {",
            "                return null;",
            "              }",
            "            });",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void navigableSetRepro() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.util.NavigableSet;",
            "import java.util.Optional;",
            "class Test {",
            "  Optional<Object> fail1(Optional<NavigableSet<Object>> myOptionalSet) {",
            "    return myOptionalSet.map(NavigableSet::first);",
            "  }",
            "  Optional<Object> fail2(Optional<NavigableSet<Object>> myOptionalSet) {",
            "    return myOptionalSet.map(NavigableSet::last);",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void navigableMapInheritedMethod() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import java.util.Map;",
            "import java.util.Set;",
            "import java.util.NavigableMap;",
            "class Test {",
            "  void f(NavigableMap<String, Integer> m) {",
            "    for (Integer e : m.values()) {",
            "    }",
            "  }",
            "}")
        .doTest();
  }

  @Test
  public void indirect() {
    testHelper
        .addSourceLines(
            "Test.java",
            "import com.google.common.collect.SortedSetMultimap;",
            "import com.google.common.collect.TreeMultimap;",
            "class Test {",
            "  void f() {",
            "    SortedSetMultimap<String, String> myMultimap = TreeMultimap.create();",
            "    String myValue = myMultimap.get(\"foo\").first();",
            "  }",
            "}")
        .doTest();
  }
}
