以前の記事で 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); } }