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 depth;
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 rootAST)
080    {
081        // Reset for each tree, just incase there are errors in preceeding
082        // trees.
083        depth = 0;
084    }
085
086    @Override
087    public void visitToken(DetailAST ast)
088    {
089        if (ast.getType() == TokenTypes.GENERIC_START) {
090            processStart(ast);
091            depth++;
092        }
093        else if (ast.getType() == TokenTypes.GENERIC_END) {
094            processEnd(ast);
095            depth--;
096        }
097    }
098
099    /**
100     * Checks the token for the end of Generics.
101     * @param ast the token to check
102     */
103    private void processEnd(DetailAST ast)
104    {
105        final String line = getLine(ast.getLineNo() - 1);
106        final int before = ast.getColumnNo() - 1;
107        final int after = ast.getColumnNo() + 1;
108
109        if ((0 <= before) && Character.isWhitespace(line.charAt(before))
110                && !Utils.whitespaceBefore(before, line))
111        {
112            log(ast.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 == depth) {
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 ((ast.getParent().getType() == TokenTypes.TYPE_ARGUMENTS)
127                    && (ast.getParent().getParent().getType()
128                        == TokenTypes.DOT)
129                    && (ast.getParent().getParent().getParent().getType()
130                        == TokenTypes.METHOD_CALL))
131                {
132                    if (Character.isWhitespace(charAfter)) {
133                        log(ast.getLineNo(), after, "ws.followed", ">");
134                    }
135                }
136                else if (!Character.isWhitespace(charAfter)
137                    && ('(' != charAfter) && (')' != charAfter)
138                    && (',' != charAfter) && ('[' != charAfter)
139                    && ('.' != charAfter) && (':' != charAfter))
140                {
141                    log(ast.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(ast.getLineNo(), after, "ws.notPreceded", "&");
159                    }
160                    else if (indexOfAmp - after != 1) {
161                        log(ast.getLineNo(), after, "ws.followed", ">");
162                    }
163                }
164                else if (line.charAt(after) == ' ') {
165                    log(ast.getLineNo(), after, "ws.followed", ">");
166                }
167            }
168        }
169    }
170
171    /**
172     * Checks the token for the start of Generics.
173     * @param ast the token to check
174     */
175    private void processStart(DetailAST ast)
176    {
177        final String line = getLine(ast.getLineNo() - 1);
178        final int before = ast.getColumnNo() - 1;
179        final int after = ast.getColumnNo() + 1;
180
181        // Need to handle two cases as in:
182        //
183        //   public static <T> Callable<T> callable(Runnable task, T result)
184        //                 ^           ^
185        //      ws reqd ---+           +--- whitespace NOT required
186        //
187        if (0 <= before) {
188            // Detect if the first case
189            final DetailAST parent = ast.getParent();
190            final DetailAST grandparent = parent.getParent();
191            if ((TokenTypes.TYPE_PARAMETERS == parent.getType())
192                && ((TokenTypes.CTOR_DEF == grandparent.getType())
193                    || (TokenTypes.METHOD_DEF == grandparent.getType())))
194            {
195                // Require whitespace
196                if (!Character.isWhitespace(line.charAt(before))) {
197                    log(ast.getLineNo(), before, "ws.notPreceded", "<");
198                }
199            }
200            // Whitespace not required
201            else if (Character.isWhitespace(line.charAt(before))
202                && !Utils.whitespaceBefore(before, line))
203            {
204                log(ast.getLineNo(), before, "ws.preceded", "<");
205            }
206        }
207
208        if ((after < line.length())
209                && Character.isWhitespace(line.charAt(after)))
210        {
211            log(ast.getLineNo(), after, "ws.followed", "<");
212        }
213    }
214
215    /**
216     * Returns whether the specified string contains only whitespace between
217     * specified indices.
218     *
219     * @param fromIndex the index to start the search from. Inclusive
220     * @param toIndex the index to finish the search. Exclusive
221     * @param line the line to check
222     * @return whether there are only whitespaces (or nothing)
223     */
224    private static boolean whitespaceBetween(
225        int fromIndex, int toIndex, String line)
226    {
227        for (int i = fromIndex; i < toIndex; i++) {
228            if (!Character.isWhitespace(line.charAt(i))) {
229                return false;
230            }
231        }
232        return true;
233    }
234}