@Test
public void shouldConstrainGeneratorsByAssumptions() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c, d) -> a > 2 && b > 2 && c > 2 && d > 2).check((a, b, c, d) -> a > 2 && b > 2 && c > 2 && d > 2);
    verifier.notFalsified();
}

@Test
public void shouldConstrainGeneratorsByAssumptions() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c, d) -> a > 2 && b > 2 && c > 2 && d > 2).check((a, b, c, d) -> a > 2 && b > 2 && c > 2 && d > 2);
    verifier.notFalsified();
}

@CheckReturnValue
public TheoryBuilder4<A, B, C, D> assuming(Predicate4<A, B, C, D> newAssumption) {
    return new TheoryBuilder4<A, B, C, D>(this.state, this.as, this.bs, this.cs, this.ds, this.assumptions.and(newAssumption));
}

@Test
public void shouldFalisyWhenAssertionsThrownAfterTypeConversion() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).as((a, b, c, d) -> a.toString() + b + c + d).checkAssert(a -> assertEquals("111a", a));
    verifier.isFalsifiedByException();
}

@Test
public void shouldFalisyWhenAssertionsThrownAfterTypeConversion() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).as((a, b, c, d) -> a.toString() + b + c + d).checkAssert(a -> assertThat("111a").isEqualTo(a));
    verifier.isFalsifiedByException();
}

@CheckReturnValue
public <T> Subject1<T> as(Function4<A, B, C, D, T> mapping) {
    return new MappingTheoryBuilder<>(this.state, combine(), tuple -> mapping.apply(tuple._1, tuple._2, tuple._3, tuple._4), t -> t.toString());
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).asWithPrecursor((a, b, c, d) -> a.toString()).checkAssert((a, b, c, d, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).asWithPrecursor((a, b, c, d) -> a.toString()).checkAssert((a, b, c, d, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@CheckReturnValue
public <T> Subject5<A, B, C, D, T> asWithPrecursor(Function4<A, B, C, D, T> mapping) {
    return asWithPrecursor(mapping, t -> t.toString());
}

@Test
public void shouldContinueToShrinkWhenSecondAtSmallestValue() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b, c, d) -> d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 1 && b == 0 && c == 1 && d == 1);
}

@Test
public void shouldContinueToShrinkWhenSecondAtSmallestValue() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b, c, d) -> d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 1 && b == 0 && c == 1 && d == 1);
}

@Test
public void shouldShrinkWhenAllSidesCanBeShrunk() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b, c, d) -> a == 5 && b == 5 && c == 5 && d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 1 && b == 1 && c == 1 && d == 1);
}

@Test
public void shouldShrinkWhenAllSidesCanBeShrunk() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b, c, d) -> a == 5 && b == 5 && c == 5 && d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 1 && b == 1 && c == 1 && d == 1);
}

@Test
public void shouldContinueToShrinkWhenRightHandSideAtSmallestValue() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0)).check((a, b, c, d) -> d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 1 && b == 1 && c == 1 && d == 0);
}

@Test
public void shouldContinueToShrinkWhenRightHandSideAtSmallestValue() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0)).check((a, b, c, d) -> d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 1 && b == 1 && c == 1 && d == 0);
}

@Test
public void shouldContinueToShrinkWhenLeftHandSideAtSmallestValue() {
    qt().forAll(arbitrary().constant(0), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b, c, d) -> d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 0 && b == 1 && c == 1 & d == 1);
}

@Test
public void shouldContinueToShrinkWhenLeftHandSideAtSmallestValue() {
    qt().forAll(arbitrary().constant(0), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b, c, d) -> d == 5);
    verifier.<Integer, Integer, Integer, Integer>smallestValueMatches((a, b, c, d) -> a == 0 && b == 1 && c == 1 & d == 1);
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c, d) -> a != 2 && b != 2 && c != 2 && d != 2).asWithPrecursor((a, b, c, d) -> a + ":" + b + ":" + c + ":" + d).check((a, b, c, d, value) -> value.equals("1:1:1:1"));
    Tuple5<Integer, Integer, Integer, Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Tuple5.of(3, 3, 3, 3, "3:3:3:3"));
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c, d) -> a != 2 && b != 2 && c != 2 && d != 2).asWithPrecursor((a, b, c, d) -> a + ":" + b + ":" + c + ":" + d).check((a, b, c, d, value) -> value.equals("1:1:1:1"));
    Tuple5<Integer, Integer, Integer, Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Tuple5.of(3, 1, 1, 1, "3:1:1:1"));
}

@Test
public void shouldFalisyWhenAssertionsThrown() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).checkAssert((a, b, c, d) -> assertThat(a + b + c + d).isEqualTo(4));
    verifier.isFalsifiedByException();
}

@Test
public void shouldFalisyWhenAssertionsThrown() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).checkAssert((a, b, c, d) -> assertThat(a + b + c + d).isEqualTo(4));
    verifier.isFalsifiedByException();
}

public final void checkAssert(final QuadConsumer<A, B, C, D> property) {
    check((a, b, c, d) -> {
        property.accept(a, b, c, d);
        return true;
    });
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).as((a, b, c, d) -> a + ":" + b + ":" + c + ":" + d).check(a -> a.equals("1:1:1:1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("2:2:2:2");
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).as((a, b, c, d) -> a + ":" + b + ":" + c + ":" + d).check(a -> a.equals("1:1:1:1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("2:1:1:1");
}


@Test
public void partialMatch() {
    Trie trie = builder().onlyWholeWords().addKeyword("sugar").build();
    Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 20, 24, "sugar");
}

@Test
public void partialMatch() {
    Trie trie = Trie.builder().onlyWholeWords().addKeyword("sugar").build();
    Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 20, 24, "sugar");
}

public Trie build() {
    getTrie().constructFailureStates();
    return getTrie();
}

public Trie build() {
    this.trie.constructFailureStates();
    return this.trie;
}

@Test
public void testZeroLength() {
    Trie trie = builder().ignoreOverlaps().onlyWholeWords().ignoreCase().addKeyword("").build();
    trie.tokenize("Try a natural lip and subtle bronzer to keep all the focus on those big bright eyes with NARS Eyeshadow Duo in Rated R And the winner is... Boots No7 Advanced Renewal Anti-ageing Glycolic Peel Kit ($25 amazon.com) won most-appealing peel.");
}

@Test
public void testZeroLength() {
    Trie trie = Trie.builder().ignoreOverlaps().onlyWholeWords().ignoreCase().addKeyword("").build();
    trie.tokenize("Try a natural lip and subtle bronzer to keep all the focus on those big bright eyes with NARS Eyeshadow Duo in Rated R And the winner is... Boots No7 Advanced Renewal Anti-ageing Glycolic Peel Kit ($25 amazon.com) won most-appealing peel.");
}

@Test
public void containsMatch() {
    Trie trie = builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    assertTrue(trie.containsMatch("ababcbab"));
}

@Test
public void containsMatch() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    assertTrue(trie.containsMatch("ababcbab"));
}

public TrieBuilder addKeyword(final CharSequence keyword) {
    getTrie().addKeyword(keyword.toString());
    return this;
}

public TrieBuilder addKeyword(final String keyword) {
    this.trie.addKeyword(keyword);
    return this;
}

@Test
public void testIgnoreCaseFirstMatch() {
    Trie trie = builder().ignoreCase().addKeywords(UNICODE).build();
    Emit firstMatch = trie.firstMatch("TurninG OnCe AgAiN BÖRKÜ");
    checkEmit(firstMatch, 0, 6, "turning");
}

@Test
public void testIgnoreCaseFirstMatch() {
    Trie trie = Trie.builder().ignoreCase().addKeywords(UNICODE).build();
    Emit firstMatch = trie.firstMatch("TurninG OnCe AgAiN BÖRKÜ");
    checkEmit(firstMatch, 0, 6, "turning");
}

public TrieBuilder ignoreCase() {
    getTrieConfig().setCaseInsensitive(true);
    return this;
}

public TrieBuilder ignoreCase() {
    this.trieConfig.setCaseInsensitive(true);
    return this;
}

@Test
public void recipesFirstMatch() {
    Trie trie = builder().addKeywords(FOOD).build();
    Emit firstMatch = trie.firstMatch("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    checkEmit(firstMatch, 2, 12, "cauliflower");
}

@Test
public void recipesFirstMatch() {
    Trie trie = Trie.builder().addKeywords(FOOD).build();
    Emit firstMatch = trie.firstMatch("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    checkEmit(firstMatch, 2, 12, "cauliflower");
}

@Test
public void testStringIndexOutOfBoundsException() {
    Trie trie = builder().ignoreCase().onlyWholeWords().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void testStringIndexOutOfBoundsException() {
    Trie trie = Trie.builder().ignoreCase().onlyWholeWords().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void longAndShortOverlappingMatch() {
    Trie trie = builder().addKeyword("he").addKeyword("hehehehe").build();
    Collection<Emit> emits = trie.parseText("hehehehehe");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 1, "he");
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 4, 5, "he");
    checkEmit(iterator.next(), 6, 7, "he");
    checkEmit(iterator.next(), 0, 7, "hehehehe");
    checkEmit(iterator.next(), 8, 9, "he");
    checkEmit(iterator.next(), 2, 9, "hehehehe");
}

@Test
public void longAndShortOverlappingMatch() {
    Trie trie = Trie.builder().addKeyword("he").addKeyword("hehehehe").build();
    Collection<Emit> emits = trie.parseText("hehehehehe");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 1, "he");
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 4, 5, "he");
    checkEmit(iterator.next(), 6, 7, "he");
    checkEmit(iterator.next(), 0, 7, "hehehehe");
    checkEmit(iterator.next(), 8, 9, "he");
    checkEmit(iterator.next(), 2, 9, "hehehehe");
}

@Test
public void testPartialMatchWhiteSpaces() {
    Trie trie = builder().onlyWholeWordsWhiteSpaceSeparated().addKeyword("#sugar-123").build();
    Collection<Emit> emits = trie.parseText("#sugar-123 #sugar-1234");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 0, 9, "#sugar-123");
}

@Test
public void testPartialMatchWhiteSpaces() {
    Trie trie = Trie.builder().onlyWholeWordsWhiteSpaceSeparated().addKeyword("#sugar-123").build();
    Collection<Emit> emits = trie.parseText("#sugar-123 #sugar-1234");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 0, 9, "#sugar-123");
}

@Test
public void variousKeywordsFirstMatch() {
    Trie trie = builder().addKeywords(ALPHABET).build();
    Emit firstMatch = trie.firstMatch("bcd");
    checkEmit(firstMatch, 0, 2, "bcd");
}

