@Test
public void testGetQueryStatements_2() {
    List<String> sqls = CommonUtil.getQueryStatements("aaa");
    Assert.assertEquals(1, sqls.size());
    Assert.assertEquals(sqls.get(0), "aaa");
}

@Test
public void testGetQueryStatements_4() {
    List<String> sqls = CommonUtil.getQueryStatements("aaa;bbb;");
    Assert.assertEquals(2, sqls.size());
    Assert.assertEquals(sqls.get(0), "aaa;");
    Assert.assertEquals(sqls.get(1), "bbb;");
}

@Test
public void testGetQueryStatements_1() {
    Assert.assertEquals(0, CommonUtil.getQueryStatements("").size());
    Assert.assertEquals(0, CommonUtil.getQueryStatements(null).size());
}

@Test
public void testGetQueryStatements_3() {
    List<String> sqls = CommonUtil.getQueryStatements("aaa;");
    Assert.assertEquals(1, sqls.size());
    Assert.assertEquals(sqls.get(0), "aaa;");
}

@Test
public void testGetQueryStatements_5() {
    List<String> sqls = CommonUtil.getQueryStatements("aaa;bbb;     ");
    Assert.assertEquals(2, sqls.size());
    Assert.assertEquals(sqls.get(0), "aaa;");
    Assert.assertEquals(sqls.get(1), "bbb;");
}


@Test
public void canonicalName() {
    assertEquals("java.lang.Object", TypeName.OBJECT.canonicalName());
    assertEquals("java.lang.Thread.State", ClassName.get(Thread.State.class).canonicalName());
    assertEquals("java.util.Map.Entry", ClassName.get(Map.Entry.class).canonicalName());
    assertEquals("Foo", ClassName.get("", "Foo").canonicalName());
    assertEquals("Foo.Bar.Baz", ClassName.get("", "Foo", "Bar", "Baz").canonicalName());
    assertEquals("a.b.c.Foo.Bar.Baz", ClassName.get("a.b.c", "Foo", "Bar", "Baz").canonicalName());
}

public String canonicalName() {
    return canonicalName;
}


@Test
public void withParameterJavaDoc() {
    MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco").addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money").addJavadoc("the amount required to buy the taco.\n").build()).addParameter(ParameterSpec.builder(TypeName.INT, "count").addJavadoc("the number of Tacos to buy.\n").build()).addJavadoc("Gets the best Taco money can buy.\n").build();
    assertThat(methodSpec.toString()).isEqualTo("" + "/**\n" + " * Gets the best Taco money can buy.\n" + " *\n" + " * @param money the amount required to buy the taco.\n" + " * @param count the number of Tacos to buy.\n" + " */\n" + "void getTaco(double money, int count) {\n" + "}\n");
}

public Builder addJavadoc(String format, Object... args) {
    javadoc.add(format, args);
    return this;
}

@Test
public void withParameterJavaDocAndWithoutMethodJavadoc() {
    MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco").addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money").addJavadoc("the amount required to buy the taco.\n").build()).addParameter(ParameterSpec.builder(TypeName.INT, "count").addJavadoc("the number of Tacos to buy.\n").build()).build();
    assertThat(methodSpec.toString()).isEqualTo("" + "/**\n" + " * @param money the amount required to buy the taco.\n" + " * @param count the number of Tacos to buy.\n" + " */\n" + "void getTaco(double money, int count) {\n" + "}\n");
}


@Test
public void addNonFinalModifier() {
    List<Modifier> modifiers = new ArrayList<>();
    modifiers.add(Modifier.FINAL);
    modifiers.add(Modifier.PUBLIC);
    try {
        ParameterSpec.builder(int.class, "foo").addModifiers(modifiers);
        fail();
    } catch (Exception e) {
        assertThat(e.getMessage()).isEqualTo("unexpected parameter modifier: public");
    }
}

public Builder addModifiers(Iterable<Modifier> modifiers) {
    checkNotNull(modifiers, "modifiers == null");
    for (Modifier modifier : modifiers) {
        this.modifiers.add(modifier);
    }
    return this;
}

public Builder addModifiers(Iterable<Modifier> modifiers) {
    checkNotNull(modifiers, "modifiers == null");
    for (Modifier modifier : modifiers) {
        if (!modifier.equals(Modifier.FINAL)) {
            throw new IllegalStateException("unexpected parameter modifier: " + modifier);
        }
        this.modifiers.add(modifier);
    }
    return this;
}


@Test
public void wrapEmbeddedNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghij\nklmn");
    lineWrapper.append("opqrstuvwxy");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde\n    fghij\nklmnopqrstuvwxy");
}

void append(String s) throws IOException {
    if (closed)
        throw new IllegalStateException("closed");
    if (indentLevel != -1) {
        int nextNewline = s.indexOf('\n');
        if (nextNewline == -1 && column + s.length() <= columnLimit) {
            buffer.append(s);
            column += s.length();
            return;
        }
        boolean wrap = nextNewline == -1 || column + nextNewline > columnLimit;
        flush(wrap);
    }
    out.append(s);
    int lastNewline = s.lastIndexOf('\n');
    column = lastNewline != -1 ? s.length() - lastNewline - 1 : column + s.length();
}

@Test
public void multipleWrite() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("ab");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("cd");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("ef");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("gh");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("ij");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("kl");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("mn");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("op");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("qr");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("ab cd ef\n  gh ij kl\n  mn op qr");
}

@Test
public void noWrapMultipleNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi\nklmnopq\nr");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("stuvwxyz");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nr stuvwxyz");
}

@Test
public void wrap() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghij");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde\n    fghij");
}

void wrappingSpace(int indentLevel) throws IOException {
    if (closed)
        throw new IllegalStateException("closed");
    if (this.indentLevel != -1)
        flush(false);
    this.column++;
    this.indentLevel = indentLevel;
}

@Test
public void wrapMultipleNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi\nklmnopq\nrs");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("tuvwxyz1");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nrs\n    tuvwxyz1");
}

@Test
public void overlyLongLinesWithLeadingSpace() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("abcdefghijkl");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("\n    abcdefghijkl");
}

@Test
public void fencepost() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.append("fghij");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("k");
    lineWrapper.append("lmnop");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcdefghij\n    klmnop");
}

void close() throws IOException {
    if (indentLevel != -1)
        flush(false);
    closed = true;
}

@Test
public void noWrap() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi");
}

@Test
public void overlyLongLinesWithoutLeadingSpace() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcdefghijkl");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcdefghijkl");
}

@Test
public void noWrapEmbeddedNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi\njklmn");
    lineWrapper.append("opqrstuvwxy");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi\njklmnopqrstuvwxy");
}


@Test
public void annotatedArrayTypeVarargsParameter() {
    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class))).annotated(TYPE_USE_ANNOTATION);
    MethodSpec varargsMethod = MethodSpec.methodBuilder("m").addParameter(ParameterSpec.builder(type, "p").build()).varargs().build();
    assertThat(varargsMethod.toString()).isEqualTo("" + "void m(java.lang.Object @" + TUA + " []... p) {\n" + "}\n");
}

public static ClassName get(Class<?> clazz) {
    checkNotNull(clazz, "clazz == null");
    checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
    checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
    checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName");
    List<String> names = new ArrayList<>();
    while (true) {
        String anonymousSuffix = "";
        while (clazz.isAnonymousClass()) {
            int lastDollar = clazz.getName().lastIndexOf('$');
            anonymousSuffix = clazz.getName().substring(lastDollar) + anonymousSuffix;
            clazz = clazz.getEnclosingClass();
        }
        names.add(clazz.getSimpleName() + anonymousSuffix);
        Class<?> enclosing = clazz.getEnclosingClass();
        if (enclosing == null)
            break;
        clazz = enclosing;
    }
    int lastDot = clazz.getName().lastIndexOf('.');
    if (lastDot != -1)
        names.add(clazz.getName().substring(0, lastDot));
    Collections.reverse(names);
    return new ClassName(names);
}

public static ClassName get(Class<?> clazz) {
    checkNotNull(clazz, "clazz == null");
    checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
    checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
    checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName");
    String anonymousSuffix = "";
    while (clazz.isAnonymousClass()) {
        int lastDollar = clazz.getName().lastIndexOf('$');
        anonymousSuffix = clazz.getName().substring(lastDollar) + anonymousSuffix;
        clazz = clazz.getEnclosingClass();
    }
    String name = clazz.getSimpleName() + anonymousSuffix;
    if (clazz.getEnclosingClass() == null) {
        int lastDot = clazz.getName().lastIndexOf('.');
        String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : null;
        return new TopLevelClassName(packageName, name);
    }
    return ClassName.get(clazz.getEnclosingClass()).nestedClass(name);
}

@Test
public void annotatedOuterMultidimensionalArrayType() {
    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class))).annotated(TYPE_USE_ANNOTATION);
    assertThat(type.toString()).isEqualTo("java.lang.Object @" + TUA + " [][]");
}

@Override
public final String toString() {
    String result = cachedString;
    if (result == null) {
        try {
            StringBuilder resultBuilder = new StringBuilder();
            CodeWriter codeWriter = new CodeWriter(resultBuilder);
            emitAnnotations(codeWriter);
            emit(codeWriter);
            result = resultBuilder.toString();
            cachedString = result;
        } catch (IOException e) {
            throw new AssertionError();
        }
    }
    return result;
}

@Override
public final String toString() {
    String result = cachedString;
    if (result == null) {
        try {
            StringBuilder resultBuilder = new StringBuilder();
            CodeWriter codeWriter = new CodeWriter(resultBuilder);
            emit(codeWriter);
            result = resultBuilder.toString();
            cachedString = result;
        } catch (IOException e) {
            throw new AssertionError();
        }
    }
    return result;
}

@Test
public void annotatedArrayElementType() {
    TypeName type = ArrayTypeName.of(ClassName.get(Object.class).annotated(TYPE_USE_ANNOTATION));
    assertThat(type.toString()).isEqualTo("java.lang. @" + TUA + " Object[]");
}

@Test
public void annotatedInnerMultidimensionalArrayType() {
    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class)).annotated(TYPE_USE_ANNOTATION));
    assertThat(type.toString()).isEqualTo("java.lang.Object[] @" + TUA + " []");
}

@Test
public void annotatedArrayType() {
    TypeName type = ArrayTypeName.of(ClassName.get(Object.class)).annotated(TYPE_USE_ANNOTATION);
    assertThat(type.toString()).isEqualTo("java.lang.Object @" + TUA + " []");
}

@Test
public void annotatedArrayTypeInVarargsParameter() {
    TypeName type = ArrayTypeName.of(ArrayTypeName.of(ClassName.get(Object.class)).annotated(TYPE_USE_ANNOTATION));
    MethodSpec varargsMethod = MethodSpec.methodBuilder("m").addParameter(ParameterSpec.builder(type, "p").build()).varargs().build();
    assertThat(varargsMethod.toString()).isEqualTo("" + "void m(java.lang.Object[] @" + TUA + " ... p) {\n" + "}\n");
}


