タツノオトシゴのブログ

主にJavaに関するものです。

Lombokで生成したメソッドをJavaDocに反映する方法

Lombok を公開するライブラリとして使用したとき、デフォルトではJavaDocに反映されないため、その手順を紹介します。

大まかな手順としては、Delombok により静的なソースを生成し、そのソースに対してJavaDocを作成するというものです。

Delombokとは?

Lombokによりコンパイル時にAST(Abstract Syntax Tree: 抽象構文木)の構造を動的に変更します。

Delombokはその逆で、動的にメソッドなどを追加するのではなく静的に変更したソースを出力する機能です。

pom.xmlの設定

Delombokのプラグインを追加します。

JavaDocプラグインには、で、生成されたソースを参照するに指定します。

それぞれ、テスト用ソースに対しても設定します。

<project>
    <build>
        <plugins>
            <!-- Delombokによるソースを出力します -->
            <plugin>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok-maven-plugin</artifactId>
                <version>1.18.12.0</version>
                <executions>
                    <!-- 通常のソースに対して生成します -->
                    <execution>
                        <id>delombok</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>delombok</goal>
                        </goals>
                        <configuration>
                            <addOutputDirectory>false</addOutputDirectory>
                            <sourceDirectory>src/main/java</sourceDirectory>
                            <encoding>${source.encode}</encoding>
                        </configuration>
                    </execution>
                    <!-- テスト用ソースに対して生成します -->
                    <execution>
                        <id>test-delombok</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>testDelombok</goal>
                        </goals>
                        <configuration>
                            <addOutputDirectory>false</addOutputDirectory>
                            <sourceDirectory>src/test/java</sourceDirectory>
                            <encoding>${source.encode}</encoding>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.2.0</version>
                <reportSets>
                    <!-- Delombokにより生成したソースに対してJavaDocを生成します -->
                    <reportSet>
                        <id>html</id>
                        <configuration>
                            <sourcepath>target/generated-sources/delombok</sourcepath>
                        </configuration>
                        <reports>
                            <report>javadoc</report>
                        </reports>
                    </reportSet>
                    <reportSet>
                        <id>test-html</id>
                        <configuration>
                            <show>private</show>
                            <sourcepath>target/generated-test-sources/delombok</sourcepath>
                        </configuration>
                        <reports>
                            <report>test-javadoc</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
        </plugins>
    </reporting>
</project>

Delombokを使う際の注意事項

Delombokによる生成されたソースについて注意事項として、無駄な空行が入ってしまったり、逆に空行がなくなったっりします。

JavaDocを生成する差には空行は関係ないので気にはしなくてもよいと思います。

Delombok前のソース
package com.github.mygreen.sqltemplate;

import lombok.Getter;
import lombok.NonNull;

/**
 * SQLテンプレートのパラメータをJavaBean として渡すときのSQLコンテキスト。
 * SQLテンプレート中では、JavaBeanのプロパティ名で参照できます。
 *
 *
 */
public class BeanPropertySqlContext extends SqlContext {

    /**
     * JavaBeanのインスタンス。
     */
    @Getter
    private final Object value;

}
Delombokにより生成したソース
// Generated by delombok at Sun Jul 26 23:32:11 JST 2020
package com.github.mygreen.sqltemplate;

/**
 * SQLテンプレートのパラメータをJavaBean として渡すときのSQLコンテキスト。

 * SQLテンプレート中では、JavaBeanのプロパティ名で参照できます。

 *

 *

 */
public class BeanPropertySqlContext extends SqlContext {
    /**
     * JavaBeanのインスタンス。
     */
    private final Object value;

    /**
     * JavaBeanを指定するコンストラクタ。

     * @param object SQLテンプレート中のパラメータとして渡すJavaBeanのインスタンス
     */
    public BeanPropertySqlContext(@NonNull final Object object) {
        super();
        if (object == null) {
            throw new java.lang.NullPointerException("object is marked non-null but is null");
        }
        this.value = object;
    }

    /**
     * JavaBeanのインスタンス。
     */
    @java.lang.SuppressWarnings("all")
    @lombok.Generated
    public Object getValue() {
        return this.value;
    }
}

SceneBuilder 11で日本語が文字化けする事象の修正方法

