com.gargoylesoftware.htmlunit.CodeStyleTest.java Source code

Java tutorial

Introduction

Here is the source code for com.gargoylesoftware.htmlunit.CodeStyleTest.java

Source

/*
 * Copyright (c) 2002-2016 Gargoyle Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gargoylesoftware.htmlunit;

import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.ISVNPropertyHandler;
import org.tmatesoft.svn.core.wc.SVNPropertyData;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCClient;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import com.gargoylesoftware.htmlunit.source.SVN;

/**
 * Test of coding style for things that cannot be detected by Checkstyle.
 *
 * @author Ahmed Ashour
 */
public class CodeStyleTest {

    private static final Pattern leadingWhitespace = Pattern.compile("^\\s+");
    private List<String> failures_ = new ArrayList<>();
    private SVNWCClient svnWCClient_;

    /**
     * After.
     */
    @After
    public void after() {
        if (svnWCClient_ != null) {
            svnWCClient_.getOperationsFactory().getRepositoryPool().dispose();
        }
        final StringBuilder sb = new StringBuilder();
        for (final String error : failures_) {
            sb.append('\n').append(error);
        }

        final int errorsNumber = failures_.size();
        if (errorsNumber == 1) {
            fail("CodeStyle error: " + sb);
        } else if (errorsNumber > 1) {
            fail("CodeStyle " + errorsNumber + " errors: " + sb);
        }
    }

    private void addFailure(final String error) {
        failures_.add(error);
    }

    /**
     * @throws IOException if the test fails
     */
    @Test
    public void codeStyle() throws IOException {
        final ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
        final ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager();
        svnWCClient_ = new SVNWCClient(authManager, options);
        process(new File("src/main"));
        process(new File("src/test"));
        licenseYear();
        versionYear();
        parentInPom();
    }

    private void process(final File dir) throws IOException {
        final File[] files = dir.listFiles();
        if (files != null) {
            for (final File file : files) {
                if (file.isDirectory() && !".svn".equals(file.getName())) {
                    process(file);
                } else {
                    final String relativePath = file.getAbsolutePath()
                            .substring(new File(".").getAbsolutePath().length() - 1);
                    svnProperties(file, relativePath);
                    if (file.getName().endsWith(".java")) {
                        final List<String> lines = FileUtils.readLines(file);
                        openingCurlyBracket(lines, relativePath);
                        year(lines, relativePath);
                        javaDocFirstLine(lines, relativePath);
                        methodFirstLine(lines, relativePath);
                        methodLastLine(lines, relativePath);
                        lineBetweenMethods(lines, relativePath);
                        runWith(lines, relativePath);
                        vs85aspx(lines, relativePath);
                        deprecated(lines, relativePath);
                        staticJSMethod(lines, relativePath);
                        singleAlert(lines, relativePath);
                        staticLoggers(lines, relativePath);
                        loggingEnabled(lines, relativePath);
                        browserVersion_isIE(lines, relativePath);
                        alerts(lines, relativePath);
                    }
                }
            }
        }
    }

    /**
     * Ensures that no opening curly bracket exists by itself in a single line.
     */
    private void openingCurlyBracket(final List<String> lines, final String path) {
        int index = 1;
        for (final String line : lines) {
            if ("{".equals(line.trim())) {
                addFailure("Opening curly bracket is alone at " + path + ", line: " + index);
            }
            index++;
        }
    }

    /**
     * Checks the year in the source.
     */
    private void year(final List<String> lines, final String path) {
        final int year = Calendar.getInstance(Locale.ROOT).get(Calendar.YEAR);
        if (lines.size() < 2 || !lines.get(1).contains("Copyright (c) 2002-" + year)) {
            addFailure("Incorrect year in " + path);
        }
    }

