001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.Objects; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * <p> 031 * Check that the {@code default} is after all the cases in a {@code switch} statement. 032 * </p> 033 * <p> 034 * Rationale: Java allows {@code default} anywhere within the 035 * {@code switch} statement. But it is more readable if it comes after the last {@code case}. 036 * </p> 037 * <ul> 038 * <li> 039 * Property {@code skipIfLastAndSharedWithCase} - Control whether to allow {@code default} 040 * along with {@code case} if they are not last. 041 * Type is {@code boolean}. 042 * Default value is {@code false}. 043 * </li> 044 * </ul> 045 * <p> 046 * To configure the check: 047 * </p> 048 * <pre> 049 * <module name="DefaultComesLast"/> 050 * </pre> 051 * <p>Example:</p> 052 * <pre> 053 * switch (i) { 054 * case 1: 055 * break; 056 * case 2: 057 * break; 058 * default: // OK 059 * break; 060 * } 061 * 062 * switch (i) { 063 * case 1: 064 * break; 065 * case 2: 066 * break; // OK, no default 067 * } 068 * 069 * switch (i) { 070 * case 1: 071 * break; 072 * default: // violation, 'default' before 'case' 073 * break; 074 * case 2: 075 * break; 076 * } 077 * 078 * switch (i) { 079 * case 1: 080 * default: // violation, 'default' before 'case' 081 * break; 082 * case 2: 083 * break; 084 * } 085 * </pre> 086 * <p>To configure the check to allow default label to be not last if it is shared with case: 087 * </p> 088 * <pre> 089 * <module name="DefaultComesLast"> 090 * <property name="skipIfLastAndSharedWithCase" value="true"/> 091 * </module> 092 * </pre> 093 * <p>Example:</p> 094 * <pre> 095 * switch (i) { 096 * case 1: 097 * break; 098 * case 2: 099 * default: // OK 100 * break; 101 * case 3: 102 * break; 103 * } 104 * 105 * switch (i) { 106 * case 1: 107 * break; 108 * default: // violation 109 * case 2: 110 * break; 111 * } 112 * </pre> 113 * <p> 114 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 115 * </p> 116 * <p> 117 * Violation Message Keys: 118 * </p> 119 * <ul> 120 * <li> 121 * {@code default.comes.last} 122 * </li> 123 * <li> 124 * {@code default.comes.last.in.casegroup} 125 * </li> 126 * </ul> 127 * 128 * @since 3.4 129 */ 130@StatelessCheck 131public class DefaultComesLastCheck extends AbstractCheck { 132 133 /** 134 * A key is pointing to the warning message text in "messages.properties" 135 * file. 136 */ 137 public static final String MSG_KEY = "default.comes.last"; 138 139 /** 140 * A key is pointing to the warning message text in "messages.properties" 141 * file. 142 */ 143 public static final String MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE = 144 "default.comes.last.in.casegroup"; 145 146 /** Control whether to allow {@code default} along with {@code case} if they are not last. */ 147 private boolean skipIfLastAndSharedWithCase; 148 149 @Override 150 public int[] getAcceptableTokens() { 151 return getRequiredTokens(); 152 } 153 154 @Override 155 public int[] getDefaultTokens() { 156 return getRequiredTokens(); 157 } 158 159 @Override 160 public int[] getRequiredTokens() { 161 return new int[] { 162 TokenTypes.LITERAL_DEFAULT, 163 }; 164 } 165 166 /** 167 * Setter to control whether to allow {@code default} along with 168 * {@code case} if they are not last. 169 * 170 * @param newValue whether to ignore checking. 171 */ 172 public void setSkipIfLastAndSharedWithCase(boolean newValue) { 173 skipIfLastAndSharedWithCase = newValue; 174 } 175 176 @Override 177 public void visitToken(DetailAST ast) { 178 final DetailAST defaultGroupAST = ast.getParent(); 179 if (skipIfLastAndSharedWithCase) { 180 if (Objects.nonNull(findNextSibling(ast, TokenTypes.LITERAL_CASE))) { 181 log(ast, MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE); 182 } 183 else if (ast.getPreviousSibling() == null 184 && Objects.nonNull(findNextSibling(defaultGroupAST, 185 TokenTypes.CASE_GROUP))) { 186 log(ast, MSG_KEY); 187 } 188 } 189 else if (Objects.nonNull(findNextSibling(defaultGroupAST, 190 TokenTypes.CASE_GROUP))) { 191 log(ast, MSG_KEY); 192 } 193 } 194 195 /** 196 * Return token type only if passed tokenType in argument is found or returns -1. 197 * 198 * @param ast root node. 199 * @param tokenType tokentype to be processed. 200 * @return token if desired token is found or else null. 201 */ 202 private static DetailAST findNextSibling(DetailAST ast, int tokenType) { 203 DetailAST token = null; 204 DetailAST node = ast.getNextSibling(); 205 while (node != null) { 206 if (node.getType() == tokenType) { 207 token = node; 208 break; 209 } 210 node = node.getNextSibling(); 211 } 212 return token; 213 } 214 215}