package com.gh.mygreen.xlsmapper.annotation; import java.awt.Point; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import com.gh.mygreen.xlsmapper.XlsMapperConfig; import com.gh.mygreen.xlsmapper.expression.CustomFunctions; /** * 書き込み時にセルの数式を定義するためのアノテーションです。 * * * <h3 class="description">式言語処理のカスタマイズ</h3> * * <p>数式を直接指定する場合は、EL式の1つの実装である <a href="http://commons.apache.org/proper/commons-jexl/" target="_blnak">JEXL</a> * が利用できますが、実装を切り替えたり、デフォルトの関数を登録したりとカスタマイズができます。 * </p> * <p>設定を変更したい場合は、{@link XlsMapperConfig#getFormulaFormatter()} の値を変更します。</p> * * <pre class="highlight"><code class="java"> * // 数式をフォーマットする際のEL関数を登録する。 * ExpressionLanguageJEXLImpl formulaEL = new ExpressionLanguageJEXLImpl(); * Map<String, Object> funcs = new HashMap<>(); * funcs.put("x", CustomFunctions.class); * formulaEL.getJexlEngine().setFunctions(funcs); * * // 数式をフォーマットするEL式の実装を変更する * XlsMapper mapper = new XlsMapper(); * mapper.getConig().getFormulaFormatter().setExpressionLanguage(formulaEL); * </code></pre> * * @since 1.5 * @author T.TSUCHIE * */ @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface XlsFormula { /** * 数式を直接指定します。 * <p>数式を指定する際に、メッセージファイルと同様に、変数やEL式が利用可能です。</p> * <ul> * <li>変数は {@literal {変数名}} で定義します。</li> * <li>EL式は {@literal ${EL式}} で定義します。 * <ul> * <li>EL式は、<a href="http://commons.apache.org/proper/commons-jexl/" target="_blank">JEXL(Java Expression Language)</a>の形式で指定します。</li> * <li>JEXLの仕様は、<a href="http://commons.apache.org/proper/commons-jexl/reference/syntax.html" target="_blank">JEXL Reference</a> を参照してください。</li> * </ul> * </li> * </ul> * * <p>EL式の中では、予め次の変数が登録されており、セルの値ごとに変わります。</p> * <ul> * <li>rowIndex : 処理対象のセルの行のインデックス。0から始まります。</li> * <li>columnIndex : 処理対象のセルの列のインデックス。0から始まります。</li> * <li>rowNumber : 処理対象のセルの行番号。1から始まります。</li> * <li>columnNumber : 処理対象のセルの列番号。1から始まります。</li> * <li>columnAlpha : 処理対象のセルの列の名前。Aから始まります。</li> * <li>address : 処理対象のセルのアドレス。 A1 の形式です。</li> * <li>targetBean : 処理対象のプロパティが定義されているJavaBeanのオブジェクトです。</li> * <li>cell : 処理対象のセルのオブジェクトです。POIのクラス{@link Cell}のオブジェクトです。</li> * </ul> * * <p>さらに、よく使う関数が登録されており、呼び出すことができます。 * <br>関数の実態は、 {@link CustomFunctions}です。 * <br>名前空間{@code x}で登録されており、{@literal x:colTlAlpha(...)}のように呼び出すことができます。 * </p> * * <pre class="highlight"><code class="java"> * {@literal @XlsSheet(name="サンプル")} * public class SampleSheet { * * // 数式の指定 * {@literal @XlsHint(order=1)} * {@literal @XlsLabelledCell(label="更新日付", type=LabelledCellType.Right)} * {@literal @XlsFormula("TODAY()")} * private Date date; * * {@literal @XlsHint(order=2)} * {@literal @XlsHorizontalRecords(tableLabel="レコード", terminal=RecordTerminal.Border, overRecord=OverRecordOperate.Insert)} * private {@literal List<SampleRecord>} records; * } * * public class SampleRecord { * * // マッピングした位置情報 * private Map<String, Point> positions; * * {@literal @XlsColumn(columnName="名前")} * private String name; * * {@literal @XlsColumn(columnName="国語")} * private int kokugo; * * {@literal @XlsColumn(columnName="算数")} * private int sansu; * * // 数式の指定(変数、EL式を使用して指定) * {@literal @XlsColumn(columnName="合計")} * {@literal @XlsFormula(value="SUM(${x:colToAlpha(targetBean.kokugoColNum)}{rowNumber}:${x:colToAlpha(targetBean.sansuColNum)}{rowNumber})", primary=true)} * private int sum; * * // プロパティ「kokugo」の列番号を返す。 * public String getKokugoColNum() { * Point point = positions.get("kokugo"); * return point.y + 1; * * } * * // プロパティ「sansu」の列番号を返す。 * public String getSansuColNum() { * Point point = positions.get("sansu"); * return point.y + 1; * } * * } * </code></pre> * * @return Excelの数式を返す。(例. {@literal SUM(A{rowNumber},B{rowNumber})}) */ String value() default ""; /** * 数式を別メソッドで組み立てる場合に、そのメソッド名を指定します。 * <p>条件により数式を変更するような場合や、複雑な数式を組み立てる場合、数式を組み立てるメソッドを指定することができます。</p> * <p>メソッドの条件は次のようになります。</p> * <ul> * <li>定義位置は、プロパティが定義してあるJavaBeanのクラスと同じ箇所。</li> * <li>修飾子は、public/private/protected などなんでもよい。</li> * <li>引数は、指定しないか、または次の値が指定可能。順番は任意。 * <ul> * <li>セルのオブジェクト : {@link Cell}</li> * <li>シートのオブジェクト : {@link Sheet}</li> * <li>セルの座標 : {@link Point}。0から始まります。</li> * <li>システム設定 : {@link XlsMapperConfig}</li> * </ul> * </li> * <li>戻り値は、String型。 * <ul> * <li>nullまたは空文字を返すと、ブランクセルとして出力されます。</li> * </ul> * </li> * </ul> * * <pre class="highlight"><code class="java"> * {@literal @XlsSheet(name="サンプル")} * public class SampleSheet { * * // 数式のメソッドの指定 * {@literal @XlsHint(order=1)} * {@literal @XlsLabelledCell(label="更新日付", type=LabelledCellType.Right)} * {@literal @XlsFormula(methodName="getDateFormula")} * private Date date; * * {@literal @XlsHint(order=2)} * {@literal @XlsHorizontalRecords(tableLabel="レコード", terminal=RecordTerminal.Border, overRecord=OverRecordOperate.Insert)} * private {@literal List<SampleRecord>} records; * * // 数式を組み立てるメソッド * public String getDateFormula() { * return "TODAY()" * } * } * * public class SampleRecord { * * // マッピングした位置情報 * private Map<String, Point> positions; * * {@literal @XlsColumn(columnName="名前")} * private String name; * * {@literal @XlsColumn(columnName="国語")} * private int kokugo; * * {@literal @XlsColumn(columnName="算数")} * private int sansu; * * // 数式の指定(メソッドを指定) * {@literal @XlsColumn(columnName="合計")} * {@literal @XlsFormula(methodName="getSumFormula", primary=true)} * private int sum; * * // 数式を組み立てるメソッド * private String getSumFormula(Point point) { * * int rowNumber = point.y + 1; * String colKokugo = CellReference.convertNumToColString(positions.get("kokugo").y); * String colSansu = CellReference.convertNumToColString(positions.get("sansu").y); * * return String.format("SUM(%s%d:%s%d)", colKokugo, rowNumber, colSansu, rowNumber); * } * * } * </code></pre> * * @return JavaBeanに定義されているメソッド名を返す。 */ String methodName() default ""; /** * 出力する際に、値が設定されていても、数式を優先するかどうかを指定します。 * <p>出力するオブジェクトのプロパティに値が設定されている場合、アノテーション {@link XlsFormula} を指定していても、デフォルトでは値が出力されます。</p> * <p>数式を優先して出力する場合、 属性 {@literal primary=true} を指定すると数式が優先されます。 * <br>特に、プリミティブ型など初期値が入っている場合や、 アノテーション {@literal @XlsConverter(defaultValue="<初期値>")} で初期値を指定している場合には、注意が必要です。 * </p> * * <pre class="highlight"><code class="java"> * public class SampleRecord { * * // マッピングした位置情報 * private Map<String, Point> positions; * * {@literal @XlsColumn(columnName="名前")} * private String name; * * {@literal @XlsColumn(columnName="国語")} * private int kokugo; * * {@literal @XlsColumn(columnName="算数")} * private int sansu; * * // 数式の指定(数式を優先する場合) * {@literal @XlsColumn(columnName="合計")} * {@literal @XlsFormula(value="SUM(B{rowNumber}:C{rowNumber})", primary=true) * private int sum; * * } * </code></pre> * * @return 'true'の場合、数式の設定を優先します。 * 'false'の場合、値が設定されていると、その値が出力されます。 */ boolean primary() default false; }