    /**
     * Checks the JavaDoc first line, it should not be empty, and should not start with lower-case.
     */
    private void javaDocFirstLine(final List<String> lines, final String relativePath) {
        for (int index = 1; index < lines.size(); index++) {
            final String previousLine = lines.get(index - 1);
            final String currentLine = lines.get(index);
            if ("/**".equals(previousLine.trim())) {
                if ("*".equals(currentLine.trim()) || currentLine.contains("*/")) {
                    addFailure("Empty line in " + relativePath + ", line: " + (index + 1));
                }
                if (currentLine.trim().startsWith("*")) {
                    final String text = currentLine.trim().substring(1).trim();
                    if (!text.isEmpty() && Character.isLowerCase(text.charAt(0))) {
                        addFailure("Lower case start in " + relativePath + ", line: " + (index + 1));
                    }
                }
            }
        }
    }

    /**
     * Checks the method first line, it should not be empty.
     */
    private void methodFirstLine(final List<String> lines, final String relativePath) {
        for (int index = 0; index < lines.size() - 1; index++) {
            final String line = lines.get(index);
            if (StringUtils.isBlank(lines.get(index + 1)) && line.length() > 4 && index > 0
                    && lines.get(index - 1).startsWith("    ") && Character.isWhitespace(line.charAt(0))
                    && line.endsWith("{") && !line.contains(" class ") && !line.contains(" interface ")
                    && !line.contains(" @interface ")
                    && (!Character.isWhitespace(line.charAt(4)) || line.trim().startsWith("public")
                            || line.trim().startsWith("protected") || line.trim().startsWith("private"))) {
                addFailure("Empty line in " + relativePath + ", line: " + (index + 2));
            }
        }
    }

    /**
     * Checks the method last line, it should not be empty.
     */
    private void methodLastLine(final List<String> lines, final String relativePath) {
        for (int index = 0; index < lines.size() - 1; index++) {
            final String line = lines.get(index);
            final String nextLine = lines.get(index + 1);
            if (StringUtils.isBlank(line) && "    }".equals(nextLine)) {
                addFailure("Empty line in " + relativePath + ", line: " + (index + 1));
            }
        }
    }

    /**
     * Checks that empty line must exist between consecutive methods.
     */
    private void lineBetweenMethods(final List<String> lines, final String relativePath) {
        for (int index = 0; index < lines.size() - 1; index++) {
            final String line = lines.get(index);
            final String nextLine = lines.get(index + 1);
            if ("    }".equals(line) && !nextLine.isEmpty() && !"}".equals(nextLine)) {
                addFailure("Non-empty line in " + relativePath + ", line: " + (index + 1));
            }
            if ("    /**".equals(nextLine) && !line.isEmpty()) {
                addFailure("Non-empty line in " + relativePath + ", line: " + (index + 2));
            }
        }
    }

    /**
     * Checks property {@code svn:eol-style}.
     */
    private void svnProperties(final File file, final String relativePath) {
        if (!isSvnPropertiesDefined(file)) {
            addFailure("'svn:eol-style' property is not defined for " + relativePath);
        }
    }

    private boolean isSvnPropertiesDefined(final File file) {
        try {
            final AtomicBoolean eol = new AtomicBoolean();
            svnWCClient_.doGetProperty(file, null, SVNRevision.WORKING, SVNRevision.WORKING, SVNDepth.EMPTY,
                    new ISVNPropertyHandler() {

                        @Override
                        public void handleProperty(final long revision, final SVNPropertyData property) {
                            // nothing to do
                        }

                        @Override
                        public void handleProperty(final SVNURL url, final SVNPropertyData property) {
                            // nothing to do
                        }

                        @Override
                        public void handleProperty(final File path, final SVNPropertyData property) {
                            final String name = property.getName();
                            final String value = property.getValue().getString();
                            if ("svn:eol-style".equals(name) && "native".equals(value)) {
                                eol.set(true);
                            }
                        }
                    }, null);

            final String fileName = file.getName().toLowerCase(Locale.ROOT);

            for (final String extension : SVN.getEolExtenstions()) {
                if (fileName.endsWith(extension)) {
                    return eol.get();
                }
            }
            return true;
        } catch (final Exception e) {
            //nothing
        }
        final String path = file.getAbsolutePath();
        // automatically generated and is outside SVN control
        return (path.contains("jQuery") && path.contains("WEB-INF") && path.contains("cgi"))
                || (path.contains("jQuery") && path.contains("csp.log"));
    }

