Hatena::ブログ(Diary)

タツノオトシゴの日記

2018-06-30

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。

詳細

後日追加。

2017-09-23

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();
    }
}

2017-09-01

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>

2016-12-11

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();

    }
}

2016-02-20

Javadocのコードにシンタックスハイライトを適用する

Javadocで、<pre>〜</pre>タグを使って、サンプルなどのコードを記述する場合、デフォルトのままだと、通常の文章と区別が付きづらくわかりづらい。

そのため、シンタックスハイライト適用したかったので、試したことをメモしておく。

今回は、mavenhighlight.jsを使う。

実際のサンプルは下記を参考。

Javadocの記述

不要なpreタグにシンタクスハイライトが当たらないように、<pre class="highlight"><code class="java">〜</code></pre>のように、preタグにもclass属性を適用する。

/*
 * クラスの説明
 *
 * <pre class="highlight"><code class="java">
 * // コードの記述
 * ・・・
 * </code></pre>
 */

stylesheet.cssの記述

javadocの標準のCSSの場合、preタグには既にCSS適用されているため、文字の大きさなど少しカスタマイズしたものを利用する。

通常に生成したJavadoc中の「stylesheet.css」の最後に下記の内容を追加して、「src/javadoc」以下に格納しておく。

〜省略
/**
 * 追加したCSS
 */
pre.highlight {
    margin-top: 10px;
    margin-bottom: 10px;
    font-size: 80%;
    border: solid 1px #9eadc0;
    background-color: #f9f9f9;
}

pom.xmlの記述

Javadocのレポートの記述の<header>タグ内に、シンタックスハイライト適用するためのJavaScriptを記述する。

<header>タグは、Javadocの見出しを設定するものなので、JavaScriptだけ記述すると、HTMLを生成した場合余分なスペースが表示されるため、ライブラリ名などを出力するように定義しておくようにする。

<reporting>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>2.10.3</version>
            <configuration>
                <source>${java.version}</source>
                <encoding>UTF-8</encoding>
                <charset>UTF-8</charset>
                <docencoding>UTF-8</docencoding>
                <locale>ja_JP</locale>
                <links>
                    <link>http://docs.oracle.com/javase/jp/7/api/</link>
                </links>
                <!-- 少しカスタマイズしたCSSを適用する。 -->
                <stylesheetfile>${basedir}/src/javadoc/stylesheet.css</stylesheetfile>

                <!-- Javadocの見出し出力定義にJavaScriptを記述する。 -->
                <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>
        </plugin>
    </plugins>
<reporting>