倭マン's BLOG

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

FactoryBuilderSupport よ、我に力を! (7) : Apache POI を構築するビルダー

今回は、前回載せたビルドスクリプトを実行できるビルダーを実装します。 実装に必要なのは

  • 各ノードに対応するファクトリ・クラスを実装する
  • ビルドを実行するビルダーに実装したファクトリ・クラス(のインスタンス)を登録する

ファクトリの実装で注目なのは、プロパティを自動で設定してくれる onHandleNodeAttributes() メソッドでしょう。 他にも、既に構築した親ノードに対応するオブジェクトの取得なども行ってます。

ファクトリ


まずは、POI に出てくる要素 (Workbook, Sheet, Row, Cell など) を構築するファクトリ・クラスに対して、共通の親クラス PoiComponentFactory を定義しておきましょう。 このクラスは groovy.util.AbstractFactory を継承させます:
PoiComponentFactory

abstract class PoiComponentFactory extends AbstractFactory{

    @Override
    boolean onHandleNodeAttributes(FactoryBuilderSupport builder, node, Map atts) {
        return true
    }
}

どの POI 要素の構築にも自動的にプロパティの設定を行わせたいので、onHandleNodeAttributes() メソッドが true を返すようにしておきます。

WorkbookFactory

次はスプレッドシートのルート要素となる workbook ノードの構築。 このノードは Workbook オブジェクトを構築します:

import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.hssf.usermodel.HSSFWorkbook

class WorkbookFactory extends PoiComponentFactory{

    @Override
    def newInstance(FactoryBuilderSupport builder, name, value, Map atts) {
        return (value instanceof Workbook) ? value : new HSSFWorkbook()
    }
}

このコードでは value 変数が Workbook オブジェクトならそのオブジェクトを返すようにしてますが、これはビルドスクリプトで

def customBook = new HSSFWorkbook()
def builer = new PoiBuilder()

builder.workbook(customBook){
    sheet{ ... }
}

のようになっているとき、customBook オブジェクトに対して構築を実行していけるようにするためです。 value 変数が Workbook オブジェクトでないなら、内部で HSSFWorkbook オブジェクトを生成してそれを返します。

SheetFactory, RowFactory, CellFactory

Workbook オブジェクト以外の POI 要素の構築はコンストラクタから行えないので、既に構築したオブジェクトを取得し、そのオブジェクトから対応するインスタンス生成を行います。 もう少し具体的には、Sheet オブジェクトは親となる Workbook オブジェクトに対して createSheet() メソッドを呼び出してインスタンス生成する必要があります。

既に構築されている親ノード(に対応するオブジェクト)を取得したい場合は、newInstance() メソッドの第1引数にある FactoryBuilderSupport オブジェクトに対して current プロパティ(もしくは getCurrent() メソッド)を使います:

import org.apache.poi.ss.usermodel.Workbook

class SheetFactory extends PoiComponentFactory{

    @Override
    def newInstance(FactoryBuilderSupport builder, name, value, Map atts) {
        if(value instanceof String){
            return builder.current.createSheet(value)
        }else{
            return builder.current.createSheet()
        }
    }
}

value 変数は Workbook#createSheet() メソッドに渡す文字列(シート名)を期待しています。 WorkbookFactory の場合のように外部オブジェクトに対して構築するようにはしてません(POI の API 的に不可能なので)。

row ノード、cell ノードを構築する RowFactory, CellFactory は SheetFactory の実装と概ね同じです。

CellValueFactory

最後のファクトリ・クラス CellValueFactory は、cellValue ノードからセルの内容を構築する ファクトリ・クラスです:

class CellValueFactory extends PoiComponentFactory{

    @Override
    def newInstance(FactoryBuilderSupport builder, name, value, Map atts) {
        return value
    }

    @Override
    void setParent(FactoryBuilderSupport builder, parent, child) {
        parent.setCellValue(child)
    }
}
  • CellValueFactory クラスは org.apache.poi.ss.usermodel パッケージにある CellValue を構築してるわけではなく、value 変数をそのまま返すようにしてます*1
  • セルの内容は自分で Cell オブジェクトに設定する必要があるので、setParent() メソッドをオーバーライでしてます。

ビルダー PoiBuilder


後は、これまで作成したファクトリをビルダーに登録するだけです:

class PoiBuilder extends FactoryBuilderSupport{
    
    PoiBuilder(){
        registerFactory('workbook', new WorkbookFactory())
        registerFactory('sheet', new SheetFactory())
        registerFactory('row', new RowFactory())
        registerFactory('cell', new CellFactory())
        registerFactory('cellValue', new CellValueFactory())
    }
}

ファクトリを登録する方法は、registerFactory() メソッドにノードと対応するファクトリオブジェクトを渡すだけです。

構築スクリプト


以上を踏まえて、ワークブックを構築スクリプトを。 前回書いたのと同じです:

@GrabResolver('http://www5.ocn.ne.jp/~coast/repo/')
@Grab('org.waman.tools:poi-builder:0.0.1')

import org.waman.tools.poi.PoiBuilder

def builder = new PoiBuilder()
// ワークブックの生成
def workbook = builder.workbook{
    // シート Sheet を生成
    sheet('Sheet'){
        for(i in 0..<5){
            row(i){
                for(j in 0..<3){
                    cell(j){
                        cellValue("$i:$j")
                    }
                }
            }
        }
    }
}

// ワークブックの書き出し
new File('test.xls').withOutputStream{ os ->
    workbook.write(os)
}

このスクリプトでは細かなプロパティの設定を行ってませんが、上記のビルダーとファクトリの実装だけでプロパティの設定もキチンと動作します。 たとえば row ノードに対して

            row(i, heightInPoints:20f){
                ...
            }

などとすれば、行の高さを設定できますヨ:-9 どんなプロパティが設定できるかは、Apache POI の API ドキュメントを参考にしてくださいな()。

囮物語 (講談社BOX)

囮物語 (講談社BOX)

*1:本当は cell ノードの cellValue 属性で設定を行えるようにしたかったのですが、Cell インターフェースにはいろいろな setCellValue() メソッドがオーバーロードされていて、かつ getCellValue() メソッドがないためか、いまいちうまく cellValue プロパティを設定してくれなかったのでこういう実装にしてます。