    /**
     * @throws Exception if an error occurs
     */
    @Test
    public void xmlStyle() throws Exception {
        processXML(new File("."), false);
        processXML(new File("src/main/resources"), true);
        processXML(new File("src/assembly"), true);
        processXML(new File("src/changes"), true);
    }

    private void processXML(final File dir, final boolean recursive) throws Exception {
        final File[] files = dir.listFiles();
        if (files != null) {
            for (final File file : files) {
                if (file.isDirectory() && !".svn".equals(file.getName())) {
                    if (recursive) {
                        processXML(file, true);
                    }
                } else {
                    if (file.getName().endsWith(".xml")) {
                        final List<String> lines = FileUtils.readLines(file);
                        final String relativePath = file.getAbsolutePath()
                                .substring(new File(".").getAbsolutePath().length() - 1);
                        mixedIndentation(lines, relativePath);
                        trailingWhitespace(lines, relativePath);
                        badIndentationLevels(lines, relativePath);
                    }
                }
            }
        }
    }

    /**
     * Verifies that no XML files have mixed indentation (tabs and spaces, mixed).
     */
    private void mixedIndentation(final List<String> lines, final String relativePath) {
        for (int i = 0; i < lines.size(); i++) {
            final String line = lines.get(i);
            if (line.indexOf('\t') != -1) {
                addFailure("Mixed indentation in " + relativePath + ", line: " + (i + 1));
            }
        }
    }

    /**
     * Verifies that no XML files have trailing whitespace.
     */
    private void trailingWhitespace(final List<String> lines, final String relativePath) {
        for (int i = 0; i < lines.size(); i++) {
            final String line = lines.get(i);
            if (!line.isEmpty()) {
                final char last = line.charAt(line.length() - 1);
                if (Character.isWhitespace(last)) {
                    addFailure("Trailing whitespace in " + relativePath + ", line: " + (i + 1));
                }
            }
        }
    }

    /**
     * Verifies that no XML files have bad indentation levels (each indentation level is 4 spaces).
     */
    private void badIndentationLevels(final List<String> lines, final String relativePath) {
        for (int i = 0; i < lines.size(); i++) {
            final int indentation = getIndentation(lines.get(i));
            if (indentation % 4 != 0) {
                addFailure("Bad indentation level (" + indentation + ") in " + relativePath + ", line: " + (i + 1));
            }
        }
    }

    /**
     * Checks the year in LICENSE.txt.
     */
    private void licenseYear() throws IOException {
        final List<String> lines = FileUtils.readLines(new File("LICENSE.txt"));
        if (!lines.get(1).contains("Copyright (c) 2002-" + Calendar.getInstance(Locale.ROOT).get(Calendar.YEAR))) {
            addFailure("Incorrect year in LICENSE.txt");
        }
    }

    /**
     * Checks the year in the {@link Version}.
     */
    private void versionYear() throws IOException {
        final List<String> lines = FileUtils
                .readLines(new File("src/main/java/com/gargoylesoftware/htmlunit/Version.java"));
        for (final String line : lines) {
            if (line.contains(
                    "return \"Copyright (c) 2002-" + Calendar.getInstance(Locale.ROOT).get(Calendar.YEAR))) {
                return;
            }
        }
        addFailure("Incorrect year in Version.getCopyright()");
    }

    /**
     * Verifies no &lt;parent&gt; tag in {@code pom.xml}.
     */
    private void parentInPom() throws IOException {
        final List<String> lines = FileUtils.readLines(new File("pom.xml"));
        for (int i = 0; i < lines.size(); i++) {
            if (lines.get(i).contains("<parent>")) {
                addFailure("'pom.xml' should not have <parent> tag in line: " + (i + 1));
                break;
            }
        }
    }

