@Test
public void testJsonSerializationForMissingValueFirst() {
    String expectedJson = "{\"my_field\":{\"missing\":\"_first\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.FIRST);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForMissingValueFirst() throws JSONException {
    String expectedJson = "{\"my_field\":{\"missing\":\"_first\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.FIRST);
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

public Map<String, Object> toMap() {
    Map<String, Object> innerMap = new HashMap<String, Object>();
    if (order != null) {
        innerMap.put("order", order.toString());
    }
    if (missing != null) {
        innerMap.put("missing", missing.toString());
    }
    if (unmapped != null) {
        innerMap.put("ignore_unmapped", unmapped);
    }
    Map<String, Object> rootMap = new HashMap<String, Object>();
    rootMap.put(field, innerMap);
    return rootMap;
}

@Test
public void testJsonSerializationForMissingValueString() {
    String expectedJson = "{\"my_field\":{\"missing\":\"***\"}}";
    Sort s = new Sort("my_field");
    s.setMissing("***");
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForMissingValueString() throws JSONException {
    String expectedJson = "{\"my_field\":{\"missing\":\"***\"}}";
    Sort s = new Sort("my_field");
    s.setMissing("***");
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

@Test
public void testJsonSerializationForDescOrder() {
    String expectedJson = "{\"my_field\":{\"order\":\"desc\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForDescOrder() throws JSONException {
    String expectedJson = "{\"my_field\":{\"order\":\"desc\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

public JsonObject toJsonObject() {
    JsonObject sortDefinition = new JsonObject();
    if (order != null) {
        sortDefinition.add("order", new JsonPrimitive(order.toString()));
    }
    if (missing != null) {
        sortDefinition.add("missing", new JsonPrimitive(missing.toString()));
    }
    if (unmapped != null) {
        sortDefinition.add("ignore_unmapped", new JsonPrimitive(unmapped));
    }
    JsonObject sortObject = new JsonObject();
    sortObject.add(field, sortDefinition);
    return sortObject;
}

@Test
public void testJsonSerializationForMissingValueInteger() {
    String expectedJson = "{\"my_field\":{\"missing\":\"-1\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(-1);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForMissingValueInteger() throws JSONException {
    String expectedJson = "{\"my_field\":{\"missing\":\"-1\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(-1);
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

@Test
public void testJsonSerializationWithUnmappedValue() {
    String expectedJson = "{\"my_field\":{\"ignore_unmapped\":true}}";
    Sort s = new Sort("my_field");
    s.setIgnoreUnmapped();
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationWithUnmappedValue() throws JSONException {
    String expectedJson = "{\"my_field\":{\"ignore_unmapped\":true}}";
    Sort s = new Sort("my_field");
    s.setIgnoreUnmapped();
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

@Test
public void testJsonSerializationWithOrderAndMissingValue() {
    String expectedJson = "{\"my_field\":{\"missing\":\"-1\",\"order\":\"desc\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    s.setMissing(-1);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationWithOrderAndMissingValue() throws JSONException {
    String expectedJson = "{\"my_field\":{\"order\":\"desc\",\"missing\":\"-1\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    s.setMissing(-1);
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

@Test
public void testJsonSerializationForAscOrder() {
    String expectedJson = "{\"my_field\":{\"order\":\"asc\"}}";
    Sort s = new Sort("my_field", Sort.Sorting.ASC);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForAscOrder() throws JSONException {
    String expectedJson = "{\"my_field\":{\"order\":\"asc\"}}";
    Sort s = new Sort("my_field", Sort.Sorting.ASC);
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

@Test
public void testJsonSerializationForMissingValueLast() {
    String expectedJson = "{\"my_field\":{\"missing\":\"_last\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.LAST);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForMissingValueLast() throws JSONException {
    String expectedJson = "{\"my_field\":{\"missing\":\"_last\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.LAST);
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}

@Test
public void testJsonSerializationForSimpleFieldSort() {
    String expectedJson = "{\"my_field\":{}}";
    Sort s = new Sort("my_field");
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForSimpleFieldSort() throws JSONException {
    String expectedJson = "{\"my_field\":{}}";
    Sort s = new Sort("my_field");
    JsonObject actualJsonObject = s.toJsonObject();
    JSONAssert.assertEquals(expectedJson, new Gson().toJson(actualJsonObject), false);
}


@Test
public void testSnapshotWithSettings() {
    final Settings.Builder registerRepositorySettings = Settings.settingsBuilder();
    registerRepositorySettings.put("indices", "index_1,index_2");
    registerRepositorySettings.put("ignore_unavailable", "true");
    registerRepositorySettings.put("include_global_state", "false");
    CreateSnapshot createSnapshot = new CreateSnapshot.Builder(repository).snapshot(snapshot).settings(registerRepositorySettings.build().getAsMap()).waitForCompletion(waitForCompletion).build();
    assertEquals("PUT", createSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola?wait_for_completion=true", createSnapshot.getURI());
    String settings = new Gson().toJson(createSnapshot.getData(new Gson()));
    assertEquals("\"{\\\"ignore_unavailable\\\":\\\"true\\\",\\\"include_global_state\\\":\\\"false\\\",\\\"indices\\\":\\\"index_1,index_2\\\"}\"", settings);
}

@Test
public void testSnapshotWithSettings() {
    final Settings.Builder registerRepositorySettings = Settings.settingsBuilder();
    registerRepositorySettings.put("indices", "index_1,index_2");
    registerRepositorySettings.put("ignore_unavailable", "true");
    registerRepositorySettings.put("include_global_state", "false");
    CreateSnapshot createSnapshot = new CreateSnapshot.Builder(repository, snapshot).settings(registerRepositorySettings.build().getAsMap()).waitForCompletion(true).build();
    assertEquals("PUT", createSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola?wait_for_completion=true", createSnapshot.getURI());
    String settings = new Gson().toJson(createSnapshot.getData(new Gson()));
    assertEquals("\"{\\\"ignore_unavailable\\\":\\\"true\\\",\\\"include_global_state\\\":\\\"false\\\",\\\"indices\\\":\\\"index_1,index_2\\\"}\"", settings);
}

public Builder snapshot(String snapshot) {
    this.snapshot = snapshot;
    return this;
}

@Test
public void testSnapshot() {
    CreateSnapshot createSnapshot = new CreateSnapshot.Builder(repository).snapshot(snapshot).waitForCompletion(waitForCompletion).build();
    assertEquals("PUT", createSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola?wait_for_completion=true", createSnapshot.getURI());
}

@Test
public void testSnapshot() {
    CreateSnapshot createSnapshot = new CreateSnapshot.Builder(repository, snapshot).waitForCompletion(true).build();
    assertEquals("PUT", createSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola?wait_for_completion=true", createSnapshot.getURI());
}


@Test
public void testSnapshot() {
    DeleteSnapshot deleteSnapshot = new DeleteSnapshot.Builder(repository).snapshot(snapshot).build();
    assertEquals("DELETE", deleteSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola", deleteSnapshot.getURI());
}

@Test
public void testSnapshot() {
    DeleteSnapshot deleteSnapshot = new DeleteSnapshot.Builder(repository, snapshot).build();
    assertEquals("DELETE", deleteSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola", deleteSnapshot.getURI());
}


@Test
public void testRepositoryAll() {
    GetSnapshotRepository getSnapshotRepository = new GetSnapshotRepository.Builder(repository).addRepository(Arrays.asList(repository, repository2)).all(true).build();
    assertEquals("/_snapshot/_all", getSnapshotRepository.getURI());
}

@Test
public void testRepositoryAll() {
    GetSnapshotRepository getSnapshotRepository = new GetSnapshotRepository.Builder().build();
    assertEquals("/_snapshot/_all", getSnapshotRepository.getURI());
}

public Builder all(boolean all) {
    this.isAll = all;
    return this;
}


@Test
public void testSnapshotAll() {
    GetSnapshot getSnapshotRepository = new GetSnapshot.Builder(repository).addSnapshot(snapshot).addSnapshot(Arrays.asList(snapshot2)).all(true).build();
    assertEquals("/_snapshot/kangsungjeon/_all", getSnapshotRepository.getURI());
}

@Test
public void testSnapshotAll() {
    GetSnapshot getSnapshotRepository = new GetSnapshot.Builder(repository).build();
    assertEquals("/_snapshot/kangsungjeon/_all", getSnapshotRepository.getURI());
}


@Test
public void testSnapshot() {
    final Settings.Builder registerRepositorySettings = Settings.settingsBuilder();
    registerRepositorySettings.put("indices", "index_1,index_2");
    registerRepositorySettings.put("ignore_unavailable", "true");
    registerRepositorySettings.put("include_global_state", "false");
    registerRepositorySettings.put("rename_pattern", "index_(.+)");
    registerRepositorySettings.put("rename_replacement", "restored_index_$1");
    RestoreSnapshot restoreSnapshot = new RestoreSnapshot.Builder(repository).snapshotName(snapshot).settings(registerRepositorySettings.build().getAsMap()).build();
    assertEquals("POST", restoreSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola/_restore", restoreSnapshot.getURI());
    String settings = new Gson().toJson(restoreSnapshot.getData(new Gson()));
    assertEquals("\"{\\\"ignore_unavailable\\\":\\\"true\\\",\\\"include_global_state\\\":\\\"false\\\",\\\"indices\\\":\\\"index_1,index_2\\\",\\\"rename_pattern\\\":\\\"index_(.+)\\\",\\\"rename_replacement\\\":\\\"restored_index_$1\\\"}\"", settings);
}

@Test
public void testSnapshot() {
    final Settings.Builder registerRepositorySettings = Settings.settingsBuilder();
    registerRepositorySettings.put("indices", "index_1,index_2");
    registerRepositorySettings.put("ignore_unavailable", "true");
    registerRepositorySettings.put("include_global_state", "false");
    registerRepositorySettings.put("rename_pattern", "index_(.+)");
    registerRepositorySettings.put("rename_replacement", "restored_index_$1");
    RestoreSnapshot restoreSnapshot = new RestoreSnapshot.Builder(repository, snapshot).settings(registerRepositorySettings.build().getAsMap()).build();
    assertEquals("POST", restoreSnapshot.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola/_restore", restoreSnapshot.getURI());
    String settings = new Gson().toJson(restoreSnapshot.getData(new Gson()));
    assertEquals("\"{\\\"ignore_unavailable\\\":\\\"true\\\",\\\"include_global_state\\\":\\\"false\\\",\\\"indices\\\":\\\"index_1,index_2\\\",\\\"rename_pattern\\\":\\\"index_(.+)\\\",\\\"rename_replacement\\\":\\\"restored_index_$1\\\"}\"", settings);
}

public Builder snapshotName(String snapshot) {
    this.snapshot = snapshot;
    return this;
}


@Test
public void testSnapshotSingleName() {
    SnapshotStatus snapshotStatus = new SnapshotStatus.Builder(repository).snapshot(snapshot).build();
    assertEquals("GET", snapshotStatus.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola/_status", snapshotStatus.getURI());
}

@Test
public void testSnapshotSingleName() {
    SnapshotStatus snapshotStatus = new SnapshotStatus.Builder(repository).addSnapshot(snapshot).build();
    assertEquals("GET", snapshotStatus.getRestMethodName());
    assertEquals("/_snapshot/leeseohoo/leeseola/_status", snapshotStatus.getURI());
}

public Builder snapshot(String snapshot) {
    this.snapshots.add(snapshot);
    return this;
}


@Test
public void testJsonSerializationForMissingValueFirst() {
    String expectedJson = "{\"my_field\":{\"missing\":\"_first\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.FIRST);
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationForMissingValueFirst() {
    String expectedJson = "{\"my_field\":{\"missing\":\"_first\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.FIRST);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

public Map<String, Object> toMap() {
    Map<String, Object> innerMap = new HashMap<String, Object>();
    if (order != null) {
        innerMap.put("order", order.toString());
    }
    if (missing != null) {
        innerMap.put("missing", missing.toString());
    }
    if (unmapped != null) {
        innerMap.put("ignore_unmapped", unmapped);
    }
    Map<String, Object> rootMap = new HashMap<String, Object>();
    rootMap.put(field, innerMap);
    return rootMap;
}

@Test
public void testJsonSerializationForMissingValueString() {
    String expectedJson = "{\"my_field\":{\"missing\":\"***\"}}";
    Sort s = new Sort("my_field");
    s.setMissing("***");
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationForMissingValueString() {
    String expectedJson = "{\"my_field\":{\"missing\":\"***\"}}";
    Sort s = new Sort("my_field");
    s.setMissing("***");
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForDescOrder() {
    String expectedJson = "{\"my_field\":{\"order\":\"desc\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationForDescOrder() {
    String expectedJson = "{\"my_field\":{\"order\":\"desc\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

public JsonObject toJsonObject() {
    JsonObject sortDefinition = new JsonObject();
    if (order != null) {
        sortDefinition.add("order", new JsonPrimitive(order.toString()));
    }
    if (missing != null) {
        sortDefinition.add("missing", new JsonPrimitive(missing.toString()));
    }
    if (unmapped != null) {
        sortDefinition.add("ignore_unmapped", new JsonPrimitive(unmapped));
    }
    JsonObject sortObject = new JsonObject();
    sortObject.add(field, sortDefinition);
    return sortObject;
}

@Test
public void testJsonSerializationForMissingValueInteger() {
    String expectedJson = "{\"my_field\":{\"missing\":\"-1\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(-1);
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationForMissingValueInteger() {
    String expectedJson = "{\"my_field\":{\"missing\":\"-1\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(-1);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationWithUnmappedValue() {
    String expectedJson = "{\"my_field\":{\"ignore_unmapped\":true}}";
    Sort s = new Sort("my_field");
    s.setIgnoreUnmapped();
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationWithUnmappedValue() {
    String expectedJson = "{\"my_field\":{\"ignore_unmapped\":true}}";
    Sort s = new Sort("my_field");
    s.setIgnoreUnmapped();
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationWithOrderAndMissingValue() {
    String expectedJson = "{\"my_field\":{\"order\":\"desc\",\"missing\":\"-1\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    s.setMissing(-1);
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationWithOrderAndMissingValue() {
    String expectedJson = "{\"my_field\":{\"missing\":\"-1\",\"order\":\"desc\"}}";
    Sort s = new Sort("my_field", Sorting.DESC);
    s.setMissing(-1);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForAscOrder() {
    String expectedJson = "{\"my_field\":{\"order\":\"asc\"}}";
    Sort s = new Sort("my_field", Sort.Sorting.ASC);
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationForAscOrder() {
    String expectedJson = "{\"my_field\":{\"order\":\"asc\"}}";
    Sort s = new Sort("my_field", Sort.Sorting.ASC);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForMissingValueLast() {
    String expectedJson = "{\"my_field\":{\"missing\":\"_last\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.LAST);
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationForMissingValueLast() {
    String expectedJson = "{\"my_field\":{\"missing\":\"_last\"}}";
    Sort s = new Sort("my_field");
    s.setMissing(Missing.LAST);
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}

@Test
public void testJsonSerializationForSimpleFieldSort() {
    String expectedJson = "{\"my_field\":{}}";
    Sort s = new Sort("my_field");
    JsonObject actualJsonObject = s.toJsonObject();
    assertEquals(expectedJson, new Gson().toJson(actualJsonObject));
}

@Test
public void testJsonSerializationForSimpleFieldSort() {
    String expectedJson = "{\"my_field\":{}}";
    Sort s = new Sort("my_field");
    Map actualMap = s.toMap();
    assertEquals(expectedJson, new Gson().toJson(actualMap));
}


@Test
public void gcd_of_array_containing_4_8_and_12_is_4() throws Exception {
    OptionalInt gcd = Snippets.arrayGcd(new int[] { 4, 8, 12 });
    assertThat(gcd).isNotEmpty();
    assertThat(gcd).hasValue(4);
}

@Test
public void gcd_of_array_containing_4_8_and_12_is_4() throws Exception {
    OptionalInt gcd = Snippets.gcd(new int[] { 4, 8, 12 });
    assertThat(gcd).isNotEmpty();
    assertThat(gcd).hasValue(4);
}

public static OptionalInt gcd(int[] numbers) {
    return Arrays.stream(numbers).reduce((a, b) -> gcd(a, b));
}

@Test
public void gcd_of_array_containing_1_to_5_is_1() throws Exception {
    OptionalInt gcd = Snippets.arrayGcd(new int[] { 1, 2, 3, 4, 5 });
    assertThat(gcd).isNotEmpty();
    assertThat(gcd).hasValue(1);
}

@Test
public void gcd_of_array_containing_1_to_5_is_1() throws Exception {
    OptionalInt gcd = Snippets.gcd(new int[] { 1, 2, 3, 4, 5 });
    assertThat(gcd).isNotEmpty();
    assertThat(gcd).hasValue(1);
}


@Test
public void annotatedTwice() {
    TypeName type = TypeName.get(String.class);
    TypeName actual = type.annotated(NEVER_NULL).annotated(TYPE_USE_ANNOTATION);
    assertThat(actual.toString()).isEqualTo("java.lang. @" + NN + " @" + TUA + " String");
}

@Test
public void annotatedTwice() {
    String expected = "@" + NN + " @java.lang.Override java.lang.String";
    TypeName type = TypeName.get(String.class);
    String actual = type.annotated(NEVER_NULL).annotated(AnnotationSpec.builder(Override.class).build()).toString();
    assertEquals(expected, actual);
}

@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;
}

@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;
}

@Test
public void annotatedType() {
    TypeName type = TypeName.get(String.class);
    TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
    assertThat(actual.toString()).isEqualTo("java.lang. @" + TUA + " String");
}

@Test
public void annotatedType() {
    String expected = "@" + NN + " java.lang.String";
    TypeName type = TypeName.get(String.class);
    String actual = type.annotated(NEVER_NULL).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedParameterizedType() {
    TypeName type = ParameterizedTypeName.get(List.class, String.class);
    TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
    assertThat(actual.toString()).isEqualTo("java.util. @" + TUA + " List<java.lang.String>");
}

@Test
public void annotatedParameterizedType() {
    String expected = "@" + NN + " java.util.List<java.lang.String>";
    TypeName type = ParameterizedTypeName.get(List.class, String.class);
    String actual = type.annotated(NEVER_NULL).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedWildcardTypeNameWithSuper() {
    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
    TypeName actual = WildcardTypeName.supertypeOf(type);
    assertThat(actual.toString()).isEqualTo("? super java.lang. @" + TUA + " String");
}

@Test
public void annotatedWildcardTypeNameWithSuper() {
    String expected = "? super @" + NN + " java.lang.String";
    TypeName type = TypeName.get(String.class).annotated(NEVER_NULL);
    String actual = WildcardTypeName.supertypeOf(type).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedWildcardTypeNameWithExtends() {
    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
    TypeName actual = WildcardTypeName.subtypeOf(type);
    assertThat(actual.toString()).isEqualTo("? extends java.lang. @" + TUA + " String");
}

@Test
public void annotatedWildcardTypeNameWithExtends() {
    String expected = "? extends @" + NN + " java.lang.String";
    TypeName type = TypeName.get(String.class).annotated(NEVER_NULL);
    String actual = WildcardTypeName.subtypeOf(type).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedArgumentOfParameterizedType() {
    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
    TypeName actual = ParameterizedTypeName.get(ClassName.get(List.class), type);
    assertThat(actual.toString()).isEqualTo("java.util.List<java.lang. @" + TUA + " String>");
}

@Test
public void annotatedArgumentOfParameterizedType() {
    String expected = "java.util.List<@" + NN + " java.lang.String>";
    TypeName type = TypeName.get(String.class).annotated(NEVER_NULL);
    ClassName list = ClassName.get(List.class);
    String actual = ParameterizedTypeName.get(list, type).toString();
    assertEquals(expected, actual);
}

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);
}

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);
}


@Test
public void statementBeginningCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1[", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[ and $] may not have an index");
    }
}

@Test
public void statementBeginningCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1[", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[, $], and $W may not have an index");
    }
}

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 $] 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");
        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;
}

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;
            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 dollarSignEscapeCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1$", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[ and $] may not have an index");
    }
}

@Test
public void dollarSignEscapeCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1$", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[, $], and $W may not have an index");
    }
}

@Test
public void statementEndingCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1]", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[ and $] may not have an index");
    }
}

@Test
public void statementEndingCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1]", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[, $], and $W may not have an index");
    }
}

@Test
public void deindentCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1<", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[ and $] may not have an index");
    }
}

@Test
public void deindentCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1<", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[, $], and $W may not have an index");
    }
}

@Test
public void indentCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1>", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[ and $] may not have an index");
    }
}

@Test
public void indentCannotBeIndexed() {
    try {
        CodeBlock.builder().add("$1>", "taco").build();
        fail();
    } catch (IllegalArgumentException exp) {
        assertThat(exp).hasMessage("$$, $>, $<, $[, $], and $W may not have an index");
    }
}


@Ignore
@Test
public void annotatedNestedType() {
    String expected = "java.util.Map.@" + TypeUseAnnotation.class.getCanonicalName() + " Entry";
    AnnotationSpec typeUseAnnotation = AnnotationSpec.builder(TypeUseAnnotation.class).build();
    TypeName type = TypeName.get(Map.Entry.class).annotated(typeUseAnnotation);
    String actual = type.toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedNestedType() {
    TypeName type = TypeName.get(Map.Entry.class).annotated(TYPE_USE_ANNOTATION);
    assertThat(type.toString()).isEqualTo("java.util.Map. @" + TUA + " Entry");
}

@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 annotatedTwice() {
    String expected = "@" + NN + " @java.lang.Override java.lang.String";
    TypeName type = TypeName.get(String.class);
    String actual = type.annotated(NEVER_NULL).annotated(AnnotationSpec.builder(Override.class).build()).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedTwice() {
    TypeName type = TypeName.get(String.class);
    TypeName actual = type.annotated(NEVER_NULL).annotated(TYPE_USE_ANNOTATION);
    assertThat(actual.toString()).isEqualTo("java.lang. @" + NN + " @" + TUA + " String");
}

@Test
public void annotatedType() {
    String expected = "@" + NN + " java.lang.String";
    TypeName type = TypeName.get(String.class);
    String actual = type.annotated(NEVER_NULL).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedType() {
    TypeName type = TypeName.get(String.class);
    TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
    assertThat(actual.toString()).isEqualTo("java.lang. @" + TUA + " String");
}

@Test
public void annotatedParameterizedType() {
    String expected = "@" + NN + " java.util.List<java.lang.String>";
    TypeName type = ParameterizedTypeName.get(List.class, String.class);
    String actual = type.annotated(NEVER_NULL).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedParameterizedType() {
    TypeName type = ParameterizedTypeName.get(List.class, String.class);
    TypeName actual = type.annotated(TYPE_USE_ANNOTATION);
    assertThat(actual.toString()).isEqualTo("java.util. @" + TUA + " List<java.lang.String>");
}

@Ignore
@Test
public void annotatedNestedParameterizedType() {
    String expected = "java.util.Map.@" + TypeUseAnnotation.class.getCanonicalName() + " Entry<java.lang.Byte, java.lang.Byte>";
    AnnotationSpec typeUseAnnotation = AnnotationSpec.builder(TypeUseAnnotation.class).build();
    TypeName type = ParameterizedTypeName.get(Map.Entry.class, Byte.class, Byte.class).annotated(typeUseAnnotation);
    String actual = type.toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedNestedParameterizedType() {
    TypeName type = ParameterizedTypeName.get(Map.Entry.class, Byte.class, Byte.class).annotated(TYPE_USE_ANNOTATION);
    assertThat(type.toString()).isEqualTo("java.util.Map. @" + TUA + " Entry<java.lang.Byte, java.lang.Byte>");
}

@Test
public void annotatedWildcardTypeNameWithSuper() {
    String expected = "? super @" + NN + " java.lang.String";
    TypeName type = TypeName.get(String.class).annotated(NEVER_NULL);
    String actual = WildcardTypeName.supertypeOf(type).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedWildcardTypeNameWithSuper() {
    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
    TypeName actual = WildcardTypeName.supertypeOf(type);
    assertThat(actual.toString()).isEqualTo("? super java.lang. @" + TUA + " String");
}

@Test
public void annotatedWildcardTypeNameWithExtends() {
    String expected = "? extends @" + NN + " java.lang.String";
    TypeName type = TypeName.get(String.class).annotated(NEVER_NULL);
    String actual = WildcardTypeName.subtypeOf(type).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedWildcardTypeNameWithExtends() {
    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
    TypeName actual = WildcardTypeName.subtypeOf(type);
    assertThat(actual.toString()).isEqualTo("? extends java.lang. @" + TUA + " String");
}

@Test
public void annotatedArgumentOfParameterizedType() {
    String expected = "java.util.List<@" + NN + " java.lang.String>";
    TypeName type = TypeName.get(String.class).annotated(NEVER_NULL);
    ClassName list = ClassName.get(List.class);
    String actual = ParameterizedTypeName.get(list, type).toString();
    assertEquals(expected, actual);
}

@Test
public void annotatedArgumentOfParameterizedType() {
    TypeName type = TypeName.get(String.class).annotated(TYPE_USE_ANNOTATION);
    TypeName actual = ParameterizedTypeName.get(ClassName.get(List.class), type);
    assertThat(actual.toString()).isEqualTo("java.util.List<java.lang. @" + TUA + " String>");
}

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);
}


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 AcceptedTriggersRespectGuards() {
    StateMachine<State, Trigger> sm = new StateMachine<State, Trigger>(State.B);
    sm.configure(State.B).permitIf(Trigger.X, State.A, new FuncBoolean() {

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

@Test
public void AcceptedTriggersRespectGuards() {
    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());
}

public StateConfiguration<TState, TTrigger> permitIf(TTrigger trigger, TState destinationState, FuncBoolean guard) {
    enforceNotIdentityTransition(destinationState);
    return publicPermitIf(trigger, destinationState, guard);
}

public StateConfiguration<TState, TTrigger> permitIf(TTrigger trigger, TState destinationState, Func<Boolean> guard) {
    enforceNotIdentityTransition(destinationState);
    return publicPermitIf(trigger, destinationState, guard);
}


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

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

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


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


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

@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) {
    }
}

@Override
public boolean resultsInTransitionFrom(TState source, Object[] args, OutVar<TState> dest) {
    return false;
}

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


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

@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);
}

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

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


@Test
public void testIdpSelection() throws Exception {
    when(metadata.getIDPEntityNames()).thenReturn(IDPS);
    mockMvc.perform(get("/saml/idpSelection").session(mockAnonymousHttpSession())).andExpect(status().isOk()).andExpect(model().attribute("idps", IDPS)).andExpect(view().name("saml/idpselection"));
}

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

@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 testIdpSelection() throws Exception {
    when(metadata.getIDPEntityNames()).thenReturn(IDPS);
    mockMvc.perform(get("/saml/discovery").session(mockAnonymousHttpSession())).andExpect(status().isOk()).andExpect(model().attribute("idps", IDPS)).andExpect(view().name("discovery"));
}

@Test
public void testIdpSelection() throws Exception {
    when(metadata.getIDPEntityNames()).thenReturn(IDPS);
    mockMvc.perform(get("/saml/discovery").session(mockAnonymousHttpSession())).andExpect(status().isOk()).andExpect(model().attribute("idps", IDPS)).andExpect(view().name("pages/discovery"));
}

@RequestMapping(value = "/discovery", 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 "discovery";
    } else {
        LOG.warn("The current user is already logged.");
        return "redirect:/landing";
    }
}

@RequestMapping(value = "/discovery", 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 "pages/discovery";
    } else {
        LOG.warn("The current user is already logged.");
        return "redirect:/landing";
    }
}


@Test
public void test_Readme_Example2() {
    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");
    at.setTheme(StandardTableThemes.LIGHT);
    at.render();
    at.setTheme(StandardTableThemes.LIGHT);
    at.render();
    at.setTheme(StandardTableThemes.DOUBLE);
    at.render();
    at.setTheme(StandardTableThemes.LIGHT_DOUBLE);
    at.render();
    at.setTheme(StandardTableThemes.DOUBLE_LIGHT);
    at.render();
    at.setTheme(StandardTableThemes.HEAVY);
    at.render();
    at.setTheme(StandardTableThemes.LATEX_LIGHT_TRIPLE_DASH);
    at.render();
}

@Test
public void test_Readme_Example2() {
    Integer[] columns = new Integer[] { 10, 15, 20 };
    V1_AsciiTable at = V1_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");
    at.setTheme(V1_StandardTableThemes.LIGHT);
    at.render();
    at.setTheme(V1_StandardTableThemes.LIGHT);
    at.render();
    at.setTheme(V1_StandardTableThemes.DOUBLE);
    at.render();
    at.setTheme(V1_StandardTableThemes.LIGHT_DOUBLE);
    at.render();
    at.setTheme(V1_StandardTableThemes.DOUBLE_LIGHT);
    at.render();
    at.setTheme(V1_StandardTableThemes.HEAVY);
    at.render();
    at.setTheme(V1_StandardTableThemes.LATEX_LIGHT_TRIPLE_DASH);
    at.render();
}

public String render() {
    StrBuilder ret = new StrBuilder(1000);
    List<StrBuilder> tab = this.renderTable(this.padChar, this.theme.getTheme());
    for (StrBuilder b : tab) {
        ret.appendln(b);
    }
    return ret.toString();
}


@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());
}

@Test
public void testAddRow() {
    V1_AsciiTable at;
    at = new V1_AsciiTable();
    assertEquals(-1, at.addRow((Object) null));
    at = V1_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 testMe() {
    AsciiTableRenderer rend = new AsciiTableRenderer();
    rend.setTheme(E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthByAbsolute().setWidth(73));
    AsciiTable at = null;
    at = new AsciiTable(1);
    at.addRuleStrong();
    at.addRow("Table Heading");
    at.addRule();
    at.addRow("first row (col1)");
    at.addRule();
    at.addRow("second row (col1)");
    at.addRule();
    at = new 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();
    at = new 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();
    at = new 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();
    at = new 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();
}

@Test
public void testMe() {
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthByAbsolute().setWidth(73));
    V2_AsciiTable at = null;
    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();
    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();
    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();
    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();
    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();
}

public TableTheme get() {
    return this.theme;
}


@Test
public void testMe() {
    System.out.println(E_TableThemes.UTF_LIGHT.get().toDoc());
    AsciiTable at = new 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();
    AsciiTableRenderer rend = new AsciiTableRenderer();
    rend.setTheme(E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthByAbsolute().setWidth(76));
    AsciiTableRendered rat = rend.render(at);
    System.out.println(rat);
}

@Test
public void testMe() {
    System.out.println(E_TableThemes.UTF_LIGHT.get().toDoc());
    AsciiTable at = new 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();
    AsciiTableRenderer rend = new AsciiTableRenderer();
    rend.setTheme(E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthByAbsolute().setWidth(76));
    RenderedAsciiTable rat = rend.render(at);
    System.out.println(rat);
}

public AsciiTableRendered render(AsciiTable table) {
    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.calculateWidth(table.getColumnCount());
    boolean[] borders = new boolean[cols.length];
    for (int i = 0; i < borders.length; i++) {
        borders[i] = true;
    }
    table.fixRules();
    for (TableRow row : table.table) {
        this.rows.add(new ProcessedRow(row, cols));
    }
    this.rows.get(0).adjustTopRuleBorder((this.rows.size() > 1) ? this.rows.get(1) : null);
    this.rows.get(this.rows.size() - 1).adjustBottomRuleBorder((this.rows.size() > 1) ? this.rows.get(this.rows.size() - 2) : null);
    if (this.rows.size() > 2) {
        for (int r = 1; r < this.rows.size() - 1; r++) {
            this.rows.get(r).adjustMidRuleBorder(this.rows.get(r - 1), (r < this.rows.size() - 2) ? this.rows.get(r + 1) : null);
        }
    }
    List<StrBuilder> ret = new LinkedList<StrBuilder>();
    for (ProcessedRow row : this.rows) {
        ret.add(this.renderRow(row, cols));
    }
    return new AsciiTableRendered(ret);
}

public RenderedAsciiTable render(AsciiTable table) {
    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.calculateWidth(table.getColumnCount());
    boolean[] borders = new boolean[cols.length];
    for (int i = 0; i < borders.length; i++) {
        borders[i] = true;
    }
    table.fixRules();
    for (TableRow row : table.table) {
        this.rows.add(new ProcessedRow(row, cols));
    }
    this.rows.get(0).adjustTopRuleBorder((this.rows.size() > 1) ? this.rows.get(1) : null);
    this.rows.get(this.rows.size() - 1).adjustBottomRuleBorder((this.rows.size() > 1) ? this.rows.get(this.rows.size() - 2) : null);
    if (this.rows.size() > 2) {
        for (int r = 1; r < this.rows.size() - 1; r++) {
            this.rows.get(r).adjustMidRuleBorder(this.rows.get(r - 1), (r < this.rows.size() - 2) ? this.rows.get(r + 1) : null);
        }
    }
    List<StrBuilder> ret = new LinkedList<StrBuilder>();
    for (ProcessedRow row : this.rows) {
        ret.add(this.renderRow(row, cols));
    }
    return new RenderedAsciiTable(ret);
}


@Test
public void testMe() {
    System.out.println(E_TableThemes.UTF_LIGHT.get().toDoc());
    AsctiiTable at = new AsctiiTable(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();
    AsciiTableRenderer rend = new AsciiTableRenderer();
    rend.setTheme(E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthByAbsolute().setWidth(76));
    AsciiTableRendered rat = rend.render(at);
    System.out.println(rat);
}

@Test
public void testMe() {
    System.out.println(E_TableThemes.UTF_LIGHT.get().toDoc());
    AsciiTable at = new 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();
    AsciiTableRenderer rend = new AsciiTableRenderer();
    rend.setTheme(E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthByAbsolute().setWidth(76));
    AsciiTableRendered rat = rend.render(at);
    System.out.println(rat);
}

public AsciiTableRendered render(AsctiiTable table) {
    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.calculateWidth(table.getColumnCount());
    boolean[] borders = new boolean[cols.length];
    for (int i = 0; i < borders.length; i++) {
        borders[i] = true;
    }
    table.fixRules();
    for (TableRow row : table.table) {
        this.rows.add(new ProcessedRow(row, cols));
    }
    this.rows.get(0).adjustTopRuleBorder((this.rows.size() > 1) ? this.rows.get(1) : null);
    this.rows.get(this.rows.size() - 1).adjustBottomRuleBorder((this.rows.size() > 1) ? this.rows.get(this.rows.size() - 2) : null);
    if (this.rows.size() > 2) {
        for (int r = 1; r < this.rows.size() - 1; r++) {
            this.rows.get(r).adjustMidRuleBorder(this.rows.get(r - 1), (r < this.rows.size() - 2) ? this.rows.get(r + 1) : null);
        }
    }
    List<StrBuilder> ret = new LinkedList<StrBuilder>();
    for (ProcessedRow row : this.rows) {
        ret.add(this.renderRow(row, cols));
    }
    return new AsciiTableRendered(ret);
}

public AsciiTableRendered render(AsciiTable table) {
    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.calculateWidth(table.getColumnCount());
    boolean[] borders = new boolean[cols.length];
    for (int i = 0; i < borders.length; i++) {
        borders[i] = true;
    }
    table.fixRules();
    for (TableRow row : table.table) {
        this.rows.add(new ProcessedRow(row, cols));
    }
    this.rows.get(0).adjustTopRuleBorder((this.rows.size() > 1) ? this.rows.get(1) : null);
    this.rows.get(this.rows.size() - 1).adjustBottomRuleBorder((this.rows.size() > 1) ? this.rows.get(this.rows.size() - 2) : null);
    if (this.rows.size() > 2) {
        for (int r = 1; r < this.rows.size() - 1; r++) {
            this.rows.get(r).adjustMidRuleBorder(this.rows.get(r - 1), (r < this.rows.size() - 2) ? this.rows.get(r + 1) : null);
        }
    }
    List<StrBuilder> ret = new LinkedList<StrBuilder>();
    for (ProcessedRow row : this.rows) {
        ret.add(this.renderRow(row, cols));
    }
    return new AsciiTableRendered(ret);
}


@Test
public void test_Example_5Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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_WidthAbsoluteEven().setWidth(76));
}