@Test
public void variousKeywordsFirstMatch() {
    Trie trie = Trie.builder().addKeywords(ALPHABET).build();
    Emit firstMatch = trie.firstMatch("bcd");
    checkEmit(firstMatch, 0, 2, "bcd");
}

public TrieBuilder addKeywords(final CharSequence... keywords) {
    for (final CharSequence keyword : keywords) {
        addKeyword(keyword);
    }
    return this;
}

public TrieBuilder addKeywords(final String... keywords) {
    this.trie.addKeywords(keywords);
    return this;
}

@Test
public void textIsLongerThanKeyword() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(" " + ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 1, 3, ALPHABET[0]);
}

@Test
public void textIsLongerThanKeyword() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(" " + ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 1, 3, ALPHABET[0]);
}

@Test
public void misleadingTestFirstMatch() {
    Trie trie = builder().addKeyword("hers").build();
    Emit firstMatch = trie.firstMatch("h he her hers");
    checkEmit(firstMatch, 9, 12, "hers");
}

@Test
public void misleadingTestFirstMatch() {
    Trie trie = Trie.builder().addKeyword("hers").build();
    Emit firstMatch = trie.firstMatch("h he her hers");
    checkEmit(firstMatch, 9, 12, "hers");
}

@Test
public void testUnicode1() {
    String target = "LİKE THIS";
    assertEquals("THIS", target.substring(5, 9));
    Trie trie = builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    Collection<Emit> emits = trie.parseText(target);
    assertEquals(1, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 5, 8, "this");
}

@Test
public void testUnicode1() {
    String target = "LİKE THIS";
    assertEquals("THIS", target.substring(5, 9));
    Trie trie = Trie.builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    Collection<Emit> emits = trie.parseText(target);
    assertEquals(1, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 5, 8, "this");
}

@Test
public void tokenizeTokensInSequence() {
    Trie trie = builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Alpha Beta Gamma");
    assertEquals(5, tokens.size());
}

@Test
public void tokenizeTokensInSequence() {
    Trie trie = Trie.builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Alpha Beta Gamma");
    assertEquals(5, tokens.size());
}

@Test
public void ushersTestWithCapitalKeywords() {
    Trie trie = builder().ignoreCase().addKeyword("HERS").addKeyword("HIS").addKeyword("SHE").addKeyword("HE").build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestWithCapitalKeywords() {
    Trie trie = Trie.builder().ignoreCase().addKeyword("HERS").addKeyword("HIS").addKeyword("SHE").addKeyword("HE").build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void testIgnoreCase() {
    Trie trie = builder().ignoreCase().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void testIgnoreCase() {
    Trie trie = Trie.builder().ignoreCase().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void partialMatchFirstMatch() {
    Trie trie = builder().onlyWholeWords().addKeyword("sugar").build();
    Emit firstMatch = trie.firstMatch("sugarcane sugarcane sugar canesugar");
    checkEmit(firstMatch, 20, 24, "sugar");
}

@Test
public void partialMatchFirstMatch() {
    Trie trie = Trie.builder().onlyWholeWords().addKeyword("sugar").build();
    Emit firstMatch = trie.firstMatch("sugarcane sugarcane sugar canesugar");
    checkEmit(firstMatch, 20, 24, "sugar");
}

@Test
public void ushersTest() {
    Trie trie = builder().addKeywords(PRONOUNS).build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTest() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestFirstMatch() {
    Trie trie = builder().addKeywords(PRONOUNS).build();
    Emit firstMatch = trie.firstMatch("ushers");
    checkEmit(firstMatch, 2, 3, "he");
}

@Test
public void ushersTestFirstMatch() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).build();
    Emit firstMatch = trie.firstMatch("ushers");
    checkEmit(firstMatch, 2, 3, "he");
}

@Test
public void misleadingTest() {
    Trie trie = builder().addKeyword("hers").build();
    Collection<Emit> emits = trie.parseText("h he her hers");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 9, 12, "hers");
}

@Test
public void misleadingTest() {
    Trie trie = Trie.builder().addKeyword("hers").build();
    Collection<Emit> emits = trie.parseText("h he her hers");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 9, 12, "hers");
}

@Test
public void nonOverlappingFirstMatch() {
    Trie trie = builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Emit firstMatch = trie.firstMatch("ababcbab");
    checkEmit(firstMatch, 0, 4, "ababc");
}

@Test
public void nonOverlappingFirstMatch() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Emit firstMatch = trie.firstMatch("ababcbab");
    checkEmit(firstMatch, 0, 4, "ababc");
}

@Test
public void keywordAndTextAreTheSameFirstMatch() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(ALPHABET[0]);
    checkEmit(firstMatch, 0, 2, ALPHABET[0]);
}

@Test
public void keywordAndTextAreTheSameFirstMatch() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(ALPHABET[0]);
    checkEmit(firstMatch, 0, 2, ALPHABET[0]);
}

@Test
public void startOfChurchillSpeech() {
    Trie trie = builder().removeOverlaps().addKeyword("T").addKeyword("u").addKeyword("ur").addKeyword("r").addKeyword("urn").addKeyword("ni").addKeyword("i").addKeyword("in").addKeyword("n").addKeyword("urning").build();
    Collection<Emit> emits = trie.parseText("Turning");
    assertEquals(2, emits.size());
}

@Test
public void startOfChurchillSpeech() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("T").addKeyword("u").addKeyword("ur").addKeyword("r").addKeyword("urn").addKeyword("ni").addKeyword("i").addKeyword("in").addKeyword("n").addKeyword("urning").build();
    Collection<Emit> emits = trie.parseText("Turning");
    assertEquals(2, emits.size());
}

@Test
public void testUnicode2() {
    String target = "LİKE THIS";
    Trie trie = builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    assertEquals("THIS", target.substring(5, 9));
    Emit firstMatch = trie.firstMatch(target);
    checkEmit(firstMatch, 5, 8, "this");
}

@Test
public void testUnicode2() {
    String target = "LİKE THIS";
    Trie trie = Trie.builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    assertEquals("THIS", target.substring(5, 9));
    Emit firstMatch = trie.firstMatch(target);
    checkEmit(firstMatch, 5, 8, "this");
}

@Test
public void tokenizeFullSentence() {
    Trie trie = builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Hear: Alpha team first, Beta from the rear, Gamma in reserve");
    assertEquals(7, tokens.size());
    Iterator<Token> tokensIt = tokens.iterator();
    assertEquals("Hear: ", tokensIt.next().getFragment());
    assertEquals("Alpha", tokensIt.next().getFragment());
    assertEquals(" team first, ", tokensIt.next().getFragment());
    assertEquals("Beta", tokensIt.next().getFragment());
    assertEquals(" from the rear, ", tokensIt.next().getFragment());
    assertEquals("Gamma", tokensIt.next().getFragment());
    assertEquals(" in reserve", tokensIt.next().getFragment());
}

@Test
public void tokenizeFullSentence() {
    Trie trie = Trie.builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Hear: Alpha team first, Beta from the rear, Gamma in reserve");
    assertEquals(7, tokens.size());
    Iterator<Token> tokensIt = tokens.iterator();
    assertEquals("Hear: ", tokensIt.next().getFragment());
    assertEquals("Alpha", tokensIt.next().getFragment());
    assertEquals(" team first, ", tokensIt.next().getFragment());
    assertEquals("Beta", tokensIt.next().getFragment());
    assertEquals(" from the rear, ", tokensIt.next().getFragment());
    assertEquals("Gamma", tokensIt.next().getFragment());
    assertEquals(" in reserve", tokensIt.next().getFragment());
}

@Test
public void ushersTestByCallback() {
    Trie trie = builder().addKeywords(PRONOUNS).build();
    final List<Emit> emits = new ArrayList<>();
    EmitHandler emitHandler = new EmitHandler() {

        @Override
        public void emit(Emit emit) {
            emits.add(emit);
        }
    };
    trie.parseText("ushers", emitHandler);
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestByCallback() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).build();
    final List<Emit> emits = new ArrayList<>();
    EmitHandler emitHandler = new EmitHandler() {

        @Override
        public void emit(Emit emit) {
            emits.add(emit);
        }
    };
    trie.parseText("ushers", emitHandler);
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestAndStopOnHit() {
    Trie trie = builder().addKeywords(PRONOUNS).stopOnHit().build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
}

@Test
public void ushersTestAndStopOnHit() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).stopOnHit().build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
}

public TrieBuilder stopOnHit() {
    return onlyFirstMatch();
}

public TrieBuilder stopOnHit() {
    trie.trieConfig.setStopOnHit(true);
    return this;
}

@Test
public void variousKeywordsOneMatch() {
    Trie trie = builder().addKeywords(ALPHABET).build();
    Collection<Emit> emits = trie.parseText("bcd");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, "bcd");
}

@Test
public void variousKeywordsOneMatch() {
    Trie trie = Trie.builder().addKeywords(ALPHABET).build();
    Collection<Emit> emits = trie.parseText("bcd");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, "bcd");
}

@Test
public void textIsLongerThanKeywordFirstMatch() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(" " + ALPHABET[0]);
    checkEmit(firstMatch, 1, 3, ALPHABET[0]);
}

@Test
public void textIsLongerThanKeywordFirstMatch() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(" " + ALPHABET[0]);
    checkEmit(firstMatch, 1, 3, ALPHABET[0]);
}

@Test
public void keywordAndTextAreTheSame() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, ALPHABET[0]);
}

@Test
public void keywordAndTextAreTheSame() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, ALPHABET[0]);
}

@Test
public void nonOverlapping() {
    Trie trie = builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Collection<Emit> emits = trie.parseText("ababcbab");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 4, "ababc");
    checkEmit(iterator.next(), 6, 7, "ab");
}

@Test
public void nonOverlapping() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Collection<Emit> emits = trie.parseText("ababcbab");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 4, "ababc");
    checkEmit(iterator.next(), 6, 7, "ab");
}

