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.checks.javadoc;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
31  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
32  import com.puppycrawl.tools.checkstyle.api.DetailAST;
33  import com.puppycrawl.tools.checkstyle.api.FullIdent;
34  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
35  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36  
37  /**
38   * Abstract class that endeavours to maintain type information for the Java
39   * file being checked. It provides helper methods for performing type
40   * information functions.
41   *
42   * @deprecated Checkstyle is not type aware tool and all Checks derived from this
43   *     class are potentially unstable.
44   * @noinspection DeprecatedIsStillUsed, AbstractClassWithOnlyOneDirectInheritor
45   */
46  @Deprecated
47  @FileStatefulCheck
48  public abstract class AbstractTypeAwareCheck extends AbstractCheck {
49  
50      /** Stack of maps for type params. */
51      private final Deque<Map<String, AbstractClassInfo>> typeParams = new ArrayDeque<>();
52  
53      /** Imports details. **/
54      private final Set<String> imports = new HashSet<>();
55  
56      /** Full identifier for package of the method. **/
57      private FullIdent packageFullIdent;
58  
59      /** Name of current class. */
60      private String currentClassName;
61  
62      /** {@code ClassResolver} instance for current tree. */
63      private ClassResolver classResolver;
64  
65      /**
66       * Whether to log class loading errors to the checkstyle report
67       * instead of throwing a RTE.
68       *
69       * <p>Logging errors will avoid stopping checkstyle completely
70       * because of a typo in javadoc. However, with modern IDEs that
71       * support automated refactoring and generate javadoc this will
72       * occur rarely, so by default we assume a configuration problem
73       * in the checkstyle classpath and throw an exception.
74       *
75       * <p>This configuration option was triggered by bug 1422462.
76       */
77      private boolean logLoadErrors = true;
78  
79      /**
80       * Whether to show class loading errors in the checkstyle report.
81       * Request ID 1491630
82       */
83      private boolean suppressLoadErrors;
84  
85      /**
86       * Called to process an AST when visiting it.
87       * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or
88       *             IMPORT tokens.
89       */
90      protected abstract void processAST(DetailAST ast);
91  
92      /**
93       * Logs error if unable to load class information.
94       * Abstract, should be overridden in subclasses.
95       * @param ident class name for which we can no load class.
96       */
97      protected abstract void logLoadError(Token ident);
98  
99      /**
100      * Controls whether to log class loading errors to the checkstyle report
101      * instead of throwing a RTE.
102      *
103      * @param logLoadErrors true if errors should be logged
104      */
105     public final void setLogLoadErrors(boolean logLoadErrors) {
106         this.logLoadErrors = logLoadErrors;
107     }
108 
109     /**
110      * Controls whether to show class loading errors in the checkstyle report.
111      *
112      * @param suppressLoadErrors true if errors shouldn't be shown
113      */
114     public final void setSuppressLoadErrors(boolean suppressLoadErrors) {
115         this.suppressLoadErrors = suppressLoadErrors;
116     }
117 
118     @Override
119     public final int[] getRequiredTokens() {
120         return new int[] {
121             TokenTypes.PACKAGE_DEF,
122             TokenTypes.IMPORT,
123             TokenTypes.CLASS_DEF,
124             TokenTypes.INTERFACE_DEF,
125             TokenTypes.ENUM_DEF,
126         };
127     }
128 
129     @Override
130     public void beginTree(DetailAST rootAST) {
131         packageFullIdent = FullIdent.createFullIdent(null);
132         imports.clear();
133         // add java.lang.* since it's always imported
134         imports.add("java.lang.*");
135         classResolver = null;
136         currentClassName = "";
137         typeParams.clear();
138     }
139 
140     @Override
141     public final void visitToken(DetailAST ast) {
142         if (ast.getType() == TokenTypes.PACKAGE_DEF) {
143             processPackage(ast);
144         }
145         else if (ast.getType() == TokenTypes.IMPORT) {
146             processImport(ast);
147         }
148         else if (ast.getType() == TokenTypes.CLASS_DEF
149                  || ast.getType() == TokenTypes.INTERFACE_DEF
150                  || ast.getType() == TokenTypes.ENUM_DEF) {
151             processClass(ast);
152         }
153         else {
154             if (ast.getType() == TokenTypes.METHOD_DEF) {
155                 processTypeParams(ast);
156             }
157             processAST(ast);
158         }
159     }
160 
161     @Override
162     public final void leaveToken(DetailAST ast) {
163         if (ast.getType() == TokenTypes.CLASS_DEF
164             || ast.getType() == TokenTypes.INTERFACE_DEF
165             || ast.getType() == TokenTypes.ENUM_DEF) {
166             // perhaps it was inner class
167             int dotIdx = currentClassName.lastIndexOf('$');
168             if (dotIdx == -1) {
169                 // perhaps just a class
170                 dotIdx = currentClassName.lastIndexOf('.');
171             }
172             if (dotIdx == -1) {
173                 // looks like a topmost class
174                 currentClassName = "";
175             }
176             else {
177                 currentClassName = currentClassName.substring(0, dotIdx);
178             }
179             typeParams.pop();
180         }
181         else if (ast.getType() == TokenTypes.METHOD_DEF) {
182             typeParams.pop();
183         }
184     }
185 
186     /**
187      * Is exception is unchecked (subclass of {@code RuntimeException}
188      * or {@code Error}.
189      *
190      * @param exception {@code Class} of exception to check
191      * @return true  if exception is unchecked
192      *         false if exception is checked
193      */
194     protected static boolean isUnchecked(Class<?> exception) {
195         return isSubclass(exception, RuntimeException.class)
196             || isSubclass(exception, Error.class);
197     }
198 
199     /**
200      * Checks if one class is subclass of another.
201      *
202      * @param child {@code Class} of class
203      *               which should be child
204      * @param parent {@code Class} of class
205      *                which should be parent
206      * @return true  if aChild is subclass of aParent
207      *         false otherwise
208      */
209     protected static boolean isSubclass(Class<?> child, Class<?> parent) {
210         return parent != null && child != null
211             && parent.isAssignableFrom(child);
212     }
213 
214     /**
215      * Returns the current tree's ClassResolver.
216      * @return {@code ClassResolver} for current tree.
217      */
218     private ClassResolver getClassResolver() {
219         if (classResolver == null) {
220             classResolver =
221                 new ClassResolver(getClassLoader(),
222                                   packageFullIdent.getText(),
223                                   imports);
224         }
225         return classResolver;
226     }
227 
228     /**
229      * Attempts to resolve the Class for a specified name.
230      * @param resolvableClassName name of the class to resolve
231      * @param className name of surrounding class.
232      * @return the resolved class or {@code null}
233      *          if unable to resolve the class.
234      * @noinspection WeakerAccess
235      */
236     // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
237     protected final Class<?> resolveClass(String resolvableClassName,
238                                           String className) {
239         Class<?> clazz;
240         try {
241             clazz = getClassResolver().resolve(resolvableClassName, className);
242         }
243         catch (final ClassNotFoundException ignored) {
244             clazz = null;
245         }
246         return clazz;
247     }
248 
249     /**
250      * Tries to load class. Logs error if unable.
251      * @param ident name of class which we try to load.
252      * @param className name of surrounding class.
253      * @return {@code Class} for a ident.
254      * @noinspection WeakerAccess
255      */
256     // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
257     protected final Class<?> tryLoadClass(Token ident, String className) {
258         final Class<?> clazz = resolveClass(ident.getText(), className);
259         if (clazz == null) {
260             logLoadError(ident);
261         }
262         return clazz;
263     }
264 
265     /**
266      * Common implementation for logLoadError() method.
267      * @param lineNo line number of the problem.
268      * @param columnNo column number of the problem.
269      * @param msgKey message key to use.
270      * @param values values to fill the message out.
271      */
272     protected final void logLoadErrorImpl(int lineNo, int columnNo,
273                                           String msgKey, Object... values) {
274         if (!logLoadErrors) {
275             final LocalizedMessage msg = new LocalizedMessage(lineNo,
276                                                     columnNo,
277                                                     getMessageBundle(),
278                                                     msgKey,
279                                                     values,
280                                                     getSeverityLevel(),
281                                                     getId(),
282                                                     getClass(),
283                                                     null);
284             throw new IllegalStateException(msg.getMessage());
285         }
286 
287         if (!suppressLoadErrors) {
288             log(lineNo, columnNo, msgKey, values);
289         }
290     }
291 
292     /**
293      * Collects the details of a package.
294      * @param ast node containing the package details
295      */
296     private void processPackage(DetailAST ast) {
297         final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
298         packageFullIdent = FullIdent.createFullIdent(nameAST);
299     }
300 
301     /**
302      * Collects the details of imports.
303      * @param ast node containing the import details
304      */
305     private void processImport(DetailAST ast) {
306         final FullIdent name = FullIdent.createFullIdentBelow(ast);
307         imports.add(name.getText());
308     }
309 
310     /**
311      * Process type params (if any) for given class, enum or method.
312      * @param ast class, enum or method to process.
313      */
314     private void processTypeParams(DetailAST ast) {
315         final DetailAST params =
316             ast.findFirstToken(TokenTypes.TYPE_PARAMETERS);
317 
318         final Map<String, AbstractClassInfo> paramsMap = new HashMap<>();
319         typeParams.push(paramsMap);
320 
321         if (params != null) {
322             for (DetailAST child = params.getFirstChild();
323                  child != null;
324                  child = child.getNextSibling()) {
325                 if (child.getType() == TokenTypes.TYPE_PARAMETER) {
326                     final DetailAST bounds =
327                         child.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS);
328                     if (bounds != null) {
329                         final FullIdent name =
330                             FullIdent.createFullIdentBelow(bounds);
331                         final AbstractClassInfo classInfo =
332                             createClassInfo(new Token(name), currentClassName);
333                         final String alias =
334                                 child.findFirstToken(TokenTypes.IDENT).getText();
335                         paramsMap.put(alias, classInfo);
336                     }
337                 }
338             }
339         }
340     }
341 
342     /**
343      * Processes class definition.
344      * @param ast class definition to process.
345      */
346     private void processClass(DetailAST ast) {
347         final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
348         String innerClass = ident.getText();
349 
350         if (!currentClassName.isEmpty()) {
351             innerClass = "$" + innerClass;
352         }
353         currentClassName += innerClass;
354         processTypeParams(ast);
355     }
356 
357     /**
358      * Returns current class.
359      * @return name of current class.
360      */
361     protected final String getCurrentClassName() {
362         return currentClassName;
363     }
364 
365     /**
366      * Creates class info for given name.
367      * @param name name of type.
368      * @param surroundingClass name of surrounding class.
369      * @return class info for given name.
370      */
371     protected final AbstractClassInfo createClassInfo(final Token name,
372                                               final String surroundingClass) {
373         final AbstractClassInfo result;
374         final AbstractClassInfo classInfo = findClassAlias(name.getText());
375         if (classInfo == null) {
376             result = new RegularClass(name, surroundingClass, this);
377         }
378         else {
379             result = new ClassAlias(name, classInfo);
380         }
381         return result;
382     }
383 
384     /**
385      * Looking if a given name is alias.
386      * @param name given name
387      * @return ClassInfo for alias if it exists, null otherwise
388      * @noinspection WeakerAccess
389      */
390     protected final AbstractClassInfo findClassAlias(final String name) {
391         AbstractClassInfo classInfo = null;
392         final Iterator<Map<String, AbstractClassInfo>> iterator = typeParams.descendingIterator();
393         while (iterator.hasNext()) {
394             final Map<String, AbstractClassInfo> paramMap = iterator.next();
395             classInfo = paramMap.get(name);
396             if (classInfo != null) {
397                 break;
398             }
399         }
400         return classInfo;
401     }
402 
403     /**
404      * Contains class's {@code Token}.
405      * @noinspection ProtectedInnerClass
406      */
407     protected abstract static class AbstractClassInfo {
408 
409         /** {@code FullIdent} associated with this class. */
410         private final Token name;
411 
412         /**
413          * Creates new instance of class information object.
414          * @param className token which represents class name.
415          */
416         protected AbstractClassInfo(final Token className) {
417             if (className == null) {
418                 throw new IllegalArgumentException(
419                     "ClassInfo's name should be non-null");
420             }
421             name = className;
422         }
423 
424         /**
425          * Returns class associated with that object.
426          * @return {@code Class} associated with an object.
427          */
428         // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
429         public abstract Class<?> getClazz();
430 
431         /**
432          * Gets class name.
433          * @return class name
434          */
435         public final Token getName() {
436             return name;
437         }
438 
439     }
440 
441     /** Represents regular classes/enums. */
442     private static final class RegularClass extends AbstractClassInfo {
443 
444         /** Name of surrounding class. */
445         private final String surroundingClass;
446         /** The check we use to resolve classes. */
447         private final AbstractTypeAwareCheck check;
448         /** Is class loadable. */
449         private boolean loadable = true;
450         /** {@code Class} object of this class if it's loadable. */
451         private Class<?> classObj;
452 
453         /**
454          * Creates new instance of of class information object.
455          * @param name {@code FullIdent} associated with new object.
456          * @param surroundingClass name of current surrounding class.
457          * @param check the check we use to load class.
458          */
459         /* package */ RegularClass(final Token name,
460                              final String surroundingClass,
461                              final AbstractTypeAwareCheck check) {
462             super(name);
463             this.surroundingClass = surroundingClass;
464             this.check = check;
465         }
466 
467         @Override
468         public Class<?> getClazz() {
469             if (loadable && classObj == null) {
470                 setClazz(check.tryLoadClass(getName(), surroundingClass));
471             }
472             return classObj;
473         }
474 
475         /**
476          * Associates {@code Class} with an object.
477          * @param clazz {@code Class} to associate with.
478          */
479         private void setClazz(Class<?> clazz) {
480             classObj = clazz;
481             loadable = clazz != null;
482         }
483 
484         @Override
485         public String toString() {
486             return "RegularClass[name=" + getName()
487                     + ", in class='" + surroundingClass + '\''
488                     + ", check=" + check.hashCode()
489                     + ", loadable=" + loadable
490                     + ", class=" + classObj
491                     + ']';
492         }
493 
494     }
495 
496     /** Represents type param which is "alias" for real type. */
497     private static class ClassAlias extends AbstractClassInfo {
498 
499         /** Class information associated with the alias. */
500         private final AbstractClassInfo classInfo;
501 
502         /**
503          * Creates new instance of the class.
504          * @param name token which represents name of class alias.
505          * @param classInfo class information associated with the alias.
506          */
507         /* package */ ClassAlias(final Token name, AbstractClassInfo classInfo) {
508             super(name);
509             this.classInfo = classInfo;
510         }
511 
512         @Override
513         public final Class<?> getClazz() {
514             return classInfo.getClazz();
515         }
516 
517         @Override
518         public String toString() {
519             return "ClassAlias[alias " + getName() + " for " + classInfo.getName() + "]";
520         }
521 
522     }
523 
524     /**
525      * Represents text element with location in the text.
526      * @noinspection ProtectedInnerClass
527      */
528     protected static class Token {
529 
530         /** Token's column number. */
531         private final int columnNo;
532         /** Token's line number. */
533         private final int lineNo;
534         /** Token's text. */
535         private final String text;
536 
537         /**
538          * Creates token.
539          * @param text token's text
540          * @param lineNo token's line number
541          * @param columnNo token's column number
542          */
543         public Token(String text, int lineNo, int columnNo) {
544             this.text = text;
545             this.lineNo = lineNo;
546             this.columnNo = columnNo;
547         }
548 
549         /**
550          * Converts FullIdent to Token.
551          * @param fullIdent full ident to convert.
552          */
553         public Token(FullIdent fullIdent) {
554             text = fullIdent.getText();
555             lineNo = fullIdent.getLineNo();
556             columnNo = fullIdent.getColumnNo();
557         }
558 
559         /**
560          * Gets line number of the token.
561          * @return line number of the token
562          */
563         public int getLineNo() {
564             return lineNo;
565         }
566 
567         /**
568          * Gets column number of the token.
569          * @return column number of the token
570          */
571         public int getColumnNo() {
572             return columnNo;
573         }
574 
575         /**
576          * Gets text of the token.
577          * @return text of the token
578          */
579         public String getText() {
580             return text;
581         }
582 
583         @Override
584         public String toString() {
585             return "Token[" + text + "(" + lineNo
586                 + "x" + columnNo + ")]";
587         }
588 
589     }
590 
591 }