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