@Test
public void recipes() {
    Trie trie = builder().addKeywords(FOOD).build();
    Collection<Emit> emits = trie.parseText("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 12, "cauliflower");
    checkEmit(iterator.next(), 18, 25, "tomatoes");
    checkEmit(iterator.next(), 40, 43, "veal");
    checkEmit(iterator.next(), 51, 58, "broccoli");
}

@Test
public void recipes() {
    Trie trie = Trie.builder().addKeywords(FOOD).build();
    Collection<Emit> emits = trie.parseText("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 12, "cauliflower");
    checkEmit(iterator.next(), 18, 25, "tomatoes");
    checkEmit(iterator.next(), 40, 43, "veal");
    checkEmit(iterator.next(), 51, 58, "broccoli");
}


@Test
public void ushersTestByCallback() {
    Trie trie = new Trie();
    trie.addKeyword("hers");
    trie.addKeyword("his");
    trie.addKeyword("she");
    trie.addKeyword("he");
    final List<Emit> emits = new ArrayList<>();
    EmitHandler emitHandler = new EmitHandler() {

        @Override
        public void emit(Emit emit) {
            emits.add(emit);
        }
    };
    trie.parseText(new ParseConfiguration().setText("ushers").setEmitHandler(emitHandler));
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestByCallback() {
    Trie trie = new Trie();
    trie.addKeyword("hers");
    trie.addKeyword("his");
    trie.addKeyword("she");
    trie.addKeyword("he");
    final List<Emit> emits = new ArrayList<>();
    EmitHandler emitHandler = new EmitHandler() {

        @Override
        public void emit(Emit emit) {
            emits.add(emit);
        }
    };
    trie.parseText("ushers", emitHandler);
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

public void parseText(ParseConfiguration parseConfiguration) {
    checkForConstructedFailureStates();
    int position = 0;
    State currentState = this.rootState;
    for (Character character : parseConfiguration) {
        if (trieConfig.isCaseInsensitive()) {
            character = Character.toLowerCase(character);
        }
        currentState = getState(currentState, character);
        storeEmits(position, currentState, parseConfiguration.getEmitHandler());
        position++;
    }
}

public void parseText(CharSequence text, EmitHandler emitHandler) {
    checkForConstructedFailureStates();
    State currentState = this.rootState;
    for (int position = 0; position < text.length(); position++) {
        Character character = text.charAt(position);
        if (trieConfig.isCaseInsensitive()) {
            character = Character.toLowerCase(character);
        }
        currentState = getState(currentState, character);
        storeEmits(position, currentState, emitHandler);
    }
}


@Test
public void partialMatch() {
    Trie trie = builder().onlyWholeWords().addKeyword("sugar").build();
    Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 20, 24, "sugar");
}

@Test
public void partialMatch() {
    Trie trie = Trie.builder().onlyWholeWords().addKeyword("sugar").build();
    Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 20, 24, "sugar");
}

@Test
public void testZeroLength() {
    Trie trie = builder().ignoreOverlaps().onlyWholeWords().ignoreCase().addKeyword("").build();
    trie.tokenize("Try a natural lip and subtle bronzer to keep all the focus on those big bright eyes with NARS Eyeshadow Duo in Rated R And the winner is... Boots No7 Advanced Renewal Anti-ageing Glycolic Peel Kit ($25 amazon.com) won most-appealing peel.");
}

@Test
public void testZeroLength() {
    Trie trie = Trie.builder().ignoreOverlaps().onlyWholeWords().ignoreCase().addKeyword("").build();
    trie.tokenize("Try a natural lip and subtle bronzer to keep all the focus on those big bright eyes with NARS Eyeshadow Duo in Rated R And the winner is... Boots No7 Advanced Renewal Anti-ageing Glycolic Peel Kit ($25 amazon.com) won most-appealing peel.");
}

@Test
public void containsMatch() {
    Trie trie = builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    assertTrue(trie.containsMatch("ababcbab"));
}

@Test
public void containsMatch() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    assertTrue(trie.containsMatch("ababcbab"));
}

@Test
public void testIgnoreCaseFirstMatch() {
    Trie trie = builder().ignoreCase().addKeywords(UNICODE).build();
    Emit firstMatch = trie.firstMatch("TurninG OnCe AgAiN BÖRKÜ");
    checkEmit(firstMatch, 0, 6, "turning");
}

@Test
public void testIgnoreCaseFirstMatch() {
    Trie trie = Trie.builder().ignoreCase().addKeywords(UNICODE).build();
    Emit firstMatch = trie.firstMatch("TurninG OnCe AgAiN BÖRKÜ");
    checkEmit(firstMatch, 0, 6, "turning");
}

@Test
public void recipesFirstMatch() {
    Trie trie = builder().addKeywords(FOOD).build();
    Emit firstMatch = trie.firstMatch("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    checkEmit(firstMatch, 2, 12, "cauliflower");
}

@Test
public void recipesFirstMatch() {
    Trie trie = Trie.builder().addKeywords(FOOD).build();
    Emit firstMatch = trie.firstMatch("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    checkEmit(firstMatch, 2, 12, "cauliflower");
}

@Test
public void testStringIndexOutOfBoundsException() {
    Trie trie = builder().ignoreCase().onlyWholeWords().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void testStringIndexOutOfBoundsException() {
    Trie trie = Trie.builder().ignoreCase().onlyWholeWords().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void longAndShortOverlappingMatch() {
    Trie trie = builder().addKeyword("he").addKeyword("hehehehe").build();
    Collection<Emit> emits = trie.parseText("hehehehehe");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 1, "he");
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 4, 5, "he");
    checkEmit(iterator.next(), 6, 7, "he");
    checkEmit(iterator.next(), 0, 7, "hehehehe");
    checkEmit(iterator.next(), 8, 9, "he");
    checkEmit(iterator.next(), 2, 9, "hehehehe");
}

@Test
public void longAndShortOverlappingMatch() {
    Trie trie = Trie.builder().addKeyword("he").addKeyword("hehehehe").build();
    Collection<Emit> emits = trie.parseText("hehehehehe");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 1, "he");
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 4, 5, "he");
    checkEmit(iterator.next(), 6, 7, "he");
    checkEmit(iterator.next(), 0, 7, "hehehehe");
    checkEmit(iterator.next(), 8, 9, "he");
    checkEmit(iterator.next(), 2, 9, "hehehehe");
}

@Test
public void testPartialMatchWhiteSpaces() {
    Trie trie = builder().onlyWholeWordsWhiteSpaceSeparated().addKeyword("#sugar-123").build();
    Collection<Emit> emits = trie.parseText("#sugar-123 #sugar-1234");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 0, 9, "#sugar-123");
}

@Test
public void testPartialMatchWhiteSpaces() {
    Trie trie = Trie.builder().onlyWholeWordsWhiteSpaceSeparated().addKeyword("#sugar-123").build();
    Collection<Emit> emits = trie.parseText("#sugar-123 #sugar-1234");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 0, 9, "#sugar-123");
}

@Test
public void variousKeywordsFirstMatch() {
    Trie trie = builder().addKeywords(ALPHABET).build();
    Emit firstMatch = trie.firstMatch("bcd");
    checkEmit(firstMatch, 0, 2, "bcd");
}

@Test
public void variousKeywordsFirstMatch() {
    Trie trie = Trie.builder().addKeywords(ALPHABET).build();
    Emit firstMatch = trie.firstMatch("bcd");
    checkEmit(firstMatch, 0, 2, "bcd");
}

@Test
public void textIsLongerThanKeyword() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(" " + ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 1, 3, ALPHABET[0]);
}

@Test
public void textIsLongerThanKeyword() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(" " + ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 1, 3, ALPHABET[0]);
}

@Test
public void misleadingTestFirstMatch() {
    Trie trie = builder().addKeyword("hers").build();
    Emit firstMatch = trie.firstMatch("h he her hers");
    checkEmit(firstMatch, 9, 12, "hers");
}

@Test
public void misleadingTestFirstMatch() {
    Trie trie = Trie.builder().addKeyword("hers").build();
    Emit firstMatch = trie.firstMatch("h he her hers");
    checkEmit(firstMatch, 9, 12, "hers");
}

@Test
public void testUnicode1() {
    String target = "LİKE THIS";
    assertEquals("THIS", target.substring(5, 9));
    Trie trie = builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    Collection<Emit> emits = trie.parseText(target);
    assertEquals(1, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 5, 8, "this");
}

@Test
public void testUnicode1() {
    String target = "LİKE THIS";
    assertEquals("THIS", target.substring(5, 9));
    Trie trie = Trie.builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    Collection<Emit> emits = trie.parseText(target);
    assertEquals(1, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 5, 8, "this");
}

@Test
public void tokenizeTokensInSequence() {
    Trie trie = builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Alpha Beta Gamma");
    assertEquals(5, tokens.size());
}

@Test
public void tokenizeTokensInSequence() {
    Trie trie = Trie.builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Alpha Beta Gamma");
    assertEquals(5, tokens.size());
}

@Test
public void ushersTestWithCapitalKeywords() {
    Trie trie = builder().ignoreCase().addKeyword("HERS").addKeyword("HIS").addKeyword("SHE").addKeyword("HE").build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestWithCapitalKeywords() {
    Trie trie = Trie.builder().ignoreCase().addKeyword("HERS").addKeyword("HIS").addKeyword("SHE").addKeyword("HE").build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void testIgnoreCase() {
    Trie trie = builder().ignoreCase().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void testIgnoreCase() {
    Trie trie = Trie.builder().ignoreCase().addKeywords(UNICODE).build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void partialMatchFirstMatch() {
    Trie trie = builder().onlyWholeWords().addKeyword("sugar").build();
    Emit firstMatch = trie.firstMatch("sugarcane sugarcane sugar canesugar");
    checkEmit(firstMatch, 20, 24, "sugar");
}

@Test
public void partialMatchFirstMatch() {
    Trie trie = Trie.builder().onlyWholeWords().addKeyword("sugar").build();
    Emit firstMatch = trie.firstMatch("sugarcane sugarcane sugar canesugar");
    checkEmit(firstMatch, 20, 24, "sugar");
}

@Test
public void ushersTest() {
    Trie trie = builder().addKeywords(PRONOUNS).build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTest() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestFirstMatch() {
    Trie trie = builder().addKeywords(PRONOUNS).build();
    Emit firstMatch = trie.firstMatch("ushers");
    checkEmit(firstMatch, 2, 3, "he");
}

@Test
public void ushersTestFirstMatch() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).build();
    Emit firstMatch = trie.firstMatch("ushers");
    checkEmit(firstMatch, 2, 3, "he");
}

@Test
public void misleadingTest() {
    Trie trie = builder().addKeyword("hers").build();
    Collection<Emit> emits = trie.parseText("h he her hers");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 9, 12, "hers");
}

@Test
public void misleadingTest() {
    Trie trie = Trie.builder().addKeyword("hers").build();
    Collection<Emit> emits = trie.parseText("h he her hers");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 9, 12, "hers");
}

@Test
public void nonOverlappingFirstMatch() {
    Trie trie = builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Emit firstMatch = trie.firstMatch("ababcbab");
    checkEmit(firstMatch, 0, 4, "ababc");
}

@Test
public void nonOverlappingFirstMatch() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Emit firstMatch = trie.firstMatch("ababcbab");
    checkEmit(firstMatch, 0, 4, "ababc");
}

