1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.coding; 21 22 import java.util.Objects; 23 24 import com.puppycrawl.tools.checkstyle.StatelessCheck; 25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 26 import com.puppycrawl.tools.checkstyle.api.DetailAST; 27 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 28 29 /** 30 * <p> 31 * Check that the {@code default} is after all the cases in a {@code switch} statement. 32 * </p> 33 * <p> 34 * Rationale: Java allows {@code default} anywhere within the 35 * {@code switch} statement. But it is more readable if it comes after the last {@code case}. 36 * </p> 37 * <ul> 38 * <li> 39 * Property {@code skipIfLastAndSharedWithCase} - Control whether to allow {@code default} 40 * along with {@code case} if they are not last. 41 * Default value is {@code false}. 42 * </li> 43 * </ul> 44 * <p> 45 * To configure the check: 46 * </p> 47 * <pre> 48 * <module name="DefaultComesLast"/> 49 * </pre> 50 * <p> 51 * To configure the check for skipIfLastAndSharedWithCase: 52 * </p> 53 * <pre> 54 * <module name="DefaultComesLast"> 55 * <property name="skipIfLastAndSharedWithCase" value="true"/> 56 * </module> 57 * </pre> 58 * <p> 59 * Example when skipIfLastAndSharedWithCase is set to true. 60 * </p> 61 * <pre> 62 * switch (i) { 63 * case 1: 64 * break; 65 * case 2: 66 * default: // No violation with the new option is expected 67 * break; 68 * case 3: 69 * break; 70 * } 71 * switch (i) { 72 * case 1: 73 * break; 74 * default: // violation with the new option is expected 75 * case 2: 76 * break; 77 * } 78 * </pre> 79 * 80 * @since 3.4 81 */ 82 @StatelessCheck 83 public class DefaultComesLastCheck extends AbstractCheck { 84 85 /** 86 * A key is pointing to the warning message text in "messages.properties" 87 * file. 88 */ 89 public static final String MSG_KEY = "default.comes.last"; 90 91 /** 92 * A key is pointing to the warning message text in "messages.properties" 93 * file. 94 */ 95 public static final String MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE = 96 "default.comes.last.in.casegroup"; 97 98 /** Control whether to allow {@code default} along with {@code case} if they are not last. */ 99 private boolean skipIfLastAndSharedWithCase; 100 101 @Override 102 public int[] getAcceptableTokens() { 103 return getRequiredTokens(); 104 } 105 106 @Override 107 public int[] getDefaultTokens() { 108 return getRequiredTokens(); 109 } 110 111 @Override 112 public int[] getRequiredTokens() { 113 return new int[] { 114 TokenTypes.LITERAL_DEFAULT, 115 }; 116 } 117 118 /** 119 * Setter to control whether to allow {@code default} along with 120 * {@code case} if they are not last. 121 * @param newValue whether to ignore checking. 122 */ 123 public void setSkipIfLastAndSharedWithCase(boolean newValue) { 124 skipIfLastAndSharedWithCase = newValue; 125 } 126 127 @Override 128 public void visitToken(DetailAST ast) { 129 final DetailAST defaultGroupAST = ast.getParent(); 130 //default keywords used in annotations too - not what we're 131 //interested in 132 if (defaultGroupAST.getType() != TokenTypes.ANNOTATION_FIELD_DEF 133 && defaultGroupAST.getType() != TokenTypes.MODIFIERS) { 134 if (skipIfLastAndSharedWithCase) { 135 if (Objects.nonNull(findNextSibling(ast, TokenTypes.LITERAL_CASE))) { 136 log(ast, MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE); 137 } 138 else if (ast.getPreviousSibling() == null 139 && Objects.nonNull(findNextSibling(defaultGroupAST, 140 TokenTypes.CASE_GROUP))) { 141 log(ast, MSG_KEY); 142 } 143 } 144 else if (Objects.nonNull(findNextSibling(defaultGroupAST, 145 TokenTypes.CASE_GROUP))) { 146 log(ast, MSG_KEY); 147 } 148 } 149 } 150 151 /** 152 * Return token type only if passed tokenType in argument is found or returns -1. 153 * 154 * @param ast root node. 155 * @param tokenType tokentype to be processed. 156 * @return token if desired token is found or else null. 157 */ 158 private static DetailAST findNextSibling(DetailAST ast, int tokenType) { 159 DetailAST token = null; 160 DetailAST node = ast.getNextSibling(); 161 while (node != null) { 162 if (node.getType() == tokenType) { 163 token = node; 164 break; 165 } 166 node = node.getNextSibling(); 167 } 168 return token; 169 } 170 171 }