@Test
public void test_Example_5Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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 WidthAbsoluteEven(76));
    System.out.println("table 5 columns");
    System.out.println(rend.render(at));
}

@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_Example_4Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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_WidthAbsoluteEven().setWidth(76));
}

@Test
public void test_Example_4Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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 WidthAbsoluteEven(76));
    System.out.println("table 4 columns");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_1Column() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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_WidthAbsoluteEven().setWidth(76));
}

@Test
public void test_Example_1Column() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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 WidthAbsoluteEven(76));
    System.out.println("table 1 columns");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_TableThemes() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("some column text");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setWidth(new V2_WidthAbsoluteEven().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());
}

@Test
public void test_Example_TableThemes() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("some column text");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setWidth(new WidthAbsoluteEven(76));
    System.out.println(rend.render(at));
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    System.out.println("themes 1");
    System.out.println(rend.render(at));
    rend.setTheme(V2_E_TableThemes.UTF_DOUBLE_LIGHT.get());
    System.out.println("themes 2");
    System.out.println(rend.render(at));
    rend.setTheme(V2_E_TableThemes.UTF_DOUBLE.get());
    System.out.println("themes 3");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_2Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow(null, "Table Heading");
    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 V2_WidthAbsoluteEven().setWidth(76));
}

@Test
public void test_Example_2Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow(null, "Table Heading");
    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 WidthAbsoluteEven(76));
    System.out.println("table 2 columns");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_Padding() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("padding 0", "padding 1", "padding 2", "padding 3", "padding 4").setPadding(new int[] { 0, 1, 2, 3, 4 });
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthAbsoluteEven().setWidth(76));
}

