001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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> 051 * To configure the check for skipIfLastAndSharedWithCase: 052 * </p> 053 * <pre> 054 * <module name="DefaultComesLast"> 055 * <property name="skipIfLastAndSharedWithCase" value="true"/> 056 * </module> 057 * </pre> 058 * <p> 059 * Example when skipIfLastAndSharedWithCase is set to true. 060 * </p> 061 * <pre> 062 * switch (i) { 063 * case 1: 064 * break; 065 * case 2: 066 * default: // No violation with the new option is expected 067 * break; 068 * case 3: 069 * break; 070 * } 071 * switch (i) { 072 * case 1: 073 * break; 074 * default: // violation with the new option is expected 075 * case 2: 076 * break; 077 * } 078 * </pre> 079 * 080 * @since 3.4 081 */ 082@StatelessCheck 083public class DefaultComesLastCheck extends AbstractCheck { 084 085 /** 086 * A key is pointing to the warning message text in "messages.properties" 087 * file. 088 */ 089 public static final String MSG_KEY = "default.comes.last"; 090 091 /** 092 * A key is pointing to the warning message text in "messages.properties" 093 * file. 094 */ 095 public static final String MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE = 096 "default.comes.last.in.casegroup"; 097 098 /** Control whether to allow {@code default} along with {@code case} if they are not last. */ 099 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 if (skipIfLastAndSharedWithCase) { 131 if (Objects.nonNull(findNextSibling(ast, TokenTypes.LITERAL_CASE))) { 132 log(ast, MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE); 133 } 134 else if (ast.getPreviousSibling() == null 135 && Objects.nonNull(findNextSibling(defaultGroupAST, 136 TokenTypes.CASE_GROUP))) { 137 log(ast, MSG_KEY); 138 } 139 } 140 else if (Objects.nonNull(findNextSibling(defaultGroupAST, 141 TokenTypes.CASE_GROUP))) { 142 log(ast, MSG_KEY); 143 } 144 } 145 146 /** 147 * Return token type only if passed tokenType in argument is found or returns -1. 148 * 149 * @param ast root node. 150 * @param tokenType tokentype to be processed. 151 * @return token if desired token is found or else null. 152 */ 153 private static DetailAST findNextSibling(DetailAST ast, int tokenType) { 154 DetailAST token = null; 155 DetailAST node = ast.getNextSibling(); 156 while (node != null) { 157 if (node.getType() == tokenType) { 158 token = node; 159 break; 160 } 161 node = node.getNextSibling(); 162 } 163 return token; 164 } 165 166}