Package com.google.errorprone.bugpatterns

Source Code of com.google.errorprone.bugpatterns.AbstractJUnit4InitMethodNotRun$AnnotationReplacements

/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* 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.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.hasAnnotationOnAnyOverriddenMethod;
import static com.google.errorprone.matchers.Matchers.not;

import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.JUnitMatchers;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.util.ASTHelpers;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.tree.JCTree;

import java.io.Serializable;
import java.util.List;

import javax.annotation.Nullable;
import javax.lang.model.element.Modifier;

/**
* Base class for JUnit4SetUp/TearDown not run. This will take care of the nitty-gritty about
* replacing @After with @Before, adding @Before on unannotated methods, making them public
* if necessary, fixing the imports of other @Before, etc.
*
* @author glorioso@google.com
*/
abstract class AbstractJUnit4InitMethodNotRun extends BugChecker implements MethodTreeMatcher {
  private static final String JUNIT_TEST = "org.junit.Test";
  protected final JUnitMatchers.JUnit4TestClassMatcher isJUnit4TestClass;

  protected AbstractJUnit4InitMethodNotRun() {
    this.isJUnit4TestClass = new JUnitMatchers.JUnit4TestClassMatcher();
  }

  /**
   * Returns a matcher that selects which methods this matcher applies to
   * (e.g. public void setUp() without @Before/@BeforeClass annotation)
   */
  protected abstract Matcher<MethodTree> methodMatcher();

  /**
   * Returns the fully qualified class name of the annotation this bugpattern should apply to
   * matched methods.
   *
   * If another annotation is on the method that has the same name, the import will be replaced
   * with the appropriate one (e.g.: com.example.Before becomes org.junit.Before)
   */
  protected abstract String correctAnnotation();

  /**
   * Returns a collection of 'before-and-after' pairs of annotations that should be replaced on
   * these methods.
   *
   * <p>If this method matcher finds a method annotated with
   * {@link AnnotationReplacements#badAnnotation}, instead of applying
   * {@link #correctAnnotation()}, instead replace it with
   * {@link AnnotationReplacements#goodAnnotation}
   */
  protected abstract List<AnnotationReplacements> annotationReplacements();


  /**
   * Matches if all of the following conditions are true:
   * 1) The method matches {@link #methodMatcher()}, (looks like setUp() or tearDown(),
   *    and none of the overrides in the hierarchy of the method have the appropriate @Before
   *    or @After annotations)
   * 2) The method is not annotated with @Test
   * 3) The enclosing class has an @RunWith annotation and does not extend TestCase. This marks
   *    that the test is intended to run with JUnit 4.
   */
  @Override
  public Description matchMethod(MethodTree methodTree, VisitorState state) {
    boolean matches = allOf(
        methodMatcher(),
        not(hasAnnotationOnAnyOverriddenMethod(JUNIT_TEST)),
        enclosingClass(isJUnit4TestClass))
        .matches(methodTree, state);
    if (!matches) {
      return Description.NO_MATCH;
    }

    // For each annotationReplacement, replace the first annotation that matches. If any of them
    // matches, don't try and do the rest of the work.
    Description description;
    for (AnnotationReplacements replacement : annotationReplacements()) {
      description = tryToReplaceAnnotation(
          methodTree, state, replacement.badAnnotation, replacement.goodAnnotation);
      if (description != null) {
        return description;
      }
    }

    // Search for another @Before annotation on the method and replace the import
    // if we find one
    String correctAnnotation = correctAnnotation();
    String unqualifiedClassName = getUnqualifiedClassName(correctAnnotation);
    for (AnnotationTree annotationNode : methodTree.getModifiers().getAnnotations()) {
      String annotationClassName =
          ASTHelpers.getSymbol(annotationNode).getQualifiedName().toString();
      if (annotationClassName.endsWith("." + unqualifiedClassName)) {
        SuggestedFix.Builder suggestedFix = SuggestedFix.builder()
            .removeImport(annotationClassName)
            .addImport(correctAnnotation);
        if (makeProtectedPublic(methodTree, state, unqualifiedClassName, suggestedFix, false)
            == null) {
          // No source position available, don't suggest a fix
          return describeMatch(annotationNode);
        }
        suggestedFix.replace(annotationNode, "@" + unqualifiedClassName);
        return describeMatch(annotationNode, suggestedFix.build());
      }
    }

    // Add correctAnnotation() to the unannotated method
    // (and convert protected to public if it is)
    SuggestedFix.Builder suggestedFix = SuggestedFix.builder().addImport(correctAnnotation);

    // The makeProtectedPublic will take care of adding the annotation for us
    Boolean annotationAdded =
        makeProtectedPublic(methodTree, state, unqualifiedClassName, suggestedFix, true);
    //
    if (annotationAdded == null) {
      // No source position available, don't suggest a fix
      return describeMatch(methodTree);
    }

    if (!annotationAdded) {
      suggestedFix.prefixWith(methodTree, "@" + unqualifiedClassName + "\n");
    }

    return describeMatch(methodTree, suggestedFix.build());
  }

  // returns null if the method source isn't available for some reason. Caller should check
  // null and not emit a fix in that case. Returns true if the method was upgraded from protected
  // to public
  @Nullable
  private Boolean makeProtectedPublic(MethodTree methodTree, VisitorState state,
      String unqualifiedClassName, SuggestedFix.Builder suggestedFix, boolean addAnnotation) {
    if (Matchers.<MethodTree>hasModifier(Modifier.PROTECTED).matches(methodTree, state)) {
      CharSequence methodSource = state.getSourceForNode((JCTree.JCMethodDecl) methodTree);
      if (methodSource == null) {
        return null;
      }
      String methodString = (addAnnotation ? "@" + unqualifiedClassName + "\n" : "")
          + methodSource.toString().replaceFirst("protected ", "public ");
      suggestedFix.replace(methodTree, methodString);
      return true;
    }
    return false;
  }


  private Description tryToReplaceAnnotation(MethodTree methodTree, VisitorState state,
                                             String badAnnotation, String goodAnnotation) {
    String finalName = getUnqualifiedClassName(goodAnnotation);
    if (hasAnnotation(badAnnotation).matches(methodTree, state)) {
      AnnotationTree annotationTree = findAnnotation(methodTree, state, badAnnotation);
      return describeMatch(annotationTree, SuggestedFix.builder()
          .addImport(goodAnnotation)
          .replace(annotationTree, "@" + finalName)
          .build());
    } else {
      return null;
    }
  }

  private String getUnqualifiedClassName(String goodAnnotation) {
    return goodAnnotation.substring(goodAnnotation.lastIndexOf(".") + 1);
  }

  private AnnotationTree findAnnotation(MethodTree methodTree, VisitorState state,
                                        String annotationName) {
    AnnotationTree annotationNode = null;
    for (AnnotationTree annotation : methodTree.getModifiers().getAnnotations()) {
      if (ASTHelpers.getSymbol(annotation)
          .equals(state.getSymbolFromString(annotationName))) {
        annotationNode = annotation;
      }
    }
    return annotationNode;
  }

  protected static class AnnotationReplacements implements Serializable {
    private final String goodAnnotation;
    private final String badAnnotation;

    protected AnnotationReplacements(String badAnnotation, String goodAnnotation) {
      this.goodAnnotation = goodAnnotation;
      this.badAnnotation = badAnnotation;
    }
  }
}
TOP

Related Classes of com.google.errorprone.bugpatterns.AbstractJUnit4InitMethodNotRun$AnnotationReplacements

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.