juman++のjavaラッパー:サーバーモードも対応

prologの自然言語二分木を作るのに形態素解析はjumanでやってきた。が、前から気になっていたjuman++と比べてみたら、出力内容についてかなりの違いがあることがわかった。そこで、juman++ にしようと思ったが、jumanは自分でサーバーモードを持っていたが、juman++は、内蔵していなくて、rubyなどのラッパーで対応している。

大規模日本語コーパスの二分木づくりは、いくつものスレッドで、並列に形態素解析を行う必要がありjumanの時は、スレッドごとにjumanやknpのサーバーを立ち上げて対応した。しかし、juman++のruby経由ではうまくいかない。

そこで、juman++のjavaラッパーを作って、対応することにした。javaでjuman++を制御できれば、もともと二分木づくりはjavaでやっているので、あえてサーバーにする必要も無くなるのだが、一応、ソケット通信にも対応するようにした。

/*
Jumanpp.java
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Jumanpp {
    //  juman++のインストールパスを指定する
    String jumanpath = "/usr/local/juman1.02/bin/jumanpp";
    OutputStreamWriter ow;
    InputStream is;
    //InputStream es;
    PrintStream pis;
    //PrintStream pes;
    
    void getOutput(String line){
        if(line.startsWith("%%CLOSE")){
            System.out.println("Jumanpp: 終了します");
            jumannppClose();
            return;
        }
        try {
            ow.write(line+"\n");
            ow.flush();
        } catch (IOException ex) {
            Logger.getLogger(Jumanpp.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    void jumannppClose(){
        try {
            pis.stopThread();
            // 標準エラーを使う場合
            //pes.stopThread();
            // スレッド終了のためのダミー
            ow.write("terminate\n");
            ow.flush();
            
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
        }

            ow.close();
            is.close();
            // 標準エラーを使う場合
            //es.close();
        } catch (IOException ex) {
            Logger.getLogger(Jumanpp.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    void jumannppStart(){
 	ProcessBuilder pb = new ProcessBuilder(jumanpath);
        System.out.println("Jumanpp: 開始します");
	pb.redirectErrorStream(true); 
        Process process;
        try {
            process = pb.start();
            //
            is = process.getInputStream();
            pis = new PrintStream(is);
            pis.start();
            // 標準エラーを使う場合
            //es = process.getErrorStream();
            //pes = new PrintStream(es);
            //pes.start();
            OutputStream os = process.getOutputStream();
            ow = new OutputStreamWriter(os);
        } catch (IOException ex) {
            Logger.getLogger(Jumanpp.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public static void main(String args[]) {
        Jumanpp jumanpp = new Jumanpp();
        jumanpp.jumannppStart();
        // サーバーモードで使わない場合は以下二行をコメントアウトする
        Server server = new Server(jumanpp,32100);
        server.start();
        try {
            jumanpp.pis.join();
            // 標準エラーを使う場合
            //jumanpp.pes.join();
        } catch (InterruptedException ex) {
            Logger.getLogger(Jumanpp.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    class PrintStream extends Thread{
        BufferedReader br;
        boolean stop = false;
        
        PrintStream(InputStream is){
            br = new BufferedReader(new InputStreamReader(is));
        }
        
        void stopThread(){
            stop = true;
        }
        
        @Override
        public void run(){
            System.out.println("PrintStream: スレッドを開始します");
            try {
                while(true){
                    String line = br.readLine();
                    if (line == null || stop) {
                        break;
                    }
                    System.out.println(line);
                }
                br.close();
            } catch (IOException ex) {
                Logger.getLogger(Jumanpp.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.out.println("PrintStream: スレッドを終了します");
        }
    }

}

ソケット通信をする場合は、以下のクラスも使う。

/*
Server.java
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Server extends Thread{
    int port;
    Jumanpp jumanpp;
    
    public Server(Jumanpp jumanpp, int port){
        this.jumanpp = jumanpp;
        this.port = port;
    }
    
    @Override
    public void run() {
        try {
            ServerSocket ss = new ServerSocket(port);
            System.out.println("jumanpp サーバースタート ...");
            //サーバー側ソケット作成
            Socket sc = ss.accept();
            String ipaddress = sc.getInetAddress().getHostAddress();
            System.out.println("Connected from: " + ipaddress);
            juman(sc);
        } catch (IOException ex) {
            System.out.println("サーバーソケットエラー");
        }
        System.out.println("サーバーは停止しました");
    }

    void juman(Socket sc){
        BufferedReader br;
        PrintWriter pw;
        try {
            br = new BufferedReader(new InputStreamReader(sc.getInputStream()));
            //pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sc.getOutputStream())));
            String data;
            while((data = br.readLine()) != null){
                System.out.println("受信データ:" + data);
                jumanpp.getOutput(data);
                if(data.startsWith("%%CLOSE")){
                    System.out.println("juman: サーバーを終了します");
                    break;
                }
            }
            //pw.println("Data received.");
            //pw.flush();
            //pw.close();
            br.close();
            sc.close();
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }    
}