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