/*
* Copyright 2016 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.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.fixes.SuggestedFixes.addMembers;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.util.ASTHelpers.isGeneratedConstructor;
import static com.sun.source.tree.Tree.Kind.CLASS;
import static com.sun.source.tree.Tree.Kind.METHOD;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
/** @author gak@google.com (Gregory Kick) */
@BugPattern(
name = "PrivateConstructorForUtilityClass",
summary =
"Utility classes (only static members) are not designed to be instantiated and should"
+ " be made noninstantiable with a default constructor.",
explanation =
"Classes that only include static members have no behavior particular to any given instance,"
+ " so instantiating them is nonsense. To prevent users from mistakenly creating"
+ " instances, the class should include a private constructor. See Effective Java,"
+ " Second Edition - Item 4.",
category = JDK,
severity = SUGGESTION
)
public final class PrivateConstructorForUtilityClass extends BugChecker
implements ClassTreeMatcher {
@Override
public Description matchClass(ClassTree classTree, VisitorState state) {
if (!classTree.getKind().equals(CLASS)
|| classTree.getExtendsClause() != null
|| !classTree.getImplementsClause().isEmpty()
|| isInPrivateScope(state)) {
return NO_MATCH;
}
FluentIterable<? extends Tree> nonSyntheticMembers =
FluentIterable.from(classTree.getMembers())
.filter(
Predicates.not(
new Predicate<Tree>() {
@Override
public boolean apply(Tree tree) {
return tree.getKind().equals(METHOD)
&& isGeneratedConstructor((MethodTree) tree);
}
}));
if (nonSyntheticMembers.isEmpty()) {
return NO_MATCH;
}
boolean isUtilityClass =
nonSyntheticMembers.allMatch(
new Predicate<Tree>() {
@Override
public boolean apply(Tree tree) {
switch (tree.getKind()) {
case CLASS:
return ((ClassTree) tree).getModifiers().getFlags().contains(STATIC);
case METHOD:
return ((MethodTree) tree).getModifiers().getFlags().contains(STATIC);
case VARIABLE:
return ((VariableTree) tree).getModifiers().getFlags().contains(STATIC);
case BLOCK:
return ((BlockTree) tree).isStatic();
case ENUM:
case ANNOTATION_TYPE:
case INTERFACE:
return true;
default:
throw new AssertionError("unknown member type:" + tree.getKind());
}
}
});
if (!isUtilityClass) {
return NO_MATCH;
}
return describeMatch(
classTree, addMembers(classTree, state, "private " + classTree.getSimpleName() + "() {}"));
}
private static boolean isInPrivateScope(VisitorState state) {
TreePath treePath = state.getPath();
do {
Tree currentLeaf = treePath.getLeaf();
if (ClassTree.class.isInstance(currentLeaf)) {
ClassTree currentClassTree = (ClassTree) currentLeaf;
if (currentClassTree.getModifiers().getFlags().contains(PRIVATE)) {
return true;
}
}
treePath = treePath.getParentPath();
} while (treePath != null);
return false;
}
}