/* * 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.android; import static com.google.errorprone.BugPattern.Category.ANDROID; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Matchers.allOf; import static com.google.errorprone.matchers.Matchers.anyOf; import static com.google.errorprone.matchers.Matchers.isSubtypeOf; import static com.google.errorprone.matchers.Matchers.kindIs; import static com.google.errorprone.matchers.Matchers.nestingKind; import static com.sun.source.tree.Tree.Kind.CLASS; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.NestingKind.MEMBER; import com.google.common.collect.ImmutableSet; 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.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; import java.util.List; import java.util.stream.Collectors; /** @author avenet@google.com (Arnaud J. Venet) */ @BugPattern( name = "FragmentNotInstantiable", altNames = {"ValidFragment"}, summary = "Subclasses of Fragment must be instantiable via Class#newInstance():" + " the class must be public, static and have a public nullary constructor", category = ANDROID, severity = WARNING ) public class FragmentNotInstantiable extends BugChecker implements ClassTreeMatcher { private static final String MESSAGE_BASE = "Fragment is not instantiable: "; private static final String FRAGMENT_CLASS = "android.app.Fragment"; // Static library support version of the framework's Fragment. Used to write apps that run on // platforms prior to Android 3.0. private static final String FRAGMENT_CLASS_V4 = "android.support.v4.app.Fragment"; private final ImmutableSet<String> fragmentClasses; private final Matcher<ClassTree> fragmentMatcher; public FragmentNotInstantiable() { this(ImmutableSet.of()); } protected FragmentNotInstantiable(Iterable<String> additionalFragmentClasses) { fragmentClasses = ImmutableSet.<String>builder() .add(FRAGMENT_CLASS) .add(FRAGMENT_CLASS_V4) .addAll(additionalFragmentClasses) .build(); fragmentMatcher = allOf( kindIs(CLASS), anyOf( fragmentClasses .stream() .map(fragmentClass -> isSubtypeOf(fragmentClass)) .collect(Collectors.toList()))); } private Description buildErrorMessage(Tree tree, String explanation) { Description.Builder description = buildDescription(tree); description.setMessage(MESSAGE_BASE + explanation + "."); return description.build(); } @Override public Description matchClass(ClassTree classTree, VisitorState state) { if (!fragmentMatcher.matches(classTree, state)) { return Description.NO_MATCH; } String className = ASTHelpers.getSymbol(classTree).toString(); if (fragmentClasses.contains(className)) { // Do not check the base class declarations (or their stubs). return Description.NO_MATCH; } // The check doesn't apply to abstract classes. if (classTree.getModifiers().getFlags().contains(ABSTRACT)) { return Description.NO_MATCH; } // A fragment must be public. if (!classTree.getModifiers().getFlags().contains(PUBLIC)) { return buildErrorMessage(classTree, "a fragment must be public"); } // A fragment that is an inner class must be static. if (nestingKind(MEMBER).matches(classTree, state) && !ASTHelpers.getSymbol(classTree).isStatic()) { return buildErrorMessage(classTree, "a fragment inner class must be static"); } List<MethodTree> constructors = ASTHelpers.getConstructors(classTree); for (MethodTree constructor : constructors) { if (constructor.getParameters().isEmpty()) { // The nullary constructor exists. We must make sure that it is public. if (!constructor.getModifiers().getFlags().contains(PUBLIC)) { return buildErrorMessage(constructor, "the nullary constructor must be public"); } return Description.NO_MATCH; } } // If we reach this point, we know for sure that the class has at least one constructor // but no nullary constructor. return buildErrorMessage(classTree, "the nullary constructor is missing"); } }