@Test
public void test_Example_Padding() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("padding 0", "padding 1", "padding 2", "padding 3", "padding 4").setPadding(new int[] { 0, 1, 2, 3, 4 });
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthAbsoluteEven(76));
    System.out.println("padding");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_RuleStyle() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addStrongRule();
    at.addRow("col1", "col2", "col3");
    at.addStrongRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addStrongRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addStrongRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setWidth(new V2_WidthAbsoluteEven().setWidth(76));
    rend.setTheme(V2_E_TableThemes.ASC7_LATEX_STYLE_STRONG.get());
    rend.setTheme(V2_E_TableThemes.ASC7_LATEX_STYLE_STRONG2.get());
}

@Test
public void test_Example_RuleStyle() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addStrongRule();
    at.addRow("col1", "col2", "col3");
    at.addStrongRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addStrongRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addStrongRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setWidth(new WidthAbsoluteEven(76));
    rend.setTheme(V2_E_TableThemes.ASC7_LATEX_STYLE_STRONG.get());
    System.out.println("rule style 1");
    System.out.println(rend.render(at));
    rend.setTheme(V2_E_TableThemes.ASC7_LATEX_STYLE_STRONG2.get());
    System.out.println("rule style 2");
    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_Example_Alignment_Justified() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow(new LoremIpsum().getWords()).setAlignment(new char[] { 'j' });
    at.addRule();
    at.addRow(new LoremIpsum().getWords()).setAlignment(new char[] { 't' });
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthAbsoluteEven().setWidth(60));
}