@Test
public void addAnnotationDisallowsNull() {
    try {
        TypeSpec.classBuilder("Foo").addAnnotation((AnnotationSpec) null);
        fail();
    } catch (NullPointerException expected) {
        assertThat(expected).hasMessageThat().isEqualTo("annotationSpec == null");
    }
    try {
        TypeSpec.classBuilder("Foo").addAnnotation((ClassName) null);
        fail();
    } catch (NullPointerException expected) {
        assertThat(expected).hasMessageThat().isEqualTo("type == null");
    }
    try {
        TypeSpec.classBuilder("Foo").addAnnotation((Class<?>) null);
        fail();
    } catch (NullPointerException expected) {
        assertThat(expected).hasMessageThat().isEqualTo("clazz == null");
    }
}

public Builder addAnnotation(AnnotationSpec annotationSpec) {
    this.annotations.add(annotationSpec);
    return this;
}

public Builder addAnnotation(AnnotationSpec annotationSpec) {
    checkNotNull(annotationSpec, "annotationSpec == null");
    this.annotations.add(annotationSpec);
    return this;
}


@Test
public void javaFileObjectInputStreamIsUtf8() throws IOException {
    JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Test").build()).addFileComment("Pi\u00f1ata\u00a1").build();
    byte[] bytes = ByteStreams.toByteArray(javaFile.toJavaFileObject().openInputStream());
    assertThat(bytes).isEqualTo(javaFile.toString().getBytes(UTF_8));
}

@Override
public InputStream openInputStream() throws IOException {
    return new ByteArrayInputStream(getCharContent(true).getBytes());
}

@Override
public InputStream openInputStream() throws IOException {
    return new ByteArrayInputStream(getCharContent(true).getBytes(UTF_8));
}


@Test
public void fileIsUtf8() throws IOException {
    JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Taco").build()).addFileComment("Pi\u00f1ata\u00a1").build();
    javaFile.writeTo(fsRoot);
    Path fooPath = fsRoot.resolve(fs.getPath("foo", "Taco.java"));
    assertThat(new String(Files.readAllBytes(fooPath), UTF_8)).isEqualTo("" + "// Pi\u00f1ata\u00a1\n" + "package foo;\n" + "\n" + "class Taco {\n" + "}\n");
}

public void writeTo(Path directory) throws IOException {
    checkArgument(Files.notExists(directory) || Files.isDirectory(directory), "path %s exists but is not a directory.", directory);
    Path outputDirectory = directory;
    if (!packageName.isEmpty()) {
        for (String packageComponent : packageName.split("\\.")) {
            outputDirectory = outputDirectory.resolve(packageComponent);
        }
        Files.createDirectories(outputDirectory);
    }
    Path outputPath = outputDirectory.resolve(typeSpec.name + ".java");
    try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath))) {
        writeTo(writer);
    }
}

public void writeTo(Path directory) throws IOException {
    checkArgument(Files.notExists(directory) || Files.isDirectory(directory), "path %s exists but is not a directory.", directory);
    Path outputDirectory = directory;
    if (!packageName.isEmpty()) {
        for (String packageComponent : packageName.split("\\.")) {
            outputDirectory = outputDirectory.resolve(packageComponent);
        }
        Files.createDirectories(outputDirectory);
    }
    Path outputPath = outputDirectory.resolve(typeSpec.name + ".java");
    try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8)) {
        writeTo(writer);
    }
}


@Test
public void isEmpty() {
    assertTrue(CodeBlock.builder().isEmpty());
    assertTrue(CodeBlock.builder().add("").isEmpty());
    assertFalse(CodeBlock.builder().add(" ").isEmpty());
}

public boolean isEmpty() {
    return formatParts.isEmpty();
}


@Test
public void nullInSuperinterfaceIterableAddition() {
    List<TypeName> superinterfaces = new ArrayList<>();
    superinterfaces.add(TypeName.get(List.class));
    superinterfaces.add(null);
    try {
        TypeSpec.classBuilder("Taco").addSuperinterfaces(superinterfaces);
        fail();
    } catch (IllegalArgumentException expected) {
        assertThat(expected.getMessage()).isEqualTo("superinterface == null");
    }
}

public Builder addSuperinterfaces(Iterable<? extends TypeName> superinterfaces) {
    checkArgument(superinterfaces != null, "superinterfaces == null");
    for (TypeName superinterface : superinterfaces) {
        this.superinterfaces.add(superinterface);
    }
    return this;
}

public Builder addSuperinterfaces(Iterable<? extends TypeName> superinterfaces) {
    checkArgument(superinterfaces != null, "superinterfaces == null");
    for (TypeName superinterface : superinterfaces) {
        addSuperinterface(superinterface);
    }
    return this;
}


@Test
public void indexButNoArguments() {
    try {
        CodeBlock.builder().add("$1T").build();
        fail();
    } catch (IllegalArgumentException expected) {
        assertThat(expected).hasMessage("index 1 for '$1T' not in range (received 0 arguments)");
    }
}

public Builder add(String format, Object... args) {
    boolean hasRelative = false;
    boolean hasIndexed = false;
    int relativeParameterCount = 0;
    int[] indexedParameterCount = new int[args.length];
    for (int p = 0; p < format.length(); ) {
        if (format.charAt(p) != '$') {
            int nextP = format.indexOf('$', p + 1);
            if (nextP == -1)
                nextP = format.length();
            formatParts.add(format.substring(p, nextP));
            p = nextP;
            continue;
        }
        p++;
        int indexStart = p;
        char c;
        do {
            checkArgument(p < format.length(), "dangling format characters in '%s'", format);
            c = format.charAt(p++);
        } while (c >= '0' && c <= '9');
        int indexEnd = p - 1;
        if (c == '$' || c == '>' || c == '<' || c == '[' || c == ']') {
            checkArgument(indexStart == indexEnd, "$$, $>, $<, $[ and $] may not have an index");
            formatParts.add("$" + c);
            continue;
        }
        int index;
        if (indexStart < indexEnd) {
            index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1;
            hasIndexed = true;
            indexedParameterCount[index % args.length]++;
        } else {
            index = relativeParameterCount;
            hasRelative = true;
            relativeParameterCount++;
        }
        checkArgument(index >= 0 && index < args.length, "index %d for '%s' not in range (received %s arguments)", index + 1, format.substring(indexStart - 1, indexEnd + 1), args.length);
        checkArgument(!hasIndexed || !hasRelative, "cannot mix indexed and positional parameters");
        switch(c) {
            case 'N':
                this.args.add(argToName(args[index]));
                break;
            case 'L':
                this.args.add(argToLiteral(args[index]));
                break;
            case 'S':
                this.args.add(argToString(args[index]));
                break;
            case 'T':
                this.args.add(argToType(args[index]));
                break;
            default:
                throw new IllegalArgumentException(String.format("invalid format string: '%s'", format));
        }
        formatParts.add("$" + c);
    }
    if (hasRelative) {
        checkArgument(relativeParameterCount >= args.length, "unused arguments: expected %s, received %s", relativeParameterCount, args.length);
    }
    if (hasIndexed) {
        List<String> unused = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            if (indexedParameterCount[i] == 0) {
                unused.add("$" + (i + 1));
            }
        }
        String s = unused.size() == 1 ? "" : "s";
        checkArgument(unused.isEmpty(), "unused argument%s: %s", s, Util.join(", ", unused));
    }
    return this;
}

public Builder add(String format, Object... args) {
    boolean hasRelative = false;
    boolean hasIndexed = false;
    int relativeParameterCount = 0;
    int[] indexedParameterCount = new int[args.length];
    for (int p = 0; p < format.length(); ) {
        if (format.charAt(p) != '$') {
            int nextP = format.indexOf('$', p + 1);
            if (nextP == -1)
                nextP = format.length();
            formatParts.add(format.substring(p, nextP));
            p = nextP;
            continue;
        }
        p++;
        int indexStart = p;
        char c;
        do {
            checkArgument(p < format.length(), "dangling format characters in '%s'", format);
            c = format.charAt(p++);
        } while (c >= '0' && c <= '9');
        int indexEnd = p - 1;
        if (isNoArgPlaceholder(c)) {
            checkArgument(indexStart == indexEnd, "$$, $>, $<, $[, $], and $W may not have an index");
            formatParts.add("$" + c);
            continue;
        }
        int index;
        if (indexStart < indexEnd) {
            index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1;
            hasIndexed = true;
            if (args.length > 0) {
                indexedParameterCount[index % args.length]++;
            }
        } else {
            index = relativeParameterCount;
            hasRelative = true;
            relativeParameterCount++;
        }
        checkArgument(index >= 0 && index < args.length, "index %d for '%s' not in range (received %s arguments)", index + 1, format.substring(indexStart - 1, indexEnd + 1), args.length);
        checkArgument(!hasIndexed || !hasRelative, "cannot mix indexed and positional parameters");
        addArgument(format, c, args[index]);
        formatParts.add("$" + c);
    }
    if (hasRelative) {
        checkArgument(relativeParameterCount >= args.length, "unused arguments: expected %s, received %s", relativeParameterCount, args.length);
    }
    if (hasIndexed) {
        List<String> unused = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            if (indexedParameterCount[i] == 0) {
                unused.add("$" + (i + 1));
            }
        }
        String s = unused.size() == 1 ? "" : "s";
        checkArgument(unused.isEmpty(), "unused argument%s: %s", s, Util.join(", ", unused));
    }
    return this;
}

@Test
public void missingNamedArgument() {
    try {
        Map<String, Object> map = new LinkedHashMap<>();
        CodeBlock.builder().addNamed("$text:S", map).build();
        fail();
    } catch (IllegalArgumentException expected) {
        assertThat(expected).hasMessage("Missing named argument for $text");
    }
}

public Builder addNamed(String format, Map<String, ?> arguments) {
    int p = 0;
    for (String argument : arguments.keySet()) {
        checkArgument(LOWERCASE.matcher(argument).matches(), "argument '%s' must start with a lowercase character", argument);
    }
    while (p < format.length()) {
        int nextP = format.indexOf("$", p);
        if (nextP == -1) {
            formatParts.add(format.substring(p, format.length()));
            break;
        }
        if (p != nextP) {
            formatParts.add(format.substring(p, nextP));
            p = nextP;
        }
        Matcher matcher = null;
        int colon = format.indexOf(':', p);
        if (colon != -1) {
            int endIndex = Math.min(colon + 2, format.length());
            matcher = NAMED_ARGUMENT.matcher(format.substring(p, endIndex));
        }
        if (matcher != null && matcher.lookingAt()) {
            String argumentName = matcher.group("argumentName");
            checkArgument(arguments.containsKey(argumentName), "Missing named argument for $%s", argumentName);
            char formatChar = matcher.group("typeChar").charAt(0);
            addArgument(format, formatChar, arguments.get(argumentName));
            formatParts.add("$" + formatChar);
            p += matcher.regionEnd();
        } else {
            checkArgument(p < format.length() - 1, "dangling $ at end");
            checkArgument(isNoArgPlaceholder(format.charAt(p + 1)), "unknown format $%s at %s in '%s'", format.charAt(p + 1), p + 1, format);
            formatParts.add(format.substring(p, p + 2));
            p += 2;
        }
    }
    return this;
}

@Test
public void namedAndNoArgFormat() {
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("text", "tacos");
    CodeBlock block = CodeBlock.builder().addNamed("$>\n$text:L for $$3.50", map).build();
    assertThat(block.toString()).isEqualTo("\n  tacos for $3.50");
}

@Test
public void simpleNamedArgument() {
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("text", "taco");
    CodeBlock block = CodeBlock.builder().addNamed("$text:S", map).build();
    assertThat(block.toString()).isEqualTo("\"taco\"");
}

