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 * Default value is {@code false}. 042 * </li> 043 * </ul> 044 * <p> 045 * To configure the check: 046 * </p> 047 * <pre> 048 * <module name="DefaultComesLast"/> 049 * </pre> 050 * <p>Example:</p> 051 * <pre> 052 * switch (i) { 053 * case 1: 054 * break; 055 * case 2: 056 * break; 057 * default: // OK 058 * break; 059 * } 060 * 061 * switch (i) { 062 * case 1: 063 * break; 064 * case 2: 065 * break; // OK, no default 066 * } 067 * 068 * switch (i) { 069 * case 1: 070 * break; 071 * default: // violation, 'default' before 'case' 072 * break; 073 * case 2: 074 * break; 075 * } 076 * 077 * switch (i) { 078 * case 1: 079 * default: // violation, 'default' before 'case' 080 * break; 081 * case 2: 082 * break; 083 * } 084 * </pre> 085 * <p>To configure the check to allow default label to be not last if it is shared with case: 086 * </p> 087 * <pre> 088 * <module name="DefaultComesLast"> 089 * <property name="skipIfLastAndSharedWithCase" value="true"/> 090 * </module> 091 * </pre> 092 * <p>Example:</p> 093 * <pre> 094 * switch (i) { 095 * case 1: 096 * break; 097 * case 2: 098 * default: // OK 099 * break; 100 * case 3: 101 * break; 102 * } 103 * 104 * switch (i) { 105 * case 1: 106 * break; 107 * default: // violation 108 * case 2: 109 * break; 110 * } 111 * </pre> 112 * 113 * @since 3.4 114 */ 115@StatelessCheck 116public class DefaultComesLastCheck extends AbstractCheck { 117 118 /** 119 * A key is pointing to the warning message text in "messages.properties" 120 * file. 121 */ 122 public static final String MSG_KEY = "default.comes.last"; 123 124 /** 125 * A key is pointing to the warning message text in "messages.properties" 126 * file. 127 */ 128 public static final String MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE = 129 "default.comes.last.in.casegroup"; 130 131 /** Control whether to allow {@code default} along with {@code case} if they are not last. */ 132 private boolean skipIfLastAndSharedWithCase; 133 134 @Override 135 public int[] getAcceptableTokens() { 136 return getRequiredTokens(); 137 } 138 139 @Override 140 public int[] getDefaultTokens() { 141 return getRequiredTokens(); 142 } 143 144 @Override 145 public int[] getRequiredTokens() { 146 return new int[] { 147 TokenTypes.LITERAL_DEFAULT, 148 }; 149 } 150 151 /** 152 * Setter to control whether to allow {@code default} along with 153 * {@code case} if they are not last. 154 * 155 * @param newValue whether to ignore checking. 156 */ 157 public void setSkipIfLastAndSharedWithCase(boolean newValue) { 158 skipIfLastAndSharedWithCase = newValue; 159 } 160 161 @Override 162 public void visitToken(DetailAST ast) { 163 final DetailAST defaultGroupAST = ast.getParent(); 164 if (skipIfLastAndSharedWithCase) { 165 if (Objects.nonNull(findNextSibling(ast, TokenTypes.LITERAL_CASE))) { 166 log(ast, MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE); 167 } 168 else if (ast.getPreviousSibling() == null 169 && Objects.nonNull(findNextSibling(defaultGroupAST, 170 TokenTypes.CASE_GROUP))) { 171 log(ast, MSG_KEY); 172 } 173 } 174 else if (Objects.nonNull(findNextSibling(defaultGroupAST, 175 TokenTypes.CASE_GROUP))) { 176 log(ast, MSG_KEY); 177 } 178 } 179 180 /** 181 * Return token type only if passed tokenType in argument is found or returns -1. 182 * 183 * @param ast root node. 184 * @param tokenType tokentype to be processed. 185 * @return token if desired token is found or else null. 186 */ 187 private static DetailAST findNextSibling(DetailAST ast, int tokenType) { 188 DetailAST token = null; 189 DetailAST node = ast.getNextSibling(); 190 while (node != null) { 191 if (node.getType() == tokenType) { 192 token = node; 193 break; 194 } 195 node = node.getNextSibling(); 196 } 197 return token; 198 } 199 200}