    /**
     * Verifies that no direct instantiation of WebClient from a test that runs with BrowserRunner.
     */
    private void runWith(final List<String> lines, final String relativePath) {
        if (relativePath.replace('\\', '/').contains("src/test/java") && !relativePath.contains("CodeStyleTest")) {
            boolean runWith = false;
            boolean browserNone = true;
            int index = 1;
            for (final String line : lines) {
                if (line.contains("@RunWith(BrowserRunner.class)")) {
                    runWith = true;
                }
                if (line.contains("@Test")) {
                    browserNone = false;
                }
                if (relativePath.contains("JavaScriptEngineTest") && line.contains("nonStandardBrowserVersion")) {
                    browserNone = true;
                }
                if (runWith) {
                    if (!browserNone && line.contains("new WebClient(") && !line.contains("getBrowserVersion()")) {
                        addFailure("Test " + relativePath + " line " + index
                                + " should never directly instantiate WebClient, please use getWebClient() instead.");
                    }
                    if (line.contains("notYetImplemented()")) {
                        addFailure("Use @NotYetImplemented instead of notYetImplemented() in " + relativePath
                                + ", line: " + index);
                    }
                }
                index++;
            }
        }
    }

    /**
     * Verifies that no "(VS.85).aspx" token exists (which is sometimes used in MSDN documentation).
     */
    private void vs85aspx(final List<String> lines, final String relativePath) {
        if (!relativePath.contains("CodeStyleTest")) {
            int i = 0;
            for (final String line : lines) {
                if (line.contains("(VS.85).aspx")) {
                    addFailure("Please remove \"(VS.85)\" from the URL found in " + relativePath + ", line: "
                            + (i + 1));
                }
                i++;
            }
        }
    }

    /**
     * Verifies that deprecated tag is followed by "As of " or "since ", and '@Deprecated' annotation follows.
     */
    private void deprecated(final List<String> lines, final String relativePath) {
        int i = 0;
        for (String line : lines) {
            line = line.trim().toLowerCase(Locale.ROOT);
            if (line.startsWith("* @deprecated")) {
                if (!line.startsWith("* @deprecated as of ") && !line.startsWith("* @deprecated since ")) {
                    addFailure("@deprecated must be immediately followed by \"As of \" or \"since \" in "
                            + relativePath + ", line: " + (i + 1));
                }
                if (!getAnnotations(lines, i).contains("@Deprecated")) {
                    addFailure("No \"@Deprecated\" annotation for " + relativePath + ", line: " + (i + 1));
                }
            }
            i++;
        }
    }

    /**
     * Returns all annotation lines that comes after the given 'javadoc' line.
     * @param lines source code lines
     * @param index the index to start searching from, must be a 'javadoc' line.
     */
    private List<String> getAnnotations(final List<String> lines, int index) {
        final List<String> annotations = new ArrayList<>();
        while (!lines.get(index++).trim().endsWith("*/")) {
            //empty;
        }
        while (lines.get(index).trim().startsWith("@")) {
            annotations.add(lines.get(index++).trim());
        }
        return annotations;
    }

    /**
     * Verifies that no static JavaScript method exists.
     */
    private void staticJSMethod(final List<String> lines, final String relativePath) {
        if (relativePath.endsWith("Console.java")) {
            return;
        }
        int i = 0;
        for (final String line : lines) {
            if (line.contains(" static ")
                    && (line.contains(" jsxFunction_") || line.contains(" jsxGet_") || line.contains(" jsxSet_"))
                    && !line.contains(" jsxFunction_write") && !line.contains(" jsxFunction_insertBefore")
                    && !line.contains(" jsxFunction_drawImage")) {
                addFailure("Use of static JavaScript function in " + relativePath + ", line: " + (i + 1));
            }
            i++;
        }
    }