@Test
public void multipleNamedArguments() {
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("pipe", System.class);
    map.put("text", "tacos");
    CodeBlock block = CodeBlock.builder().addNamed("$pipe:T.out.println(\"Let's eat some $text:L\");", map).build();
    assertThat(block.toString()).isEqualTo("java.lang.System.out.println(\"Let's eat some tacos\");");
}

@Test
public void namedNewline() {
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("clazz", Integer.class);
    CodeBlock block = CodeBlock.builder().addNamed("$clazz:T\n", map).build();
    assertThat(block.toString()).isEqualTo("java.lang.Integer\n");
}

@Test
public void repeatedNamedArgument() {
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("text", "tacos");
    CodeBlock block = CodeBlock.builder().addNamed("\"I like \" + $text:S + \". Do you like \" + $text:S + \"?\"", map).build();
    assertThat(block.toString()).isEqualTo("\"I like \" + \"tacos\" + \". Do you like \" + \"tacos\" + \"?\"");
}

@Test
public void danglingNamed() {
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("clazz", Integer.class);
    try {
        CodeBlock.builder().addNamed("$clazz:T$", map).build();
        fail();
    } catch (IllegalArgumentException expected) {
        assertThat(expected).hasMessage("dangling $ at end");
    }
}

@Test
public void lowerCaseNamed() {
    try {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Text", "tacos");
        CodeBlock block = CodeBlock.builder().addNamed("$Text:S", map).build();
        fail();
    } catch (IllegalArgumentException expected) {
        assertThat(expected).hasMessage("argument 'Text' must start with a lowercase character");
    }
}


@Test
public void wrapEmbeddedNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghij\nklmn");
    lineWrapper.append("opqrstuvwxy");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde\n    fghij\nklmnopqrstuvwxy");
}

@Test
public void overlyLongLinesWithoutLeadingSpace() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcdefghijkl");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcdefghijkl");
}

@Test
public void noWrapEmbeddedNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi\njklmn");
    lineWrapper.append("opqrstuvwxy");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi\njklmnopqrstuvwxy");
}

@Test
public void multipleWrite() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("ab");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("cd");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("ef");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("gh");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("ij");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("kl");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("mn");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("op");
    lineWrapper.wrappingSpace(1);
    lineWrapper.append("qr");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("ab cd ef\n  gh ij kl\n  mn op qr");
}

@Test
public void noWrapMultipleNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi\nklmnopq\nr");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("stuvwxyz");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nr stuvwxyz");
}

@Test
public void wrap() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghij");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde\n    fghij");
}

@Test
public void wrapMultipleNewlines() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi\nklmnopq\nrs");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("tuvwxyz1");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi\nklmnopq\nrs\n    tuvwxyz1");
}

@Test
public void overlyLongLinesWithLeadingSpace() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("abcdefghijkl");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("\n    abcdefghijkl");
}

@Test
public void fencepost() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.append("fghij");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("k");
    lineWrapper.append("lmnop");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcdefghij\n    klmnop");
}

@Test
public void noWrap() throws Exception {
    StringBuffer out = new StringBuffer();
    LineWrapper lineWrapper = new LineWrapper(out, "  ", 10);
    lineWrapper.append("abcde");
    lineWrapper.wrappingSpace(2);
    lineWrapper.append("fghi");
    lineWrapper.close();
    assertThat(out.toString()).isEqualTo("abcde fghi");
}


@Test
public void nullInSuperinterfaceIterableAddition() {
    List<TypeName> superinterfaces = new ArrayList<>();
    superinterfaces.add(TypeName.get(List.class));
    superinterfaces.add(null);
    try {
        TypeSpec.classBuilder("Taco").addSuperinterfaces(superinterfaces);
        fail();
    } catch (IllegalArgumentException expected) {
        assertThat(expected.getMessage()).isEqualTo("superinterface == null");
    }
}

@Test
public void nullSingleSuperinterfaceAddition() {
    try {
        TypeSpec.classBuilder("Taco").addSuperinterface((TypeName) null);
        fail();
    } catch (IllegalArgumentException expected) {
        assertThat(expected.getMessage()).isEqualTo("superinterface == null");
    }
}

public Builder addSuperinterface(TypeName superinterface) {
    this.superinterfaces.add(superinterface);
    return this;
}

public Builder addSuperinterface(TypeName superinterface) {
    checkArgument(superinterface != null, "superinterface == null");
    this.superinterfaces.add(superinterface);
    return this;
}


@Test
public void modifyMethodName() {
    MethodSpec methodSpec = MethodSpec.methodBuilder("initialMethod").build().toBuilder().setName("revisedMethod").build();
    assertThat(methodSpec.toString()).isEqualTo("" + "void revisedMethod() {\n" + "}\n");
}

public Builder setName(String name) {
    checkNotNull(name, "name == null");
    checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name), "not a valid name: %s", name);
    this.name = name;
    this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID;
    return this;
}


@Test
public void withParameterJavaDoc() {
    MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco").addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money").addJavadoc("the amount required to buy the taco.\n").build()).addParameter(ParameterSpec.builder(TypeName.INT, "count").addJavadoc("the number of Tacos to buy.\n").build()).addJavadoc("Gets the best Taco money can buy.\n").build();
    assertThat(methodSpec.toString()).isEqualTo("" + "/**\n" + " * Gets the best Taco money can buy.\n" + " *\n" + " * @param money the amount required to buy the taco.\n" + " * @param count the number of Tacos to buy.\n" + " */\n" + "void getTaco(double money, int count) {\n" + "}\n");
}

@Test
public void withParameterJavaDocAndWithoutMethodJavadoc() {
    MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco").addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money").addJavadoc("the amount required to buy the taco.\n").build()).addParameter(ParameterSpec.builder(TypeName.INT, "count").addJavadoc("the number of Tacos to buy.\n").build()).build();
    assertThat(methodSpec.toString()).isEqualTo("" + "/**\n" + " * @param money the amount required to buy the taco.\n" + " * @param count the number of Tacos to buy.\n" + " */\n" + "void getTaco(double money, int count) {\n" + "}\n");
}


@Test
public void joiningWithPrefixAndSuffix() {
    List<CodeBlock> codeBlocks = new ArrayList<>();
    codeBlocks.add(CodeBlock.of("$S", "hello"));
    codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
    codeBlocks.add(CodeBlock.of("need tacos"));
    CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || ", "start {", "} end"));
    assertThat(joined.toString()).isEqualTo("start {\"hello\" || world.World || need tacos} end");
}

public static Collector<CodeBlock, ?, CodeBlock> joining(String separator, String prefix, String suffix) {
    Builder builder = builder().add("$N", prefix);
    return Collector.of(() -> new CodeBlockJoiner(separator, builder), CodeBlockJoiner::add, CodeBlockJoiner::merge, joiner -> {
        builder.add(CodeBlock.of("$N", suffix));
        return joiner.join();
    });
}

@Test
public void joiningSingle() {
    List<CodeBlock> codeBlocks = new ArrayList<>();
    codeBlocks.add(CodeBlock.of("$S", "hello"));
    CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || "));
    assertThat(joined.toString()).isEqualTo("\"hello\"");
}

public static Collector<CodeBlock, ?, CodeBlock> joining(String separator) {
    return Collector.of(() -> new CodeBlockJoiner(separator, builder()), CodeBlockJoiner::add, CodeBlockJoiner::merge, CodeBlockJoiner::join);
}

@Test
public void joining() {
    List<CodeBlock> codeBlocks = new ArrayList<>();
    codeBlocks.add(CodeBlock.of("$S", "hello"));
    codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
    codeBlocks.add(CodeBlock.of("need tacos"));
    CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || "));
    assertThat(joined.toString()).isEqualTo("\"hello\" || world.World || need tacos");
}

@Test
public void join() {
    List<CodeBlock> codeBlocks = new ArrayList<>();
    codeBlocks.add(CodeBlock.of("$S", "hello"));
    codeBlocks.add(CodeBlock.of("$T", ClassName.get("world", "World")));
    codeBlocks.add(CodeBlock.of("need tacos"));
    CodeBlock joined = CodeBlock.join(codeBlocks, " || ");
    assertThat(joined.toString()).isEqualTo("\"hello\" || world.World || need tacos");
}

public static CodeBlock join(Iterable<CodeBlock> codeBlocks, String separator) {
    return StreamSupport.stream(codeBlocks.spliterator(), false).collect(joining(separator));
}


@Test
public void TransitionActionIsPerformedBetweenExitAndEntry() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    List<Integer> list = new ArrayList<Integer>();
    Action exitAction = new CountingAction(list, new Integer(1));
    Action transitionAction = new CountingAction(list, new Integer(2));
    Action entryAction = new CountingAction(list, new Integer(3));
    config.configure(State.A).onExit(exitAction).permit(Trigger.Z, State.B, transitionAction);
    config.configure(State.B).onEntry(entryAction);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.Z);
    assertEquals(State.B, sm.getState());
    assertEquals(3, list.size());
    assertEquals(new Integer(1), list.get(0));
    assertEquals(new Integer(2), list.get(1));
    assertEquals(new Integer(3), list.get(2));
}

public StateConfiguration<S, T> permit(T trigger, S destinationState, final Action action) {
    enforceNotIdentityTransition(destinationState);
    return publicPermit(trigger, destinationState, action);
}


@Test
public void TransitionActionIsPerformedBetweenExitAndEntry() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    List<Integer> list = new ArrayList<Integer>();
    Action exitAction = new CountingAction(list, new Integer(1));
    Action transitionAction = new CountingAction(list, new Integer(2));
    Action entryAction = new CountingAction(list, new Integer(3));
    config.disableEntryActionOfInitialState();
    config.configure(State.A).onExit(exitAction).permit(Trigger.Z, State.B, transitionAction);
    config.configure(State.B).onEntry(entryAction);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.Z);
    assertEquals(State.B, sm.getState());
    assertEquals(3, list.size());
    assertEquals(new Integer(1), list.get(0));
    assertEquals(new Integer(2), list.get(1));
    assertEquals(new Integer(3), list.get(2));
}

@Test(expected = IllegalStateException.class)
public void ActionWithNegativeGuardIsNotPerformed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    TripwireAction action = new TripwireAction();
    config.configure(State.A).permitIf(Trigger.X, State.C, IgnoredTriggerBehaviourTests.returnFalse, action);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
}

public StateConfiguration<S, T> permitIf(T trigger, S destinationState, FuncBoolean guard, Action action) {
    enforceNotIdentityTransition(destinationState);
    return publicPermitIf(trigger, destinationState, guard, action);
}


@Test
public void traceDelegateCanBeDisabled() {
    fsm.setTrace(null);
    fsm.fire(Trigger.NEXT);
    assertEquals(State.BRAVO, fsm.getState());
    verify(trace, never()).trigger(any(Trigger.class));
    verify(trace, never()).transition(any(Trigger.class), any(State.class), any(State.class));
}

public void setTrace(Trace<S, T> trace) {
    this.trace = trace;
}


@Test(expected = IllegalStateException.class)
public void MultipleInternalActionsWithSameTriggerNotAllowed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    TripwireAction action1 = new TripwireAction();
    TripwireAction action2 = new TripwireAction();
    config.configure(State.A).permitInternal(Trigger.X, action1).permitInternal(Trigger.X, action2);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
}

