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,</p>
18   * <pre>
19   *     private List&lt;Number&gt; numbers = new ArrayList&lt;Integer&gt;(); // error
20   * </pre>
21   *
22   * <p>will return compilation error (because <code>ArrayList&lt;Integer&gt;</code> is not
23   * a subclass of <code>List&lt;Number&gt;</code>).</p>
24   *
25   * <p>Hence, the only possible way to create a generic instance is copying type arguments from
26   * the variable declaration.</p>
27   * <pre>
28   *     private List&lt;Number&gt; numbers = new ArrayList&lt;Number&gt;();
29   * </pre>
30   *
31   * <p>In that case, Diamond Operator should always be used.</p>
32   * <pre>
33   *     private List&lt;Number&gt; numbers = new ArrayList&lt;&gt;();
34   * </pre>
35   *
36   * <p>Exceptions to the rule above are wildcards, with them it's possible
37   * to have different type parameters for left and right parts of variable declaration.</p>
38   * <pre>
39   *     // will compile
40   *     private List&lt;? extends Number&gt; numbers = new ArrayList&lt;Integer&gt;();
41   *     private List&lt;? super Integer&gt; list = new ArrayList&lt;Number&gt;();
42   * </pre>
43   *
44   * <p>Although, this is not considered as good codestyle,
45   * so it's better to use diamond operator here either.</p>
46   *
47   * @since 0.17
48   */
49  public final class DiamondOperatorCheck extends AbstractCheck {
50  
51      /**
52       * Default constructor.
53       */
54      public DiamondOperatorCheck() {
55          // nothing to initialize
56      }
57  
58      @Override
59      public int[] getDefaultTokens() {
60          return new int[]{TokenTypes.VARIABLE_DEF};
61      }
62  
63      @Override
64      public int[] getAcceptableTokens() {
65          return this.getDefaultTokens();
66      }
67  
68      @Override
69      public int[] getRequiredTokens() {
70          return this.getDefaultTokens();
71      }
72  
73      @Override
74      public void visitToken(final DetailAST node) {
75          final DetailAST generic = DiamondOperatorCheck.findFirstChildNodeOfType(
76              node.findFirstToken(TokenTypes.TYPE), TokenTypes.TYPE_ARGUMENTS
77          );
78          final DetailAST assign = node.findFirstToken(TokenTypes.ASSIGN);
79          final DetailAST instance;
80          if (assign == null || generic == null) {
81              instance = null;
82          } else {
83              instance = assign.getFirstChild().getFirstChild();
84          }
85          if (instance != null && instance.getType() == TokenTypes.LITERAL_NEW
86              && DiamondOperatorCheck.validUsage(instance)) {
87              final DetailAST type =
88                  DiamondOperatorCheck.findFirstChildNodeOfType(
89                      instance, TokenTypes.TYPE_ARGUMENTS
90                  );
91              if (type != null && !DiamondOperatorCheck.isDiamondOperatorUsed(type)) {
92                  log(type, "Use diamond operator");
93              }
94          }
95      }
96  
97      private static boolean validUsage(final DetailAST node) {
98          return DiamondOperatorCheck.isNotObjectBlock(node)
99              && DiamondOperatorCheck.isNotArray(node)
100             && !DiamondOperatorCheck.isInitUsingDiamond(node);
101     }
102 
103     private static boolean isNotArray(final DetailAST node) {
104         return node.findFirstToken(TokenTypes.ARRAY_DECLARATOR) == null;
105     }
106 
107     private static boolean isNotObjectBlock(final DetailAST node) {
108         return node.getLastChild().getType() != TokenTypes.OBJBLOCK;
109     }
110 
111     private static boolean isInitUsingDiamond(final DetailAST node) {
112         final DetailAST init = node.findFirstToken(TokenTypes.ELIST);
113         boolean typed = false;
114         if (init != null) {
115             final DetailAST inst = DiamondOperatorCheck.secondChild(init);
116             if (inst != null && inst.getType() == TokenTypes.LITERAL_NEW) {
117                 typed =
118                     DiamondOperatorCheck.isDiamondOperatorUsed(
119                         inst.findFirstToken(TokenTypes.TYPE_ARGUMENTS)
120                     );
121             }
122         }
123         return typed;
124     }
125 
126     private static DetailAST secondChild(final DetailAST node) {
127         DetailAST result = null;
128         if (node != null) {
129             final DetailAST first = node.getFirstChild();
130             if (first != null) {
131                 result = first.getFirstChild();
132             }
133         }
134         return result;
135     }
136 
137     private static boolean isDiamondOperatorUsed(final DetailAST node) {
138         return node != null && node.getChildCount() == 2
139             && node.getFirstChild().getType() == TokenTypes.GENERIC_START
140             && node.getLastChild().getType() == TokenTypes.GENERIC_END;
141     }
142 
143     private static DetailAST findFirstChildNodeOfType(
144         final DetailAST node, final int type
145     ) {
146         DetailAST result = node.findFirstToken(type);
147         if (result == null) {
148             final DetailAST child = node.getFirstChild();
149             if (child != null) {
150                 result = DiamondOperatorCheck
151                     .findFirstChildNodeOfType(child, type);
152             }
153         }
154         return result;
155     }
156 }