001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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    /** 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        /** {@code FullIdent} associated with this class. */
409        private final Token name;
410
411        /**
412         * Creates new instance of class information object.
413         * @param className token which represents class name.
414         */
415        protected AbstractClassInfo(final Token className) {
416            if (className == null) {
417                throw new IllegalArgumentException(
418                    "ClassInfo's name should be non-null");
419            }
420            name = className;
421        }
422
423        /**
424         * Returns class associated with that object.
425         * @return {@code Class} associated with an object.
426         */
427        // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
428        public abstract Class<?> getClazz();
429
430        /**
431         * Gets class name.
432         * @return class name
433         */
434        public final Token getName() {
435            return name;
436        }
437    }
438
439    /** Represents regular classes/enums. */
440    private static final class RegularClass extends AbstractClassInfo {
441        /** Name of surrounding class. */
442        private final String surroundingClass;
443        /** The check we use to resolve classes. */
444        private final AbstractTypeAwareCheck check;
445        /** Is class loadable. */
446        private boolean loadable = true;
447        /** {@code Class} object of this class if it's loadable. */
448        private Class<?> classObj;
449
450        /**
451         * Creates new instance of of class information object.
452         * @param name {@code FullIdent} associated with new object.
453         * @param surroundingClass name of current surrounding class.
454         * @param check the check we use to load class.
455         */
456        RegularClass(final Token name,
457                             final String surroundingClass,
458                             final AbstractTypeAwareCheck check) {
459            super(name);
460            this.surroundingClass = surroundingClass;
461            this.check = check;
462        }
463
464        @Override
465        public Class<?> getClazz() {
466            if (loadable && classObj == null) {
467                setClazz(check.tryLoadClass(getName(), surroundingClass));
468            }
469            return classObj;
470        }
471
472        /**
473         * Associates {@code Class} with an object.
474         * @param clazz {@code Class} to associate with.
475         */
476        private void setClazz(Class<?> clazz) {
477            classObj = clazz;
478            loadable = clazz != null;
479        }
480
481        @Override
482        public String toString() {
483            return "RegularClass[name=" + getName()
484                    + ", in class='" + surroundingClass + '\''
485                    + ", check=" + check.hashCode()
486                    + ", loadable=" + loadable
487                    + ", class=" + classObj
488                    + ']';
489        }
490    }
491
492    /** Represents type param which is "alias" for real type. */
493    private static class ClassAlias extends AbstractClassInfo {
494        /** Class information associated with the alias. */
495        private final AbstractClassInfo classInfo;
496
497        /**
498         * Creates new instance of the class.
499         * @param name token which represents name of class alias.
500         * @param classInfo class information associated with the alias.
501         */
502        ClassAlias(final Token name, AbstractClassInfo classInfo) {
503            super(name);
504            this.classInfo = classInfo;
505        }
506
507        @Override
508        public final Class<?> getClazz() {
509            return classInfo.getClazz();
510        }
511
512        @Override
513        public String toString() {
514            return "ClassAlias[alias " + getName() + " for " + classInfo.getName() + "]";
515        }
516    }
517
518    /**
519     * Represents text element with location in the text.
520     * @noinspection ProtectedInnerClass
521     */
522    protected static class Token {
523        /** Token's column number. */
524        private final int columnNo;
525        /** Token's line number. */
526        private final int lineNo;
527        /** Token's text. */
528        private final String text;
529
530        /**
531         * Creates token.
532         * @param text token's text
533         * @param lineNo token's line number
534         * @param columnNo token's column number
535         */
536        public Token(String text, int lineNo, int columnNo) {
537            this.text = text;
538            this.lineNo = lineNo;
539            this.columnNo = columnNo;
540        }
541
542        /**
543         * Converts FullIdent to Token.
544         * @param fullIdent full ident to convert.
545         */
546        public Token(FullIdent fullIdent) {
547            text = fullIdent.getText();
548            lineNo = fullIdent.getLineNo();
549            columnNo = fullIdent.getColumnNo();
550        }
551
552        /**
553         * Gets line number of the token.
554         * @return line number of the token
555         */
556        public int getLineNo() {
557            return lineNo;
558        }
559
560        /**
561         * Gets column number of the token.
562         * @return column number of the token
563         */
564        public int getColumnNo() {
565            return columnNo;
566        }
567
568        /**
569         * Gets text of the token.
570         * @return text of the token
571         */
572        public String getText() {
573            return text;
574        }
575
576        @Override
577        public String toString() {
578            return "Token[" + text + "(" + lineNo
579                + "x" + columnNo + ")]";
580        }
581    }
582}