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.whitespace;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.api.Utils;
025
026/**
027 * <p>
028 * Checks that the whitespace around the Generic tokens (angle brackets)
029 * "&lt;" and "&gt;" are correct to the <i>typical</i> convention.
030 * The convention is not configurable.
031 * </p>
032 * <br>
033 * <p>
034 * Left angle bracket ("&lt;"):
035 * </p>
036 * <br>
037 * <ul>
038 * <li> should be preceded with whitespace only
039 *   in generic methods definitions.</li>
040 * <li> should not be preceded with whitespace
041 *   when it is precede method name or following type name.</li>
042 * <li> should not be followed with whitespace in all cases.</li>
043 * </ul>
044 * <br>
045 * <p>
046 * Right angle bracket ("&gt;"):
047 * </p>
048 * <br>
049 * <ul>
050 * <li> should not be preceded with whitespace in all cases.</li>
051 * <li> should be followed with whitespace in almost all cases,
052 *   except diamond operators and when preceding method name.</li></ul>
053 * <br>
054 * <p>
055 * Examples with correct spacing:
056 * </p>
057 * <br>
058 * <pre>
059 * public void &lt;K, V extends Number&gt; boolean foo(K, V) {}  // Generic methods definitions
060 * class name&lt;T1, T2, ..., Tn&gt; {}                          // Generic type definition
061 * OrderedPair&lt;String, Box&lt;Integer&gt;&gt; p;              // Generic type reference
062 * boolean same = Util.&lt;Integer, String&gt;compare(p1, p2);   // Generic preceded method name
063 * Pair&lt;Integer, String&gt; p1 = new Pair&lt;&gt;(1, "apple");// Diamond operator
064 * </pre>
065 * @author Oliver Burn
066 */
067public class GenericWhitespaceCheck extends Check
068{
069    /** Used to count the depth of a Generic expression. */
070    private int mDepth;
071
072    @Override
073    public int[] getDefaultTokens()
074    {
075        return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
076    }
077
078    @Override
079    public void beginTree(DetailAST aRootAST)
080    {
081        // Reset for each tree, just incase there are errors in preceeding
082        // trees.
083        mDepth = 0;
084    }
085
086    @Override
087    public void visitToken(DetailAST aAST)
088    {
089        if (aAST.getType() == TokenTypes.GENERIC_START) {
090            processStart(aAST);
091            mDepth++;
092        }
093        else if (aAST.getType() == TokenTypes.GENERIC_END) {
094            processEnd(aAST);
095            mDepth--;
096        }
097    }
098
099    /**
100     * Checks the token for the end of Generics.
101     * @param aAST the token to check
102     */
103    private void processEnd(DetailAST aAST)
104    {
105        final String line = getLines()[aAST.getLineNo() - 1];
106        final int before = aAST.getColumnNo() - 1;
107        final int after = aAST.getColumnNo() + 1;
108
109        if ((0 <= before) && Character.isWhitespace(line.charAt(before))
110                && !Utils.whitespaceBefore(before, line))
111        {
112            log(aAST.getLineNo(), before, "ws.preceded", ">");
113        }
114
115        if (after < line.length()) {
116
117            // Check if the last Generic, in which case must be a whitespace
118            // or a '(),[.'.
119            if (1 == mDepth) {
120                final char charAfter = line.charAt(after);
121
122                // Need to handle a number of cases. First is:
123                //    Collections.<Object>emptySet();
124                //                        ^
125                //                        +--- whitespace not allowed
126                if ((aAST.getParent().getType() == TokenTypes.TYPE_ARGUMENTS)
127                    && (aAST.getParent().getParent().getType()
128                        == TokenTypes.DOT)
129                    && (aAST.getParent().getParent().getParent().getType()
130                        == TokenTypes.METHOD_CALL))
131                {
132                    if (Character.isWhitespace(charAfter)) {
133                        log(aAST.getLineNo(), after, "ws.followed", ">");
134                    }
135                }
136                else if (!Character.isWhitespace(charAfter)
137                    && ('(' != charAfter) && (')' != charAfter)
138                    && (',' != charAfter) && ('[' != charAfter)
139                    && ('.' != charAfter))
140                {
141                    log(aAST.getLineNo(), after, "ws.illegalFollow", ">");
142                }
143            }
144            else {
145                // In a nested Generic type, so can only be a '>' or ',' or '&'
146
147                // In case of several extends definitions:
148                //
149                //   class IntEnumValueType<E extends Enum<E> & IntEnum>
150                //                                          ^
151                //   should be whitespace if followed by & -+
152                //
153                final int indexOfAmp = line.indexOf('&', after);
154                if ((indexOfAmp != -1)
155                    && whitespaceBetween(after, indexOfAmp, line))
156                {
157                    if (indexOfAmp - after == 0) {
158                        log(aAST.getLineNo(), after, "ws.notPreceded", "&");
159                    }
160                    else if (indexOfAmp - after != 1) {
161                        log(aAST.getLineNo(), after, "ws.followed", ">");
162                    }
163                }
164                else if ((line.charAt(after) != '>')
165                         && (line.charAt(after) != ',')
166                         && (line.charAt(after) != '['))
167                {
168                    log(aAST.getLineNo(), after, "ws.followed", ">");
169                }
170            }
171        }
172    }
173
174    /**
175     * Checks the token for the start of Generics.
176     * @param aAST the token to check
177     */
178    private void processStart(DetailAST aAST)
179    {
180        final String line = getLines()[aAST.getLineNo() - 1];
181        final int before = aAST.getColumnNo() - 1;
182        final int after = aAST.getColumnNo() + 1;
183
184        // Need to handle two cases as in:
185        //
186        //   public static <T> Callable<T> callable(Runnable task, T result)
187        //                 ^           ^
188        //      ws reqd ---+           +--- whitespace NOT required
189        //
190        if (0 <= before) {
191            // Detect if the first case
192            final DetailAST parent = aAST.getParent();
193            final DetailAST grandparent = parent.getParent();
194            if ((TokenTypes.TYPE_PARAMETERS == parent.getType())
195                && ((TokenTypes.CTOR_DEF == grandparent.getType())
196                    || (TokenTypes.METHOD_DEF == grandparent.getType())))
197            {
198                // Require whitespace
199                if (!Character.isWhitespace(line.charAt(before))) {
200                    log(aAST.getLineNo(), before, "ws.notPreceded", "<");
201                }
202            }
203            // Whitespace not required
204            else if (Character.isWhitespace(line.charAt(before))
205                && !Utils.whitespaceBefore(before, line))
206            {
207                log(aAST.getLineNo(), before, "ws.preceded", "<");
208            }
209        }
210
211        if ((after < line.length())
212                && Character.isWhitespace(line.charAt(after)))
213        {
214            log(aAST.getLineNo(), after, "ws.followed", "<");
215        }
216    }
217
218    /**
219     * Returns whether the specified string contains only whitespace between
220     * specified indices.
221     *
222     * @param aFromIndex the index to start the search from. Inclusive
223     * @param aToIndex the index to finish the search. Exclusive
224     * @param aLine the line to check
225     * @return whether there are only whitespaces (or nothing)
226     */
227    private static boolean whitespaceBetween(
228        int aFromIndex, int aToIndex, String aLine)
229    {
230        for (int i = aFromIndex; i < aToIndex; i++) {
231            if (!Character.isWhitespace(aLine.charAt(i))) {
232                return false;
233            }
234        }
235        return true;
236    }
237}