@Test
public void test_Example_Alignment_Justified() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow(new LoremIpsum().getWords()).setAlignment(new char[] { 'j' });
    at.addRule();
    at.addRow(new LoremIpsum().getWords()).setAlignment(new char[] { 't' });
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthAbsoluteEven(60));
    System.out.println("align justified");
    System.out.println(rend.render(at));
}

public V2_WidthAbsoluteEven setWidth(int width) {
    if (width >= 3) {
        this.width = width;
    }
    return this;
}

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

@Test
public void test_Example_PaddingChar() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("some text with padding");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setWidth(new WidthAbsoluteEven(76));
    System.out.println("padding char 1");
    System.out.println(rend.render(at));
    rend.setPaddingChar('*');
    System.out.println("padding char 2");
    System.out.println(rend.render(at));
    rend.setPaddingChar('-');
    System.out.println("padding char 3");
    System.out.println(rend.render(at));
    rend.setPaddingChar('␣');
    System.out.println("padding char 4");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_3Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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_WidthAbsoluteEven().setWidth(76));
}

@Test
public void test_Example_3Columns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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 WidthAbsoluteEven(76));
    System.out.println("table 4 columns");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_Alignment_Std() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("left", "right", "center").setAlignment(new char[] { 'l', 'r', 'c' });
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthAbsoluteEven().setWidth(80));
}