@Test
public void keywordAndTextAreTheSameFirstMatch() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(ALPHABET[0]);
    checkEmit(firstMatch, 0, 2, ALPHABET[0]);
}

@Test
public void keywordAndTextAreTheSameFirstMatch() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(ALPHABET[0]);
    checkEmit(firstMatch, 0, 2, ALPHABET[0]);
}

@Test
public void startOfChurchillSpeech() {
    Trie trie = builder().removeOverlaps().addKeyword("T").addKeyword("u").addKeyword("ur").addKeyword("r").addKeyword("urn").addKeyword("ni").addKeyword("i").addKeyword("in").addKeyword("n").addKeyword("urning").build();
    Collection<Emit> emits = trie.parseText("Turning");
    assertEquals(2, emits.size());
}

@Test
public void startOfChurchillSpeech() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("T").addKeyword("u").addKeyword("ur").addKeyword("r").addKeyword("urn").addKeyword("ni").addKeyword("i").addKeyword("in").addKeyword("n").addKeyword("urning").build();
    Collection<Emit> emits = trie.parseText("Turning");
    assertEquals(2, emits.size());
}

@Test
public void testUnicode2() {
    String target = "LİKE THIS";
    Trie trie = builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    assertEquals("THIS", target.substring(5, 9));
    Emit firstMatch = trie.firstMatch(target);
    checkEmit(firstMatch, 5, 8, "this");
}

@Test
public void testUnicode2() {
    String target = "LİKE THIS";
    Trie trie = Trie.builder().ignoreCase().onlyWholeWords().addKeyword("this").build();
    assertEquals("THIS", target.substring(5, 9));
    Emit firstMatch = trie.firstMatch(target);
    checkEmit(firstMatch, 5, 8, "this");
}

@Test
public void tokenizeFullSentence() {
    Trie trie = builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Hear: Alpha team first, Beta from the rear, Gamma in reserve");
    assertEquals(7, tokens.size());
    Iterator<Token> tokensIt = tokens.iterator();
    assertEquals("Hear: ", tokensIt.next().getFragment());
    assertEquals("Alpha", tokensIt.next().getFragment());
    assertEquals(" team first, ", tokensIt.next().getFragment());
    assertEquals("Beta", tokensIt.next().getFragment());
    assertEquals(" from the rear, ", tokensIt.next().getFragment());
    assertEquals("Gamma", tokensIt.next().getFragment());
    assertEquals(" in reserve", tokensIt.next().getFragment());
}

@Test
public void tokenizeFullSentence() {
    Trie trie = Trie.builder().addKeywords(GREEK_LETTERS).build();
    Collection<Token> tokens = trie.tokenize("Hear: Alpha team first, Beta from the rear, Gamma in reserve");
    assertEquals(7, tokens.size());
    Iterator<Token> tokensIt = tokens.iterator();
    assertEquals("Hear: ", tokensIt.next().getFragment());
    assertEquals("Alpha", tokensIt.next().getFragment());
    assertEquals(" team first, ", tokensIt.next().getFragment());
    assertEquals("Beta", tokensIt.next().getFragment());
    assertEquals(" from the rear, ", tokensIt.next().getFragment());
    assertEquals("Gamma", tokensIt.next().getFragment());
    assertEquals(" in reserve", tokensIt.next().getFragment());
}

@Test
public void ushersTestByCallback() {
    Trie trie = builder().addKeywords(PRONOUNS).build();
    final List<Emit> emits = new ArrayList<>();
    EmitHandler emitHandler = new EmitHandler() {

        @Override
        public void emit(Emit emit) {
            emits.add(emit);
        }
    };
    trie.parseText("ushers", emitHandler);
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestByCallback() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).build();
    final List<Emit> emits = new ArrayList<>();
    EmitHandler emitHandler = new EmitHandler() {

        @Override
        public void emit(Emit emit) {
            emits.add(emit);
        }
    };
    trie.parseText("ushers", emitHandler);
    assertEquals(3, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
    checkEmit(iterator.next(), 2, 5, "hers");
}

@Test
public void ushersTestAndStopOnHit() {
    Trie trie = builder().addKeywords(PRONOUNS).stopOnHit().build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
}

@Test
public void ushersTestAndStopOnHit() {
    Trie trie = Trie.builder().addKeywords(PRONOUNS).stopOnHit().build();
    Collection<Emit> emits = trie.parseText("ushers");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 1, 3, "she");
}

@Test
public void variousKeywordsOneMatch() {
    Trie trie = builder().addKeywords(ALPHABET).build();
    Collection<Emit> emits = trie.parseText("bcd");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, "bcd");
}

@Test
public void variousKeywordsOneMatch() {
    Trie trie = Trie.builder().addKeywords(ALPHABET).build();
    Collection<Emit> emits = trie.parseText("bcd");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, "bcd");
}

@Test
public void textIsLongerThanKeywordFirstMatch() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(" " + ALPHABET[0]);
    checkEmit(firstMatch, 1, 3, ALPHABET[0]);
}

@Test
public void textIsLongerThanKeywordFirstMatch() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Emit firstMatch = trie.firstMatch(" " + ALPHABET[0]);
    checkEmit(firstMatch, 1, 3, ALPHABET[0]);
}

@Test
public void keywordAndTextAreTheSame() {
    Trie trie = builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, ALPHABET[0]);
}

@Test
public void keywordAndTextAreTheSame() {
    Trie trie = Trie.builder().addKeyword(ALPHABET[0]).build();
    Collection<Emit> emits = trie.parseText(ALPHABET[0]);
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 2, ALPHABET[0]);
}

@Test
public void nonOverlapping() {
    Trie trie = builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Collection<Emit> emits = trie.parseText("ababcbab");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 4, "ababc");
    checkEmit(iterator.next(), 6, 7, "ab");
}

@Test
public void nonOverlapping() {
    Trie trie = Trie.builder().removeOverlaps().addKeyword("ab").addKeyword("cba").addKeyword("ababc").build();
    Collection<Emit> emits = trie.parseText("ababcbab");
    assertEquals(2, emits.size());
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 4, "ababc");
    checkEmit(iterator.next(), 6, 7, "ab");
}

@Test
public void recipes() {
    Trie trie = builder().addKeywords(FOOD).build();
    Collection<Emit> emits = trie.parseText("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 12, "cauliflower");
    checkEmit(iterator.next(), 18, 25, "tomatoes");
    checkEmit(iterator.next(), 40, 43, "veal");
    checkEmit(iterator.next(), 51, 58, "broccoli");
}

@Test
public void recipes() {
    Trie trie = Trie.builder().addKeywords(FOOD).build();
    Collection<Emit> emits = trie.parseText("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 12, "cauliflower");
    checkEmit(iterator.next(), 18, 25, "tomatoes");
    checkEmit(iterator.next(), 40, 43, "veal");
    checkEmit(iterator.next(), 51, 58, "broccoli");
}


@Test
public void zeroLengthTestBug7InGithubReportedByXCurry() {
    Trie trie = new Trie().removeOverlaps().onlyWholeWords().caseInsensitive();
    trie.addKeyword("");
    trie.tokenize("Try a natural lip and subtle bronzer to keep all the focus on those big bright eyes with NARS Eyeshadow Duo in Rated R And the winner is... Boots No7 Advanced Renewal Anti-ageing Glycolic Peel Kit ($25 amazon.com) won most-appealing peel.");
}

@Test
public void zeroLengthTestBug7InGithubReportedByXCurry() {
    Trie trie = Trie.builder().removeOverlaps().onlyWholeWords().caseInsensitive().addKeyword("").build();
    trie.tokenize("Try a natural lip and subtle bronzer to keep all the focus on those big bright eyes with NARS Eyeshadow Duo in Rated R And the winner is... Boots No7 Advanced Renewal Anti-ageing Glycolic Peel Kit ($25 amazon.com) won most-appealing peel.");
}

public Trie build() {
    trie.constructFailureStates();
    return trie;
}

@Test
public void misleadingTest() {
    Trie trie = new Trie();
    trie.addKeyword("hers");
    Collection<Emit> emits = trie.parseText("h he her hers");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 9, 12, "hers");
}

@Test
public void partialMatch() {
    Trie trie = new Trie().onlyWholeWords();
    trie.addKeyword("sugar");
    Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 20, 24, "sugar");
}

@Test
public void partialMatch() {
    Trie trie = Trie.builder().onlyWholeWords().addKeyword("sugar").build();
    Collection<Emit> emits = trie.parseText("sugarcane sugarcane sugar canesugar");
    assertEquals(1, emits.size());
    checkEmit(emits.iterator().next(), 20, 24, "sugar");
}

@Test
public void startOfChurchillSpeech() {
    Trie trie = new Trie().removeOverlaps();
    trie.addKeyword("T");
    trie.addKeyword("u");
    trie.addKeyword("ur");
    trie.addKeyword("r");
    trie.addKeyword("urn");
    trie.addKeyword("ni");
    trie.addKeyword("i");
    trie.addKeyword("in");
    trie.addKeyword("n");
    trie.addKeyword("urning");
    Collection<Emit> emits = trie.parseText("Turning");
    assertEquals(2, emits.size());
}

@Test
public void caseInsensitive() {
    Trie trie = new Trie().caseInsensitive();
    trie.addKeyword("turning");
    trie.addKeyword("once");
    trie.addKeyword("again");
    trie.addKeyword("börkü");
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void caseInsensitive() {
    Trie trie = Trie.builder().caseInsensitive().addKeyword("turning").addKeyword("once").addKeyword("again").addKeyword("börkü").build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

public Trie caseInsensitive() {
    this.trieConfig.setCaseInsensitive(true);
    return this;
}

@Test
public void bug5InGithubReportedByXCurry() {
    Trie trie = new Trie().caseInsensitive().onlyWholeWords();
    trie.addKeyword("turning");
    trie.addKeyword("once");
    trie.addKeyword("again");
    trie.addKeyword("börkü");
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void bug5InGithubReportedByXCurry() {
    Trie trie = Trie.builder().caseInsensitive().onlyWholeWords().addKeyword("turning").addKeyword("once").addKeyword("again").addKeyword("börkü").build();
    Collection<Emit> emits = trie.parseText("TurninG OnCe AgAiN BÖRKÜ");
    assertEquals(4, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 0, 6, "turning");
    checkEmit(it.next(), 8, 11, "once");
    checkEmit(it.next(), 13, 17, "again");
    checkEmit(it.next(), 19, 23, "börkü");
}

@Test
public void longAndShortOverlappingMatch() {
    Trie trie = new Trie();
    trie.addKeyword("he");
    trie.addKeyword("hehehehe");
    Collection<Emit> emits = trie.parseText("hehehehehe");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 1, "he");
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 4, 5, "he");
    checkEmit(iterator.next(), 6, 7, "he");
    checkEmit(iterator.next(), 0, 7, "hehehehe");
    checkEmit(iterator.next(), 8, 9, "he");
    checkEmit(iterator.next(), 2, 9, "hehehehe");
}

@Test
public void longAndShortOverlappingMatch() {
    Trie trie = Trie.builder().addKeyword("he").addKeyword("hehehehe").build();
    Collection<Emit> emits = trie.parseText("hehehehehe");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 0, 1, "he");
    checkEmit(iterator.next(), 2, 3, "he");
    checkEmit(iterator.next(), 4, 5, "he");
    checkEmit(iterator.next(), 6, 7, "he");
    checkEmit(iterator.next(), 0, 7, "hehehehe");
    checkEmit(iterator.next(), 8, 9, "he");
    checkEmit(iterator.next(), 2, 9, "hehehehe");
}

