/*
 * Copyright 2020 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 com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Unit tests for {@link ConstantPatternCompile} bug pattern.
 *
 * @author bhagwani@google.com (Sumit Bhagwani)
 */
@RunWith(JUnit4.class)
public class ConstantPatternCompileTest {
  private final CompilationTestHelper compilationHelper =
      CompilationTestHelper.newInstance(ConstantPatternCompile.class, getClass());
  private final BugCheckerRefactoringTestHelper testHelper =
      BugCheckerRefactoringTestHelper.newInstance(ConstantPatternCompile.class, getClass());

  @Test
  public void inlineExpressions() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              boolean isCar(String input) {
                return Pattern.compile("car").matcher(input).matches();
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              boolean isCar(String input) {
                return INPUT_PATTERN.matcher(input).matches();
              }

              private static final Pattern INPUT_PATTERN = Pattern.compile("car");
            }
            """)
        .doTest();
  }

  @Test
  public void variableNameFromField() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              private static final String INPUT = null;

              boolean isCar() {
                return Pattern.compile("car").matcher(INPUT).matches();
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              private static final String INPUT = null;

              boolean isCar() {
                return INPUT_PATTERN.matcher(INPUT).matches();
              }

              private static final Pattern INPUT_PATTERN = Pattern.compile("car");
            }
            """)
        .doTest();
  }

  @Test
  public void inlineExpression_argumentIsMethodCall() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              String getText() {
                return null;
              }

              boolean isCar() {
                return Pattern.compile("car").matcher(getText()).matches();
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              String getText() {
                return null;
              }

              boolean isCar() {
                return GET_TEXT_PATTERN.matcher(getText()).matches();
              }

              private static final Pattern GET_TEXT_PATTERN = Pattern.compile("car");
            }
            """)
        .doTest();
  }

  @Test
  public void inlineExpression_nameDefaultsToPattern() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              boolean isCar() {
                return Pattern.compile("car").matcher("").matches();
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              boolean isCar() {
                return PATTERN.matcher("").matches();
              }

              private static final Pattern PATTERN = Pattern.compile("car");
            }
            """)
        .doTest();
  }

  @Test
  public void multipleInlineExpressions() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              boolean isBlueCar(String input) {
                return Pattern.compile("car").matcher(input).matches()
                    && Pattern.compile("blue").matcher(input).matches();
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
"""
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class Test {
  boolean isBlueCar(String input) {
    return INPUT_PATTERN.matcher(input).matches() && INPUT_PATTERN2.matcher(input).matches();
  }

  private static final Pattern INPUT_PATTERN = Pattern.compile("car");
  private static final Pattern INPUT_PATTERN2 = Pattern.compile("blue");
}
""")
        .doTest();
  }

  @Test
  public void sameNameInDifferentMethods() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              boolean isCar(String input) {
                return Pattern.compile("car").matcher(input).matches();
              }

              boolean isDog(String input) {
                return Pattern.compile("dog").matcher(input).matches();
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              boolean isCar(String input) {
                return INPUT_PATTERN.matcher(input).matches();
              }

              boolean isDog(String input) {
                return INPUT_PATTERN2.matcher(input).matches();
              }

              private static final Pattern INPUT_PATTERN = Pattern.compile("car");
              private static final Pattern INPUT_PATTERN2 = Pattern.compile("dog");
            }
            """)
        .doTest();
  }

  @Test
  public void fixGenerationStatic() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              static final String MY_COOL_PATTERN = "a+";

              public static void myPopularStaticMethod() {
                Pattern somePattern = Pattern.compile(MY_COOL_PATTERN);
                Matcher m = somePattern.matcher("aaaaab");
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              static final String MY_COOL_PATTERN = "a+";

              public static void myPopularStaticMethod() {
                Matcher m = SOME_PATTERN.matcher("aaaaab");
              }

              private static final Pattern SOME_PATTERN = Pattern.compile(MY_COOL_PATTERN);
            }
            """)
        .doTest();
  }

  @Test
  public void fixGeneration_multiplePatterns() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Pattern;

            class Test {
              public static boolean match() {
                String line = "abcd";
                Pattern p1 = Pattern.compile("a+");
                Pattern p2 = Pattern.compile("b+");
                if (p1.matcher(line).matches() && p2.matcher(line).matches()) {
                  return true;
                }
                Pattern p3 = Pattern.compile("c+");
                Pattern p4 = Pattern.compile("d+");
                return p3.matcher(line).matches() && p4.matcher(line).matches();
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Pattern;

            class Test {
              public static boolean match() {
                String line = "abcd";
                if (P1.matcher(line).matches() && P2.matcher(line).matches()) {
                  return true;
                }
                return P3.matcher(line).matches() && P4.matcher(line).matches();
              }

              private static final Pattern P1 = Pattern.compile("a+");
              private static final Pattern P2 = Pattern.compile("b+");
              private static final Pattern P3 = Pattern.compile("c+");
              private static final Pattern P4 = Pattern.compile("d+");
            }
            """)
        .doTest();
  }

  @Test
  public void fixGenerationWithJavadoc() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              /** This is a javadoc. * */
              public static void myPopularStaticMethod() {
                Pattern myPattern = Pattern.compile("a+");
                Matcher m = myPattern.matcher("aaaaab");
              }
            }
            """)
        .addOutputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              /** This is a javadoc. * */
              public static void myPopularStaticMethod() {
                Matcher m = MY_PATTERN.matcher("aaaaab");
              }

              private static final Pattern MY_PATTERN = Pattern.compile("a+");
            }
            """)
        .doTest();
  }

  @Test
  public void fixGeneration_nonStaticInnerClass() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              static final String MY_COOL_PATTERN = "a+";

              class Inner {
                public void myPopularStaticMethod() {
                  Pattern myPattern = Pattern.compile(MY_COOL_PATTERN);
                  Matcher m = myPattern.matcher("aaaaab");
                }
              }
            }
            """)
        .addOutputLines(
            "out/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              static final String MY_COOL_PATTERN = "a+";

              class Inner {
                public void myPopularStaticMethod() {
                  Matcher m = MY_PATTERN.matcher("aaaaab");
                }

                private final Pattern MY_PATTERN = Pattern.compile(MY_COOL_PATTERN);
              }
            }
            """)
        .doTest();
  }

  @Test
  public void negativeCases() {
    compilationHelper
        .addSourceLines(
            "in/Test.java",
            """
            import com.google.errorprone.annotations.CompileTimeConstant;
            import java.util.regex.Pattern;

            class Test {
              private static String pattern;
              private static final Pattern MY_COOL_PATTERN = Pattern.compile(pattern);
              private static final Pattern LOWER_CASE_ONLY =
                  Pattern.compile("^([a-z]+)$", Pattern.CASE_INSENSITIVE);

              private void myPopularNonStaticMethod(String arg) {
                Pattern pattern = Pattern.compile(arg + "+");
              }

              private void myPopularMethod(@CompileTimeConstant String arg) {
                Pattern pattern = Pattern.compile(arg);
              }

              private final String patString = "a+";

              private void patternCompileOnNonStaticArg() {
                Pattern pattern = Pattern.compile(patString);
              }
            }
            """)
        .doTest();
  }

  @Test
  public void negativeCases_multiArg() {
    compilationHelper
        .addSourceLines(
            "in/Test.java",
            """
            import com.google.errorprone.annotations.CompileTimeConstant;
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              public static int getMatchCount(CharSequence content, String regex) {
                int count = 0;
                Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
                Matcher matcher = pattern.matcher(content);
                while (matcher.find()) {
                  count++;
                }
                return count;
              }
            }
            """)
        .doTest();
  }

  @Test
  public void negativeCase_staticBlock() {
    compilationHelper
        .addSourceLines(
            "in/Test.java",
            """
            import com.google.errorprone.annotations.CompileTimeConstant;
            import java.util.regex.Pattern;

            class Test {
              private static final String pattern = "a+";

              static {
                Pattern MY_COOL_PATTERN = Pattern.compile(pattern);
              }
            }
            """)
        .doTest();
  }

  // Don't convert String constants to patterns if they're used anywhere other than a single
  // Pattern.compile call.

  @Test
  public void onlyCode_noFinding() {
    compilationHelper
        .addSourceLines(
            "in/Test.java",
            """
            import java.util.regex.Pattern;

            class Test {
              public static void test() {
                Pattern pattern = Pattern.compile(".*");
              }
            }
            """)
        .setArgs("-XepCompilingTestOnlyCode")
        .doTest();
  }

  @Test
  public void withinList_noFinding() {
    compilationHelper
        .addSourceLines(
            "Test.java",
"""
import com.google.common.collect.ImmutableList;
import java.util.regex.Pattern;

class Test {
  private static final ImmutableList<Pattern> patterns = ImmutableList.of(Pattern.compile(".*"));
}
""")
        .doTest();
  }

  @Test
  public void suppressible() {
    testHelper
        .addInputLines(
            "in/Test.java",
            """
            import java.util.regex.Matcher;
            import java.util.regex.Pattern;

            class Test {
              @SuppressWarnings("ConstantPatternCompile")
              boolean isCar(String input) {
                return Pattern.compile("car").matcher(input).matches();
              }
            }
            """)
        .expectUnchanged()
        .doTest();
  }
}