    /**
     * Single @Alert does not need curly brackets.
     */
    private void singleAlert(final List<String> lines, final String relativePath) {
        int i = 0;
        for (final String line : lines) {
            if (line.trim().startsWith("@Alerts") && line.contains("@Alerts({") && line.contains("})")) {
                final String alert = line.substring(line.indexOf('{'), line.indexOf('}'));
                if (!alert.contains(",") && alert.contains("\"")
                        && alert.indexOf('"', alert.indexOf('"') + 1) != -1) {
                    addFailure("No need for curly brackets in " + relativePath + ", line: " + (i + 1));
                }
            }
            i++;
        }
    }

    /**
     * Verifies that only static loggers exist.
     */
    private void staticLoggers(final List<String> lines, final String relativePath) {
        int i = 0;
        final String logClassName = Log.class.getSimpleName();
        for (String line : lines) {
            line = line.trim();
            if (line.contains(" " + logClassName + " ") && !line.contains(" LOG ") && !line.contains(" static ")
                    && !line.startsWith("//") && !line.contains("httpclient.wire")) {
                addFailure("Non-static logger in " + relativePath + ", line: " + (i + 1));
            }
            i++;
        }
    }

    /**
     * Verifies that there is code to check log enablement.
     * <p> For example,
     * <code><pre>
     *    if (log.isDebugEnabled()) {
     *        ... do something expensive ...
     *        log.debug(theResult);
     *    }
     * </pre></code>
     * </p>
     */
    private void loggingEnabled(final List<String> lines, final String relativePath) {
        if (relativePath.contains("CodeStyleTest")) {
            return;
        }
        int i = 0;
        for (String line : lines) {
            if (line.contains("LOG.trace(")) {
                loggingEnabled(lines, i, "Trace", relativePath);
            } else if (line.contains("LOG.debug(")) {
                loggingEnabled(lines, i, "Debug", relativePath);
            }
            i++;
        }
    }

    private void loggingEnabled(final List<String> lines, final int index, final String method,
            final String relativePath) {
        final int indentation = getIndentation(lines.get(index));
        for (int i = index - 1; i >= 0; i--) {
            final String line = lines.get(i);
            if (getIndentation(line) < indentation && line.contains("LOG.is" + method + "Enabled()")) {
                return;
            }
            if (getIndentation(line) == 4) { // a method
                addFailure("You must check \"if (LOG.is" + method + "Enabled())\" around " + relativePath
                        + ", line: " + (index + 1));
                return;
            }
        }
    }

    private int getIndentation(final String line) {
        final Matcher matcher = leadingWhitespace.matcher(line);
        if (matcher.find()) {
            return matcher.end() - matcher.start();
        }
        return 0;
    }

    /**
     * Verifies that not invocation of browserVersion.isIE(), .isFirefox() or .getBrowserVersionNumeric().
     */
    private void browserVersion_isIE(final List<String> lines, final String relativePath) {
        if (relativePath.replace('\\', '/').contains("src/main/java")
                && !relativePath.contains("JavaScriptConfiguration")
                && !relativePath.contains("BrowserVersionFeatures") && !relativePath.contains("HTMLDocument")) {
            int index = 1;
            for (final String line : lines) {
                if (line.contains(".isIE()") || line.contains(".isFirefox()")) {
                    addFailure(".isIE() and .isFirefox() should not be used, please use .hasFeature(): "
                            + relativePath + ", line: " + index);
                }
                if (line.contains(".getBrowserVersionNumeric()")
                        && !relativePath.contains("IEConditionalCommentExpressionEvaluator")
                        && !relativePath.contains("IEConditionalCompilationScriptPreProcessor")
                        && !relativePath.contains("BrowserConfiguration")
                        && !relativePath.contains("Window.java")) {
                    addFailure(".getBrowserVersionNumeric() should not be used, please use .hasFeature(): "
                            + relativePath + ", line: " + index);
                }
                index++;
            }
        }
    }

