1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.utils;
21  
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Modifier;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.ResourceBundle;
30  import java.util.function.Consumer;
31  import java.util.function.Predicate;
32  import java.util.stream.Collectors;
33  
34  import com.puppycrawl.tools.checkstyle.api.DetailAST;
35  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36  
37  /**
38   * Contains utility methods for tokens.
39   *
40   */
41  public final class TokenUtil {
42  
43      /** Maps from a token name to value. */
44      private static final Map<String, Integer> TOKEN_NAME_TO_VALUE;
45      /** Maps from a token value to name. */
46      private static final String[] TOKEN_VALUE_TO_NAME;
47  
48      /** Array of all token IDs. */
49      private static final int[] TOKEN_IDS;
50  
51      /** Prefix for exception when getting token by given id. */
52      private static final String TOKEN_ID_EXCEPTION_PREFIX = "given id ";
53  
54      /** Prefix for exception when getting token by given name. */
55      private static final String TOKEN_NAME_EXCEPTION_PREFIX = "given name ";
56  
57      // initialise the constants
58      static {
59          TOKEN_NAME_TO_VALUE = nameToValueMapFromPublicIntFields(TokenTypes.class);
60          TOKEN_VALUE_TO_NAME = valueToNameArrayFromNameToValueMap(TOKEN_NAME_TO_VALUE);
61          TOKEN_IDS = TOKEN_NAME_TO_VALUE.values().stream().mapToInt(Integer::intValue).toArray();
62      }
63  
64      /** Stop instances being created. **/
65      private TokenUtil() {
66      }
67  
68      /**
69       * Gets the value of a static or instance field of type int or of another primitive type
70       * convertible to type int via a widening conversion. Does not throw any checked exceptions.
71       * @param field from which the int should be extracted
72       * @param object to extract the int value from
73       * @return the value of the field converted to type int
74       * @throws IllegalStateException if this Field object is enforcing Java language access control
75       *         and the underlying field is inaccessible
76       * @see Field#getInt(Object)
77       */
78      public static int getIntFromField(Field field, Object object) {
79          try {
80              return field.getInt(object);
81          }
82          catch (final IllegalAccessException exception) {
83              throw new IllegalStateException(exception);
84          }
85      }
86  
87      /**
88       * Creates a map of 'field name' to 'field value' from all {@code public} {@code int} fields
89       * of a class.
90       * @param cls source class
91       * @return unmodifiable name to value map
92       */
93      public static Map<String, Integer> nameToValueMapFromPublicIntFields(Class<?> cls) {
94          final Map<String, Integer> map = Arrays.stream(cls.getDeclaredFields())
95              .filter(fld -> Modifier.isPublic(fld.getModifiers()) && fld.getType() == Integer.TYPE)
96              .collect(Collectors.toMap(Field::getName, fld -> getIntFromField(fld, fld.getName())));
97          return Collections.unmodifiableMap(map);
98      }
99  
100     /**
101      * Creates an array of map keys for quick value-to-name lookup for the map.
102      * @param map source map
103      * @return array of map keys
104      */
105     public static String[] valueToNameArrayFromNameToValueMap(Map<String, Integer> map) {
106         String[] valueToNameArray = CommonUtil.EMPTY_STRING_ARRAY;
107 
108         for (Map.Entry<String, Integer> entry : map.entrySet()) {
109             final int value = entry.getValue();
110             // JavadocTokenTypes.EOF has value '-1' and is handled explicitly.
111             if (value >= 0) {
112                 if (value >= valueToNameArray.length) {
113                     final String[] temp = new String[value + 1];
114                     System.arraycopy(valueToNameArray, 0, temp, 0, valueToNameArray.length);
115                     valueToNameArray = temp;
116                 }
117                 valueToNameArray[value] = entry.getKey();
118             }
119         }
120         return valueToNameArray;
121     }
122 
123     /**
124      * Get total number of TokenTypes.
125      * @return total number of TokenTypes.
126      */
127     public static int getTokenTypesTotalNumber() {
128         return TOKEN_IDS.length;
129     }
130 
131     /**
132      * Get all token IDs that are available in TokenTypes.
133      * @return array of token IDs
134      */
135     public static int[] getAllTokenIds() {
136         final int[] safeCopy = new int[TOKEN_IDS.length];
137         System.arraycopy(TOKEN_IDS, 0, safeCopy, 0, TOKEN_IDS.length);
138         return safeCopy;
139     }
140 
141     /**
142      * Returns the name of a token for a given ID.
143      * @param id the ID of the token name to get
144      * @return a token name
145      */
146     public static String getTokenName(int id) {
147         if (id > TOKEN_VALUE_TO_NAME.length - 1) {
148             throw new IllegalArgumentException(TOKEN_ID_EXCEPTION_PREFIX + id);
149         }
150         final String name = TOKEN_VALUE_TO_NAME[id];
151         if (name == null) {
152             throw new IllegalArgumentException(TOKEN_ID_EXCEPTION_PREFIX + id);
153         }
154         return name;
155     }
156 
157     /**
158      * Returns the ID of a token for a given name.
159      * @param name the name of the token ID to get
160      * @return a token ID
161      */
162     public static int getTokenId(String name) {
163         final Integer id = TOKEN_NAME_TO_VALUE.get(name);
164         if (id == null) {
165             throw new IllegalArgumentException(TOKEN_NAME_EXCEPTION_PREFIX + name);
166         }
167         return id;
168     }
169 
170     /**
171      * Returns the short description of a token for a given name.
172      * @param name the name of the token ID to get
173      * @return a short description
174      */
175     public static String getShortDescription(String name) {
176         if (!TOKEN_NAME_TO_VALUE.containsKey(name)) {
177             throw new IllegalArgumentException(TOKEN_NAME_EXCEPTION_PREFIX + name);
178         }
179 
180         final String tokenTypes =
181             "com.puppycrawl.tools.checkstyle.api.tokentypes";
182         final ResourceBundle bundle = ResourceBundle.getBundle(tokenTypes, Locale.ROOT);
183         return bundle.getString(name);
184     }
185 
186     /**
187      * Is argument comment-related type (SINGLE_LINE_COMMENT,
188      * BLOCK_COMMENT_BEGIN, BLOCK_COMMENT_END, COMMENT_CONTENT).
189      * @param type
190      *        token type.
191      * @return true if type is comment-related type.
192      */
193     public static boolean isCommentType(int type) {
194         return type == TokenTypes.SINGLE_LINE_COMMENT
195                 || type == TokenTypes.BLOCK_COMMENT_BEGIN
196                 || type == TokenTypes.BLOCK_COMMENT_END
197                 || type == TokenTypes.COMMENT_CONTENT;
198     }
199 
200     /**
201      * Is argument comment-related type name (SINGLE_LINE_COMMENT,
202      * BLOCK_COMMENT_BEGIN, BLOCK_COMMENT_END, COMMENT_CONTENT).
203      * @param type
204      *        token type name.
205      * @return true if type is comment-related type name.
206      */
207     public static boolean isCommentType(String type) {
208         return isCommentType(getTokenId(type));
209     }
210 
211     /**
212      * Finds the first {@link Optional} child token of {@link DetailAST} root node
213      * which matches the given predicate.
214      * @param root root node.
215      * @param predicate predicate.
216      * @return {@link Optional} of {@link DetailAST} node which matches the predicate.
217      */
218     public static Optional<DetailAST> findFirstTokenByPredicate(DetailAST root,
219                                                                 Predicate<DetailAST> predicate) {
220         Optional<DetailAST> result = Optional.empty();
221         for (DetailAST ast = root.getFirstChild(); ast != null; ast = ast.getNextSibling()) {
222             if (predicate.test(ast)) {
223                 result = Optional.of(ast);
224                 break;
225             }
226         }
227         return result;
228     }
229 
230     /**
231      * Performs an action for each child of {@link DetailAST} root node
232      * which matches the given token type.
233      * @param root root node.
234      * @param type token type to match.
235      * @param action action to perform on the nodes.
236      */
237     public static void forEachChild(DetailAST root, int type, Consumer<DetailAST> action) {
238         for (DetailAST ast = root.getFirstChild(); ast != null; ast = ast.getNextSibling()) {
239             if (ast.getType() == type) {
240                 action.accept(ast);
241             }
242         }
243     }
244 
245 }