倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

org.w3c.dom.Document を java.io.Reader に変換する

以前の記事で org.w3c.dom.Document オブジェクトを java.io.Writer へ書き出す処理を見ました。

今回はそれを踏まえて、「org.w3c.dom.Document オブジェクトを java.io.Reader オブジェクトとして扱う」方法を考えます。 この目的のために java.io.Reader クラスを拡張した DOMDocumentReader というクラスを作成します。

使用方法


実装の前に DOMDocumentReader クラスの使用方法を見てみましょう。 まぁ、テストケースの一部って事で。

Document doc = ...;
Reader reader = new DOMDocumentReader(doc);

単に、コンストラクタに org.w3c.dom.Document オブジェクトをとるってだけで、あとは通常の Reader として使えるようにします。 結構便利そうではないですか*1

DOMDocumentReader の実装 (1):フィールドとコンストラクタ


まずはフィールドとコンストラクタ。 フィールドとしては

  • Reader クラスとしての処理を委譲するreader : java.io.Reader
  • org.w3c.dom.Document オブジェクトを書き出すスレッド executor : java.util.concurrency.ExecutorService
  • 書き出しの際に IOException が発生した場合に、それを格納しておく exception : java.io.Exception

を定義します。 コンストラクタは、書き出したい org.w3c.dom.Document オブジェクトをとるものを定義します。

public class DOMDocumentReader extends Reader{
    
    /** 処理を委譲される (Piped)Reader フィールド */
    private final Reader reader;

    /** Document オブジェクトを (Piped)Writer に書き出す ExecutorService フィールド */
    private final ExecutorService executor;
        
    /**
       * Document オブジェクトを書き出す際に IOException した場合に、
       * その例外を格納しておくフィールド
       */
    private IOException exception;
    
    public DOMDocumentReader(Document doc){
        PipedWriter writer = new PipedWriter();
        
        Reader r = null;
        try{
            r = new PipedReader(writer);
        }catch(IOException ex){
            this.exception = ex;
        }
        
        this.reader = r;
        this.executor = Executors.newSingleThreadExecutor();
        this.executor.execute(new WriteDownThread(doc, writer));
    }
}

この手の処理には java.io.PipedReader, java.io.PipedWriter を使うのが普通でしょうか? あまり使ったことないですが。 同様に java.util.concurrency パッケージもあまり使ったこと無いんですけど*2

DOMDocumentReader の実装 (2):書き下しスレッド


org.w3c.dom.Document を (Piped)Writer に書き出す処理は以前の記事と同じです。 ただし、例外処理がチョット面倒。 書き出し時に例外が発生した場合、DOMDocumentReader オブジェクトのフィールド exception (IOException) にその例外をセットするようにします。 これには同期が必要

public class DOMDocumentReader extends Reader{

    ...
    
    private static final TransformerFactory FACTORY = TransformerFactory.newInstance(); 
    
    /** Document オブジェクトを (Piped)Writer へ書き出すスレッド */
    private class WriteDownThread implements Runnable{

        private final Transformer transformer;
        private final Document source;
        private final Writer result;
        
        public WriteDownThread(Document doc, Writer writer){
            try{
                this.transformer = FACTORY.newTransformer();
                
            }catch(TransformerConfigurationException ex){
                throw new RuntimeException(ex);
            }
                
            this.source = doc;
            this.result = writer;
        }
        
        @Override public void run() {
            try{
                this.transformer.transform(
                        new DOMSource(this.source), 
                        new StreamResult(this.result));
                this.result.flush();
                
            }catch(IOException ex){
                setIOException(ex);
            }catch(TransformerException ex){
                setIOException(new IOException(ex));
            }finally{
                try{
                    if(this.result != null)
                        this.result.close();
                    
                }catch(IOException ex){
                    // close() 以外で例外が発生していればそちらを優先
                    setIOExceptionIfUnset(ex);
                }
            }
        }
    }
    
    /** 例外をセットする */
    private synchronized void setIOException(IOException ex){
        this.exception = ex;
    }
    
    /** まだ例外がセットされていなければ、例外をセットする */
    private synchronized void setIOExceptionIfUnset(IOException ex){
        if(this.exception == null)
            this.exception = ex;
    }
}

DOMDocumentReader の実装 (3):reader フィールドへの委譲メソッド


java.io.Reader オブジェクトに定義されているメソッドは、全て reader フィールドへ処理を委譲するようにオーバーライドします。 ただし、IOException を投げるメソッドは委譲前に、書き出し時に例外が発生していないかチェックします(checkIOException() メソッド)。  close() メソッドでは、加えて executor フィールドの後処理 ExecutorService#shutdown() メソッドを呼び出します。

public class DOMDocumentReader extends Reader{

    ...

    // Document の書き出し時に例外が発生していないかチェック
    private void checkIOException()throws IOException{
        if(this.exception != null)
            throw this.exception;
    }