    /**
     * Verifies that \@Alerts is correctly defined.
     */
    private void alerts(final List<String> lines, final String relativePath) {
        for (int i = 0; i < lines.size(); i++) {
            if (lines.get(i).startsWith("    @Alerts(")) {
                final List<String> alerts = alertsToList(lines, i, true);
                alertVerify(alerts, relativePath, i);
            }
        }
    }

    /**
     * Returns array of String of the alerts which are in the specified index.
     *
     * @param lines the list of strings
     * @param alertsIndex the index in which the \@Alerts is defined
     * @return array of alert strings
     */
    public static List<String> alertsToList(final List<String> lines, final int alertsIndex) {
        return alertsToList(lines, alertsIndex, false);
    }

    private static List<String> alertsToList(final List<String> lines, final int alertsIndex,
            final boolean preserveCommas) {
        if ("    @Alerts".equals(lines.get(alertsIndex))) {
            lines.set(alertsIndex, "    @Alerts()");
        }
        if (!lines.get(alertsIndex).startsWith("    @Alerts(")) {
            throw new IllegalArgumentException("No @Alerts found in " + (alertsIndex + 1));
        }
        final StringBuilder alerts = new StringBuilder();
        for (int i = alertsIndex;; i++) {
            final String line = lines.get(i);
            if (alerts.length() != 0) {
                alerts.append('\n');
            }
            if (line.startsWith("    @Alerts(")) {
                alerts.append(line.substring("    @Alerts(".length()));
            } else {
                alerts.append(line);
            }
            if (line.endsWith(")")) {
                alerts.deleteCharAt(alerts.length() - 1);
                break;
            }
        }
        final List<String> list = alertsToList(alerts.toString());
        if (!preserveCommas) {
            for (int i = 0; i < list.size(); i++) {
                String value = list.get(i);
                if (value.startsWith(",")) {
                    value = value.substring(1).trim();
                }
                list.set(i, value);
            }
        }
        return list;
    }

    /**
     * Verifies a specific \@Alerts definition.
     */
    private void alertVerify(final List<String> alerts, final String relativePath, final int lineIndex) {
        if (alerts.size() == 1) {
            if (alerts.get(0).contains("DEFAULT")) {
                addFailure("No need for \"DEFAULT\" in " + relativePath + ", line: " + (lineIndex + 1));
            }
        } else {
            final List<String> names = new ArrayList<>();
            for (String alert : alerts) {
                if (alert.charAt(0) == ',') {
                    if (alert.charAt(1) != '\n') {
                        addFailure("Expectation must be in a separate line in " + relativePath + ", line: "
                                + (lineIndex + 1));
                    }
                    alert = alert.substring(1).trim();
                }

                final int quoteIndex = alert.indexOf('"');
                final int equalsIndex = alert.indexOf('=');
                if (equalsIndex != -1 && equalsIndex < quoteIndex) {
                    final String name = alert.substring(0, equalsIndex - 1);
                    alertVerifyOrder(name, names, relativePath, lineIndex);
                    names.add(name);
                }
            }
        }
    }