SceneBuilder11のインストーラ形式を入れて、起動するとメニューなどが文字化けする。

ver10のときから直っていないので、11用の直し方を説明する。

moondream.hatenablog.com

1.アプリ本体「scenebuilder-11.0.0-all.jar」の取得

SceneBuilder11からインストール個所を選択できなくなり、インストール個所は「C:\Program Files\SceneBuilder\all」と固定となった。

Program Files以下だと権限の都合上エラーとなるため、「scenebuilder-11.0.0-all.jar」を作業用のディレクトリ「c:\temp」以下にコピーする。


2.コマンドプロンプトで以下を実行し、元のjarをバックアップする。

> copy scenebuilder-11.0.0-all.jar scenebuilder-11.0.0-all.jar.org


3.プロパティファイル「SceneBuilderApp_ja.properties」を抽出する。
Java SDKにパスを通しておく必要がある。

> %JAVA_HOME%\bin\jar xvf scenebuilder-11.0.0-all.jar com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties


4.文字化けしていないプロパティファイルをGitHubから取得して、抽出したファイルに上書きする。
※プロパティファイル「SceneBuilderApp_ja.properties」は、UTF-8で保存する。

  • ダウンロードしたファイルを格納するフォルダ
C:\temp\com\oracle\javafx\scenebuilder\app\i18n\


5.ダウンロードしたプロパティファイルをjarに取り込む。

> %JAVA_HOME%\bin\jar uf scenebuilder-11.0.0-all.jar com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties


6.更新した「scenebuilder-11.0.0-all.jar」を元のインストールした箇所にコピーする。

 C:\Program Files\SceneBuilder\app\scenebuilder-11.0.0-all.jar


7.SceneBuilderを起動してみると、無事文字化けが治っていることが確認できます。

f:id:tatsu-no-toshigo:20190702001412p:plain
SceneBuilder11文字化け修正後

SceneBuilder 10で日本語が文字化けする事象の修正方法

SceneBuilderのインストーラ形式を入れて、起動するとメニューなどが文字化けする。

対処方法として知られているのが、メニューを英語で表示し対応するというもの。
qiita.com

だけど、やっぱし、日本語を正しく表示したい。ということで、調査結果とその対応方法を紹介します。 

【文字化けしたメニュー】
f:id:tatsu-no-toshigo:20181031220619p:plain

なぜ文字化けするのか?

まず、SceneBuilder本体のjarの中身を見てみます。

「<インストールフォルダ>\SceneBuilder\app\dist.jar」 が本体です。

jarを解凍してできた中身の「com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties」が画面などのプロパティファイルになります。
マルチバイトはASCIIコードに変換されているので当然だよね。

# -----------------------------------------------------------------------------
# Generic labels
# -----------------------------------------------------------------------------
label.ok = OK
label.cancel = \u00e5\ufffd\u2013\u00e6\u00b6\u02c6
label.close = \u00e9\u2013\u2030\u00e3\ufffd\u02dc\u00e3\u201a\u2039
label.delete = \u00e5\u2030\u0160\u00e9\u2122\u00a4
label.copy = \u00e3\u201a\u00b3\u00e3\u0192\u201d\u00e3\u0192\u00bc


と思い、プロパティエディタで開いてみると、文字化けしていた。これが原因ですね。
 f:id:tatsu-no-toshigo:20181031222621p:plain

 

修正方法

元のファイルをリポジトリからとってきてとか面倒なので、「Executable Jar」版からプロパティファイルを抽出し、入れ変えて試します。

1. まず、Executable Jarである「scenebuilder-10.0.0-all.jar」をSceneBuilderのサイトからダウンロードしてきます。

gluonhq.com

2.ダウンロードしてきたファイル「scenebuilder-10.0.0-all.jar」を、SceneBuilderのインストールしたフォルダの「SceneBuilder/app/dist.jar」と同じディレクトリに配置します。

3. コマンドプロンプトで以下を実行し、jarからプロパティファイルを抽出し、dist.jarに取り込みます。

dist.jarが存在するフォルダに移動します。(環境によって異なります)

> C:\Java\SceneBuilder\app

dist.jarをコピーして、バックアップします。