@Test
public void unicodeIssueBug8ReportedByDwyerk() {
    String target = "LİKE THIS";
    Trie trie = new Trie().caseInsensitive().onlyWholeWords();
    assertEquals("THIS", target.substring(5, 9));
    trie.addKeyword("this");
    Collection<Emit> emits = trie.parseText(target);
    assertEquals(1, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 5, 8, "this");
}

@Test
public void unicodeIssueBug8ReportedByDwyerk() {
    String target = "LİKE THIS";
    assertEquals("THIS", target.substring(5, 9));
    Trie trie = Trie.builder().caseInsensitive().onlyWholeWords().addKeyword("this").build();
    Collection<Emit> emits = trie.parseText(target);
    assertEquals(1, emits.size());
    Iterator<Emit> it = emits.iterator();
    checkEmit(it.next(), 5, 8, "this");
}

@Test
public void recipes() {
    Trie trie = new Trie();
    trie.addKeyword("veal");
    trie.addKeyword("cauliflower");
    trie.addKeyword("broccoli");
    trie.addKeyword("tomatoes");
    Collection<Emit> emits = trie.parseText("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 12, "cauliflower");
    checkEmit(iterator.next(), 18, 25, "tomatoes");
    checkEmit(iterator.next(), 40, 43, "veal");
    checkEmit(iterator.next(), 51, 58, "broccoli");
}

@Test
public void recipes() {
    Trie trie = Trie.builder().addKeyword("veal").addKeyword("cauliflower").addKeyword("broccoli").addKeyword("tomatoes").build();
    Collection<Emit> emits = trie.parseText("2 cauliflowers, 3 tomatoes, 4 slices of veal, 100g broccoli");
    Iterator<Emit> iterator = emits.iterator();
    checkEmit(iterator.next(), 2, 12, "cauliflower");
    checkEmit(iterator.next(), 18, 25, "tomatoes");
    checkEmit(iterator.next(), 40, 43, "veal");
    checkEmit(iterator.next(), 51, 58, "broccoli");
}

@Test
public void tokenizeTokensInSequence() {
    Trie trie = new Trie();
    trie.addKeyword("Alpha");
    trie.addKeyword("Beta");
    trie.addKeyword("Gamma");
    Collection<Token> tokens = trie.tokenize("Alpha Beta Gamma");
    assertEquals(5, tokens.size());
}

@Test
public void tokenizeTokensInSequence() {
    Trie trie = Trie.builder().addKeyword("Alpha").addKeyword("Beta").addKeyword("Gamma").build();
    Collection<Token> tokens = trie.tokenize("Alpha Beta Gamma");
    assertEquals(5, tokens.size());
}


@Test
public void testBanChatMemberWithEmptyChatId() {
    BanChatMember banChatMember = BanChatMember.builder().chatId("").userId(12345L).untilDate(1000).revokeMessages(true).build();
    assertEquals("banChatMember", banChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, banChatMember::validate);
    assertEquals("ChatId can't be empty", thrown.getMessage());
}

@Test
public void testBanChatMemberWithEmptyChatId() {
    BanChatMember banChatMember = BanChatMember.builder().chatId("").userId(12345L).untilDate(1000).revokeMessages(true).build();
    assertEquals("banChatMember", banChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, banChatMember::validate);
    assertEquals(Validations.CHAT_ID_VALIDATION, thrown.getMessage());
}

@Override
public void validate() throws TelegramApiValidationException {
    if (chatId.isEmpty()) {
        throw new TelegramApiValidationException("ChatId can't be empty", this);
    }
    if (userId == 0) {
        throw new TelegramApiValidationException("UserId can't be null or 0", this);
    }
}

@Override
public void validate() throws TelegramApiValidationException {
    Validations.requiredChatId(chatId, this);
    Validations.requiredUserId(userId, this);
}

@Test
public void testBanChatMemberWithEmptyUserId() {
    BanChatMember banChatMember = BanChatMember.builder().chatId("12345").userId(0L).untilDate(1000).revokeMessages(true).build();
    assertEquals("banChatMember", banChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, banChatMember::validate);
    assertEquals("UserId can't be null or 0", thrown.getMessage());
}

@Test
public void testBanChatMemberWithEmptyUserId() {
    BanChatMember banChatMember = BanChatMember.builder().chatId("12345").userId(0L).untilDate(1000).revokeMessages(true).build();
    assertEquals("banChatMember", banChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, banChatMember::validate);
    assertEquals(Validations.USER_ID_VALIDATION, thrown.getMessage());
}


@Test
public void testGetChatMemberCountWithEmptyChatId() {
    GetChatMemberCount getChatMemberCount = GetChatMemberCount.builder().chatId("").build();
    assertEquals("getChatMemberCount", getChatMemberCount.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, getChatMemberCount::validate);
    assertEquals("ChatId can't be empty", thrown.getMessage());
}

@Test
public void testGetChatMemberCountWithEmptyChatId() {
    GetChatMemberCount getChatMemberCount = GetChatMemberCount.builder().chatId("").build();
    assertEquals("getChatMemberCount", getChatMemberCount.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, getChatMemberCount::validate);
    assertEquals(Validations.CHAT_ID_VALIDATION, thrown.getMessage());
}

@Override
public void validate() throws TelegramApiValidationException {
    if (chatId.isEmpty()) {
        throw new TelegramApiValidationException("ChatId can't be empty", this);
    }
}

@Override
public void validate() throws TelegramApiValidationException {
    Validations.requiredChatId(chatId, this);
}


@Test
public void testPromoteChatMemberWithEmptyChatId() {
    PromoteChatMember promoteChatMember = PromoteChatMember.builder().chatId("").userId(12345L).build();
    assertEquals("promoteChatMember", promoteChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, promoteChatMember::validate);
    assertEquals("ChatId can't be empty", thrown.getMessage());
}

@Test
public void testPromoteChatMemberWithEmptyChatId() {
    PromoteChatMember promoteChatMember = PromoteChatMember.builder().chatId("").userId(12345L).build();
    assertEquals("promoteChatMember", promoteChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, promoteChatMember::validate);
    assertEquals(Validations.CHAT_ID_VALIDATION, thrown.getMessage());
}

@Override
public void validate() throws TelegramApiValidationException {
    if (chatId.isEmpty()) {
        throw new TelegramApiValidationException("ChatId can't be empty", this);
    }
    if (userId == 0L) {
        throw new TelegramApiValidationException("UserId can't be empty", this);
    }
}

@Test
public void testPromoteChatMemberWithEmptyUserId() {
    PromoteChatMember promoteChatMember = PromoteChatMember.builder().chatId("12345").userId(0L).build();
    assertEquals("promoteChatMember", promoteChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, promoteChatMember::validate);
    assertEquals("UserId can't be empty", thrown.getMessage());
}

@Test
public void testPromoteChatMemberWithEmptyUserId() {
    PromoteChatMember promoteChatMember = PromoteChatMember.builder().chatId("12345").userId(0L).build();
    assertEquals("promoteChatMember", promoteChatMember.getMethod());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, promoteChatMember::validate);
    assertEquals(Validations.USER_ID_VALIDATION, thrown.getMessage());
}


@Test
public void chatIdCantBeEmpty() {
    SendInvoice sendInvoice = createSendInvoiceObject();
    sendInvoice.setChatId("");
    Throwable thrown = assertThrows(TelegramApiValidationException.class, sendInvoice::validate);
    assertEquals("ChatId parameter can't be empty", thrown.getMessage());
}

@Test
public void chatIdCantBeEmpty() {
    SendInvoice sendInvoice = createSendInvoiceObject();
    sendInvoice.setChatId("");
    Throwable thrown = assertThrows(TelegramApiValidationException.class, sendInvoice::validate);
    assertEquals(Validations.CHAT_ID_VALIDATION, thrown.getMessage());
}

@Override
public void validate() throws TelegramApiValidationException {
    if (StringUtils.isEmpty(chatId)) {
        throw new TelegramApiValidationException("ChatId parameter can't be empty", this);
    }
    if (StringUtils.isEmpty(title) || title.length() > 32) {
        throw new TelegramApiValidationException("Title parameter can't be empty or longer than 32 chars", this);
    }
    if (StringUtils.isEmpty(description) || description.length() > 255) {
        throw new TelegramApiValidationException("Description parameter can't be empty or longer than 255 chars", this);
    }
    if (StringUtils.isEmpty(payload)) {
        throw new TelegramApiValidationException("Payload parameter can't be empty", this);
    }
    if (StringUtils.isEmpty(currency)) {
        throw new TelegramApiValidationException("Currency parameter can't be empty", this);
    }
    if (prices.isEmpty()) {
        throw new TelegramApiValidationException("Prices parameter can't be empty", this);
    } else {
        for (LabeledPrice price : prices) {
            price.validate();
        }
    }
    if (suggestedTipAmounts != null && !suggestedTipAmounts.isEmpty() && suggestedTipAmounts.size() > 4) {
        throw new TelegramApiValidationException("No more that 4 suggested tips allowed", this);
    }
    if (replyMarkup != null) {
        replyMarkup.validate();
    }
    if (replyParameters != null) {
        replyParameters.validate();
    }
}