@Test
public void test_Example_Alignment_Std() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("left", "right", "center").setAlignment(new char[] { 'l', 'r', 'c' });
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthAbsoluteEven(80));
    System.out.println("align standard");
    System.out.println(rend.render(at));
}

@Test
public void test_Example_ColSpanning() {
    V2_AsciiTable at = new V2_AsciiTable().setDefaultPadding(1);
    at.addRule();
    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_WidthAbsoluteEven().setWidth(81));
}

@Test
public void test_Example_ColSpanning() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    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 WidthAbsoluteEven(81));
    System.out.println("col spanning 1");
    System.out.println(rend.render(at));
}

public V2_AsciiTable setDefaultPadding(int padding) {
    this.defaultPadding = padding;
    return this;
}

@Test
public void test_Example_WidthFixedColumns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthFixedColumns().add(10).add(20).add(30));
    rend.setWidth(new V2_WidthFixedColumns().add(5).add(10).add(15));
    rend.setWidth(new V2_WidthFixedColumns().add(3).add(5).add(7));
}

@Test
public void test_Example_WidthFixedColumns() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthFixedColumns().add(10).add(20).add(30));
    System.out.println("fixed columns 1");
    System.out.println(rend.render(at));
    rend.setWidth(new WidthFixedColumns().add(5).add(10).add(15));
    System.out.println("fixed columns 2");
    System.out.println(rend.render(at));
    rend.setWidth(new WidthFixedColumns().add(3).add(5).add(7));
    System.out.println("fixed columns 3");
    System.out.println(rend.render(at));
}

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