public StateConfiguration<S, T> permitInternal(T trigger, Action action) {
    return permitInternalIf(trigger, NO_GUARD, action);
}

@Test(expected = IllegalStateException.class)
public void ActionWithNegativeGuardIsNotPerformed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    TripwireAction action = new TripwireAction();
    config.configure(State.A).permitInternalIf(Trigger.X, IgnoredTriggerBehaviourTests.returnFalse, action);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
}

public StateConfiguration<S, T> permitInternalIf(T trigger, FuncBoolean guard, Action action) {
    assert guard != null : "guard is null";
    assert action != null : "action is null";
    representation.addTriggerBehaviour(new InternalTriggerBehaviour<S, T>(trigger, guard, action));
    return this;
}


@Test
public void TransitionIsInternal() {
    InternalTriggerBehaviour<State, Trigger> ignored = new InternalTriggerBehaviour<>(Trigger.X, returnTrue, nopAction);
    assertTrue(ignored.isInternal());
}

@Override
public boolean isInternal() {
    return true;
}

@Test
public void StateRemainsUnchanged() {
    InternalTriggerBehaviour<State, Trigger> ignored = new InternalTriggerBehaviour<>(Trigger.X, returnTrue, nopAction);
    OutVar<State> out = new OutVar<>();
    ignored.resultsInTransitionFrom(State.B, new Object[0], out);
    assertEquals(State.B, out.get());
}

@Override
public boolean resultsInTransitionFrom(S source, Object[] args, OutVar<S> dest) {
    dest.set(source);
    return true;
}


@Test
public void TransitionIsExternal() {
    TransitioningTriggerBehaviour<State, Trigger> transtioning = new TransitioningTriggerBehaviour<>(Trigger.X, State.C, IgnoredTriggerBehaviourTests.returnTrue, IgnoredTriggerBehaviourTests.nopAction);
    assertFalse(transtioning.isInternal());
}

public boolean isInternal() {
    return false;
}


@Test(expected = IllegalStateException.class)
public void ActionWithNegativeGuardIsNotPerformed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    TripwireAction action = new TripwireAction();
    config.configure(State.A).permitInternalIf(Trigger.X, InternalTriggerBehaviourTests.returnFalse, action);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
}


@Test
public void TransitionIsExternal() {
    TransitioningTriggerBehaviour<State, Trigger> transtioning = new TransitioningTriggerBehaviour<>(Trigger.X, State.C, InternalTriggerBehaviourTests.returnTrue, InternalTriggerBehaviourTests.nopAction);
    assertFalse(transtioning.isInternal());
}


@Test
public void GuardedDynamicTransitionActionsArePerformed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    List<Integer> list = new ArrayList<Integer>();
    FixedAccumulator<Integer> actionZero = new FixedAccumulator<Integer>(list, new Integer(0));
    AccumulatingAction<Integer> actionOne = new AccumulatingAction<Integer>(list);
    AccumulatingAction<Integer> actionTwo = new AccumulatingAction<Integer>(list);
    AccumulatingAction<Integer> actionThree = new AccumulatingAction<Integer>(list);
    config.configure(State.A).permitDynamicIf(Trigger.X, gotoB, InternalTriggerBehaviourTests.returnTrue, actionZero).permitDynamicIf(TriggerY2, gotoB, InternalTriggerBehaviourTests.returnTrue, actionTwo);
    config.configure(State.B).permitDynamicIf(TriggerX1, gotoA, InternalTriggerBehaviourTests.returnTrue, actionOne).permitDynamicIf(TriggerY3, gotoA, InternalTriggerBehaviourTests.returnTrue, actionThree);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
    sm.fire(TriggerX1, new Integer(3));
    sm.fire(TriggerY2, new Integer(6), new Integer(9));
    sm.fire(TriggerY3, new Integer(12), new Integer(15), new Integer(18));
    assertEquals(State.A, sm.getState());
    assertEquals(7, list.size());
    assertEquals(new Integer(0), list.get(0));
    assertEquals(new Integer(3), list.get(1));
    assertEquals(new Integer(6), list.get(2));
    assertEquals(new Integer(9), list.get(3));
    assertEquals(new Integer(12), list.get(4));
    assertEquals(new Integer(15), list.get(5));
    assertEquals(new Integer(18), list.get(6));
}

public <TArg0> StateConfiguration<S, T> permitDynamicIf(TriggerWithParameters1<TArg0, S, T> trigger, final Func2<TArg0, S> destinationStateSelector, FuncBoolean guard, final Action1<TArg0> action) {
    assert trigger != null : "trigger is null";
    assert destinationStateSelector != null : "destinationStateSelector is null";
    return publicPermitDynamicIf(trigger.getTrigger(), new Func2<Object[], S>() {

        @SuppressWarnings("unchecked")
        @Override
        public S call(Object[] args) {
            return destinationStateSelector.call((TArg0) args[0]);
        }
    }, guard, new Action1<Object[]>() {

        @SuppressWarnings("unchecked")
        @Override
        public void doIt(Object[] args) {
            action.doIt((TArg0) args[0]);
        }
    });
}

@Test
public void UnguardedDynamicTransitionActionsArePerformed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    List<Integer> list = new ArrayList<Integer>();
    FixedAccumulator<Integer> actionZero = new FixedAccumulator<Integer>(list, new Integer(0));
    AccumulatingAction<Integer> actionOne = new AccumulatingAction<Integer>(list);
    AccumulatingAction<Integer> actionTwo = new AccumulatingAction<Integer>(list);
    AccumulatingAction<Integer> actionThree = new AccumulatingAction<Integer>(list);
    config.configure(State.A).permitDynamic(Trigger.X, gotoB, actionZero).permitDynamic(TriggerY2, gotoB, actionTwo);
    config.configure(State.B).permitDynamic(TriggerX1, gotoA, actionOne).permitDynamic(TriggerY3, gotoA, actionThree);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
    sm.fire(TriggerX1, new Integer(2));
    sm.fire(TriggerY2, new Integer(4), new Integer(6));
    sm.fire(TriggerY3, new Integer(8), new Integer(10), new Integer(12));
    assertEquals(State.A, sm.getState());
    assertEquals(7, list.size());
    assertEquals(new Integer(0), list.get(0));
    assertEquals(new Integer(2), list.get(1));
    assertEquals(new Integer(4), list.get(2));
    assertEquals(new Integer(6), list.get(3));
    assertEquals(new Integer(8), list.get(4));
    assertEquals(new Integer(10), list.get(5));
    assertEquals(new Integer(12), list.get(6));
}

public StateConfiguration<S, T> permitDynamic(T trigger, final Func<S> destinationStateSelector, Action action) {
    return permitDynamicIf(trigger, destinationStateSelector, NO_GUARD, action);
}


@Test
public void testInitialStateEntryActionNotExecutedIfDisabled() {
    final State initial = State.B;
    StateMachineConfig<State, Trigger> config = config(initial);
    config.disableEntryActionOfInitialState();
    StateMachine<State, Trigger> sm = new StateMachine<>(initial, config);
    assertEquals(initial, sm.getState());
    assertFalse(executed);
}

public void disableEntryActionOfInitialState() {
    this.entryActionOfInitialStateEnabled = true;
}

public void disableEntryActionOfInitialState() {
    this.entryActionOfInitialStateEnabled = false;
}


@Test
public void StateRemainsUnchanged() {
    InternalTriggerBehaviour<State, Trigger> ignored = new InternalTriggerBehaviour<>(Trigger.X, returnTrue, nopAction);
    State target = ignored.transitionsTo(State.B, new Object[0]);
    assertEquals(State.B, target);
}

@Override
public S transitionsTo(S source, Object[] args) {
    return source;
}


@Test(expected = IllegalStateException.class)
public void ActionWithNegativeGuardIsNotPerformed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    TripwireAction action = new TripwireAction();
    config.configure(State.A).permitIf(Trigger.X, State.C, InternalTriggerBehaviourTests.returnFalse, action);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
}

@Test
public void ReentryActionIsPerformedBetweenExitAndEntry() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    List<Integer> list = new ArrayList<Integer>();
    Action entryAction = new CountingAction(list, new Integer(3));
    Action transitionAction = new CountingAction(list, new Integer(2));
    Action exitAction = new CountingAction(list, new Integer(1));
    config.configure(State.A).onEntry(entryAction).onExit(exitAction).permitReentry(Trigger.Z, transitionAction);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.Z);
    assertEquals(State.A, sm.getState());
    assertEquals(3, list.size());
    assertEquals(new Integer(1), list.get(0));
    assertEquals(new Integer(2), list.get(1));
    assertEquals(new Integer(3), list.get(2));
}

public StateConfiguration<S, T> permitReentry(T trigger, Action action) {
    return publicPermit(trigger, representation.getUnderlyingState(), action);
}


@Test
public void GuardedDynamicTransitionActionsArePerformed() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    List<Integer> list = new ArrayList<Integer>();
    FixedAccumulator<Integer> actionZero = new FixedAccumulator<Integer>(list, new Integer(0));
    AccumulatingAction<Integer> actionOne = new AccumulatingAction<Integer>(list);
    AccumulatingAction<Integer> actionTwo = new AccumulatingAction<Integer>(list);
    AccumulatingAction<Integer> actionThree = new AccumulatingAction<Integer>(list);
    config.configure(State.A).permitDynamicIf(Trigger.X, gotoB, IgnoredTriggerBehaviourTests.returnTrue, actionZero).permitDynamicIf(TriggerY2, gotoB, IgnoredTriggerBehaviourTests.returnTrue, actionTwo);
    config.configure(State.B).permitDynamicIf(TriggerX1, gotoA, IgnoredTriggerBehaviourTests.returnTrue, actionOne).permitDynamicIf(TriggerY3, gotoA, IgnoredTriggerBehaviourTests.returnTrue, actionThree);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
    sm.fire(TriggerX1, new Integer(3));
    sm.fire(TriggerY2, new Integer(6), new Integer(9));
    sm.fire(TriggerY3, new Integer(12), new Integer(15), new Integer(18));
    assertEquals(State.A, sm.getState());
    assertEquals(7, list.size());
    assertEquals(new Integer(0), list.get(0));
    assertEquals(new Integer(3), list.get(1));
    assertEquals(new Integer(6), list.get(2));
    assertEquals(new Integer(9), list.get(3));
    assertEquals(new Integer(12), list.get(4));
    assertEquals(new Integer(15), list.get(5));
    assertEquals(new Integer(18), list.get(6));
}


@Test
public void UnguardedActionIsPerformedOnReentry() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    TripwireAction action = new TripwireAction();
    config.configure(State.A).permitReentry(Trigger.Z, action);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.Z);
    assertEquals(State.A, sm.getState());
    assertTrue(action.wasPerformed());
}

@Test
public void ActionWithPositiveGuardIsPerformedOnReentry() {
    StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    TripwireAction action = new TripwireAction();
    config.configure(State.A).permitReentryIf(Trigger.X, IgnoredTriggerBehaviourTests.returnTrue, action);
    StateMachine<State, Trigger> sm = new StateMachine<>(State.A, config);
    sm.fire(Trigger.X);
    assertEquals(State.A, sm.getState());
    assertTrue(action.wasPerformed());
}

