Guavaを使ってみる。 ～CharMatcher～

# CharMatcherとは
文字列を1文字ずつ評価するためのクラスです。
文字列の中に0～9の数字が何文字存在するかを数えるといったことができます。

# 開発環境
Java JDK 1.8
Google guava 18.0
Junit 4.11

# CharMatcherオブジェクト
CharMatcherは抽象クラスなので、char比較条件によって実装が異なります。
なお、後述のテストで利用しているcountIn()メソッドは、
「条件に一致する文字の数を返す」メソッドです。

## 全一致・全不一致
```java
// 全一致
CharMatcher anyMathcer = CharMatcher.ANY;
assertThat(anyMathcer.countIn("abc123あいう!?  "), is(16));

// 全不一致
CharMatcher noneMathcer = CharMatcher.NONE;
assertThat(noneMathcer.countIn("abc123あいう!?  "), is(0));
```
使う機会なさそう。

## 特定の文字のみ
```java
 // 数値のみ
 CharMatcher digitMathcer = CharMatcher.DIGIT;
 assertThat(digitMathcer.countIn("abc123あいう!?  "), is(3));

// 半角文字
CharMatcher singleMatcher = CharMatcher.SINGLE_WIDTH;
assertThat(singleMatcher.countIn("abc123あいう!?  "), is(10));

// 空白文字
CharMatcher whiteMatcher = CharMatcher.WHITESPACE;
assertThat(whiteMatcher.countIn("abc123あいう!?  "), is(2));

// 小文字英字
CharMatcher lowerMathcer = CharMatcher.JAVA_LOWER_CASE;
assertThat(lowerMathcer.countIn("abc123あいう!?  "), is(3));

// 大文字英字
CharMatcher upperMathcer = CharMatcher.JAVA_UPPER_CASE;
assertThat(upperMathcer.countIn("abc123あいう!?  "), is(0));
```
「文字列が全て数値か否か」といった判定で使えそうです。
プログラミング言語の入門問題でよくあるやつですね。

## 利用側でマッチング文字を指定
```java
// 指定した文字だけ
CharMatcher isMathcer = CharMatcher.is('a');
assertThat(isMathcer.countIn("abc123あいう!?  "), is(1));

// 指定した文字以外
CharMatcher isNotMatcher = CharMatcher.isNot('a');
assertThat(isNotMatcher.countIn("abc123あいう!?  "), is(15));

// 指定した文字のいずれか
CharMatcher anyOfMatcher = CharMatcher.anyOf("ab12!");
assertThat(anyOfMatcher.countIn("abc123あいう!?  "), is(5));

// 指定した文字のどれでもない
CharMatcher noneOfMatcher = CharMatcher.noneOf("ab12!");
assertThat(noneOfMatcher.countIn("abc123あいう!?  "), is(11));

// 範囲
CharMatcher inRangeMatcher = CharMatcher.inRange('a', 'z');
assertThat(inRangeMatcher.countIn("abc123あいう!?  "), is(3));

// 条件を自作
CharMatcher myMathcer = CharMatcher.forPredicate(c -> 'a' <= c && c <= 'z');
assertThat(myMathcer.countIn("abc123あいう!?  "), is(3));
```
マッチング文字を指定することも出来ます。まあ、当たり前ですね。
Predicateを渡すことも出来るので、それなりに色々できます。

## 条件のAND結合、OR結合、NOT演算
```java
// CharMatcherをAND結合
CharMatcher andMatcher = inRangeMatcher.and(anyOfMatcher);
assertThat(andMatcher.countIn("abc123あいう!?  "), is(2));
// CharMatcherをOR結合
CharMatcher orMatcher = inRangeMatcher.or(digitMathcer);
assertThat(orMatcher.countIn("abc123あいう!?  "), is(6));
// 条件を逆にする
CharMatcher negateMatcher = anyMathcer.negate();
assertThat(negateMatcher.countIn("abc123あいう!?  "), is(0));
```
条件式を結合することもできます。
CharMatcherを使うメリットの上位に入るのではと思います。
~~他はだいたい初心者が数行で書けるレベルだし。~~

# CharMatcherメソッド
## 条件判定系
```java
/**
 * boolean matches(char)
 * 指定した文字がCharMatcherを満たすか否かを返します。
 */
assertThat(CharMatcher.JAVA_LOWER_CASE.matches('a'), is(true));
assertThat(CharMatcher.JAVA_LOWER_CASE.matches('A'), is(false));

/**
 * boolean	matchesAllOf(CharSequence)
 * 指定した文字列の全ての文字がCharMatcherを満たすか否かを返します。
 */
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesAllOf("abcdefg"), is(true));
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesAllOf("Abcdefg"), is(false));

/**
 * boolean	matchesAnyOf(CharSequence)
 * 指定した文字列のいずれかの文字がCharMatcherを満たすか否かを返します。
 */
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesAnyOf("abcdefg"), is(true));
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesAnyOf("Abcdefg"), is(true));
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesAnyOf("ABCDEFG"), is(false));

/**
 * boolean	matchesAnyOf(CharSequence)
 * 指定した文字列の全ての文字がCharMatcherを満たさないか否かを返します。
 */
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesNoneOf("abcdefg"), is(false));
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesNoneOf("Abcdefg"), is(false));
assertThat(CharMatcher.JAVA_LOWER_CASE.matchesNoneOf("ABCDEFG"), is(true));
```

