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