public StateConfiguration<S, T> permitReentryIf(T trigger, FuncBoolean guard, Action action) {
    return publicPermitIf(trigger, representation.getUnderlyingState(), guard, action);
}


@Test
public void DestinationStateIsCalculatedBasedOnTriggerParameters() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.A);
    TriggerWithParameters1<Integer, State, Trigger> trigger = sm.setTriggerParameters(Trigger.X, Integer.class);
    sm.configure(State.A).permitDynamic(trigger, new Func2<Integer, State>() {

        public State call(Integer i) throws Exception {
            return i == 1 ? State.B : State.C;
        }
    });
    sm.fire(trigger, 1);
    Assert.assertEquals(State.B, sm.getState());
}

public <TArg0> TriggerWithParameters1<TArg0, TState, TTrigger> setTriggerParameters(TTrigger trigger, Class<TArg0> classe0) throws Exception {
    TriggerWithParameters1<TArg0, TState, TTrigger> configuration = new TriggerWithParameters1<>(trigger, classe0);
    saveTriggerConfiguration(configuration);
    return configuration;
}

@Test
public void DestinationStateIsDynamic() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.A);
    sm.configure(State.A).permitDynamic(Trigger.X, new Func<State>() {

        public State call() {
            return State.B;
        }
    });
    sm.fire(Trigger.X);
    Assert.assertEquals(State.B, sm.getState());
}

public StateConfiguration<TState, TTrigger> permitDynamic(TTrigger trigger, final Func<TState> destinationStateSelector) throws Exception {
    return permitDynamicIf(trigger, destinationStateSelector, NO_GUARD);
}


@Test
public void ExposesCorrectUnderlyingTrigger() {
    IgnoredTriggerBehaviour<State, Trigger> ignored = new IgnoredTriggerBehaviour<State, Trigger>(Trigger.X, returnTrue);
    Assert.assertEquals(Trigger.X, ignored.getTrigger());
}

public TTrigger getTrigger() {
    return trigger;
}

@Test
public void StateRemainsUnchanged() {
    IgnoredTriggerBehaviour<State, Trigger> ignored = new IgnoredTriggerBehaviour<State, Trigger>(Trigger.X, returnTrue);
    try {
        ignored.resultsInTransitionFrom(State.B, new Object[0]);
        Assert.fail();
    } catch (Exception e) {
    }
}

public TState resultsInTransitionFrom(TState source, Object... args) throws Exception {
    throw new Exception();
}

@Test
public void WhenGuardConditionFalse_IsGuardConditionMetIsFalse() throws Exception {
    IgnoredTriggerBehaviour<State, Trigger> ignored = new IgnoredTriggerBehaviour<State, Trigger>(Trigger.X, returnFalse);
    Assert.assertFalse(ignored.isGuardConditionMet());
}

public Boolean isGuardConditionMet() throws Exception {
    return guard.call();
}

@Test
public void WhenGuardConditionTrue_IsGuardConditionMetIsTrue() throws Exception {
    IgnoredTriggerBehaviour<State, Trigger> ignored = new IgnoredTriggerBehaviour<State, Trigger>(Trigger.X, returnTrue);
    Assert.assertTrue(ignored.isGuardConditionMet());
}


@Test
public void WhenDiscriminatedByGuard_ChoosesPermitedTransition() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    sm.configure(State.B).permitIf(Trigger.X, State.A, IgnoredTriggerBehaviourTests.returnFalse).permitIf(Trigger.X, State.C, IgnoredTriggerBehaviourTests.returnTrue);
    sm.fire(Trigger.X);
    assertEquals(State.C, sm.getState());
}

public void fire(TTrigger trigger) throws Exception {
    publicFire(trigger);
}

@Test
public void WhenInSubstate_TriggerIgnoredInSuperstate_RemainsInSubstate() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    sm.configure(State.B).substateOf(State.C);
    sm.configure(State.C).ignore(Trigger.X);
    sm.fire(Trigger.X);
    assertEquals(State.B, sm.getState());
}

public TState getState() throws Exception {
    return stateAccessor.call();
}

@Test
public void TriggerParametersAreImmutableOnceSet() throws Exception {
    try {
        StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
        sm.setTriggerParameters(Trigger.X, String.class, int.class);
        sm.setTriggerParameters(Trigger.X, String.class);
        fail();
    } catch (Exception e) {
    }
}

@Test
public void InitialStateIsCurrent() throws Exception {
    State initial = State.B;
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(initial);
    assertEquals(initial, sm.getState());
}

@Test
public void PermittedTriggersIncludeSuperstatePermittedTriggers() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    sm.configure(State.A).permit(Trigger.Z, State.B);
    sm.configure(State.B).substateOf(State.C).permit(Trigger.X, State.A);
    sm.configure(State.C).permit(Trigger.Y, State.A);
    List<Trigger> permitted = sm.getPermittedTriggers();
    assertTrue(permitted.contains(Trigger.X));
    assertTrue(permitted.contains(Trigger.Y));
    assertFalse(permitted.contains(Trigger.Z));
}

public List<TTrigger> getPermittedTriggers() throws Exception {
    return getCurrentRepresentation().getPermittedTriggers();
}

@Test
public void SubstateIsIncludedInCurrentState() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    sm.configure(State.B).substateOf(State.C);
    assertEquals(State.B, sm.getState());
    assertTrue(sm.isInState(State.C));
}

public Boolean isInState(TState state) throws Exception {
    return getCurrentRepresentation().isIncludedIn(state);
}

@Test
public void WhenTriggerIsIgnored_ActionsNotExecuted() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    fired = false;
    sm.configure(State.B).onEntry(new Action() {

        public void doIt() {
            setFired();
        }
    }).ignore(Trigger.X);
    sm.fire(Trigger.X);
    assertFalse(fired);
}

@Test
public void ImplicitReentryIsDisallowed() {
    try {
        StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
        sm.configure(State.B).permit(Trigger.X, State.B);
    } catch (Exception e) {
    }
}

public StateConfiguration<TState, TTrigger> configure(TState state) throws Exception {
    return new StateConfiguration<>(getRepresentation(state), new Func2<TState, StateRepresentation<TState, TTrigger>>() {

        public StateRepresentation<TState, TTrigger> call(TState arg0) {
            return getRepresentation(arg0);
        }
    });
}

@Test
public void PermittedTriggersAreDistinctValues() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<>(State.B);
    sm.configure(State.B).substateOf(State.C).permit(Trigger.X, State.A);
    sm.configure(State.C).permit(Trigger.X, State.B);
    List<Trigger> permitted = sm.getPermittedTriggers();
    assertEquals(1, permitted.size());
    assertEquals(Trigger.X, permitted.get(0));
}

@Test
public void AcceptedTriggersRespectGuards() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    sm.configure(State.B).permitIf(Trigger.X, State.A, new Func<Boolean>() {

        public Boolean call() {
            return false;
        }
    });
    assertEquals(0, sm.getPermittedTriggers().size());
}

@Test
public void IfSelfTransitionPermited_ActionsFire() throws Exception {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    fired = false;
    sm.configure(State.B).onEntry(new Action() {

        public void doIt() {
            setFired();
        }
    }).permitReentry(Trigger.X);
    sm.fire(Trigger.X);
    assertTrue(fired);
}


@Test
public void IdentityTransitionIsNotChange() {
    Transition<Integer, Integer> t = new Transition<Integer, Integer>(1, 1, 0);
    Assert.assertTrue(t.isReentry());
}

public Boolean isReentry() {
    return getSource().equals(getDestination());
}

@Test
public void TransitioningTransitionIsChange() {
    Transition<Integer, Integer> t = new Transition<Integer, Integer>(1, 2, 0);
    Assert.assertFalse(t.isReentry());
}


@Test
public void TransitionsToDestinationState() {
    TransitioningTriggerBehaviour<State, Trigger> transtioning = new TransitioningTriggerBehaviour<State, Trigger>(Trigger.X, State.C, IgnoredTriggerBehaviourTests.returnTrue);
    State destination = transtioning.resultsInTransitionFrom(State.B, new Object[0]);
    Assert.assertEquals(State.C, destination);
}

public TState resultsInTransitionFrom(TState source, Object... args) {
    return destination;
}


@Test
public void WhenGuardConditionTrue_IsGuardConditionMetIsTrue() throws Exception {
    TransitioningTriggerBehaviour<State, Trigger> transtioning = new TransitioningTriggerBehaviour<State, Trigger>(Trigger.X, State.C, IgnoredTriggerBehaviourTests.returnTrue);
    Assert.assertTrue(transtioning.isGuardConditionMet());
}

@Test
public void WhenGuardConditionFalse_IsGuardConditionMetIsFalse() throws Exception {
    TransitioningTriggerBehaviour<State, Trigger> transtioning = new TransitioningTriggerBehaviour<State, Trigger>(Trigger.X, State.C, IgnoredTriggerBehaviourTests.returnFalse);
    Assert.assertFalse(transtioning.isGuardConditionMet());
}

@Test
public void ExposesCorrectUnderlyingTrigger() {
    TransitioningTriggerBehaviour<State, Trigger> transtioning = new TransitioningTriggerBehaviour<State, Trigger>(Trigger.X, State.C, IgnoredTriggerBehaviourTests.returnTrue);
    Assert.assertEquals(Trigger.X, transtioning.getTrigger());
}


@Test
public void IncompatibleParametersAreNotValid() {
    try {
        TriggerWithParameters1<String, State, Trigger> twp = new TriggerWithParameters1<String, State, Trigger>(Trigger.X, String.class);
        twp.validateParameters(new Object[] { 123 });
        fail();
    } catch (Exception e) {
    }
}

public void validateParameters(Object[] args) throws Exception {
    Enforce.argumentNotNull(args, "args");
    ParameterConversion.validate(args, argumentTypes);
}

@Test
public void ParametersOfCorrectTypeAreAccepted() throws Exception {
    TriggerWithParameters1<String, State, Trigger> twp = new TriggerWithParameters1<String, State, Trigger>(Trigger.X, String.class);
    twp.validateParameters(new Object[] { "arg" });
}

@Test
public void DescribesUnderlyingTrigger() throws Exception {
    TriggerWithParameters1<String, State, Trigger> twp = new TriggerWithParameters1<String, State, Trigger>(Trigger.X, String.class);
    assertEquals(Trigger.X, twp.getTrigger());
}

public TTrigger getTrigger() {
    return underlyingTrigger;
}

@Test
public void TooFewParametersDetected() throws Exception {
    try {
        TriggerWithParameters2<String, String, State, Trigger> twp = new TriggerWithParameters2<String, String, State, Trigger>(Trigger.X, String.class, String.class);
        twp.validateParameters(new Object[] { "a" });
        fail();
    } catch (Exception e) {
    }
}

@Test
public void TooManyParametersDetected() throws Exception {
    try {
        TriggerWithParameters2<String, String, State, Trigger> twp = new TriggerWithParameters2<String, String, State, Trigger>(Trigger.X, String.class, String.class);
        twp.validateParameters(new Object[] { "a", "b", "c" });
        fail();
    } catch (Exception e) {
    }
}

@Test
public void ParametersArePolymorphic() throws Exception {
    TriggerWithParameters1<String, State, Trigger> twp = new TriggerWithParameters1<String, State, Trigger>(Trigger.X, String.class);
    twp.validateParameters(new Object[] { "arg" });
}


