JavaでJavaScriptのように正規表現で置換する

トリックオアトリート！！
お菓子あげないからいたずらしてください、あらっきぃです。

10月31日に記事を上げるはずがいつの間にか二日も経っていました。何てこった。
ノリが季節外れな感じがしますが気にしないでください。

##JavaScriptの`String.prototype.replace`

JavaとJavaScriptは違います。が、ときとしてJavaでJavaScriptのようなことをしたくなりたくなることがあります。

……というか、JavaScriptの`String.prototype.replace`メソッド便利すぎです。`for`や`while`を用いずに全てのマッチに対して処理できるわけですから。こんな風に、

```js
var
str = 'Trick or Treat',

rep = str.replace(/[a-zA-Z]/g, function (c, i, str) {
  console.log("%s:%d:%s", c, i, str);
  return 'a' <= c && c <= 'z' && c.toUpperCase() ||
         'A' <= c && c <= 'Z' && c.toLowerCase();
});

console.log(rep);
```

実行するとこんな結果になります。

```
T:0:Trick or Treat
r:1:Trick or Treat
i:2:Trick or Treat
c:3:Trick or Treat
k:4:Trick or Treat
o:6:Trick or Treat
r:7:Trick or Treat
T:9:Trick or Treat
r:10:Trick or Treat
e:11:Trick or Treat
a:12:Trick or Treat
t:13:Trick or Treat
tRICK OR tREAT
```

めったに使いませんがコールバックの引数として、`マッチした文字列`と`マッチしたグループ`のあとに`マッチした位置`と`もとの文字列`が入ってきます。

便利ですよね～。

##Javaって…

これと同じことをJavaでやるとですね…

```java:RegexSample.java
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexSample {

    static public void main(String... args) {
        String str = "Trick or Treat";
        Pattern pat = Pattern.compile("[a-zA-Z]");
        Matcher m = pat.matcher(str);

        StringBuilder sb = new StringBuilder();
        int start = 0;
        while(m.find()) {
            System.out.printf("%s:%d:%s\n", m.group(), m.start(), str);
            sb.append(str.substring(start, m.start()));
            start = m.end();
            char c = m.group().charAt(0);
            if ('a' <= c && c <= 'z') sb.append((char)(c - 0x20));
            else if ('A' <= c && c <= 'Z') sb.append((char)(c + 0x20));
        }
        sb.append(str.substring(start));

        System.out.println(sb.toString());
    }

}
```

こんな感じでしょうか？ そもそも正規表現を使うのがナンセンスな気がします。

##ちょっとwrapしてみる

なのでこんなクラスを用意してみました。

```java:Replacer.java
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Replacer {

    static String replace(String str, Pattern pat, ReplaceCallback callback) {
        StringBuilder sb = new StringBuilder();

        Matcher m = pat.matcher(str);
        int start = 0;
        while (m.find()) {
            sb.append(str.substring(start, m.start()));
            String matched = str.substring(m.start(), m.end());
            start = m.end();
            ArrayList<String> groups = new ArrayList<>();
            for (int j = 0; j <= m.groupCount(); j++) {
                groups.add(m.group(j));
            }
            sb.append(callback.replaceString(matched, groups, str, pat, m.toMatchResult()));
        }
        sb.append(str.substring(start));

        return sb.toString();
    }

}
```

```java:ReplaceCallback.java
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

public interface ReplaceCallback {
    String replaceString(String matched, List<String> groups, String str, Pattern pat, MatchResult matchResult);
}
```

これを使うと、さっきのコードをこんな風に書きなおすことができます。

```java:RegexExample.java
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

public class RegexSample {

    static public void main(String... args) {
        String str = "Trick or Treat";
        Pattern pat = Pattern.compile("[a-zA-Z]");
        String rep = Replacer.replace(str, pat, new ReplaceCallback() {
            @Override
            public String replaceString(String matched, List<String> groups,
                    String str, Pattern pat, MatchResult matchResult) {
                System.out.printf("%s:%d:%s\n", matched, matchResult.start(), str);
                char c = matched.charAt(0);
                c = (char)('a' <= c && c <= 'z' ? c - 0x20 :
                           'A' <= c && c <= 'Z' ? c + 0x20 : c);
                return Character.toString(c);
            }
        });

        System.out.println(rep);
    }
}
```

どうでしょうか？ さっきよりもJavaScriptのコードに近づいて、読みやすくなった気がします。

今回載せたコードは全てパブリックドメインとして煮るなり焼くなり好きにしてやってください。

###TODO

JavaDocを書く。