Package com.puppycrawl.tools.checkstyle.filters

Source Code of com.puppycrawl.tools.checkstyle.filters.SuppressWithNearbyCommentFilter

////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2014  Oliver Burn
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.filters;

import com.google.common.collect.Lists;
import com.puppycrawl.tools.checkstyle.api.AuditEvent;
import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
import com.puppycrawl.tools.checkstyle.api.FileContents;
import com.puppycrawl.tools.checkstyle.api.Filter;
import com.puppycrawl.tools.checkstyle.api.TextBlock;
import com.puppycrawl.tools.checkstyle.api.Utils;
import com.puppycrawl.tools.checkstyle.checks.FileContentsHolder;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.beanutils.ConversionException;

/**
* <p>
* A filter that uses nearby comments to suppress audit events.
* </p>
* <p>
* This check is philosophically similar to {@link SuppressionCommentFilter}.
* Unlike {@link SuppressionCommentFilter}, this filter does not require
* pairs of comments.  This check may be used to suppress warnings in the
* current line:
* <pre>
*    offendingLine(for, whatever, reason); // SUPPRESS ParameterNumberCheck
* </pre>
* or it may be configured to span multiple lines, either forward:
* <pre>
*    // PERMIT MultipleVariableDeclarations NEXT 3 LINES
*    double x1 = 1.0, y1 = 0.0, z1 = 0.0;
*    double x2 = 0.0, y2 = 1.0, z2 = 0.0;
*    double x3 = 0.0, y3 = 0.0, z3 = 1.0;
* </pre>
* or reverse:
* <pre>
*   try {
*     thirdPartyLibrary.method();
*   } catch (RuntimeException e) {
*     // ALLOW ILLEGAL CATCH BECAUSE third party API wraps everything
*     // in RuntimeExceptions.
*     ...
*   }
* </pre>
*
* <p>
* See {@link SuppressionCommentFilter} for usage notes.
*
*
* @author Mick Killianey
*/
public class SuppressWithNearbyCommentFilter
    extends AutomaticBean
    implements Filter
{
    /**
     * A Tag holds a suppression comment and its location.
     */
    public class Tag implements Comparable<Tag>
    {
        /** The text of the tag. */
        private final String mText;

        /** The first line where warnings may be suppressed. */
        private int mFirstLine;

        /** The last line where warnings may be suppressed. */
        private int mLastLine;

        /** The parsed check regexp, expanded for the text of this tag. */
        private Pattern mTagCheckRegexp;

        /** The parsed message regexp, expanded for the text of this tag. */
        private Pattern mTagMessageRegexp;

        /**
         * Constructs a tag.
         * @param aText the text of the suppression.
         * @param aLine the line number.
         * @throws ConversionException if unable to parse expanded aText.
         * on.
         */
        public Tag(String aText, int aLine)
            throws ConversionException
        {
            mText = aText;

            mTagCheckRegexp = mCheckRegexp;
            //Expand regexp for check and message
            //Does not intern Patterns with Utils.getPattern()
            String format = "";
            try {
                format = expandFromComment(aText, mCheckFormat, mCommentRegexp);
                mTagCheckRegexp = Pattern.compile(format);
                if (mMessageFormat != null) {
                    format = expandFromComment(
                         aText, mMessageFormat, mCommentRegexp);
                    mTagMessageRegexp = Pattern.compile(format);
                }
                int influence = 0;
                if (mInfluenceFormat != null) {
                    format = expandFromComment(
                        aText, mInfluenceFormat, mCommentRegexp);
                    try {
                        if (format.startsWith("+")) {
                            format = format.substring(1);
                        }
                        influence = Integer.parseInt(format);
                    }
                    catch (final NumberFormatException e) {
                        throw new ConversionException(
                            "unable to parse influence from '" + aText
                                + "' using " + mInfluenceFormat, e);
                    }
                }
                if (influence >= 0) {
                    mFirstLine = aLine;
                    mLastLine = aLine + influence;
                }
                else {
                    mFirstLine = aLine + influence;
                    mLastLine = aLine;
                }
            }
            catch (final PatternSyntaxException e) {
                throw new ConversionException(
                    "unable to parse expanded comment " + format,
                    e);
            }
        }

        /** @return the text of the tag. */
        public String getText()
        {
            return mText;
        }

        /** @return the line number of the first suppressed line. */
        public int getFirstLine()
        {
            return mFirstLine;
        }

        /** @return the line number of the last suppressed line. */
        public int getLastLine()
        {
            return mLastLine;
        }

        /**
         * Compares the position of this tag in the file
         * with the position of another tag.
         * @param aOther the tag to compare with this one.
         * @return a negative number if this tag is before the other tag,
         * 0 if they are at the same position, and a positive number if this
         * tag is after the other tag.
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(Tag aOther)
        {
            if (mFirstLine == aOther.mFirstLine) {
                return mLastLine - aOther.mLastLine;
            }

            return (mFirstLine - aOther.mFirstLine);
        }

        /**
         * Determines whether the source of an audit event
         * matches the text of this tag.
         * @param aEvent the <code>AuditEvent</code> to check.
         * @return true if the source of aEvent matches the text of this tag.
         */
        public boolean isMatch(AuditEvent aEvent)
        {
            final int line = aEvent.getLine();
            if (line < mFirstLine) {
                return false;
            }
            if (line > mLastLine) {
                return false;
            }
            final Matcher tagMatcher =
                mTagCheckRegexp.matcher(aEvent.getSourceName());
            if (tagMatcher.find()) {
                return true;
            }
            if (mTagMessageRegexp != null) {
                final Matcher messageMatcher =
                    mTagMessageRegexp.matcher(aEvent.getMessage());
                return messageMatcher.find();
            }
            return false;
        }

        /**
         * Expand based on a matching comment.
         * @param aComment the comment.
         * @param aString the string to expand.
         * @param aRegexp the parsed expander.
         * @return the expanded string
         */
        private String expandFromComment(
            String aComment,
            String aString,
            Pattern aRegexp)
        {
            final Matcher matcher = aRegexp.matcher(aComment);
            // Match primarily for effect.
            if (!matcher.find()) {
                ///CLOVER:OFF
                return aString;
                ///CLOVER:ON
            }
            String result = aString;
            for (int i = 0; i <= matcher.groupCount(); i++) {
                // $n expands comment match like in Pattern.subst().
                result = result.replaceAll("\\$" + i, matcher.group(i));
            }
            return result;
        }

        /** {@inheritDoc} */
        @Override
        public final String toString()
        {
            return "Tag[lines=[" + getFirstLine() + " to " + getLastLine()
                + "]; text='" + getText() + "']";
        }
    }

    /** Format to turns checkstyle reporting off. */
    private static final String DEFAULT_COMMENT_FORMAT =
        "SUPPRESS CHECKSTYLE (\\w+)";

    /** Default regex for checks that should be suppressed. */
    private static final String DEFAULT_CHECK_FORMAT = ".*";

    /** Default regex for messages that should be suppressed. */
    private static final String DEFAULT_MESSAGE_FORMAT = null;

    /** Default regex for lines that should be suppressed. */
    private static final String DEFAULT_INFLUENCE_FORMAT = "0";

    /** Whether to look for trigger in C-style comments. */
    private boolean mCheckC = true;

    /** Whether to look for trigger in C++-style comments. */
    private boolean mCheckCPP = true;

    /** Parsed comment regexp that marks checkstyle suppression region. */
    private Pattern mCommentRegexp;

    /** The comment pattern that triggers suppression. */
    private String mCheckFormat;

    /** The parsed check regexp. */
    private Pattern mCheckRegexp;

    /** The message format to suppress. */
    private String mMessageFormat;

    /** The influence of the suppression comment. */
    private String mInfluenceFormat;


    //TODO: Investigate performance improvement with array
    /** Tagged comments */
    private final List<Tag> mTags = Lists.newArrayList();

    /**
     * References the current FileContents for this filter.
     * Since this is a weak reference to the FileContents, the FileContents
     * can be reclaimed as soon as the strong references in TreeWalker
     * and FileContentsHolder are reassigned to the next FileContents,
     * at which time filtering for the current FileContents is finished.
     */
    private WeakReference<FileContents> mFileContentsReference =
        new WeakReference<FileContents>(null);

    /**
     * Constructs a SuppressionCommentFilter.
     * Initializes comment on, comment off, and check formats
     * to defaults.
     */
    public SuppressWithNearbyCommentFilter()
    {
        if (DEFAULT_COMMENT_FORMAT != null) {
            setCommentFormat(DEFAULT_COMMENT_FORMAT);
        }
        if (DEFAULT_CHECK_FORMAT != null) {
            setCheckFormat(DEFAULT_CHECK_FORMAT);
        }
        if (DEFAULT_MESSAGE_FORMAT != null) {
            setMessageFormat(DEFAULT_MESSAGE_FORMAT);
        }
        if (DEFAULT_INFLUENCE_FORMAT != null) {
            setInfluenceFormat(DEFAULT_INFLUENCE_FORMAT);
        }
    }

    /**
     * Set the format for a comment that turns off reporting.
     * @param aFormat a <code>String</code> value.
     * @throws ConversionException unable to parse aFormat.
     */
    public void setCommentFormat(String aFormat)
        throws ConversionException
    {
        try {
            mCommentRegexp = Utils.getPattern(aFormat);
        }
        catch (final PatternSyntaxException e) {
            throw new ConversionException("unable to parse " + aFormat, e);
        }
    }

    /** @return the FileContents for this filter. */
    public FileContents getFileContents()
    {
        return mFileContentsReference.get();
    }

    /**
     * Set the FileContents for this filter.
     * @param aFileContents the FileContents for this filter.
     */
    public void setFileContents(FileContents aFileContents)
    {
        mFileContentsReference = new WeakReference<FileContents>(aFileContents);
    }

    /**
     * Set the format for a check.
     * @param aFormat a <code>String</code> value
     * @throws ConversionException unable to parse aFormat
     */
    public void setCheckFormat(String aFormat)
        throws ConversionException
    {
        try {
            mCheckRegexp = Utils.getPattern(aFormat);
            mCheckFormat = aFormat;
        }
        catch (final PatternSyntaxException e) {
            throw new ConversionException("unable to parse " + aFormat, e);
        }
    }

    /**
     * Set the format for a message.
     * @param aFormat a <code>String</code> value
     * @throws ConversionException unable to parse aFormat
     */
    public void setMessageFormat(String aFormat)
        throws ConversionException
    {
        // check that aFormat parses
        try {
            Utils.getPattern(aFormat);
        }
        catch (final PatternSyntaxException e) {
            throw new ConversionException("unable to parse " + aFormat, e);
        }
        mMessageFormat = aFormat;
    }

    /**
     * Set the format for the influence of this check.
     * @param aFormat a <code>String</code> value
     * @throws ConversionException unable to parse aFormat
     */
    public void setInfluenceFormat(String aFormat)
        throws ConversionException
    {
        // check that aFormat parses
        try {
            Utils.getPattern(aFormat);
        }
        catch (final PatternSyntaxException e) {
            throw new ConversionException("unable to parse " + aFormat, e);
        }
        mInfluenceFormat = aFormat;
    }


    /**
     * Set whether to look in C++ comments.
     * @param aCheckCPP <code>true</code> if C++ comments are checked.
     */
    public void setCheckCPP(boolean aCheckCPP)
    {
        mCheckCPP = aCheckCPP;
    }

    /**
     * Set whether to look in C comments.
     * @param aCheckC <code>true</code> if C comments are checked.
     */
    public void setCheckC(boolean aCheckC)
    {
        mCheckC = aCheckC;
    }

    /** {@inheritDoc} */
    public boolean accept(AuditEvent aEvent)
    {
        if (aEvent.getLocalizedMessage() == null) {
            return true;        // A special event.
        }

        // Lazy update. If the first event for the current file, update file
        // contents and tag suppressions
        final FileContents currentContents = FileContentsHolder.getContents();
        if (currentContents == null) {
            // we have no contents, so we can not filter.
            // TODO: perhaps we should notify user somehow?
            return true;
        }
        if (getFileContents() != currentContents) {
            setFileContents(currentContents);
            tagSuppressions();
        }
        for (final Iterator<Tag> iter = mTags.iterator(); iter.hasNext();) {
            final Tag tag = iter.next();
            if (tag.isMatch(aEvent)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Collects all the suppression tags for all comments into a list and
     * sorts the list.
     */
    private void tagSuppressions()
    {
        mTags.clear();
        final FileContents contents = getFileContents();
        if (mCheckCPP) {
            tagSuppressions(contents.getCppComments().values());
        }
        if (mCheckC) {
            final Collection<List<TextBlock>> cComments =
                contents.getCComments().values();
            for (final List<TextBlock> element : cComments) {
                tagSuppressions(element);
            }
        }
        Collections.sort(mTags);
    }

    /**
     * Appends the suppressions in a collection of comments to the full
     * set of suppression tags.
     * @param aComments the set of comments.
     */
    private void tagSuppressions(Collection<TextBlock> aComments)
    {
        for (final TextBlock comment : aComments) {
            final int startLineNo = comment.getStartLineNo();
            final String[] text = comment.getText();
            tagCommentLine(text[0], startLineNo);
            for (int i = 1; i < text.length; i++) {
                tagCommentLine(text[i], startLineNo + i);
            }
        }
    }

    /**
     * Tags a string if it matches the format for turning
     * checkstyle reporting on or the format for turning reporting off.
     * @param aText the string to tag.
     * @param aLine the line number of aText.
     */
    private void tagCommentLine(String aText, int aLine)
    {
        final Matcher matcher = mCommentRegexp.matcher(aText);
        if (matcher.find()) {
            addTag(matcher.group(0), aLine);
        }
    }

    /**
     * Adds a comment suppression <code>Tag</code> to the list of all tags.
     * @param aText the text of the tag.
     * @param aLine the line number of the tag.
     */
    private void addTag(String aText, int aLine)
    {
        final Tag tag = new Tag(aText, aLine);
        mTags.add(tag);
    }
}
TOP

Related Classes of com.puppycrawl.tools.checkstyle.filters.SuppressWithNearbyCommentFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.