@Test
public void testChangesValidation() throws Exception {
    File deb = File.createTempFile("jdeb", ".deb");
    File changesSave = File.createTempFile("changesSave", ".txt");
    File directory = new File(getClass().getResource("deb/data").toURI());
    DebMaker maker = new DebMaker(new NullConsole(), List.of(new UseNullAsInputStream()), null);
    assertThrows(PackagingException.class, maker::validate);
    maker.setControl(new File(getClass().getResource("deb/control").toURI()));
    assertThrows(PackagingException.class, maker::validate);
    maker.setDeb(deb);
    maker.validate();
    maker.setChangesEnabled(true);
    maker.validate();
    maker.setChangesIn(directory);
    assertThrows(PackagingException.class, maker::validate);
    maker.setChangesIn(new File(getClass().getResource("changes/changes.txt").toURI()));
    maker.validate();
    maker.setChangesSave(directory);
    assertThrows(PackagingException.class, maker::validate);
    maker.setChangesSave(null);
    maker.setChangesOut(directory);
    assertThrows(PackagingException.class, maker::validate);
    maker.setChangesOut(null);
    maker.setChangesSave(changesSave);
    maker.validate();
    maker.setChangesIn(null);
    assertThrows(PackagingException.class, maker::validate);
    maker.setChangesEnabled(false);
    maker.setCompression(null);
    assertThrows(PackagingException.class, maker::validate);
}

public void validate() throws PackagingException {
    if (control == null || !control.isDirectory()) {
        throw new PackagingException("The 'control' attribute doesn't point to a directory. " + control);
    }
    if (changesIn != null) {
        if (changesIn.exists() && (!changesIn.isFile() || !changesIn.canRead())) {
            throw new PackagingException("The 'changesIn' setting needs to point to a readable file. " + changesIn + " was not found/readable.");
        }
        if (changesOut != null && !isWritableFile(changesOut)) {
            throw new PackagingException("Cannot write the output for 'changesOut' to " + changesOut);
        }
        if (changesSave != null && !isWritableFile(changesSave)) {
            throw new PackagingException("Cannot write the output for 'changesSave' to " + changesSave);
        }
    } else {
        if (changesOut != null || changesSave != null) {
            throw new PackagingException("The 'changesOut' or 'changesSave' settings may only be used when there is a 'changesIn' specified.");
        }
    }
    if (Compression.toEnum(compression) == null) {
        throw new PackagingException("The compression method '" + compression + "' is not supported (expected 'none', 'gzip', 'bzip2' or 'xz')");
    }
    if (deb == null) {
        throw new PackagingException("You need to specify where the deb file is supposed to be created.");
    }
    PGPSigner.getDigestCode(digest);
}

public void validate() throws PackagingException {
    if (control == null || !control.isDirectory()) {
        throw new PackagingException("The 'control' attribute doesn't point to a directory. " + control);
    }
    if (changesEnabled) {
        if (changesIn != null) {
            if (changesIn.exists() && (!changesIn.isFile() || !changesIn.canRead())) {
                throw new PackagingException("The 'changesIn' setting needs to point to a readable file. " + changesIn + " was not found/readable.");
            }
            if (changesOut != null && !isWritableFile(changesOut)) {
                throw new PackagingException("Cannot write the output for 'changesOut' to " + changesOut);
            }
            if (changesSave != null && !isWritableFile(changesSave)) {
                throw new PackagingException("Cannot write the output for 'changesSave' to " + changesSave);
            }
        } else {
            if (changesOut != null || changesSave != null) {
                throw new PackagingException("The 'changesOut' or 'changesSave' settings may only be used when there is a 'changesIn' specified.");
            }
        }
    }
    if (Compression.toEnum(compression) == null) {
        throw new PackagingException("The compression method '" + compression + "' is not supported (expected 'none', 'gzip', 'bzip2' or 'xz')");
    }
    if (deb == null) {
        throw new PackagingException("You need to specify where the deb file is supposed to be created.");
    }
    PGPSigner.getDigestCode(digest);
}


@Test
public void testOneEdgeOldEnough() {
    long twoDaysPriorBenchmark = benchmarkTimeInMillis - oneDayInMillis * 2;
    SmallArrayBasedLongToDoubleMap socialProof = new SmallArrayBasedLongToDoubleMap();
    socialProof.put(100L, 1.0, twoDaysPriorBenchmark);
    SmallArrayBasedLongToDoubleMap[] socialProofs = { null, null, null, null, socialProof };
    long elapsedTimeInMillis = System.currentTimeMillis() - benchmarkTimeInMillis;
    RecentEdgeMetadataFilter filter = new RecentEdgeMetadataFilter(elapsedTimeInMillis + oneDayInMillis, (byte) RecommendationRequest.AUTHOR_SOCIAL_PROOF_TYPE, new NullStatsReceiver());
    assertEquals(false, filter.filterResult(1L, socialProofs));
}

@Override
public boolean filterResult(long resultNode, SmallArrayBasedLongToDoubleMap[] socialProofs) {
    SmallArrayBasedLongToDoubleMap socialProof = socialProofs[socialProofType];
    if (socialProof == null) {
        return false;
    }
    long[] allMetadata = socialProof.metadata();
    for (long metadataTimestamp : allMetadata) {
        if (cutoff < metadataTimestamp) {
            return true;
        }
    }
    return false;
}

@Test
public void testOneEdgeTooRecent() {
    long halfDaysPriorBenchmark = benchmarkTimeInMillis - oneDayInMillis / 2;
    SmallArrayBasedLongToDoubleMap socialProof = new SmallArrayBasedLongToDoubleMap();
    socialProof.put(100L, 1.0, halfDaysPriorBenchmark);
    SmallArrayBasedLongToDoubleMap[] socialProofs = { null, null, null, null, socialProof };
    long elapsedTimeInMillis = System.currentTimeMillis() - benchmarkTimeInMillis;
    RecentEdgeMetadataFilter filter = new RecentEdgeMetadataFilter(elapsedTimeInMillis + oneDayInMillis, (byte) RecommendationRequest.AUTHOR_SOCIAL_PROOF_TYPE, new NullStatsReceiver());
    assertEquals(true, filter.filterResult(1L, socialProofs));
}

@Test
public void testTwoEdgeTooRecent() {
    long halfDaysPriorBenchmark = benchmarkTimeInMillis - oneDayInMillis / 2;
    long twoDaysPriorBenchmark = benchmarkTimeInMillis - oneDayInMillis * 2;
    SmallArrayBasedLongToDoubleMap socialProof = new SmallArrayBasedLongToDoubleMap();
    socialProof.put(100L, 1.0, halfDaysPriorBenchmark);
    socialProof.put(101L, 1.0, twoDaysPriorBenchmark);
    SmallArrayBasedLongToDoubleMap[] socialProofs = { null, null, null, null, socialProof };
    long elapsedTimeInMillis = System.currentTimeMillis() - benchmarkTimeInMillis;
    RecentEdgeMetadataFilter filter = new RecentEdgeMetadataFilter(elapsedTimeInMillis + oneDayInMillis, (byte) RecommendationRequest.AUTHOR_SOCIAL_PROOF_TYPE, new NullStatsReceiver());
    assertEquals(true, filter.filterResult(1L, socialProofs));
}


@Test
public void testLesMisGraph() throws Exception {
    OutIndexedPowerLawMultiSegmentDirectedGraph graph = new OutIndexedPowerLawMultiSegmentDirectedGraph(1, 1000, 100, 10, 2, new IdentityEdgeTypeMask(), new NullStatsReceiver());
    for (int i = 0; i < LES_MIS_GRAPH.length; i++) {
        graph.addEdge(LES_MIS_GRAPH[i][0], LES_MIS_GRAPH[i][1], (byte) 0);
    }
    assertEquals(7, graph.getOutDegree(76));
    assertEquals(new LongArrayList(new long[] { 64, 65, 66, 63, 62, 48, 58 }), new LongArrayList(graph.getOutEdges(76)));
    assertEquals(1, graph.getOutDegree(30));
    assertEquals(new LongArrayList(new long[] { 23 }), new LongArrayList(graph.getOutEdges(30)));
    assertEquals(4, graph.getOutDegree(11));
    assertEquals(new LongArrayList(new long[] { 10, 3, 2, 0 }), new LongArrayList(graph.getOutEdges(11)));
    LongOpenHashSet nodes = new LongOpenHashSet();
    long maxNodeId = 0;
    for (int i = 0; i < LES_MIS_GRAPH.length; i++) {
        if (!nodes.contains(LES_MIS_GRAPH[i][0]))
            nodes.add(LES_MIS_GRAPH[i][0]);
        if (!nodes.contains(LES_MIS_GRAPH[i][1]))
            nodes.add(LES_MIS_GRAPH[i][1]);
        if (LES_MIS_GRAPH[i][0] > maxNodeId)
            maxNodeId = LES_MIS_GRAPH[i][0];
        if (LES_MIS_GRAPH[i][1] > maxNodeId)
            maxNodeId = LES_MIS_GRAPH[i][1];
    }
    assertEquals(76, maxNodeId);
    MultiThreadedPageRank pr = new MultiThreadedPageRank(graph, new LongArrayList(nodes), maxNodeId, 0.85, 10, 1e-15, 3);
    int numIterations = pr.run();
    double normL1 = pr.getL1Norm();
    AtomicDoubleArray pagerank = pr.getPageRankVector();
    assertEquals(10, numIterations);
    assertEquals(0.00108, normL1, 10e-4);
    List<Map.Entry<Long, Double>> scores = new ArrayList<>();
    for (int i = 0; i < maxNodeId + 1; i++) {
        scores.add(new AbstractMap.SimpleEntry<>((long) i, pagerank.get(i)));
    }
    scores.sort((e1, e2) -> e2.getValue() > e1.getValue() ? 1 : e2.getKey().compareTo(e1.getKey()));
    assertEquals(11, (long) scores.get(0).getKey());
    assertEquals(0.1088995, scores.get(0).getValue(), 10e-4);
    assertEquals(0, (long) scores.get(1).getKey());
    assertEquals(0.09538347, scores.get(1).getValue(), 10e-4);
    assertEquals(16, (long) scores.get(2).getKey());
    assertEquals(0.05104386, scores.get(2).getValue(), 10e-4);
    assertEquals(23, (long) scores.get(3).getKey());
    assertEquals(0.04389916, scores.get(3).getValue(), 10e-4);
    assertEquals(25, (long) scores.get(4).getKey());
    assertEquals(0.04095956, scores.get(4).getValue(), 10e-4);
    assertEquals(2, (long) scores.get(5).getKey());
    assertEquals(0.03868165, scores.get(5).getValue(), 10e-4);
    assertEquals(24, (long) scores.get(6).getKey());
    assertEquals(0.03617344, scores.get(6).getValue(), 10e-4);
    assertEquals(48, (long) scores.get(7).getKey());
    assertEquals(0.0290502, scores.get(7).getValue(), 10e-4);
    assertEquals(10, (long) scores.get(8).getKey());
    assertEquals(0.02714507, scores.get(8).getValue(), 10e-4);
    assertEquals(3, (long) scores.get(9).getKey());
    assertEquals(0.02714507, scores.get(9).getValue(), 10e-4);
    double totalMass = 0.0;
    for (int i = 0; i < maxNodeId + 1; i++) {
        totalMass += scores.get(i).getValue();
    }
    assertEquals(1.0, totalMass, 10e-10);
}

