View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 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.findFirstChildNodeOfType(
67              node.findFirstToken(TokenTypes.TYPE), TokenTypes.TYPE_ARGUMENTS
68          );
69          final DetailAST assign = node.findFirstToken(TokenTypes.ASSIGN);
70          final DetailAST instance;
71          if (assign == null || generic == null) {
72              instance = null;
73          } else {
74              instance = assign.getFirstChild().getFirstChild();
75          }
76          if (instance != null && instance.getType() == TokenTypes.LITERAL_NEW
77              && DiamondOperatorCheck.validUsage(instance)) {
78              final DetailAST type =
79                  DiamondOperatorCheck.findFirstChildNodeOfType(
80                      instance, TokenTypes.TYPE_ARGUMENTS
81                  );
82              if (type != null && !DiamondOperatorCheck.isDiamondOperatorUsed(type)) {
83                  log(type, "Use diamond operator");
84              }
85          }
86      }
87  
88      /**
89       * Checks if diamond is not required.
90       * @param node Node
91       * @return True if not array
92       */
93      private static boolean validUsage(final DetailAST node) {
94          return DiamondOperatorCheck.isNotObjectBlock(node)
95              && DiamondOperatorCheck.isNotArray(node)
96              && !DiamondOperatorCheck.isInitUsingDiamond(node);
97      }
98  
99      /**
100      * Checks if node is not array.
101      * @param node Node
102      * @return True if not array
103      */
104     private static boolean isNotArray(final DetailAST node) {
105         return node.findFirstToken(TokenTypes.ARRAY_DECLARATOR) == null;
106     }
107 
108     /**
109      * Checks if node is object block.
110      * @param node Node
111      * @return True if not object block
112      */
113     private static boolean isNotObjectBlock(final DetailAST node) {
114         return node.getLastChild().getType() != TokenTypes.OBJBLOCK;
115     }
116 
117     /**
118      * Checks if node has initialization with diamond operator.
119      * @param node Node
120      * @return True if not object block
121      */
122     private static boolean isInitUsingDiamond(final DetailAST node) {
123         final DetailAST init = node.findFirstToken(TokenTypes.ELIST);
124         boolean typed = false;
125         if (init != null) {
126             final DetailAST inst = DiamondOperatorCheck.secondChild(init);
127             if (inst != null && inst.getType() == TokenTypes.LITERAL_NEW) {
128                 typed =
129                     DiamondOperatorCheck.isDiamondOperatorUsed(
130                         inst.findFirstToken(TokenTypes.TYPE_ARGUMENTS)
131                     );
132             }
133         }
134         return typed;
135     }
136 
137     /**
138      * Checks if node has initialization with diamond operator.
139      * @param node Node
140      * @return True if not object block
141      */
142     private static DetailAST secondChild(final DetailAST node) {
143         DetailAST result = null;
144         if (node != null) {
145             final DetailAST first = node.getFirstChild();
146             if (first != null) {
147                 result = first.getFirstChild();
148             }
149         }
150         return result;
151     }
152 
153     /**
154      * Checks if node contains empty set of type parameters and
155      * comprises angle brackets only (<>).
156      * @param node Node of type arguments
157      * @return True if node contains angle brackets only
158      */
159     private static boolean isDiamondOperatorUsed(final DetailAST node) {
160         return node != null && node.getChildCount() == 2
161             && node.getFirstChild().getType() == TokenTypes.GENERIC_START
162             && node.getLastChild().getType() == TokenTypes.GENERIC_END;
163     }
164 
165     /**
166      * Returns the first child node of a specified type.
167      * @param node AST subtree to process
168      * @param type Type of token
169      * @return Child node of specified type OR NULL!
170      */
171     private static DetailAST findFirstChildNodeOfType(
172         final DetailAST node, final int type
173     ) {
174         DetailAST result = node.findFirstToken(type);
175         if (result == null) {
176             final DetailAST child = node.getFirstChild();
177             if (child != null) {
178                 result = DiamondOperatorCheck
179                     .findFirstChildNodeOfType(child, type);
180             }
181         }
182         return result;
183     }
184 }