> copy dist.jar dist.jar.org

scenebuilder-10.0.0-all.jarから、プロパティファイル「SceneBuilderApp_ja.properties」を抽出します。

> jar xvf scenebuilder-10.0.0-all.jar com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties

抽出したプロパティファイル「SceneBuilderApp_ja.properties」をdist.jarに取り込みます。

> jar uf dist.jar com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp_ja.properties

さあ、これでSceneBuilderを起動してみましょう。
f:id:tatsu-no-toshigo:20181031224123p:plain

メニューなどが正しく日本語で表示されましたね。

さあ、良いJavaFXライフを!!

何故こんなことが起こったのか!?

ビルド環境がおかしいとしか言いようがないですね。
予想ですが、元々、ファイルをUTF-8で管理しており、それをnative2asciiで変換するときに、Shift_JISで読み込んで変換してしまったと考えられます。

というのは、SDKのプロパティファイル「SceneBuilderKit_ja.properties」はUTF-8の日本語だからです。
でもこちらは文字化けしていない。

あと、中国語のプロパティファイル「SceneBuilderApp_zh_CN.properties」もあるのですが、こちらは何故か文字化けしていませんでした。

補足「Javaをインストールしていない場合」

1. jarファイルは、zipなので拡張子を変えてWindowsエクスプローラで直接入れ替えることもできます。

2 EclipsePleiadesがある場合は、Javaのフォルダにパスを通します。

set JAVA_HOME=C:\pleiades-2018-09\java\8
set PATH=%PATH%;%JAVA_HOME%\bin

XlsMapper 2.0リリース

遂に、やっと、XlsMepper 2.0がリリースできました。
v1.6のリリースから約1年半かかりました。
その間、転職したり、色々とあり、遅々として作業が進まず、なんども萎えました。

v1.6 ⇒ v2.0 とメジャーバージョンが上がったことによる影響は大きいです。
互換性がなくなったアノテーションは多々あります。

詳細は、リリースノートを参照してください。
16. リリースノート — XlsMapper 2.0 ドキュメント

概要

  1. 前提として、Java8になります。
    • Java9/10は未確認のため後日確認します。
  2. 前提として、最新版のPOI-3.17のみをサポートします。
    • POIの以前のバージョンは未サポートとなります。
  3. 新しいマッピング用のアノテーションとして、@XlsArrayCells/@XlsLabelledArrayCells が追加になっています。
  4. 書き込み時の設定を別アノテーションとして分離しました。
    • @XlsHorizontalRecords(overRecord=..., remainedRecord=...) を、@XlsRecordOption で指定するようにしました。
  5. 一部のクラス名など変更になっています。例. XlsConfig ⇒ Configuration。

詳細

後日追加。

Super CSV Annotation v2.1のリリース

最近、処理速度が速いと言われていた univocity-parsers に対して、Super CSV Annotationがカラム番号を指定しないでラベル名でマッピングできないとかという記事を見たので、Super Csv Annotation v2.1 として、機能追加してリリースした。

あと、univocity-parsersは固定長のカラムもできるので、その機能も追加した。

詳細は、マニュアルを参照。

固定長カラムの対応


固定長のカラムとは、「書き込み時は任意の長さになるよう空白などを詰め、読み込み時は書き込み時に詰めた空白などの文字を除去する」ことだと定義して、それを実現する変換用のアノテーションを合成したアノテーション @CsvFixedSize を付与します。

  • 属性sizeで固定長のサイズを指定します。
  • 属性rightAlignで、右寄せするかどうか指定します。falseの場合は左寄席せになります。
  • 属性padCharで、パディングする文字を指定することができます。
    • デフォルトは半角空白で、全角空白なども指定できます。

ヘッダーもカラムの設定と同様に固定長にするには、@CsvBean(headerMapper=FixedSizeHeaderMapper.class) と指定します。

import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;
import com.github.mygreen.supercsv.annotation.CsvFixedSize;
import com.github.mygreen.supercsv.builder.FixedSizeHeaderMapper;

// ヘッダーも固定長にするには、「headerMapper=FixedSizeHeaderMapper.class」を指定します。
@CsvBean(header=true, headerMapper=FixedSizeHeaderMapper.class)
public class SampleCsv {

