1 /*
2 * Copyright (c) 2011-2025 Yegor Bugayenko
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met: 1) Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer. 2) Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following
12 * disclaimer in the documentation and/or other materials provided
13 * with the distribution. 3) Neither the name of the Qulice.com nor
14 * the names of its contributors may be used to endorse or promote
15 * products derived from this software without specific prior written
16 * permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31 package com.qulice.checkstyle;
32
33 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
34 import com.puppycrawl.tools.checkstyle.api.DetailAST;
35 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36
37 /**
38 * Checks if possible to use Diamond operator in generic instances creation.
39 *
40 * <p>Check is performed for variable declarations. Since parameterized types are invariant
41 * in generics, Diamond operator should always be used in variable declarations.</p>
42 *
43 * <p>For example,
44 * <pre>
45 * private List<Number> numbers = new ArrayList<Integer>(); // error
46 * </pre>
47 * will return compilation error (because <code>ArrayList<Integer></code> is not
48 * a subclass of <code>List<Number></code>).
49 * </p>
50 * <p>Hence, the only possible way to create a generic instance is copying type arguments from
51 * the variable declaration.
52 * <pre>
53 * private List<Number> numbers = new ArrayList<Number>();
54 * </pre>
55 * In that case, Diamond Operator should always be used.
56 * <pre>
57 * private List<Number> numbers = new ArrayList<>();
58 * </pre>
59 * </p>
60 * <p>Exceptions to the rule above are wildcards, with them it's possible
61 * to have different type parameters for left and right parts of variable declaration.
62 * <pre>
63 * // will compile
64 * private List<? extends Number> numbers = new ArrayList<Integer>();
65 * private List<? super Integer> list = new ArrayList<Number>();
66 *</pre>
67 * Although, this is not considered as good codestyle,
68 * so it's better to use diamond operator here either.
69 * </p>
70 *
71 * @since 0.17
72 */
73 public final class DiamondOperatorCheck extends AbstractCheck {
74
75 @Override
76 public int[] getDefaultTokens() {
77 return new int[]{TokenTypes.VARIABLE_DEF};
78 }
79
80 @Override
81 public int[] getAcceptableTokens() {
82 return this.getDefaultTokens();
83 }
84
85 @Override
86 public int[] getRequiredTokens() {
87 return this.getDefaultTokens();
88 }
89
90 @Override
91 public void visitToken(final DetailAST node) {
92 final DetailAST generic = DiamondOperatorCheck
93 .findFirstChildNodeOfType(
94 node.findFirstToken(TokenTypes.TYPE), TokenTypes.TYPE_ARGUMENTS
95 );
96 final DetailAST assign = node.findFirstToken(TokenTypes.ASSIGN);
97 final DetailAST instance;
98 if (assign == null || generic == null) {
99 instance = null;
100 } else {
101 instance = assign.getFirstChild().getFirstChild();
102 }
103 if (instance != null && instance.getType() == TokenTypes.LITERAL_NEW
104 && DiamondOperatorCheck.validUsage(instance)) {
105 final DetailAST type =
106 DiamondOperatorCheck.findFirstChildNodeOfType(
107 instance, TokenTypes.TYPE_ARGUMENTS
108 );
109 if (type != null && !DiamondOperatorCheck.isDiamondOperatorUsed(type)) {
110 log(type, "Use diamond operator");
111 }
112 }
113 }
114
115 /**
116 * Checks if diamond is not required.
117 *
118 * @param node Node
119 * @return True if not array
120 */
121 private static boolean validUsage(final DetailAST node) {
122 return DiamondOperatorCheck.isNotObjectBlock(node)
123 && DiamondOperatorCheck.isNotArray(node)
124 && !DiamondOperatorCheck.isInitUsingDiamond(node);
125 }
126
127 /**
128 * Checks if node is not array.
129 *
130 * @param node Node
131 * @return True if not array
132 */
133 private static boolean isNotArray(final DetailAST node) {
134 return node.findFirstToken(TokenTypes.ARRAY_DECLARATOR) == null;
135 }
136
137 /**
138 * Checks if node is object block.
139 *
140 * @param node Node
141 * @return True if not object block
142 */
143 private static boolean isNotObjectBlock(final DetailAST node) {
144 return node.getLastChild().getType() != TokenTypes.OBJBLOCK;
145 }
146
147 /**
148 * Checks if node has initialization with diamond operator.
149 *
150 * @param node Node
151 * @return True if not object block
152 */
153 private static boolean isInitUsingDiamond(final DetailAST node) {
154 final DetailAST init = node.findFirstToken(TokenTypes.ELIST);
155 boolean typed = false;
156 if (init != null) {
157 final DetailAST inst = DiamondOperatorCheck.secondChild(init);
158 if (inst != null && inst.getType() == TokenTypes.LITERAL_NEW) {
159 typed =
160 DiamondOperatorCheck.isDiamondOperatorUsed(
161 inst.findFirstToken(TokenTypes.TYPE_ARGUMENTS)
162 );
163 }
164 }
165 return typed;
166 }
167
168 /**
169 * Checks if node has initialization with diamond operator.
170 *
171 * @param node Node
172 * @return True if not object block
173 */
174 private static DetailAST secondChild(final DetailAST node) {
175 DetailAST result = null;
176 if (node != null) {
177 final DetailAST first = node.getFirstChild();
178 if (first != null) {
179 result = first.getFirstChild();
180 }
181 }
182 return result;
183 }
184
185 /**
186 * Checks if node contains empty set of type parameters and
187 * comprises angle brackets only (<>).
188 * @param node Node of type arguments
189 * @return True if node contains angle brackets only
190 */
191 private static boolean isDiamondOperatorUsed(final DetailAST node) {
192 return node != null && node.getChildCount() == 2
193 && node.getFirstChild().getType() == TokenTypes.GENERIC_START
194 && node.getLastChild().getType() == TokenTypes.GENERIC_END;
195 }
196
197 /**
198 * Returns the first child node of a specified type.
199 *
200 * @param node AST subtree to process.
201 * @param type Type of token
202 * @return Child node of specified type OR NULL!
203 */
204 private static DetailAST findFirstChildNodeOfType(
205 final DetailAST node, final int type
206 ) {
207 DetailAST result = node.findFirstToken(type);
208 if (result == null) {
209 final DetailAST child = node.getFirstChild();
210 if (child != null) {
211 result = DiamondOperatorCheck
212 .findFirstChildNodeOfType(child, type);
213 }
214 }
215 return result;
216 }
217 }