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