@Test
public void test_Example_SimpleTable() {
    V2_AsciiTable at = new V2_AsciiTable();
    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 V2_WidthAbsoluteEven().setWidth(76));
}

@Test
public void test_Example_SimpleTable() {
    V2_AsciiTable at = new V2_AsciiTable();
    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 WidthAbsoluteEven(76));
    RenderedTable rt = rend.render(at);
    System.out.println("simple table");
    System.out.println(rt);
}

@Test
public void test_Example_WidthAbsoluteEven() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new V2_WidthAbsoluteEven().setWidth(50));
    rend.setWidth(new V2_WidthAbsoluteEven().setWidth(30));
    rend.setWidth(new V2_WidthAbsoluteEven().setWidth(20));
}

@Test
public void test_Example_WidthAbsoluteEven() {
    V2_AsciiTable at = new V2_AsciiTable();
    at.addRule();
    at.addRow("col1", "col2", "col3");
    at.addRule();
    V2_AsciiTableRenderer rend = new V2_AsciiTableRenderer();
    rend.setTheme(V2_E_TableThemes.UTF_LIGHT.get());
    rend.setWidth(new WidthAbsoluteEven(50));
    System.out.println("absolute even 1");
    System.out.println(rend.render(at));
    rend.setWidth(new WidthAbsoluteEven(30));
    System.out.println("absolute even 2");
    System.out.println(rend.render(at));
    rend.setWidth(new WidthAbsoluteEven(20));
    System.out.println("absolute even 3");
    System.out.println(rend.render(at));
}