## 抽出系
```java
/**
 * String removeFrom(CharSequence)
 * CharMatcherを満たす文字を削除した文字列を返します。
 */
assertThat(CharMatcher.anyOf(" !").removeFrom("   h e l l o   w o r l d ! !   "), is("helloworld"));

/**
 * String retainFrom(CharSequence)
 * CharMatcherを満たす文字だけを残した文字列を返します。
 */
assertThat(CharMatcher.anyOf("abc").retainFrom("abcdedcbabcde"), is("abccbabc"));
assertThat(CharMatcher.anyOf("13579").retainFrom("2147483647"), is("1737"));
```

## トリム処理
```java
/**
 * String trimFrom(CharSequence)
 * CharMatcherを満たす前後の文字を削除した文字列を返します。
 */
assertThat(CharMatcher.is(' ').trimFrom("   h e l l o   w o r l d ! !   "), is("h e l l o   w o r l d ! !"));
assertThat(CharMatcher.is('"').trimFrom("\"trimFrom(\"str\")\""), is("trimFrom(\"str\")"));

/**
 * String trimLeadingFrom(CharSequence)
 * CharMatcherを満たす前方の文字を削除した文字列を返します。
 */
assertThat(CharMatcher.is(' ').trimLeadingFrom("   h e l l o   w o r l d ! !   "), is("h e l l o   w o r l d ! !   "));
assertThat(CharMatcher.is('"').trimLeadingFrom("\"trimFrom(\"str\")\""), is("trimFrom(\"str\")\""));

/**
 * String trimTrailingFrom(CharSequence)
 * CharMatcherを満たす後方の文字を削除した文字列を返します。
 */
assertThat(CharMatcher.is(' ').trimTrailingFrom("   h e l l o   w o r l d ! !   "), is("   h e l l o   w o r l d ! !"));
assertThat(CharMatcher.is('"').trimTrailingFrom("\"trimFrom(\"str\")\""), is("\"trimFrom(\"str\")"));
```
処理前に半角空白などの不純物を取り除きたい！　というケースはままあるので、
かなり有用な処理だと感じます。私がCharMatcherを調べようと思った理由のひとつです。

## 置換
```java
/**
 * String replaceFrom(CharSequence, char)
 * String replaceFrom(CharSequence, CharSequence)
 * CharMatcherを満たす文字を指定した文字または文字列で置換した文字列を返します。
 */
assertThat(CharMatcher.is(' ').replaceFrom("   h e l l o   w o r l d ! !   ", '.'), is("...h.e.l.l.o...w.o.r.l.d.!.!..."));
assertThat(CharMatcher.is('o').replaceFrom("google", "ooo"), is("goooooogle"));

/**
 * String collapseFrom(CharSequence, char)
 * CharMatcherを満たす文字を指定した文字または文字列で置換した文字列を返します。
 * 指定した文字が連続して存在する場合は、それを1つとして扱います。
 */
assertThat(CharMatcher.is(' ').collapseFrom("   h e l l o   w o r l d ! !   ", '.'), is(".h.e.l.l.o.w.o.r.l.d.!.!."));
assertThat(CharMatcher.anyOf(" #").collapseFrom("##### h e l l o   w o r l d ! ! #####", '.'), is(".h.e.l.l.o.w.o.r.l.d.!.!."));

/**
 * String trimAndCollapseFrom(CharSequence, char)
 * CharMatcherを満たす前後の文字を削除し、それ以外は指定した文字で置換した文字列を返します。
 * 指定した文字が連続して存在する場合は、それを1つとして扱います。
 */
assertThat(CharMatcher.is(' ').trimAndCollapseFrom("   h e l l o   w o r l d ! !   ", '.'), is("h.e.l.l.o.w.o.r.l.d.!.!"));
assertThat(CharMatcher.anyOf(" #").trimAndCollapseFrom("##### h e l l o   w o r l d ! ! #####", '.'), is("h.e.l.l.o.w.o.r.l.d.!.!"));
```
これもトリム処理同様便利な処理だと思います。
String.replaceAll()だと正規表現を使っていますが、
これの実装は単純なForループになっています。
CharMatcherで変換可能であれば、こちらを使った方が良さそうですね。

## その他
インデックス検索を行う`indexIn()`、`lastIndexIn()`や、
冒頭で散々使っていた個数を数える`countIn()`があります。

# 最後に
地味ですが、所々で役立つ部分がありそうなクラスです。
中を覗いてみれば分かりますが、実装自体はとても簡単なので、
プログラミング初心者の方のソースコードリーディング対象としても適当ではないでしょうか。