/*
* Copyright 2013 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.inject.guice;
import static com.google.errorprone.BugPattern.Category.GUICE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.InjectMatchers.ASSISTED_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.ASSISTED_INJECT_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.GUICE_SCOPE_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.JAVAX_SCOPE_ANNOTATION;
import static com.google.errorprone.matchers.InjectMatchers.hasInjectAnnotation;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.constructor;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
/**
* This checker matches iff *both* of the following conditions are true: 1) The class is assisted:
* a) If there is a constructor that is annotated with @Inject and that constructor has at least one
* parameter that is annotated with @Assisted. b) If there is no @Inject constructor and at least
* one constructor is annotated with {@code @AssistedInject}. 2) There is an annotation on the
* class, and the annotation is itself annotated with {@code @ScopeAnnotation}.
*
* @author eaftan@google.com (Eddie Aftandilian)
*/
@BugPattern(
name = "GuiceAssistedInjectScoping",
summary = "Scope annotation on implementation class of AssistedInject factory is not allowed",
explanation =
"Classes that AssistedInject factories create may not be annotated with scope "
+ "annotations, such as @Singleton. This will cause a Guice error at runtime.\n\n"
+ "See [https://code.google.com/p/google-guice/issues/detail?id=742 this bug report] for "
+ "details.",
category = GUICE,
severity = ERROR
)
public class AssistedInjectScoping extends BugChecker implements ClassTreeMatcher {
/** Matches classes that have an annotation that itself is annotated with @ScopeAnnotation. */
private static final MultiMatcher<ClassTree, AnnotationTree> CLASS_TO_SCOPE_ANNOTATIONS =
annotations(
AT_LEAST_ONE,
anyOf(hasAnnotation(GUICE_SCOPE_ANNOTATION), hasAnnotation(JAVAX_SCOPE_ANNOTATION)));
/** Matches if any constructor of a class is annotated with an @Inject annotation. */
private static final MultiMatcher<ClassTree, MethodTree> CLASS_TO_INJECTED_CONSTRUCTORS =
constructor(AT_LEAST_ONE, hasInjectAnnotation());
/**
* Matches if: 1) If there is a constructor that is annotated with @Inject and that constructor
* has at least one parameter that is annotated with @Assisted. 2) If there is no @Inject
* constructor and at least one constructor is annotated with @AssistedInject.
*/
private static final Matcher<ClassTree> HAS_ASSISTED_CONSTRUCTOR =
new Matcher<ClassTree>() {
@Override
public boolean matches(ClassTree classTree, VisitorState state) {
MultiMatchResult<MethodTree> injectedConstructors =
CLASS_TO_INJECTED_CONSTRUCTORS.multiMatchResult(classTree, state);
if (injectedConstructors.matches()) {
// Check constructor with @Inject annotation for parameter with @Assisted annotation.
return methodHasParameters(AT_LEAST_ONE, hasAnnotation(ASSISTED_ANNOTATION))
.matches(injectedConstructors.matchingNodes().get(0), state);
}
return constructor(AT_LEAST_ONE, hasAnnotation(ASSISTED_INJECT_ANNOTATION))
.matches(classTree, state);
}
};
@Override
public final Description matchClass(ClassTree classTree, VisitorState state) {
MultiMatchResult<AnnotationTree> hasScopeAnnotations =
CLASS_TO_SCOPE_ANNOTATIONS.multiMatchResult(classTree, state);
if (!hasScopeAnnotations.matches() || !HAS_ASSISTED_CONSTRUCTOR.matches(classTree, state)) {
return Description.NO_MATCH;
}
AnnotationTree annotationWithScopeAnnotation = hasScopeAnnotations.matchingNodes().get(0);
if (annotationWithScopeAnnotation == null) {
throw new IllegalStateException(
"Expected to find an annotation that was annotated with @ScopeAnnotation");
}
return describeMatch(
annotationWithScopeAnnotation, SuggestedFix.delete(annotationWithScopeAnnotation));
}
}