    // 右詰めする
    @CsvColumn(number=1)
    @CsvFixedSize(size=10, rightAlign=true)
    private int id;

    // パディング文字を全角空白にする。
    // 全角を入力する前提としたカラムと想定し、さらに @CsvFullChar で半角を全角に変換します。
    @CsvColumn(number=2, label="氏名")
    @CsvFixedSize(size=20, padChar=' ')
    @CsvFullChar
    private String name;

    // パディング文字をアンダースコア(_)にする。
    @CsvColumn(number=3, label="生年月日")
    @CsvFixedSize(length=10, padChar='_')
    @CsvDateTimeFormat(pattern="uuuu-MM-dd")
    private LocalDate birthday;

    // 文字サイズを超えている場合は、切り出す。
    @CsvColumn(number=4, label="備考")
    @CsvFixedSize(size=20, chopped=true)
    private String comment;

    // getter, setterは省略
}

固定長としてパディングする場合、サイズのカウント方法の考え方は、複数あります。
例えば、半角は1文字、全角は2文字分として換算する。 または、文字のバイト数で換算することもあります。

そこで、属性 paddingProcessor でパディング処理の実装クラスを切り替えることができます。
標準では以下の実装が提供されています。

  • SimplePaddingProcessor - 文字の種別にかかわらず1文字としてカウントしてパディングします。
  • CharWidthPaddingProcessor - 文字の幅(半角は1文字、全角は2文字)によってカウントしてパディングします。デフォルトの実装です。
  • ByteSizePaddingProcessor - バイト数によってカウントしてパディングします。
    • バイト数で換算する場合、文字コードに依存するため、文字コードに対応したサブクラスを指定する必要があります。
import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;
import com.github.mygreen.supercsv.annotation.CsvFixedSize;
import com.github.mygreen.supercsv.builder.FixedSizeHeaderMapper;
import com.github.mygreen.supercsv.cellprocessor.conversion.ByteSizePaddingProcessor;
import com.github.mygreen.supercsv.cellprocessor.conversion.CharWidthPaddingProcessor;
import com.github.mygreen.supercsv.cellprocessor.conversion.SimplePaddingProcessor;

@CsvBean(header=true, headerMapper=FixedSizeHeaderMapper.class)
public class SampleCsv {

    // 文字の種別にかかわらず1文字としてカウントしてパディングします。
    @CsvColumn(number=1)
    @CsvFixedSize(size=10, paddingProcessor=SimplePaddingProcessor.class)
    private int id;

    // 文字の幅(半角は1文字、全角は2文字)によってカウントしてパディングします。
    @CsvColumn(number=2)
    @CsvFixedSize(size=20, paddingProcessor=CharWidthPaddingProcessor.class)
    private String name;

    // バイト数によってカウントしてパディングします。
    @CsvColumn(number=3)
    @CsvFixedSize(size=20, paddingProcessor=ByteSizePaddingProcessor.Windows31j.class)
    private String comment;

    // 以下、省略
}

ラベルによるカラムのマッピング

Beanの定義は、ラベルのみによるマッピングは、アノテーション @CsvColumn の属性 number を省略します。
従来の属性 number でカラム番号で指定することもできます。

import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;

@CsvBean(header=true, validateHeader=true)
public class SampleBean {

    // ラベルがフィールド名
    @CsvColumn
    private int no;

    // 従来のカラム番号を指定
    @CsvColumn(number=2)
    private String name;

    // ラベルだけ指定
    @CsvColumn(label="生年月日")
    @CsvDateTimeFormat(pattern="uuuu/MM/dd")
    private LocalDate birthday;

    // カラム番号とラベルの両方を指定
    @CsvColumn(number=4, label="備考")
    private String comment;

    // getter/setterの定義は省略

}

ラベルによるマッピングの定義で読み込むには、LazyCsvAnnotationBeanReader を使用します。

  • 全件読み込む場合の使用方法は、基本的に既存の CsvAnnotationBeanReader と変わりません。
  • 1件ずつ読み込む場合は、メソッド LazyCsvAnnotationBeanReader#init() を呼んでマッピング情報を初期化します。