    //********** reader フィールドへの委譲メソッド **********
    @Override public void close() throws IOException {
        // ExecutorService をシャットダウンする
        this.executor.shutdown();
        checkIOException();
        this.reader.close();
    }

    @Override public void mark(int readAheadLimit) throws IOException {
        checkIOException();
        this.reader.mark(readAheadLimit);
    }

    @Override public boolean markSupported() {
        return this.reader.markSupported();
    }

    @Override public int read() throws IOException {
        checkIOException();
        return this.reader.read();
    }

    @Override public int read(char[] cbuf, int off, int len) throws IOException {
        checkIOException();
        return this.reader.read(cbuf, off, len);
    }

    @Override public int read(char[] cbuf) throws IOException {
        checkIOException();
        return this.reader.read(cbuf);
    }

    @Override public int read(CharBuffer target) throws IOException {
        checkIOException();
        return this.reader.read(target);
    }

    @Override public boolean ready() throws IOException {
        checkIOException();
        return this.reader.ready();
    }

    @Override public void reset() throws IOException {
        checkIOException();
        this.reader.reset();
    }

    @Override public long skip(long n) throws IOException {
        checkIOException();
        return this.reader.skip(n);
    }
}

DOMDocumentReader の実装のまとめ

public class DOMDocumentReader extends Reader{

    private static final TransformerFactory FACTORY = TransformerFactory.newInstance(); 
    
    /** 処理を委譲される (Piped)Reader フィールド */
    private final Reader reader;

    /** Document オブジェクトを (Piped)Writer に書き出す ExecutorService フィールド */
    private final ExecutorService executor;
        
    /**
       * Document オブジェクトを書き出す際に IOException した場合に、
       * その例外を格納しておくフィールド
       */
    private volatile IOException exception;
    
    public DOMDocumentReader(Document doc){
        PipedWriter writer = new PipedWriter();
        
        Reader r = null;
        try{
            r = new PipedReader(writer);
        }catch(IOException ex){
            this.exception = ex;
        }
        
        this.reader = r;
        this.executor = Executors.newSingleThreadExecutor();
        this.executor.execute(new WriteDownThread(doc, writer));
    }
    
    /** Document オブジェクトを (Piped)Writer へ書き出すスレッド */
    private class WriteDownThread implements Runnable{

        private final Transformer transformer;
        private final Document source;
        private final Writer result;
        
        public WriteDownThread(Document doc, Writer writer){
            try{
                this.transformer = FACTORY.newTransformer();
                
            }catch(TransformerConfigurationException ex){
                throw new RuntimeException(ex);
            }
                
            this.source = doc;
            this.result = writer;
        }
        
        @Override public void run() {
            try{
                this.transformer.transform(
                        new DOMSource(this.source), 
                        new StreamResult(this.result));
                this.result.flush();
                
            }catch(IOException ex){
                setIOException(ex);
            }catch(TransformerException ex){
                setIOException(new IOException(ex));
            }finally{
                try{
                    if(this.result != null)
                        this.result.close();
                    
                }catch(IOException ex){
                    // close() 以外で例外が発生していればそちらを優先
                    setIOExceptionIfUnset(ex);
                }
            }
        }
    }
    
    /** 例外をセットする */
    private synchronized void setIOException(IOException ex){
        this.exception = ex;
    }
    
    /** まだ例外がセットされていなければ、例外をセットする */
    private synchronized void setIOExceptionIfUnset(IOException ex){
        if(this.exception == null)
            this.exception = ex;
    }

    /** Document の書き出し時に例外が発生していないかチェック */
    private synchronized void checkIOException()throws IOException{
        if(this.exception != null)
            throw this.exception;
    }

    //********** reader フィールドへの委譲メソッド **********
    @Override public void close() throws IOException {
        // ExecutorService をシャットダウンする
        this.executor.shutdown();
        checkIOException();
        this.reader.close();
    }

    @Override public void mark(int readAheadLimit) throws IOException {
        checkIOException();
        this.reader.mark(readAheadLimit);
    }

    @Override public boolean markSupported() {
        return this.reader.markSupported();
    }

    @Override public int read() throws IOException {
        checkIOException();
        return this.reader.read();
    }

    @Override public int read(char[] cbuf, int off, int len) throws IOException {
        checkIOException();
        return this.reader.read(cbuf, off, len);
    }

    @Override public int read(char[] cbuf) throws IOException {
        checkIOException();
        return this.reader.read(cbuf);
    }

    @Override public int read(CharBuffer target) throws IOException {
        checkIOException();
        return this.reader.read(target);
    }

    @Override public boolean ready() throws IOException {
        checkIOException();
        return this.reader.ready();
    }

    @Override public void reset() throws IOException {
        checkIOException();
        this.reader.reset();
    }

    @Override public long skip(long n) throws IOException {
        checkIOException();
        return this.reader.skip(n);
    }
}

*1:JDOM や dom4j にも似たようなクラスがあっていいと思うのですが。

*2:とりあえず、ExecutorService#shutdown() は DOMDocumentReader#close() メソッド内で呼び出してますが。