/* * SonarQube Java * Copyright (C) 2012-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.java.checks; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.AnnotationTree; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.ParameterizedTypeTree; import org.sonar.plugins.java.api.tree.ReturnStatementTree; import org.sonar.plugins.java.api.tree.Tree; import javax.annotation.Nullable; import java.util.Deque; import java.util.List; import java.util.Set; @Rule(key = "S1168") public class ReturnEmptyArrayNotNullCheck extends IssuableSubscriptionVisitor { private static final Set<String> COLLECTION_TYPES = ImmutableSet.of( "Collection", "BeanContext", "BeanContextServices", "BlockingDeque", "BlockingQueue", "Deque", "List", "NavigableSet", "Queue", "Set", "SortedSet", "AbstractCollection", "AbstractList", "AbstractQueue", "AbstractSequentialList", "AbstractSet", "ArrayBlockingQueue", "ArrayDeque", "ArrayList", "AttributeList", "BeanContextServicesSupport", "BeanContextSupport", "ConcurrentLinkedQueue", "ConcurrentSkipListSet", "CopyOnWriteArrayList", "CopyOnWriteArraySet", "DelayQueue", "EnumSet", "HashSet", "JobStateReasons", "LinkedBlockingDeque", "LinkedBlockingQueue", "LinkedHashSet", "LinkedList", "PriorityBlockingQueue", "PriorityQueue", "RoleList", "RoleUnresolvedList", "Stack", "SynchronousQueue", "TreeSet", "Vector"); private final Deque<Returns> returnType = Lists.newLinkedList(); private enum Returns { ARRAY, COLLECTION, OTHERS; public static Returns getReturnType(@Nullable Tree tree) { if (tree != null) { Tree returnType = tree; while (returnType.is(Tree.Kind.PARAMETERIZED_TYPE)) { returnType = ((ParameterizedTypeTree) returnType).type(); } if (returnType.is(Tree.Kind.ARRAY_TYPE)) { return ARRAY; } else if (isCollection(returnType)) { return COLLECTION; } } return OTHERS; } private static boolean isCollection(Tree methodReturnType) { IdentifierTree identifierTree = null; if (methodReturnType.is(Tree.Kind.IDENTIFIER)) { identifierTree = (IdentifierTree) methodReturnType; } else if (methodReturnType.is(Tree.Kind.MEMBER_SELECT)) { identifierTree = ((MemberSelectExpressionTree) methodReturnType).identifier(); } return identifierTree != null && COLLECTION_TYPES.contains(identifierTree.name()); } } @Override public void scanFile(JavaFileScannerContext context) { super.scanFile(context); returnType.clear(); } @Override public List<Tree.Kind> nodesToVisit() { return ImmutableList.of(Tree.Kind.METHOD, Tree.Kind.CONSTRUCTOR, Tree.Kind.RETURN_STATEMENT, Tree.Kind.LAMBDA_EXPRESSION); } @Override public void visitNode(Tree tree) { if (tree.is(Tree.Kind.METHOD)) { MethodTree methodTree = (MethodTree) tree; if (isAllowingNull(methodTree)) { returnType.push(Returns.OTHERS); } else { returnType.push(Returns.getReturnType(methodTree.returnType())); } } else if (tree.is(Tree.Kind.CONSTRUCTOR, Tree.Kind.LAMBDA_EXPRESSION)) { returnType.push(Returns.OTHERS); } else { ReturnStatementTree returnStatement = (ReturnStatementTree) tree; if (isReturningNull(returnStatement)) { if (returnType.peek().equals(Returns.ARRAY)) { reportIssue(returnStatement.expression(), "Return an empty array instead of null."); } else if (returnType.peek().equals(Returns.COLLECTION)) { reportIssue(returnStatement.expression(), "Return an empty collection instead of null."); } } } } @Override public void leaveNode(Tree tree) { if (!tree.is(Tree.Kind.RETURN_STATEMENT)) { returnType.pop(); } } private static boolean isReturningNull(ReturnStatementTree tree) { ExpressionTree expression = tree.expression(); return expression != null && expression.is(Tree.Kind.NULL_LITERAL); } private static boolean isAllowingNull(MethodTree methodTree) { for (AnnotationTree annotation : methodTree.modifiers().annotations()) { Type type = annotation.annotationType().symbolType(); if (type.is("javax.annotation.Nullable") || type.is("javax.annotation.CheckForNull")) { return true; } } return false; } }