@Override
public void validate() throws TelegramApiValidationException {
    Validations.requiredChatId(chatId, this);
    if (StringUtils.isEmpty(title) || title.length() > 32) {
        throw new TelegramApiValidationException("Title parameter can't be empty or longer than 32 chars", this);
    }
    if (StringUtils.isEmpty(description) || description.length() > 255) {
        throw new TelegramApiValidationException("Description parameter can't be empty or longer than 255 chars", this);
    }
    if (StringUtils.isEmpty(payload)) {
        throw new TelegramApiValidationException("Payload parameter can't be empty", this);
    }
    if (StringUtils.isEmpty(currency)) {
        throw new TelegramApiValidationException("Currency parameter can't be empty", this);
    }
    if (prices.isEmpty()) {
        throw new TelegramApiValidationException("Prices parameter can't be empty", this);
    } else {
        for (LabeledPrice price : prices) {
            price.validate();
        }
    }
    if (suggestedTipAmounts != null && !suggestedTipAmounts.isEmpty() && suggestedTipAmounts.size() > 4) {
        throw new TelegramApiValidationException("No more that 4 suggested tips allowed", this);
    }
    if (replyMarkup != null) {
        replyMarkup.validate();
    }
    if (replyParameters != null) {
        replyParameters.validate();
    }
}