import com.github.mygreen.supercsv.io.LazyCsvAnnotationBeanReader;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class Sample {

    // 全レコードを一度に読み込む場合
    public void sampleReadAll() {

        LazyCsvAnnotationBeanReader<SampleBean> csvReader = new LazyCsvAnnotationBeanReader<>(
                SampleBean.class,
                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        List<SampleBean> list = csvReader.readAll();

        csvReader.close();
    }

    // レコードを1件ずつ読み込む場合
    public void sampleRead() {

        LazyCsvAnnotationBeanReader<SampleBean> csvReader = new LazyCsvAnnotationBeanReader<>(
                SampleBean.class,
                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // ヘッダー行を読み込み初期化します
        csvReader.init();

        List<SampleBean> list = new ArrayList<>();

        SampleBean record = null;
        while((record = csvReader.read()) != null) {
            list.add(record);
        }

        csvReader.close();
    }
}

ファイルに書き出すときには、LazyCsvAnnotationBeanWriterを使用します。

  • 全件読み出す場合の使用方法は、基本的に既存の CsvAnnotationBeanWriter と変わりません。
  • 1件ずつ書き出す場合は、メソッド CsvAnnotationBeanWriter#init() を呼んでマッピング情報を初期化します。
    • カラムの出力順は、フィールド名の昇順になります。
    • 任意の順序でカラムを出力したい場合、メソッド #init("見出し1","見出し2",...) でヘッダー情報を直接指定し、初期化します。
import com.github.mygreen.supercsv.io.LazyCsvAnnotationBeanWriter;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.supercsv.prefs.CsvPreference;

public class Sample {

    // 全レコードを一度に書き込む場合
    public void sampleWriteAll() {

        LazyCsvAnnotationBeanWriter<UserCsv> csvWriter = new LazyCsvAnnotationBeanWriter<>(
                SampleCsv.class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // 書き出し用のデータの作成
        List<SampleCsv> list = new ArrayList<>();

        SampleCsv record1 = new SampleCsv();
        record1.setNo(1);
        record1.setName("山田太郎");
        record1.setBirthday(LocalDate.of(2000, 10, 1));
        record1.setComment("あいうえお");
        liad.add(record1);

        SampleCsv record2 = new SampleCsv();
        record2.setNo(2);
        record2.setName("鈴木次郎");
        record2.setBirthday(LocalDate.of(2012, 1, 2));
        record2.setComment(null);
        liad.add(record2);

        // ヘッダー行と全レコードデータの書き出し
        csvWriter.writeAll(list);

        csvWriter.close();
    }

    // レコードを1件ずつ書き出す場合
    public void sampleWrite() {

        LazyCsvAnnotationBeanWriter<SampleCsv> csvWriter = new LazyCsvAnnotationBeanWriter<>(
                UserCsv.class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // 初期化を行います
        csvWriter.init();

        // ヘッダー行の書き出し
        csvWriter.writeHeader();

        // レコードのデータの書き出し
        SampleCsv record1 = new UserCsv();
        record1.setNo(1);
        record1.setName("山田太郎");
        record1.setBirthday(LocalDate.of(2000, 10, 1));
        record1.setComment("あいうえお");
        csvWriter.write(record1);

        SampleCsv record2 = new UserCsv();
        record2.setNo(2);
        record2.setName("鈴木次郎");
        record2.setBirthday(LocalDate.of(2012, 1, 2));
        record2.setComment(null);
        csvWriter.write(record2);

        csvWrier.flush();
        csvWrier.close();

    }

    // 全レコードを一度に書き込む場合  - 任意のカラムの順番で出力
    public void sampleWriteAll() {

        LazyCsvAnnotationBeanWriter<UserCsv> csvWriter = new LazyCsvAnnotationBeanWriter<>(
                SampleCsv.class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // カラムの順番指定して初期化します。
        csvWriter.init("no", "name", "生年月日", "備考");

        // 書き出し用のデータの作成
        List<SampleCsv> list = new ArrayList<>();
        //・・・省略

        // ヘッダー行と全レコードデータの書き出し
        csvWriter.writeAll(list);

        csvWriter.close();
    }
}

Java 1.8.0_121以降でJavaScriptを含むJavadocを生成する(maven)

JDK 1.8.0_121以降から、JavadocJavaScriptを含むJavaScriptを含む場合、オプション「--allow-script-in-comments」が必要になり、次のようなエラーがでるようになりました。

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.083 s
[INFO] Finished at: 2017-09-01T23:49:17+09:00
[INFO] Final Memory: 17M/226M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-javadoc-plugin:2.10.4:javadoc (default-cli) on project XXX: An error has occurred in JavaDocs report generation: 
[ERROR] Exit code: 1 - javadoc: エラー - -headerの引数にJavaScriptが含まれています。
[ERROR] --allow-script-in-commentsを使用して、JavaScriptの使用を許可してください。
[ERROR] 
[ERROR] Command line was: C:\usr\java\jdk1.8.0_121\jre\..\bin\javadoc.exe -J-Xmx512m @options @packages
・・・

対応として、エラーメッセージの通りに指定します。
maven-javadoc-pluginを使用している場合は次のように指定します。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>2.10.4</version>
    <configuration>
        <nohelp>true</nohelp>
        <charset>UTF-8</charset>
        <encoding>UTF-8</encoding>
        <docencoding>UTF-8</docencoding>
        <locale>ja_JP</locale>
        <maxmemory>512m</maxmemory>
        <links>
            <link>https://docs.oracle.com/javase/jp/8/docs/api/</link>
            <link>http://super-csv.github.io/super-csv/apidocs/</link>
        </links>
        <stylesheetfile>${basedir}/src/main/javadoc/stylesheet.css</stylesheetfile>
        <docfilessubdirs>true</docfilessubdirs>
        
        <!-- スクリプトの埋め込む記述を許可する指定 -->
        <additionalparam>--allow-script-in-comments</additionalparam>
        
        <!-- ヘッダーにスクリプトを埋め込む記述 -->
        <header>${project.name} ${project.version}
<![CDATA[
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/default.min.css">
<script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
<script>
$(document).ready(function() {
$('pre.highlight code').each(function(i, block) {
hljs.highlightBlock(block);
});
});
</script>
]]>
        </header>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>jar</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Super CSV Annotation 2.0のリリース

久しぶりの更新です。

JavaCSVファイルを扱うためのライブラリ Super CSV に対してアノテーション機能を付け加えた独自のライブラリ Super CSV Annotation のver2.0をリリースしました。

主な変更点

作り直したため、互換性はなくなりましたが、基本的な使用方法は変わりません。
パッケージ名、クラス名は大きく変わっています。


機能的には変わりませんが、上記の機能に対する独自の拡張やアノテーションの追加が圧倒的に容易にできるようになりました。


機能一覧は、マニュアルの目次 を見ていただければわかります。


また、Bean Validationのように属性「groups」でアノテーションを適用するケースを絞り込めるようにしました。
よく使う、読み込み、書き込み時の区別は、別な属性「cases」でできるようにしました。


さらに、Bean Validationの使い勝手の悪い、順番の指定ができるようにしています。
順番や適用するケースが指定できることで、かなり柔軟な加工ができるようになりました。
まあ、現在、仕様策定中の Bean Validation 2.0 からは順番の指定ができるようになるそうですが。


機能的には、別なCSVライブラリ uniVocity を参考にしました。

基本的な使い方

マニュアルの内容そのままです。

pom.xmlには下記を追加。

<dependency>
    <groupId>com.github.mygreen</groupId>
    <artifactId>super-csv-annotation</artifactId>
    <version>2.0</version>
</dependency>
Beanの定義

基本的なアノテーション @CsvBean、@CsvColumn は変わりません。

ただし、@CsvColumnの番号指定は index→numberに代わり、1から指定するようにしました。
カラム数が多くなるとこれは設計書からBeanを定義するとき、個人的によくずれて定義してしまったためです。

import java.time.LocalDate;

import com.github.mygreen.supercsv.annotation.CsvBean;
import com.github.mygreen.supercsv.annotation.CsvColumn;
import com.github.mygreen.supercsv.annotation.constraint.CsvNumberMin;
import com.github.mygreen.supercsv.annotation.constraint.CsvRequire;
import com.github.mygreen.supercsv.annotation.constraint.CsvUnique;
import com.github.mygreen.supercsv.annotation.conversion.CsvDefaultValue;
import com.github.mygreen.supercsv.annotation.conversion.CsvNullConvert;
import com.github.mygreen.supercsv.annotation.format.CsvDateTimeFormat;
import com.github.mygreen.supercsv.annotation.format.CsvNumberFormat;
import com.github.mygreen.supercsv.builder.BuildCase;

@CsvBean
public class SampleCsv {

    @CsvColumn(number=1, label="ID")
    @CsvRequire                        // 必須チェックを行う
    @CsvUnique(order=1)                // 全レコード内で値がユニークかチェックする(順番指定)
    @CsvNumberMin(value="0", order=2)  // 最小値かどかチェックする(順番指定)
    private Integer id;

    @CsvColumn(number=2, label="名前")
    private String name;

    @CsvColumn(number=3, label="誕生日")
    @CsvDateTimeFormat(pattern="yyyy年MM月dd日")   // 日時の書式を指定する
    private LocalDate birthday;

    @CsvColumn(number=4, label="給料")
    @CsvNumberFormat(pattern="#,###0")                    // 数値の書式を指定する
    @CsvDefaultValue(value="N/A", cases=BuildCase.Write)  // 書き込み時に値がnull(空)の場合、「N/A」として出力します。
    @CsvNullConvert(value="N/A", cases=BuildCase.Read)    // 読み込み時に値が「N/A」のとき、nullとして読み込みます。
    private Integer salary;

    // getter/setterは省略

}
読み書きの方法

読み書きするためのクラス CsvAnnotationBeanReader/CsvAnnotationBeanWriterは大きく変わりません。
ただし、一度に読み書きする readAll()/writeAll() メソッドを追加しました。

import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
import com.github.mygreen.supercsv.io.CsvAnnotationBeanWriter;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.supercsv.prefs.CsvPreference;

public class Sample {

    // 全レコードを一度に読み込む場合
    public void sampleReadAll() {

        CsvAnnotationBeanReader<SampleCsv > csvReader = new CsvAnnotationBeanReader<>(
                SampleCsv .class,
                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        List<SampleCsv > list = csvReader.readAll();

        csvReader.close();
    }

    // レコードを1件ずつ読み込む場合
    public void sampleRead() {

        CsvAnnotationBeanReader<SampleCsv > csvReader = new CsvAnnotationBeanReader<>(
                SampleCsv .class,
                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        List<SampleCsv > list = new ArrayList<>();

        // ヘッダー行の読み込み
        String headers[] = csvReader.getHeader(true);

        UserCsv record = null;
        while((record = csvReader.read()) != null) {
            list.add(record);
        }

        csvReader.close();
    }

    // 全レコードを一度に書き込む場合
    public void sampleWriteAll() {

        CsvAnnotationBeanWriter<UserCsv> csvWriter = new CsvAnnotationBeanWriter<>(
                UserCsv.class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // 書き込み用のデータの作成
        List<SampleCsv > list = new ArrayList<>();
        SampleCsv record1 = new SampleCsv ();
        record1.setNo(1);
        record1.setName("山田太郎");
        liad.add(record1);

        SampleCsv record2 = new SampleCsv ();
        record2.setNo(2);
        record2.setName("鈴木次郎");
        liad.add(record2);

        // ヘッダー行と全レコードデータの書き込み
        csvWriter.writeAll(list);

        csvWriter.close();
    }

    // レコードを1件ずつ読み込む場合
    public void sampleWrite() {

        CsvAnnotationBeanWriter<SampleCsv > csvWriter = new CsvAnnotationBeanWriter<>(
                SampleCsv .class,
                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
                CsvPreference.STANDARD_PREFERENCE);

        // ヘッダー行の書き込み
        csvWriter.writeHeader();

        // レコードのデータの書き込み
        SampleCsv record1 = new SampleCsv ();
        record1.setNo(1);
        record1.setName("山田太郎");
        csvWriter.write(record1);

        SampleCsv record2 = new SampleCsv ();
        record2.setNo(2);
        record2.setName("鈴木次郎");
        csvWriter.write(record2);

        csvWrier.flush();
        csvWrier.close();

    }
}