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