001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.javadoc;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.Map;
028import java.util.Set;
029
030import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
031import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.FullIdent;
034import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
035import com.puppycrawl.tools.checkstyle.api.TokenTypes;
036
037/**
038 * Abstract class that endeavours to maintain type information for the Java
039 * file being checked. It provides helper methods for performing type
040 * information functions.
041 *
042 * @deprecated Checkstyle is not type aware tool and all Checks derived from this
043 *     class are potentially unstable.
044 * @noinspection DeprecatedIsStillUsed, AbstractClassWithOnlyOneDirectInheritor
045 */
046@Deprecated
047@FileStatefulCheck
048public abstract class AbstractTypeAwareCheck extends AbstractCheck {
049
050    /** Stack of maps for type params. */
051    private final Deque<Map<String, AbstractClassInfo>> typeParams = new ArrayDeque<>();
052
053    /** Imports details. **/
054    private final Set<String> imports = new HashSet<>();
055
056    /** Full identifier for package of the method. **/
057    private FullIdent packageFullIdent;
058
059    /** Name of current class. */
060    private String currentClassName;
061
062    /** {@code ClassResolver} instance for current tree. */
063    private ClassResolver classResolver;
064
065    /**
066     * Whether to log class loading errors to the checkstyle report
067     * instead of throwing a RTE.
068     *
069     * <p>Logging errors will avoid stopping checkstyle completely
070     * because of a typo in javadoc. However, with modern IDEs that
071     * support automated refactoring and generate javadoc this will
072     * occur rarely, so by default we assume a configuration problem
073     * in the checkstyle classpath and throw an exception.
074     *
075     * <p>This configuration option was triggered by bug 1422462.
076     */
077    private boolean logLoadErrors = true;
078
079    /**
080     * Whether to show class loading errors in the checkstyle report.
081     * Request ID 1491630
082     */
083    private boolean suppressLoadErrors;
084
085    /**
086     * Called to process an AST when visiting it.
087     * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or
088     *             IMPORT tokens.
089     */
090    protected abstract void processAST(DetailAST ast);
091
092    /**
093     * Logs error if unable to load class information.
094     * Abstract, should be overridden in subclasses.
095     * @param ident class name for which we can no load class.
096     */
097    protected abstract void logLoadError(Token ident);
098
099    /**
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        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        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}