public int run() {
    LongArrayList noOuts = new LongArrayList();
    LongIterator iter = nodes.iterator();
    while (iter.hasNext()) {
        long v = iter.nextLong();
        if (graph.getOutDegree(v) == 0) {
            noOuts.add(v);
        }
    }
    double dampingAmount = (1.0 - dampingFactor) / nodeCount;
    prVector = new AtomicDoubleArray((int) (maxNodeId + 1));
    nodes.forEach(v -> prVector.set((int) (long) v, 1.0 / nodeCount));
    LongArrayList[] nodePartitions = new LongArrayList[threads];
    int partitionSize = nodes.size() / threads;
    for (int i = 0; i < threads; i++) {
        int startPos = i * partitionSize;
        int endPos = i == (threads - 1) ? nodes.size() : (i + 1) * partitionSize;
        nodePartitions[i] = new LongArrayList(nodes.subList(startPos, endPos));
    }
    int i = 0;
    while (i < this.maxIterations && normL1 > tolerance) {
        iterate(dampingAmount, noOuts, nodePartitions);
        i++;
    }
    return i;
}


@Test
public void testIdpSelectionWithoutForwarding() throws Exception {
    mockMvc.perform(get("/saml/idpSelection").session(mockAnonymousHttpSession())).andExpect(status().isOk()).andExpect(view().name("redirect:/"));
}

@RequestMapping(value = "/idpSelection", method = RequestMethod.GET)
public String idpSelection(HttpServletRequest request, Model model) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if (auth == null)
        LOG.debug("Current authentication instance from security context is null");
    else
        LOG.debug("Current authentication instance from security context: " + this.getClass().getSimpleName());
    if (auth == null || (auth instanceof AnonymousAuthenticationToken)) {
        Set<String> idps = metadata.getIDPEntityNames();
        for (String idp : idps) LOG.info("Configured Identity Provider for SSO: " + idp);
        model.addAttribute("idps", idps);
        return "saml/idpselection";
    } else {
        LOG.warn("The current user is already logged.");
        return "redirect:/landing";
    }
}

@RequestMapping(value = "/idpSelection", method = RequestMethod.GET)
public String idpSelection(HttpServletRequest request, Model model) {
    if (!(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken)) {
        LOG.warn("The current user is already logged.");
        return "redirect:/landing";
    } else {
        if (isForwarded(request)) {
            Set<String> idps = metadata.getIDPEntityNames();
            for (String idp : idps) LOG.info("Configured Identity Provider for SSO: " + idp);
            model.addAttribute("idps", idps);
            return "saml/idpselection";
        } else {
            LOG.warn("Direct accesses to '/idpSelection' route are not allowed");
            return "redirect:/";
        }
    }
}


@Test
public void test_Readme_Example_1Column() {
    V2_AsciiTable at = new V2_AsciiTable(1);
    at.addRuleStrong();
    at.addRow("Table Heading");
    at.addRule();
    at.addRow("first row (col1)");
    at.addRule();
    at.addRow("second row (col1)");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
}

public final void addRule() {
    if (this.table.size() == 0) {
        this.table.add(new V2_TableRow(V2_E_RuleType.TOP, this.columns));
    } else {
        this.table.add(new V2_TableRow(V2_E_RuleType.MID, this.columns));
    }
}

@Test
public void test_Readme_Example_3Columns() {
    V2_AsciiTable at = new V2_AsciiTable(3);
    at.addRuleStrong();
    at.addRow(null, null, "Table Heading");
    at.addRule();
    at.addRow("first row (col1)", "with some information", "and more information");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)", "and more information (col3)");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
}

@Test
public void test_Readme_Example_4Columns() {
    V2_AsciiTable at = new V2_AsciiTable(4);
    at.addRuleStrong();
    at.addRow(null, null, null, "Table Heading");
    at.addRule();
    at.addRow("first row (col1)", "with some information", "and more information", "even more");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)", "and more information (col3)", "even more");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
}

@Test
public void test_Readme_Example_ColSpanning() {
    V2_AsciiTable at = new V2_AsciiTable(5);
    at.addRuleStrong();
    at.addRow(null, null, null, null, "span all 5 columns");
    at.addRule();
    at.addRow(null, null, null, "span 4 columns", "just 1 column");
    at.addRule();
    at.addRow(null, null, "span 3 columns", null, "span 2 columns");
    at.addRule();
    at.addRow(null, "span 2 columns", null, null, "span 3 columns");
    at.addRule();
    at.addRow("just 1 column", null, null, null, "span 4 columns");
    at.addRule();
    at.addRow("just 1 column", "just 1 column", "just 1 column", "just 1 column", "just 1 column");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
}

public final void addRuleStrong() {
    if (this.table.size() == 0) {
        this.table.add(new V2_TableRow(V2_E_RuleType.TOP, V2_E_RuleStyle.STRONG, this.columns));
    } else {
        this.table.add(new V2_TableRow(V2_E_RuleType.MID, V2_E_RuleStyle.STRONG, this.columns));
    }
}

@Test
public void test_Readme_Example_TableThemes() {
    V2_AsciiTable at = new V2_AsciiTable(1);
    at.addRule();
    at.addRow("some text with padding");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setTheme(V2_E_TableThemes.UTF_DOUBLE_LIGHT.get());
    rend.setTheme(V2_E_TableThemes.UTF_DOUBLE.get());
}

public V2_AsciiTableRenderer setTheme(V2_TableTheme theme) {
    if (theme != null) {
        V2_Validator.testTableTheme(theme);
        this.theme = theme;
    }
    return this;
}

@Test
public void test_Readme_Example_PaddingChar() {
    V2_AsciiTable at = new V2_AsciiTable(1);
    at.addRule();
    at.addRow("some text with padding");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
    rend.setPaddingChar('*');
    rend.setPaddingChar('-');
    rend.setPaddingChar('␣');
}

public V2_AsciiTableRenderer setPaddingChar(char pChar) {
    this.paddingChar = pChar;
    return this;
}

@Test
public void test_Readme_Example_2Columns() {
    V2_AsciiTable at = new V2_AsciiTable(2);
    at.addRuleStrong();
    at.addRow(null, "Table Heading");
    at.addRule();
    at.addRow("first row (col1)", "with some information");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
}

@Test
public void test_Readme_Example_5Columns() {
    V2_AsciiTable at = new V2_AsciiTable(5);
    at.addRuleStrong();
    at.addRow(null, null, null, null, "Table Heading");
    at.addRule();
    at.addRow("first row (col1)", "with some information", "and more information", "even more", "more");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)", "and more information (col3)", "even more", "more");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthByAbsolute().setWidth(76));
}


@Test
public void testAddRow() {
    AsciiTable at;
    at = new AsciiTable();
    assertEquals(-1, at.addRow((Object) null));
    at = AsciiTable.newTable(3, 76);
    assertNotNull(at);
    assertEquals(0, at.addRow((Object) null));
    assertEquals(0, at.addRow());
    assertEquals(0, at.addRow(new Object[] {}));
    assertEquals(0, at.addRow(""));
    assertEquals(0, at.addRow("", ""));
    assertEquals(0, at.addRow("", "", "", ""));
    assertEquals(0, at.addRow("", "", "", "", ""));
    assertEquals(1, at.addRow(null, null, null));
    assertEquals(1, at.table.size());
    assertEquals(2, at.addRow("", "", ""));
    assertEquals(2, at.table.size());
    assertEquals(3, at.addRow("column 1 string, well in range", "column 2 string, well in range", "column 3 string, well in range"));
    assertEquals(3, at.table.size());
}

public int addRow(Object... columns) {
    if (this.columns == null) {
        return -1;
    }
    if (columns == null || columns.length != this.getColumnCount()) {
        return 0;
    }
    String[][] ar = new String[this.getColumnCount()][];
    for (int i = 0; i < columns.length; i++) {
        Object o = columns[i];
        ar[i] = this.obj2At(o, i + 1);
    }
    ar = ArrayTransformations.NORMALISE_ARRAY(this.getColumnCount(), ar);
    ar = ArrayTransformations.FLIP_ARRAY(ar);
    this.table.put(this.table.size() + 1, ar);
    return this.table.size();
}

@Test
public void testAPIDocInts() {
    AsciiTable at = AsciiTable.newTable(3, 76);
    at.addRow(null, null, "Table Heading");
    at.addRow("first row (col1)", "with some information", "and more information");
    at.addRow("second row (col1)", "with some information (col2)", "and more information (col3)");
}

@Test
public void testAPIDocArray() {
    Integer[] columns = new Integer[] { 10, 15, 20 };
    AsciiTable at = AsciiTable.newTable(columns);
    at.addRow(null, null, "Table Heading");
    at.addRow("row 1", "this is col 2", "and this is column 3");
    at.addRow("row 2", "some text for column 2", "and some text for column 3");
}


@Test
public void testMe() {
    Table at = new Table(3);
    at.addMidRule();
    at.addRow(null, null, "Table Heading");
    at.addMidRule();
    at.addRow("first row (col1)", "with some information", "and more information");
    at.addMidRule();
    at.addRow("second row (col1)", "with some information (col2)", "and more information (col3)");
    at.addMidRule();
    TableRowRenderer rend = new TableRowRenderer(at.getColumnCount(), 76);
    rend.setTheme(SimpleThemes.DOUBLE);
    List<StrBuilder> list = rend.render(at.table);
    for (StrBuilder sb : list) {
        System.out.println(sb.toString());
    }
}

public List<StrBuilder> render(List<TableRow> table) {
    this.prepareTable(table);
    List<StrBuilder> ret = new LinkedList<StrBuilder>();
    for (TableRow row : table) {
        ret.add(this.renderRow(row));
    }
    return ret;
}


@Test
public void test_TableWithoutArrayNeed() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("1-1", "1-2", "1-3");
    at.addRule();
    at.addRow("2-1", "2-2", "2-3");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthAbsoluteEven(19));
    RenderedTable rt = rend.render(at);
    System.out.println(rt);
}

@Override
public V2_AsciiTableRenderer setTheme(V2_TableTheme theme) {
    if (theme != null) {
        V2_TableThemeBuilder.testTableTheme(theme);
        this.theme = theme;
    }
    return this;
}

@Override
public V2_AsciiTableRenderer setTheme(V2_TableTheme theme) {
    if (theme != null) {
        ThemeValidator.validateTableTheme(theme);
        this.theme = theme;
    }
    return this;
}

@Test
public void test_TableWithArrayNeed() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("1-1", "1-2 text", "1-3");
    at.addRule();
    at.addRow("2-1", "2-2", "2-3");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthAbsoluteEven(19));
    RenderedTable rt = rend.render(at);
    System.out.println(rt);
}


@Test
public void test_Example_WidthLongestWord() {
    V2_AsciiTable at = new V2_AsciiTable(0);
    at.addRule();
    at.addRow("first row (col1)", "with some information (col2)");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthLongestWord());
    System.out.println("longest word 1");
    System.out.println(rend.render(at));
    at = new V2_AsciiTable(1);
    at.addRule();
    at.addRow("first row (col1)", "with some information (col2)");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)");
    at.addRule();
    rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthLongestWord());
    System.out.println("longest word 2");
    System.out.println(rend.render(at));
    at = new V2_AsciiTable(0);
    at.addRule();
    at.addRow("first row (col1)", "with some information (col2)").setPadding(new int[] { 2, 3 });
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)").setPadding(new int[] { 3, 4 });
    at.addRule();
    rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthLongestWord());
    System.out.println("longest word 3");
    System.out.println(rend.render(at));
}