@Test
public void test_Themes() {
    for (V2_E_RowThemes t : V2_E_RowThemes.values()) {
        assertTrue(t.get() != null);
        V2_RowThemeBuilder.testRowTheme(t.get());
        System.out.println(t.name() + "\n" + t.get().toDoc() + "\n\n");
    }
}

@Test
public void test_Themes() {
    for (V2_E_RowThemes t : V2_E_RowThemes.values()) {
        assertTrue(t.get() != null);
        ThemeValidator.validateRowTheme(t.get());
        System.out.println(t.name() + "\n" + t.get().toDoc() + "\n\n");
    }
}

public static void testRowTheme(V2_RowTheme theme) {
    if (theme.getLeftBorder() == 0) {
        throw new TableException("row theme incomplete", "no left border defined");
    }
    if (theme.getRightBorder() == 0) {
        throw new TableException("row theme incomplete", "no right border defined");
    }
    if (theme.getMid() == 0) {
        throw new TableException("row theme incomplete", "no mid character defined");
    }
    if (theme.getMidBorderAll() == 0) {
        throw new TableException("row theme incomplete", "no mid all character defined");
    }
    if (theme.getMidBorderDown() == 0) {
        throw new TableException("row theme incomplete", "no mid down character defined");
    }
    if (theme.getMidBorderUp() == 0) {
        throw new TableException("row theme incomplete", "no mid up character defined");
    }
    if (theme.getDescription() == null) {
        throw new TableException("row theme incomplete", "no description defined");
    }
}


