/*
 * Copyright 2013 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 javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Unit tests for {@link RequiredModifiersChecker}.
 *
 * @author sgoldfeder@google.com (Steven Goldfeder)
 */
@RunWith(JUnit4.class)
public class RequiredModifiersCheckerTest {

  private final CompilationTestHelper compilationHelper =
      CompilationTestHelper.newInstance(RequiredModifiersChecker.class, getClass())
          .addSourceLines(
              "test/AbstractRequired.java",
              """
              package test;

              import static javax.lang.model.element.Modifier.ABSTRACT;
              import com.google.errorprone.annotations.RequiredModifiers;

              @RequiredModifiers(ABSTRACT)
              public @interface AbstractRequired {}
              """)
          .addSourceLines(
              "test/PublicAndFinalRequired.java",
              """
              package test;

              import static javax.lang.model.element.Modifier.FINAL;
              import static javax.lang.model.element.Modifier.PUBLIC;
              import com.google.errorprone.annotations.RequiredModifiers;

              @RequiredModifiers({PUBLIC, FINAL})
              public @interface PublicAndFinalRequired {}
              """);

  JavaFileObject abstractRequired;
  JavaFileObject publicAndFinalRequired;

  @Test
  public void annotationWithRequiredModifiersMissingOnClassFails() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
package test;

import test.AbstractRequired;

// BUG: Diagnostic contains: The annotation '@AbstractRequired' has specified that it must be used
// together with the following modifiers: [abstract]
@AbstractRequired
public class RequiredModifiersTestCase {}
""")
        .doTest();
  }

  @Test
  public void annotationWithRequiredModifiersMissingOnFieldFails1() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
package test;

import test.PublicAndFinalRequired;

public class RequiredModifiersTestCase {
  // BUG: Diagnostic contains: The annotation '@PublicAndFinalRequired' has specified that it must
  // be used together with the following modifiers: [public, final]
  @PublicAndFinalRequired int n = 0;
}
""")
        .doTest();
  }

  @Test
  public void annotationWithRequiredModifiersMissingOnFieldFails2() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
package test;

import test.PublicAndFinalRequired;

public class RequiredModifiersTestCase {
  // BUG: Diagnostic contains: The annotation '@PublicAndFinalRequired' has specified that it must
  // be used together with the following modifiers: [final]
  @PublicAndFinalRequired public int n = 0;
}
""")
        .doTest();
  }

  @Test
  public void annotationWithRequiredModifiersMissingOnFieldFails3() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
package test;

import test.PublicAndFinalRequired;

public class RequiredModifiersTestCase {
  // BUG: Diagnostic contains: The annotation '@PublicAndFinalRequired' has specified that it must
  // be used together with the following modifiers: [public]
  @PublicAndFinalRequired final int n = 0;
}
""")
        .doTest();
  }

  @Test
  public void annotationWithRequiredModifiersMissingOnMethodFails1() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
package test;

import test.PublicAndFinalRequired;

public class RequiredModifiersTestCase {
  // BUG: Diagnostic contains: The annotation '@PublicAndFinalRequired' has specified that it must
  // be used together with the following modifiers: [public, final]
  @PublicAndFinalRequired
  private void foo() {}
}
""")
        .doTest();
  }

  @Test
  public void annotationWithRequiredModifiersMissingOnMethodFails2() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
package test;

import test.PublicAndFinalRequired;

public class RequiredModifiersTestCase {
  // BUG: Diagnostic contains: The annotation '@PublicAndFinalRequired' has specified that it must
  // be used together with the following modifiers: [final]
  @PublicAndFinalRequired
  public void foo() {}
}
""")
        .doTest();
  }

  @Test
  public void annotationWithRequiredModifiersMissingOnMethodFails3() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
package test;

import test.PublicAndFinalRequired;

public class RequiredModifiersTestCase {
  // BUG: Diagnostic contains: The annotation '@PublicAndFinalRequired' has specified that it must
  // be used together with the following modifiers: [public]
  @PublicAndFinalRequired
  final void foo() {}
}
""")
        .doTest();
  }

  @Test
  public void hasRequiredModifiersSucceeds() {
    compilationHelper
        .addSourceLines(
            "test/RequiredModifiersTestCase.java",
            """
            package test;

            import test.AbstractRequired;

            abstract class RequiredModifiersTestCase {}
            """)
        .doTest();
  }

  // Regression test for #313
  @Test
  public void negativeNestedAnnotations() {
    compilationHelper
        .addSourceLines(
            "test/Test.java",
            """
            package test;

            @interface Foos {
              Foo[] value();
            }

            @interface Foo {}

            @Foos({@Foo, @Foo})
            public class Test {}
            """)
        .doTest();
  }

  // Regression test for #313
  @Test
  public void negativePackageAnnotation() {
    compilationHelper
        .addSourceLines(
            "testdata/Anno.java",
            """
            package testdata;

            import java.lang.annotation.Target;
            import java.lang.annotation.ElementType;

            @Target(ElementType.PACKAGE)
            public @interface Anno {}
            """)
        .addSourceLines(
            "testdata/package-info.java",
            """
            @Anno
            package testdata;
            """)
        .doTest();
  }

  @Test
  public void refactoring() {
    BugCheckerRefactoringTestHelper.newInstance(RequiredModifiersChecker.class, getClass())
        .addInputLines(
            "test/AbstractRequired.java",
            """
            package test;

            import static javax.lang.model.element.Modifier.ABSTRACT;
            import com.google.errorprone.annotations.RequiredModifiers;

            @RequiredModifiers(ABSTRACT)
            public @interface AbstractRequired {}
            """)
        .expectUnchanged()
        .addInputLines(
            "test/RequiredModifiersTestCase.java",
            """
            package test;

            import test.AbstractRequired;

            @AbstractRequired
            class RequiredModifiersTestCase {}
            """)
        .addOutputLines(
            "test/RequiredModifiersTestCase.java",
            """
            package test;

            import test.AbstractRequired;

            @AbstractRequired
            abstract class RequiredModifiersTestCase {}
            """)
        .doTest();
  }
}