@Override
public RenderedTable render(V2_AsciiTable table) {
    this.rows.clear();
    if (table == null || table.getColumnCount() == 0) {
        throw new IllegalArgumentException("wrong table argument: table is null or has no columns");
    }
    if (this.width == null) {
        throw new IllegalArgumentException("wrong table width argument: no width set");
    }
    int[] cols = this.width.setColumnCount(table.getColumnCount()).getColumnWidths(table.getDefaultPadding());
    V2_Utilities.fixTableRules(table);
    for (V2_Row row : table.getTable()) {
        this.rows.add(new ProcessedRow(row, cols, table.getColumnCount()));
    }
    BorderType[] array = V2_Utilities.getBorderTypes_TopRule((this.rows.size() > 1) ? this.rows.get(1) : null, this.rows.get(0).getOriginalRow(), table.getColumnCount());
    this.rows.get(0).setBorderTypes(array);
    array = V2_Utilities.getBorderTypes_BottomRule((this.rows.size() > 1) ? this.rows.get(this.rows.size() - 2) : null, this.rows.get(this.rows.size() - 1).getOriginalRow(), table.getColumnCount());
    this.rows.get(this.rows.size() - 1).setBorderTypes(array);
    if (this.rows.size() > 2) {
        for (int r = 1; r < this.rows.size() - 1; r++) {
            array = V2_Utilities.getBorderTypes_MidRule(this.rows.get(r - 1), (r < this.rows.size() - 2) ? this.rows.get(r + 1) : null, this.rows.get(r).getOriginalRow(), table.getColumnCount());
            this.rows.get(r).setBorderTypes(array);
        }
    }
    List<StrBuilder> ret = new LinkedList<StrBuilder>();
    for (ProcessedRow row : this.rows) {
        V2_Row original = row.getOriginalRow();
        if (original instanceof ContentRow) {
            ret.add(this.renderContentRow(row, cols));
        } else if (original instanceof RuleRow) {
            ret.add(this.renderRuleRow(row, cols));
        } else {
            System.err.println("ERROR in renderering");
        }
    }
    return new RenderedTable(ret);
}

@Override
public RenderedTable render(V2_AsciiTable table) {
    this.rows.clear();
    if (table == null || table.getColumnCount() == 0) {
        throw new IllegalArgumentException("wrong table argument: table is null or has no columns");
    }
    if (this.width == null) {
        throw new IllegalArgumentException("wrong table width argument: no width set");
    }
    int[] cols = this.width.getColumnWidths(table);
    for (V2_Row row : table.getTable()) {
        ProcessedRow pr = new ProcessedRow(row, cols, table.getColumnCount());
        if (row instanceof ContentRow) {
            ContentRow crow = (ContentRow) row;
            String[][] procColumns = RenderUtilities.createContentArray(crow.getColumns(), cols, crow.getPadding());
            pr.setProcessedColumns(procColumns);
            pr.setBorderTypes(RenderUtilities.getBorderTypes_ContentRow(procColumns[0], crow, table.getColumnCount()));
        }
        this.rows.add(pr);
    }
    BorderType[] array = RenderUtilities.getBorderTypes_TopRule((this.rows.size() > 1) ? this.rows.get(1) : null, this.rows.get(0).getOriginalRow(), table.getColumnCount());
    this.rows.get(0).setBorderTypes(array);
    array = RenderUtilities.getBorderTypes_BottomRule((this.rows.size() > 1) ? this.rows.get(this.rows.size() - 2) : null, this.rows.get(this.rows.size() - 1).getOriginalRow(), table.getColumnCount());
    this.rows.get(this.rows.size() - 1).setBorderTypes(array);
    if (this.rows.size() > 2) {
        for (int r = 1; r < this.rows.size() - 1; r++) {
            array = RenderUtilities.getBorderTypes_MidRule(this.rows.get(r - 1), (r < this.rows.size() - 2) ? this.rows.get(r + 1) : null, this.rows.get(r).getOriginalRow(), table.getColumnCount());
            this.rows.get(r).setBorderTypes(array);
        }
    }
    List<StrBuilder> ret = new LinkedList<StrBuilder>();
    for (int i = 0; i < this.rows.size(); i++) {
        V2_Row original = this.rows.get(i).getOriginalRow();
        if (original instanceof ContentRow) {
            ret.add(this.renderContentRow(this.rows.get(i), cols));
        } else if (original instanceof RuleRow) {
            if (i == 0) {
                ret.add(this.renderRuleRow(this.rows.get(i), cols, RuleRowType.TOP));
            } else if (i == (this.rows.size() - 1)) {
                ret.add(this.renderRuleRow(this.rows.get(i), cols, RuleRowType.BOTTOM));
            } else {
                ret.add(this.renderRuleRow(this.rows.get(i), cols, null));
            }
        } else {
            System.err.println("ERROR in renderering");
        }
    }
    return new RenderedTable(ret);
}


@Test
public void test_TableWithoutArrayNeed() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("1-1", "1-2", "1-3");
    at.addRule();
    at.addRow("2-1", "2-2", "2-3");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthAbsoluteEven(25));
    rend.setPaddingChar('_');
    RenderedTable rt = rend.render(at);
    System.out.println(rt);
}


@Test
public void test_CodeForDoc() {
    WidthAbsoluteEven w;
    int[] cols;
    V2_AsciiTable at;
    w = new WidthAbsoluteEven(74);
    at = new V2_AsciiTable();
    at.addRow("one");
    cols = w.getColumnWidths(at);
    assertEquals(1, cols.length);
    assertEquals(72, cols[0]);
    System.out.println(ArrayUtils.toString(cols));
    w = new WidthAbsoluteEven(74);
    at = new V2_AsciiTable();
    at.addRow("one", "two");
    cols = w.getColumnWidths(at);
    assertEquals(2, cols.length);
    assertEquals(36, cols[0]);
    assertEquals(35, cols[1]);
    System.out.println(ArrayUtils.toString(cols));
    w = new WidthAbsoluteEven(74);
    at = new V2_AsciiTable();
    at.addRow("one", "two", "three");
    cols = w.getColumnWidths(at);
    assertEquals(3, cols.length);
    assertEquals(24, cols[0]);
    assertEquals(23, cols[1]);
    assertEquals(23, cols[2]);
    System.out.println(ArrayUtils.toString(cols));
    w = new WidthAbsoluteEven(74);
    at = new V2_AsciiTable();
    at.addRow("one", "two", "three", "four");
    cols = w.getColumnWidths(at);
    assertEquals(4, cols.length);
    assertEquals(18, cols[0]);
    assertEquals(17, cols[1]);
    assertEquals(17, cols[2]);
    assertEquals(17, cols[3]);
    System.out.println(ArrayUtils.toString(cols));
    w = new WidthAbsoluteEven(74);
    at = new V2_AsciiTable();
    at.addRow("one", "two", "three", "four", "five");
    cols = w.getColumnWidths(at);
    assertEquals(5, cols.length);
    assertEquals(14, cols[0]);
    assertEquals(14, cols[1]);
    assertEquals(14, cols[2]);
    assertEquals(13, cols[3]);
    assertEquals(13, cols[4]);
    System.out.println(ArrayUtils.toString(cols));
    w = new WidthAbsoluteEven(79);
    at = new V2_AsciiTable();
    at.addRow("one", "two", "three", "four", "five");
    cols = w.getColumnWidths(at);
    assertEquals(5, cols.length);
    assertEquals(15, cols[0]);
    assertEquals(15, cols[1]);
    assertEquals(15, cols[2]);
    assertEquals(14, cols[3]);
    assertEquals(14, cols[4]);
    System.out.println(ArrayUtils.toString(cols));
}

@Override
public int[] getColumnWidths(V2_AsciiTable table) {
    if (table == null) {
        return null;
    }
    int colNumber = table.getColumnCount();
    int[] ret = new int[colNumber];
    int content = 0;
    if (table.getDefaultPadding() > 0) {
        content = colNumber * table.getDefaultPadding() + colNumber;
    } else {
        content = colNumber * 3;
    }
    int borders = colNumber + 1;
    if (this.width < (content + borders)) {
        throw new IllegalArgumentException("wrong width argument: width must allow for borders");
    }
    int distribute = this.width - 1 - colNumber;
    int colmin = distribute / colNumber;
    int leftover = distribute - colmin * colNumber;
    for (int i = 0; i < colNumber; i++) {
        ret[i] = colmin;
        if (leftover != 0) {
            ret[i] += 1;
            leftover -= 1;
        }
    }
    return ret;
}


@Test
public void testAdd() {
    WidthFixedColumns cw = new WidthFixedColumns();
    cw.add(5);
    assertEquals(1, cw.ar.length);
    assertEquals(5, cw.ar[0]);
    cw.add(3);
    assertEquals(2, cw.ar.length);
    assertEquals(5, cw.ar[0]);
    assertEquals(3, cw.ar[1]);
    cw.add(1);
    assertEquals(2, cw.ar.length);
    assertEquals(5, cw.ar[0]);
    assertEquals(3, cw.ar[1]);
}

public WidthFixedColumns add(int width) {
    if (width >= 3) {
        this.ar = ArrayUtils.add(this.ar, width);
    }
    return this;
}


@Test
public void test_CodeForDoc() {
    V2_AsciiTable at;
    int[] cols;
    V2_Width width;
    at = new V2_AsciiTable(0);
    at.addRule();
    at.addRow("first row (col1)", "with some information (col2)");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)");
    at.addRule();
    width = new WidthLongestWord();
    cols = width.getColumnWidths(at);
    assertEquals(2, cols.length);
    assertEquals(6, cols[0]);
    assertEquals(11, cols[1]);
    System.out.println(ArrayUtils.toString(cols));
    at = new V2_AsciiTable(1);
    at.addRule();
    at.addRow("first row (col1)", "with some information (col2)");
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)");
    at.addRule();
    width = new WidthLongestWord();
    cols = width.getColumnWidths(at);
    assertEquals(2, cols.length);
    assertEquals(8, cols[0]);
    assertEquals(13, cols[1]);
    System.out.println(ArrayUtils.toString(cols));
    at = new V2_AsciiTable(0);
    at.addRule();
    at.addRow("first row (col1)", "with some information (col2)").setPadding(new int[] { 2, 3 });
    at.addRule();
    at.addRow("second row (col1)", "with some information (col2)").setPadding(new int[] { 3, 4 });
    at.addRule();
    width = new WidthLongestWord();
    cols = width.getColumnWidths(at);
    assertEquals(2, cols.length);
    assertEquals(12, cols[0]);
    assertEquals(19, cols[1]);
    System.out.println(ArrayUtils.toString(cols));
    at = new V2_AsciiTable();
    at.addRow(new LoremIpsum().getWords());
    width = new WidthLongestWord();
    cols = width.getColumnWidths(at);
    assertEquals(1, cols.length);
    assertEquals(12, cols[0]);
    System.out.println(ArrayUtils.toString(cols));
}

@Override
public int[] getColumnWidths(V2_AsciiTable table) {
    return (table == null) ? null : WidthUtilities.longestWord(table);
}