@Test
public void testBotCommandScopeChatMemberWith0UserId() {
    BotCommandScopeChatMember botCommandScope = BotCommandScopeChatMember.builder().chatId("12345").userId(0L).build();
    assertEquals("chat_member", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals("UserId parameter can't be empty", thrown.getMessage());
}

@Test
public void testBotCommandScopeChatMemberWith0UserId() {
    BotCommandScopeChatMember botCommandScope = BotCommandScopeChatMember.builder().chatId("12345").userId(0L).build();
    assertEquals("chat_member", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals(Validations.USER_ID_VALIDATION, thrown.getMessage());
}

@Override
public void validate() throws TelegramApiValidationException {
    if (chatId.isEmpty()) {
        throw new TelegramApiValidationException("ChatId parameter can't be empty", this);
    }
    if (userId == 0L) {
        throw new TelegramApiValidationException("UserId parameter can't be empty", this);
    }
}

@Test
public void testBotCommandScopeChatMemberWithEmptyChatId() {
    BotCommandScopeChatMember botCommandScope = BotCommandScopeChatMember.builder().chatId("").userId(12345L).build();
    assertEquals("chat_member", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals("ChatId parameter can't be empty", thrown.getMessage());
}

@Test
public void testBotCommandScopeChatMemberWithEmptyChatId() {
    BotCommandScopeChatMember botCommandScope = BotCommandScopeChatMember.builder().chatId("").userId(12345L).build();
    assertEquals("chat_member", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals(Validations.CHAT_ID_VALIDATION, thrown.getMessage());
}

@Test
public void testBotCommandScopeChatWithEmptyChatId() {
    BotCommandScopeChat botCommandScope = BotCommandScopeChat.builder().chatId("").build();
    assertEquals("chat", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals("ChatId parameter can't be empty", thrown.getMessage());
}

@Test
public void testBotCommandScopeChatWithEmptyChatId() {
    BotCommandScopeChat botCommandScope = BotCommandScopeChat.builder().chatId("").build();
    assertEquals("chat", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals(Validations.CHAT_ID_VALIDATION, thrown.getMessage());
}

@Override
public void validate() throws TelegramApiValidationException {
    if (chatId.isEmpty()) {
        throw new TelegramApiValidationException("ChatId parameter can't be empty", this);
    }
}

@Test
public void testBotCommandScopeChatAdministratorsWithEmptyChatId() {
    BotCommandScopeChatAdministrators botCommandScope = BotCommandScopeChatAdministrators.builder().chatId("").build();
    assertEquals("chat_administrators", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals("ChatId parameter can't be empty", thrown.getMessage());
}

@Test
public void testBotCommandScopeChatAdministratorsWithEmptyChatId() {
    BotCommandScopeChatAdministrators botCommandScope = BotCommandScopeChatAdministrators.builder().chatId("").build();
    assertEquals("chat_administrators", botCommandScope.getType());
    Throwable thrown = assertThrows(TelegramApiValidationException.class, botCommandScope::validate);
    assertEquals(Validations.CHAT_ID_VALIDATION, thrown.getMessage());
}


@Test
public void testEncodeUrlWithoutPadding() {
    final String helloWorldEncoded = "SGVsbG8gV29ybGQ";
    final String helloWorldTwoLinesEncoded = "SGVsbG8gV29ybGQNCk5ldyBMaW5lMg";
    final String helloWorldTwoLinesAndNewLineEncoded = "SGVsbG8gV29ybGQNClNlY29uZCBMaW5lDQo";
    final String helloWorldDifferentCharsEncoded = "YDEyMzQ1Njc4OTAtPX4hQCMkJV4mKigpXyvRkdCBIuKEljs6P3F3ZXJ0eXVpb3B" + "bXWFzZGZnaGprbDsnenhjdmJubSwuL1FXRVJUWVVJT1B7fXxBU0RGR0hKS0w6WlhDVkJOTTw-P9C50YTRj9GG0YvRh9GD0LLRgdC" + "60LDQvNC10L_QuNC90YDRgtCz0L7RjNGI0LvQsdGJ0LTRjtC30LYu0YXRjdGKXNCZ0KTQr9Cm0KvQp9Cj0JLQodCa0JDQnNCV0J_" + "QmNCd0KDQotCT0J7QrNCo0JvQkdCp0JTQrtCX0JYs0KXQrdCqLw0JDCcICg";
    final String str = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMPQ5BCMxlUq2TYy" + "iRIoEUsz6HGTJhHuasS2nx1Se4Co3lxwxyubVdFj8AuhHNJSmJvjlpbTsGOjLZpr" + "HyDEDdJmf1Fensh1MhUnBZ4a7uLrZrKzFHHJdamX9pxapB89vLeHlCot9hVXdrZH" + "nNtg6FdmRKH_8gbs8iDyIayFvzYDAgMBAAECgYA-c9MpTBy9cQsR9BAvkEPjvkx2" + "XL4ZnfbDgpNA4Nuu7yzsQrPjPomiXMNkkiAFHH67yVxwAlgRjyuuQlgNNTpKvyQt" + "XcHxffnU0820VmE23M-L7jg2TlB3-rUnEDmDvCoyjlwGDR6lNb7t7Fgg2iR-iaov" + "0iVzz-l9w0slRlyGsQJBAPWXW2m3NmFgqfDxtw8fsKC2y8o17_cnPjozRGtWb8LQ" + "g3VCb8kbOFHOYNGazq3M7-wD1qILF2h_HecgK9eQrZ0CQQDMHXoJMfKKbrFrTKgE" + "zyggO1gtuT5OXYeFewMEb5AbDI2FfSc2YP7SHij8iQ2HdukBrbTmi6qxh3HmIR58" + "I_AfAkEA0Y9vr0tombsUB8cZv0v5OYoBZvCTbMANtzfb4AOHpiKqqbohDOevLQ7_" + "SpvgVCmVaDz2PptcRAyEBZ5MCssneQJAB2pmvaDH7Ambfod5bztLfOhLCtY5EkXJ" + "n6rZcDbRaHorRhdG7m3VtDKOUKZ2DF7glkQGV33phKukErVPUzlHBwJAScD9TqaG" + "wJ3juUsVtujV23SnH43iMggXT7m82STpPGam1hPfmqu2Z0niePFo927ogQ7H1EMJ" + "UHgqXmuvk2X_Ww";
    assertEquals(helloWorldEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldDifferentCharsBytes));
    assertEquals(str, java8Base64.internalEncodeUrlWithoutPadding(bytes));
    assertEquals(helloWorldEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldDifferentCharsBytes));
    assertEquals(str, commonsCodecBase64.internalEncodeUrlWithoutPadding(bytes));
}

@Test
public void testEncodeUrlWithoutPadding() {
    final String helloWorldEncoded = "SGVsbG8gV29ybGQ";
    final String helloWorldTwoLinesEncoded = "SGVsbG8gV29ybGQNCk5ldyBMaW5lMg";
    final String helloWorldTwoLinesAndNewLineEncoded = "SGVsbG8gV29ybGQNClNlY29uZCBMaW5lDQo";
    final String helloWorldDifferentCharsEncoded = "YDEyMzQ1Njc4OTAtPX4hQCMkJV4mKigpXyvRkdCBIuKEljs6P3F3ZXJ0eXVpb3B" + "bXWFzZGZnaGprbDsnenhjdmJubSwuL1FXRVJUWVVJT1B7fXxBU0RGR0hKS0w6WlhDVkJOTTw-P9C50YTRj9GG0YvRh9GD0LLRgdC" + "60LDQvNC10L_QuNC90YDRgtCz0L7RjNGI0LvQsdGJ0LTRjtC30LYu0YXRjdGKXNCZ0KTQr9Cm0KvQp9Cj0JLQodCa0JDQnNCV0J_" + "QmNCd0KDQotCT0J7QrNCo0JvQkdCp0JTQrtCX0JYs0KXQrdCqLw0JDCcICg";
    final String str = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMPQ5BCMxlUq2TYy" + "iRIoEUsz6HGTJhHuasS2nx1Se4Co3lxwxyubVdFj8AuhHNJSmJvjlpbTsGOjLZpr" + "HyDEDdJmf1Fensh1MhUnBZ4a7uLrZrKzFHHJdamX9pxapB89vLeHlCot9hVXdrZH" + "nNtg6FdmRKH_8gbs8iDyIayFvzYDAgMBAAECgYA-c9MpTBy9cQsR9BAvkEPjvkx2" + "XL4ZnfbDgpNA4Nuu7yzsQrPjPomiXMNkkiAFHH67yVxwAlgRjyuuQlgNNTpKvyQt" + "XcHxffnU0820VmE23M-L7jg2TlB3-rUnEDmDvCoyjlwGDR6lNb7t7Fgg2iR-iaov" + "0iVzz-l9w0slRlyGsQJBAPWXW2m3NmFgqfDxtw8fsKC2y8o17_cnPjozRGtWb8LQ" + "g3VCb8kbOFHOYNGazq3M7-wD1qILF2h_HecgK9eQrZ0CQQDMHXoJMfKKbrFrTKgE" + "zyggO1gtuT5OXYeFewMEb5AbDI2FfSc2YP7SHij8iQ2HdukBrbTmi6qxh3HmIR58" + "I_AfAkEA0Y9vr0tombsUB8cZv0v5OYoBZvCTbMANtzfb4AOHpiKqqbohDOevLQ7_" + "SpvgVCmVaDz2PptcRAyEBZ5MCssneQJAB2pmvaDH7Ambfod5bztLfOhLCtY5EkXJ" + "n6rZcDbRaHorRhdG7m3VtDKOUKZ2DF7glkQGV33phKukErVPUzlHBwJAScD9TqaG" + "wJ3juUsVtujV23SnH43iMggXT7m82STpPGam1hPfmqu2Z0niePFo927ogQ7H1EMJ" + "UHgqXmuvk2X_Ww";
    assertEquals(helloWorldEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, java8Base64.internalEncodeUrlWithoutPadding(helloWorldDifferentCharsBytes));
    assertEquals(str, java8Base64.internalEncodeUrlWithoutPadding(bytes));
    assertEquals(helloWorldEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, commonsCodecBase64.internalEncodeUrlWithoutPadding(helloWorldDifferentCharsBytes));
    assertEquals(str, commonsCodecBase64.internalEncodeUrlWithoutPadding(bytes));
    assertEquals(helloWorldEncoded, jaxbBase64.internalEncodeUrlWithoutPadding(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, jaxbBase64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, jaxbBase64.internalEncodeUrlWithoutPadding(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, jaxbBase64.internalEncodeUrlWithoutPadding(helloWorldDifferentCharsBytes));
    assertEquals(str, jaxbBase64.internalEncodeUrlWithoutPadding(bytes));
}

@Override
protected String internalEncodeUrlWithoutPadding(byte[] bytes) {
    String string = DatatypeConverter.printBase64Binary(bytes);
    while (string.endsWith("=")) {
        string = string.substring(0, string.length() - 1);
    }
    return string.replace('+', '-').replace('/', '_');
}

@Test
public void testEncode() {
    final String helloWorldEncoded = "SGVsbG8gV29ybGQ=";
    final String helloWorldTwoLinesEncoded = "SGVsbG8gV29ybGQNCk5ldyBMaW5lMg==";
    final String helloWorldTwoLinesAndNewLineEncoded = "SGVsbG8gV29ybGQNClNlY29uZCBMaW5lDQo=";
    final String helloWorldDifferentCharsEncoded = "YDEyMzQ1Njc4OTAtPX4hQCMkJV4mKigpXyvRkdCBIuKEljs6P3F3ZXJ0eXVpb3B" + "bXWFzZGZnaGprbDsnenhjdmJubSwuL1FXRVJUWVVJT1B7fXxBU0RGR0hKS0w6WlhDVkJOTTw+P9C50YTRj9GG0YvRh9GD0LLRgdC" + "60LDQvNC10L/QuNC90YDRgtCz0L7RjNGI0LvQsdGJ0LTRjtC30LYu0YXRjdGKXNCZ0KTQr9Cm0KvQp9Cj0JLQodCa0JDQnNCV0J/" + "QmNCd0KDQotCT0J7QrNCo0JvQkdCp0JTQrtCX0JYs0KXQrdCqLw0JDCcICg==";
    final String str = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMPQ5BCMxlUq2TYy" + "iRIoEUsz6HGTJhHuasS2nx1Se4Co3lxwxyubVdFj8AuhHNJSmJvjlpbTsGOjLZpr" + "HyDEDdJmf1Fensh1MhUnBZ4a7uLrZrKzFHHJdamX9pxapB89vLeHlCot9hVXdrZH" + "nNtg6FdmRKH/8gbs8iDyIayFvzYDAgMBAAECgYA+c9MpTBy9cQsR9BAvkEPjvkx2" + "XL4ZnfbDgpNA4Nuu7yzsQrPjPomiXMNkkiAFHH67yVxwAlgRjyuuQlgNNTpKvyQt" + "XcHxffnU0820VmE23M+L7jg2TlB3+rUnEDmDvCoyjlwGDR6lNb7t7Fgg2iR+iaov" + "0iVzz+l9w0slRlyGsQJBAPWXW2m3NmFgqfDxtw8fsKC2y8o17/cnPjozRGtWb8LQ" + "g3VCb8kbOFHOYNGazq3M7+wD1qILF2h/HecgK9eQrZ0CQQDMHXoJMfKKbrFrTKgE" + "zyggO1gtuT5OXYeFewMEb5AbDI2FfSc2YP7SHij8iQ2HdukBrbTmi6qxh3HmIR58" + "I/AfAkEA0Y9vr0tombsUB8cZv0v5OYoBZvCTbMANtzfb4AOHpiKqqbohDOevLQ7/" + "SpvgVCmVaDz2PptcRAyEBZ5MCssneQJAB2pmvaDH7Ambfod5bztLfOhLCtY5EkXJ" + "n6rZcDbRaHorRhdG7m3VtDKOUKZ2DF7glkQGV33phKukErVPUzlHBwJAScD9TqaG" + "wJ3juUsVtujV23SnH43iMggXT7m82STpPGam1hPfmqu2Z0niePFo927ogQ7H1EMJ" + "UHgqXmuvk2X/Ww==";
    assertEquals(helloWorldEncoded, java8Base64.internalEncode(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, java8Base64.internalEncode(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, java8Base64.internalEncode(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, java8Base64.internalEncode(helloWorldDifferentCharsBytes));
    assertEquals(str, java8Base64.internalEncode(bytes));
    assertEquals(helloWorldEncoded, commonsCodecBase64.internalEncode(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, commonsCodecBase64.internalEncode(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, commonsCodecBase64.internalEncode(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, commonsCodecBase64.internalEncode(helloWorldDifferentCharsBytes));
    assertEquals(str, commonsCodecBase64.internalEncode(bytes));
}

@Test
public void testEncode() {
    final String helloWorldEncoded = "SGVsbG8gV29ybGQ=";
    final String helloWorldTwoLinesEncoded = "SGVsbG8gV29ybGQNCk5ldyBMaW5lMg==";
    final String helloWorldTwoLinesAndNewLineEncoded = "SGVsbG8gV29ybGQNClNlY29uZCBMaW5lDQo=";
    final String helloWorldDifferentCharsEncoded = "YDEyMzQ1Njc4OTAtPX4hQCMkJV4mKigpXyvRkdCBIuKEljs6P3F3ZXJ0eXVpb3B" + "bXWFzZGZnaGprbDsnenhjdmJubSwuL1FXRVJUWVVJT1B7fXxBU0RGR0hKS0w6WlhDVkJOTTw+P9C50YTRj9GG0YvRh9GD0LLRgdC" + "60LDQvNC10L/QuNC90YDRgtCz0L7RjNGI0LvQsdGJ0LTRjtC30LYu0YXRjdGKXNCZ0KTQr9Cm0KvQp9Cj0JLQodCa0JDQnNCV0J/" + "QmNCd0KDQotCT0J7QrNCo0JvQkdCp0JTQrtCX0JYs0KXQrdCqLw0JDCcICg==";
    final String str = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMPQ5BCMxlUq2TYy" + "iRIoEUsz6HGTJhHuasS2nx1Se4Co3lxwxyubVdFj8AuhHNJSmJvjlpbTsGOjLZpr" + "HyDEDdJmf1Fensh1MhUnBZ4a7uLrZrKzFHHJdamX9pxapB89vLeHlCot9hVXdrZH" + "nNtg6FdmRKH/8gbs8iDyIayFvzYDAgMBAAECgYA+c9MpTBy9cQsR9BAvkEPjvkx2" + "XL4ZnfbDgpNA4Nuu7yzsQrPjPomiXMNkkiAFHH67yVxwAlgRjyuuQlgNNTpKvyQt" + "XcHxffnU0820VmE23M+L7jg2TlB3+rUnEDmDvCoyjlwGDR6lNb7t7Fgg2iR+iaov" + "0iVzz+l9w0slRlyGsQJBAPWXW2m3NmFgqfDxtw8fsKC2y8o17/cnPjozRGtWb8LQ" + "g3VCb8kbOFHOYNGazq3M7+wD1qILF2h/HecgK9eQrZ0CQQDMHXoJMfKKbrFrTKgE" + "zyggO1gtuT5OXYeFewMEb5AbDI2FfSc2YP7SHij8iQ2HdukBrbTmi6qxh3HmIR58" + "I/AfAkEA0Y9vr0tombsUB8cZv0v5OYoBZvCTbMANtzfb4AOHpiKqqbohDOevLQ7/" + "SpvgVCmVaDz2PptcRAyEBZ5MCssneQJAB2pmvaDH7Ambfod5bztLfOhLCtY5EkXJ" + "n6rZcDbRaHorRhdG7m3VtDKOUKZ2DF7glkQGV33phKukErVPUzlHBwJAScD9TqaG" + "wJ3juUsVtujV23SnH43iMggXT7m82STpPGam1hPfmqu2Z0niePFo927ogQ7H1EMJ" + "UHgqXmuvk2X/Ww==";
    assertEquals(helloWorldEncoded, java8Base64.internalEncode(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, java8Base64.internalEncode(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, java8Base64.internalEncode(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, java8Base64.internalEncode(helloWorldDifferentCharsBytes));
    assertEquals(str, java8Base64.internalEncode(bytes));
    assertEquals(helloWorldEncoded, commonsCodecBase64.internalEncode(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, commonsCodecBase64.internalEncode(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, commonsCodecBase64.internalEncode(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, commonsCodecBase64.internalEncode(helloWorldDifferentCharsBytes));
    assertEquals(str, commonsCodecBase64.internalEncode(bytes));
    assertEquals(helloWorldEncoded, jaxbBase64.internalEncode(helloWorldBytes));
    assertEquals(helloWorldTwoLinesEncoded, jaxbBase64.internalEncode(helloWorldTwoLinesBytes));
    assertEquals(helloWorldTwoLinesAndNewLineEncoded, jaxbBase64.internalEncode(helloWorldTwoLinesAndNewLineBytes));
    assertEquals(helloWorldDifferentCharsEncoded, jaxbBase64.internalEncode(helloWorldDifferentCharsBytes));
    assertEquals(str, jaxbBase64.internalEncode(bytes));
}

@Override
protected String internalEncode(byte[] bytes) {
    return DatatypeConverter.printBase64Binary(bytes);
}


@Test
public void shouldExtractTokenFromResponseWithCallbackConfirmed() throws IOException {
    final String response = "oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03" + "&callback_confirmed=true";
    final OAuth1Token extracted = extractor.extract(ok(response));
    assertEquals("hh5s93j4hdidpola", extracted.getToken());
    assertEquals("hdhd0244k9j7ao03", extracted.getTokenSecret());
}

@Test
public void shouldExtractTokenFromResponseWithCallbackConfirmed() throws IOException {
    final String responseBody = "oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03" + "&callback_confirmed=true";
    final OAuth1Token extracted;
    try (Response response = ok(responseBody)) {
        extracted = extractor.extract(response);
    }
    assertEquals("hh5s93j4hdidpola", extracted.getToken());
    assertEquals("hdhd0244k9j7ao03", extracted.getTokenSecret());
}

@Override
public void close() throws IOException {
    if (stream != null) {
        stream.close();
    }
}


@Test
public void shouldExtractTokenFromResponseWithExpiresParam() throws IOException {
    final String response = "access_token=166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159" + "|RsXNdKrpxg8L6QNLWcs2TVTmcaE&expires_in=5108";
    final OAuth2AccessToken extracted = extractor.extract(ok(response));
    assertEquals("166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159|RsXNdKrpxg8L6QNLWcs2TVTmcaE", extracted.getAccessToken());
    assertEquals(Integer.valueOf(5108), extracted.getExpiresIn());
}

@Test
public void shouldExtractTokenFromResponseWithExpiresParam() throws IOException {
    final String responseBody = "access_token=166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159" + "|RsXNdKrpxg8L6QNLWcs2TVTmcaE&expires_in=5108";
    final OAuth2AccessToken extracted;
    try (Response response = ok(responseBody)) {
        extracted = extractor.extract(response);
    }
    assertEquals("166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159|RsXNdKrpxg8L6QNLWcs2TVTmcaE", extracted.getAccessToken());
    assertEquals(Integer.valueOf(5108), extracted.getExpiresIn());
}

@Test
public void shouldExtractTokenFromResponseWithExpiresAndRefreshParam() throws IOException {
    final String response = "access_token=166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159" + "|RsXNdKrpxg8L6QNLWcs2TVTmcaE&expires_in=5108&token_type=bearer&refresh_token=166942940015970";
    final OAuth2AccessToken extracted = extractor.extract(ok(response));
    assertEquals("166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159|RsXNdKrpxg8L6QNLWcs2TVTmcaE", extracted.getAccessToken());
    assertEquals(Integer.valueOf(5108), extracted.getExpiresIn());
    assertEquals("bearer", extracted.getTokenType());
    assertEquals("166942940015970", extracted.getRefreshToken());
}

@Test
public void shouldExtractTokenFromResponseWithExpiresAndRefreshParam() throws IOException {
    final String responseBody = "access_token=166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159" + "|RsXNdKrpxg8L6QNLWcs2TVTmcaE&expires_in=5108&token_type=bearer&refresh_token=166942940015970";
    final OAuth2AccessToken extracted;
    try (Response response = ok(responseBody)) {
        extracted = extractor.extract(response);
    }
    assertEquals("166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159|RsXNdKrpxg8L6QNLWcs2TVTmcaE", extracted.getAccessToken());
    assertEquals(Integer.valueOf(5108), extracted.getExpiresIn());
    assertEquals("bearer", extracted.getTokenType());
    assertEquals("166942940015970", extracted.getRefreshToken());
}

@Test
public void shouldExtractTokenFromOAuthStandardResponse() throws IOException {
    final String response = "access_token=166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159" + "|RsXNdKrpxg8L6QNLWcs2TVTmcaE";
    final OAuth2AccessToken extracted = extractor.extract(ok(response));
    assertEquals("166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159|RsXNdKrpxg8L6QNLWcs2TVTmcaE", extracted.getAccessToken());
}

@Test
public void shouldExtractTokenFromOAuthStandardResponse() throws IOException {
    final String responseBody = "access_token=166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159" + "|RsXNdKrpxg8L6QNLWcs2TVTmcaE";
    final OAuth2AccessToken extracted;
    try (Response response = ok(responseBody)) {
        extracted = extractor.extract(response);
    }
    assertEquals("166942940015970|2.2ltzWXYNDjCtg5ZDVVJJeg__.3600.1295816400-548517159|RsXNdKrpxg8L6QNLWcs2TVTmcaE", extracted.getAccessToken());
}


@Test
public void testEscapedJsonInResponse() throws IOException {
    final OAuth2AccessToken token = extractor.extract(ok("{ \"access_token\":\"I0122HKLEM2\\/MV3ABKFTDT3T5X\"," + "\"token_type\":\"example\"}"));
    assertEquals("I0122HKLEM2/MV3ABKFTDT3T5X", token.getAccessToken());
}

@Test
public void testEscapedJsonInResponse() throws IOException {
    final String responseBody = "{ \"access_token\":\"I0122HKLEM2\\/MV3ABKFTDT3T5X\"," + "\"token_type\":\"example\"}";
    final OAuth2AccessToken token;
    try (Response response = ok(responseBody)) {
        token = extractor.extract(response);
    }
    assertEquals("I0122HKLEM2/MV3ABKFTDT3T5X", token.getAccessToken());
}

@Test
public void shouldParseResponse() throws IOException {
    final OAuth2AccessToken token = extractor.extract(ok("{ \"access_token\":\"I0122HHJKLEM21F3WLPYHDKGKZULAUO4SGMV3ABKFTDT3T3X\", " + "\"token_type\":\"example\"}"));
    assertEquals("I0122HHJKLEM21F3WLPYHDKGKZULAUO4SGMV3ABKFTDT3T3X", token.getAccessToken());
}

@Test
public void shouldParseResponse() throws IOException {
    final String responseBody = "{ \"access_token\":\"I0122HHJKLEM21F3WLPYHDKGKZULAUO4SGMV3ABKFTDT3T3X\", " + "\"token_type\":\"example\"}";
    final OAuth2AccessToken token;
    try (Response response = ok(responseBody)) {
        token = extractor.extract(response);
    }
    assertEquals("I0122HHJKLEM21F3WLPYHDKGKZULAUO4SGMV3ABKFTDT3T3X", token.getAccessToken());
}


@Test
public void shouldThrowExceptionIfResponseIsError() throws IOException {
    final String body = "{" + "\"error_description\":\"unknown, invalid, or expired refresh token\"," + "\"error\":\"invalid_grant\"" + "}";
    try {
        extractor.extract(error(body));
        fail();
    } catch (OAuth2AccessTokenErrorResponse oaer) {
        assertEquals(OAuth2AccessTokenErrorResponse.ErrorCode.INVALID_GRANT, oaer.getErrorCode());
        assertEquals("unknown, invalid, or expired refresh token", oaer.getErrorDescription());
    }
}

@Test
public void shouldThrowExceptionIfResponseIsError() throws IOException {
    final String body = "{" + "\"error_description\":\"unknown, invalid, or expired refresh token\"," + "\"error\":\"invalid_grant\"" + "}";
    try {
        extractor.extract(error(body));
        fail();
    } catch (OAuth2AccessTokenErrorResponse oaer) {
        assertEquals(OAuth2AccessTokenErrorResponse.ErrorCode.INVALID_GRANT, oaer.getErrorCode());
        assertEquals(OAuth2Error.INVALID_GRANT, oaer.getError());
        assertEquals("unknown, invalid, or expired refresh token", oaer.getErrorDescription());
    }
}

public OAuth2Error getError() {
    return error;
}


@Test
public void testErrorExtraction() {
    final FitBitJsonTokenExtractor extractor = new FitBitJsonTokenExtractor();
    thrown.expect(OAuth2AccessTokenErrorResponse.class);
    thrown.expect(new ErrorCodeFeatureMatcher(ErrorCode.invalid_grant));
    thrown.expect(new ErrorDescriptionFeatureMatcher(ERROR_DESCRIPTION));
    extractor.generateError(ERROR_JSON);
}

@Test
public void testErrorExtraction() {
    final FitBitJsonTokenExtractor extractor = new FitBitJsonTokenExtractor();
    thrown.expect(OAuth2AccessTokenErrorResponse.class);
    thrown.expect(new ErrorCodeFeatureMatcher(ErrorCode.INVALID_GRANT));
    thrown.expect(new ErrorDescriptionFeatureMatcher(ERROR_DESCRIPTION));
    extractor.generateError(ERROR_JSON);
}

@Override
public void generateError(String response) {
    final String errorInString = extractParameter(response, ERROR_REGEX_PATTERN, true);
    final String errorDescription = extractParameter(response, ERROR_DESCRIPTION_REGEX_PATTERN, false);
    OAuth2AccessTokenErrorResponse.ErrorCode errorCode;
    try {
        errorCode = OAuth2AccessTokenErrorResponse.ErrorCode.valueOf(errorInString);
    } catch (IllegalArgumentException iaE) {
        errorCode = null;
    }
    throw new OAuth2AccessTokenErrorResponse(errorCode, errorDescription, null, response);
}

@Override
public void generateError(String response) {
    final String errorInString = extractParameter(response, ERROR_REGEX_PATTERN, true);
    final String errorDescription = extractParameter(response, ERROR_DESCRIPTION_REGEX_PATTERN, false);
    OAuth2AccessTokenErrorResponse.ErrorCode errorCode;
    try {
        errorCode = OAuth2AccessTokenErrorResponse.ErrorCode.parseFrom(errorInString);
    } catch (IllegalArgumentException iaE) {
        errorCode = null;
    }
    throw new OAuth2AccessTokenErrorResponse(errorCode, errorDescription, null, response);
}


@Test(expected = IllegalArgumentException.class)
public void testConstructionWithNullType() {
    new Doc("idx", null, "id");
    fail("Constructor should have thrown an exception when type was null");
}

@Test
public void testConstructionWithNullType() {
    String index = "idx0";
    String id = "00001_AE";
    Map<String, Object> expectedMap = new HashMap<String, Object>();
    expectedMap.put("_index", index);
    expectedMap.put("_id", id);
    Doc doc = new Doc(index, id);
    Map<String, Object> actualMap = doc.toMap();
    assertEquals(2, actualMap.size());
    assertEquals(expectedMap, actualMap);
}

protected Map<String, Object> toMap() {
    Map<String, Object> retval = new LinkedHashMap<String, Object>();
    retval.put("_index", index);
    retval.put("_type", type);
    retval.put("_id", id);
    if (!fields.isEmpty()) {
        retval.put("fields", fields);
    }
    if (StringUtils.isNotEmpty(routing)) {
        retval.put("_routing", routing);
    }
    if (source != null) {
        retval.put("_source", source);
    }
    return retval;
}

protected Map<String, Object> toMap() {
    Map<String, Object> retval = new LinkedHashMap<String, Object>();
    retval.put("_index", index);
    if (StringUtils.isNotEmpty(type)) {
        retval.put("_type", type);
    }
    retval.put("_id", id);
    if (!fields.isEmpty()) {
        retval.put("fields", fields);
    }
    if (StringUtils.isNotEmpty(routing)) {
        retval.put("_routing", routing);
    }
    if (source != null) {
        retval.put("_source", source);
    }
    return retval;
}