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