    /**
     * Converts the given alerts definition to an array of expressions.
     */
    private static List<String> alertsToList(final String string) {
        final List<String> list = new ArrayList<>();
        if ("\"\"".equals(string)) {
            list.add(string);
        } else {
            final StringBuilder currentToken = new StringBuilder();

            boolean insideString = true;
            boolean startsWithBraces = false;
            for (final String token : string.split("(?<!\\\\)\"")) {
                insideString = !insideString;
                if (currentToken.length() != 0) {
                    currentToken.append('"');
                } else {
                    startsWithBraces = token.toString().contains("{");
                }

                if (!insideString && token.startsWith(",") && !startsWithBraces) {
                    list.add(currentToken.toString());
                    currentToken.setLength(0);
                    startsWithBraces = token.toString().contains("{");
                }

                if (!insideString && token.contains("}")) {
                    final int curlyIndex = token.indexOf('}') + 1;
                    currentToken.append(token.substring(0, curlyIndex));
                    list.add(currentToken.toString());
                    currentToken.setLength(0);
                    currentToken.append(token.substring(curlyIndex));
                } else {
                    if (!insideString && token.contains(",") && !startsWithBraces) {
                        final String[] expressions = token.split(",");
                        currentToken.append(expressions[0]);
                        if (currentToken.length() != 0) {
                            list.add(currentToken.toString());
                        }
                        for (int i = 1; i < expressions.length - 1; i++) {
                            list.add(',' + expressions[i]);
                        }
                        currentToken.setLength(0);
                        currentToken.append(',' + expressions[expressions.length - 1]);
                    } else {
                        currentToken.append(token);
                    }
                }
            }
            if (currentToken.length() != 0) {
                if (!currentToken.toString().contains("\"")) {
                    currentToken.insert(0, '"');
                }
                int totalQuotes = 0;
                for (int i = 0; i < currentToken.length(); i++) {
                    if (currentToken.charAt(i) == '"' && (i == 0 || currentToken.charAt(i - 1) != '\\')) {
                        totalQuotes++;
                    }
                }
                if (totalQuotes % 2 != 0) {
                    currentToken.append('"');
                }

                list.add(currentToken.toString());
            }
        }
        return list;
    }

    /**
     * Verifies \@Alerts specific order.
     *
     * @param browserName the browser name
     * @param previousList the previously defined browser names
     */
    private void alertVerifyOrder(final String browserName, final List<String> previousList,
            final String relativePath, final int lineIndex) {
        switch (browserName) {
        case "DEFAULT":
            if (!previousList.isEmpty()) {
                addFailure("DEFAULT must be first in " + relativePath + ", line: " + (lineIndex + 1));
            }
            break;

        case "IE":
            if (previousList.contains("IE11")) {
                addFailure(
                        "IE must be before specifc IE version in " + relativePath + ", line: " + (lineIndex + 1));
            }
            break;

        case "FF":
            if (previousList.contains("FF31") || previousList.contains("FF38")) {
                addFailure(
                        "FF must be before specifc FF version in " + relativePath + ", line: " + (lineIndex + 1));
            }
            break;

        default:
        }
    }

    /**
     * Tests if all JUnit 4 candidate test methods declare <tt>@Test</tt> annotation.
     * @throws Exception if the test fails
     */
    @Test
    public void tests() throws Exception {
        testTests(new File("src/test/java"));
    }

    private void testTests(final File dir) throws Exception {
        for (final File file : dir.listFiles()) {
            if (file.isDirectory()) {
                if (!".svn".equals(file.getName())) {
                    testTests(file);
                }
            } else {
                if (file.getName().endsWith(".java")) {
                    final int index = new File("src/test/java").getAbsolutePath().length();
                    String name = file.getAbsolutePath();
                    name = name.substring(index + 1, name.length() - 5);
                    name = name.replace(File.separatorChar, '.');
                    final Class<?> clazz;
                    try {
                        clazz = Class.forName(name);
                    } catch (final Exception e) {
                        continue;
                    }
                    name = file.getName();
                    if (name.endsWith("Test.java") || name.endsWith("TestCase.java")) {
                        for (final Constructor<?> ctor : clazz.getConstructors()) {
                            if (ctor.getParameterTypes().length == 0) {
                                for (final Method method : clazz.getDeclaredMethods()) {
                                    if (Modifier.isPublic(method.getModifiers())
                                            && method.getAnnotation(Before.class) == null
                                            && method.getAnnotation(BeforeClass.class) == null
                                            && method.getAnnotation(After.class) == null
                                            && method.getAnnotation(AfterClass.class) == null
                                            && method.getAnnotation(Test.class) == null
                                            && method.getReturnType() == Void.TYPE
                                            && method.getParameterTypes().length == 0) {
                                        fail("Method '" + method.getName() + "' in " + name
                                                + " does not declare @Test annotation");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

}