@Test
public void test_Themes() {
    for (V2_E_TableThemes t : V2_E_TableThemes.values()) {
        assertTrue(t.get() != null);
        V2_TableThemeBuilder.testTableTheme(t.get());
        System.out.println(t.name() + "\n" + t.get().toDoc() + "\n\n");
    }
}

@Test
public void test_Themes() {
    for (V2_E_TableThemes t : V2_E_TableThemes.values()) {
        assertTrue(t.get() != null);
        ThemeValidator.validateTableTheme(t.get());
        System.out.println(t.name() + "\n" + t.get().toDoc() + "\n\n");
    }
}

public static void testTableTheme(V2_TableTheme theme) {
    if (theme.getTop() == null) {
        throw new TableException("table theme incomplete", "no top row defined");
    }
    if (theme.getTopStrong() == null) {
        throw new TableException("table theme incomplete", "no top row strong defined");
    }
    if (theme.getMid() == null) {
        throw new TableException("table theme incomplete", "no mid row strong defined");
    }
    if (theme.getMidStrong() == null) {
        throw new TableException("table theme incomplete", "no mid row strong defined");
    }
    if (theme.getBottom() == null) {
        throw new TableException("table theme incomplete", "no bottom row strong defined");
    }
    if (theme.getBottomStrong() == null) {
        throw new TableException("table theme incomplete", "no bottom row strong defined");
    }
    if (theme.getContent() == null) {
        throw new TableException("table theme incomplete", "no content row defined");
    }
    if (theme.getDescription() == null) {
        throw new TableException("table theme incomplete", "no description defined");
    }
}