View Javadoc
1   /*
2    * Copyright (c) 2011-2024 Qulice.com
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&lt;Number&gt; numbers = new ArrayList&lt;Integer&gt;(); // error
46   * </pre>
47   * will return compilation error (because <code>ArrayList&lt;Integer&gt;</code> is not
48   * a subclass of <code>List&lt;Number&gt;</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&lt;Number&gt; numbers = new ArrayList&lt;Number&gt;();
54   * </pre>
55   * In that case, Diamond Operator should always be used.
56   * <pre>
57   *     private List&lt;Number&gt; numbers = new ArrayList&lt;&gt;();
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&lt;? extends Number&gt; numbers = new ArrayList&lt;Integer&gt;();
65   *     private List&lt;? super Integer&gt; list = new ArrayList&lt;Number&gt;();
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 }