View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  
11  /**
12   * Checks if possible to use Diamond operator in generic instances creation.
13   *
14   * <p>Check is performed for variable declarations. Since parameterized types are invariant
15   * in generics, Diamond operator should always be used in variable declarations.</p>
16   *
17   * <p>For example,
18   * <pre>
19   *     private List&lt;Number&gt; numbers = new ArrayList&lt;Integer&gt;(); // error
20   * </pre>
21   * will return compilation error (because <code>ArrayList&lt;Integer&gt;</code> is not
22   * a subclass of <code>List&lt;Number&gt;</code>).
23   * </p>
24   * <p>Hence, the only possible way to create a generic instance is copying type arguments from
25   * the variable declaration.
26   * <pre>
27   *     private List&lt;Number&gt; numbers = new ArrayList&lt;Number&gt;();
28   * </pre>
29   * In that case, Diamond Operator should always be used.
30   * <pre>
31   *     private List&lt;Number&gt; numbers = new ArrayList&lt;&gt;();
32   * </pre>
33   * </p>
34   * <p>Exceptions to the rule above are wildcards, with them it's possible
35   * to have different type parameters for left and right parts of variable declaration.
36   * <pre>
37   *     // will compile
38   *     private List&lt;? extends Number&gt; numbers = new ArrayList&lt;Integer&gt;();
39   *     private List&lt;? super Integer&gt; list = new ArrayList&lt;Number&gt;();
40   *</pre>
41   * Although, this is not considered as good codestyle,
42   * so it's better to use diamond operator here either.
43   * </p>
44   *
45   * @since 0.17
46   */
47  public final class DiamondOperatorCheck extends AbstractCheck {
48  
49      @Override
50      public int[] getDefaultTokens() {
51          return new int[]{TokenTypes.VARIABLE_DEF};
52      }
53  
54      @Override
55      public int[] getAcceptableTokens() {
56          return this.getDefaultTokens();
57      }
58  
59      @Override
60      public int[] getRequiredTokens() {
61          return this.getDefaultTokens();
62      }
63  
64      @Override
65      public void visitToken(final DetailAST node) {
66          final DetailAST generic = DiamondOperatorCheck
67              .findFirstChildNodeOfType(
68                  node.findFirstToken(TokenTypes.TYPE), TokenTypes.TYPE_ARGUMENTS
69              );
70          final DetailAST assign = node.findFirstToken(TokenTypes.ASSIGN);
71          final DetailAST instance;
72          if (assign == null || generic == null) {
73              instance = null;
74          } else {
75              instance = assign.getFirstChild().getFirstChild();
76          }
77          if (instance != null && instance.getType() == TokenTypes.LITERAL_NEW
78              && DiamondOperatorCheck.validUsage(instance)) {
79              final DetailAST type =
80                  DiamondOperatorCheck.findFirstChildNodeOfType(
81                      instance, TokenTypes.TYPE_ARGUMENTS
82                  );
83              if (type != null && !DiamondOperatorCheck.isDiamondOperatorUsed(type)) {
84                  log(type, "Use diamond operator");
85              }
86          }
87      }
88  
89      /**
90       * Checks if diamond is not required.
91       *
92       * @param node Node
93       * @return True if not array
94       */
95      private static boolean validUsage(final DetailAST node) {
96          return DiamondOperatorCheck.isNotObjectBlock(node)
97              && DiamondOperatorCheck.isNotArray(node)
98              && !DiamondOperatorCheck.isInitUsingDiamond(node);
99      }
100 
101     /**
102      * Checks if node is not array.
103      *
104      * @param node Node
105      * @return True if not array
106      */
107     private static boolean isNotArray(final DetailAST node) {
108         return node.findFirstToken(TokenTypes.ARRAY_DECLARATOR) == null;
109     }
110 
111     /**
112      * Checks if node is object block.
113      *
114      * @param node Node
115      * @return True if not object block
116      */
117     private static boolean isNotObjectBlock(final DetailAST node) {
118         return node.getLastChild().getType() != TokenTypes.OBJBLOCK;
119     }
120 
121     /**
122      * Checks if node has initialization with diamond operator.
123      *
124      * @param node Node
125      * @return True if not object block
126      */
127     private static boolean isInitUsingDiamond(final DetailAST node) {
128         final DetailAST init = node.findFirstToken(TokenTypes.ELIST);
129         boolean typed = false;
130         if (init != null) {
131             final DetailAST inst = DiamondOperatorCheck.secondChild(init);
132             if (inst != null && inst.getType() == TokenTypes.LITERAL_NEW) {
133                 typed =
134                     DiamondOperatorCheck.isDiamondOperatorUsed(
135                         inst.findFirstToken(TokenTypes.TYPE_ARGUMENTS)
136                     );
137             }
138         }
139         return typed;
140     }
141 
142     /**
143      * Checks if node has initialization with diamond operator.
144      *
145      * @param node Node
146      * @return True if not object block
147      */
148     private static DetailAST secondChild(final DetailAST node) {
149         DetailAST result = null;
150         if (node != null) {
151             final DetailAST first = node.getFirstChild();
152             if (first != null) {
153                 result = first.getFirstChild();
154             }
155         }
156         return result;
157     }
158 
159     /**
160      * Checks if node contains empty set of type parameters and
161      * comprises angle brackets only (<>).
162      * @param node Node of type arguments
163      * @return True if node contains angle brackets only
164      */
165     private static boolean isDiamondOperatorUsed(final DetailAST node) {
166         return node != null && node.getChildCount() == 2
167             && node.getFirstChild().getType() == TokenTypes.GENERIC_START
168             && node.getLastChild().getType() == TokenTypes.GENERIC_END;
169     }
170 
171     /**
172      * Returns the first child node of a specified type.
173      *
174      * @param node AST subtree to process.
175      * @param type Type of token
176      * @return Child node of specified type OR NULL!
177      */
178     private static DetailAST findFirstChildNodeOfType(
179         final DetailAST node, final int type
180     ) {
181         DetailAST result = node.findFirstToken(type);
182         if (result == null) {
183             final DetailAST child = node.getFirstChild();
184             if (child != null) {
185                 result = DiamondOperatorCheck
186                     .findFirstChildNodeOfType(child, type);
187             }
188         }
189         return result;
190     }
191 }