覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Failsafeによるリトライ処理 (2)

 前回エントリで FailSafe によるリトライ処理について書きました。

FailSafeは前回書いた内容よりももっと色々制御できるので、それらを紹介する内容のエントリになります。
基本的には上記の前回エントリの内容がベースにあります。


前回エントリでは、基本的にRetryPolicyを以下のように指定しました。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class)  // リトライ対象とする例外クラス
                .withDelay(2, TimeUnit.SECONDS)         // リトライ時の間隔
                .withMaxRetries(3);                     // 最大リトライ回数

これは、

  • 対象処理実行時にBusinessLogicExceptionがスローされたらリトライする
  • リトライする際に2秒のdelayを入れる
  • 最大リトライ回数は3回

という内容になります。

この内容をいじって、リトライの方法をもう少し制御します


複数の例外クラスをリトライ対象にする

BusinessLogicException以外にもFatalException がスローされる可能性があり、
そのいずれもリトライ対象としたい場合は以下のように記述できます。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class) // リトライ対象とする例外クラス1
                .retryOn(FatalException.class)         // リトライ対象とする例外クラス2
                .withDelay(2, TimeUnit.SECONDS)         // リトライ時の間隔
                .withMaxRetries(3);                     // 最大リトライ回数

または、

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class, FatalException.class) // リトライ対象とする例外クラス
                .withDelay(2, TimeUnit.SECONDS)         // リトライ時の間隔
                .withMaxRetries(3);                     // 最大リトライ回数

このいずれかの記述をすることで、BusinessLogicException, FatalExceptionのいずれがスローされてもリトライされます。


リトライ対象の例外クラスを instanceofでチェックする

リトライ対象とする例外クラスをinstanceofを使って判定することも可能です。
以下のように記述します。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(failure -> failure instanceof BusinessLogicException) // リトライ対象とする例外をinstanceofでチェック
                .withDelay(2, TimeUnit.SECONDS)         // リトライ時の間隔
                .withMaxRetries(3);                     // 最大リトライ回数


リトライ時の遅延をランダムに変化させる

リトライ時の遅延を withDelay で指定しますが、これで指定した時間きっちり遅延させた上で次の再試行が行われます。
この遅延をランダムに変化させたい場合はwithJitterメソッドを利用します。

以下のように記述します。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class)  // リトライ対象とする例外クラス
                .withDelay(2, TimeUnit.SECONDS)         // リトライ時の間隔
                .withJitter(500, TimeUnit.MILLISECONDS) // Delayをランダムに変化させる値
                .withMaxRetries(3);                     // 最大リトライ回数

上記例でいくと、2秒にランダムで-500〜+500ミリ秒 を 加算した値が遅延時間になります。


リトライのたびに遅延時間を伸ばす

リトライするたびに遅延時間を伸ばしたいというケースがあると思います。
その場合、withBackoffメソッドを利用します。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class) // リトライ対象とする例外クラス
                .withBackoff(1, 10, TimeUnit.SECONDS)  // 遅延時間は最小値1秒, 最大値10秒
                .withMaxRetries(10);                   // 最大リトライ回数

上記の例の場合は、最初の遅延時間は1秒で最大の遅延時間は10秒です。
リトライのたびに、1, 2, 4, 8, 10, 10 ・・・(秒) という遅延をするようになります。
(初期値の遅延時間を2倍していって、最大値になったら遅延時間を伸ばすのは打ち止め というような動きになります)


上記例では遅延を伸ばすための係数が自動的に2 になっているので、2倍されていく動きになっていますが、
この係数を自分で指定したい場合は、withBackoff の第4引数で指定可能です。
以下例では係数は 3 となっています。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class)    // リトライ対象とする例外クラス
                .withBackoff(1, 10, TimeUnit.SECONDS, 3)  // 遅延時間を伸ばすための係数は3
                .withMaxRetries(10);                      // 最大リトライ回数

このケースでは、リトライのたびに、1, 3, 9, 10, 10, 10 ・・・(秒) という遅延をするようになります。


実行する処理の戻り値でリトライを判定する

これまで実行対象の処理が例外をスローした場合にリトライするようにしていましたが、
対象の処理が戻り値を返す場合にその戻り値でリトライの有無を判定することも可能です。
これを実現したい場合は、retryIfメソッドを利用します。

例えば戻り値がnullの場合はリトライするとしたい場合は、以下のようになります。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryIf(result -> result == null)  // 戻り値がnullの場合はリトライ
                .withDelay(2, TimeUnit.SECONDS)     // リトライ時の間隔
                .withMaxRetries(3);                 // 最大リトライ回数


または以下のようにretryWhenというメソッドでも同様のことが実現可能です

RetryPolicy retryPolicy = new RetryPolicy()
                .retryWhen(null)                 // 戻り値がnullの場合はリトライ
                .withDelay(2, TimeUnit.SECONDS)  // リトライ時の間隔
                .withMaxRetries(3);              // 最大リトライ回数


ただし、これらの戻り値での判定をするRetryPolicyを利用する場合は、 実際の処理実行においてrunメソッドで使うのでなく

Failsafe.with(retryPolicy)
        .run(() -> doSomething());


getメソッド(戻り値を取得する方法)を使う必要がありそうです。

Failsafe.with(retryPolicy)
        .get(() -> doSomething());


runメソッドだと挙動が意図しないものになるように思われます。


特定の例外がスローされたらリトライを中止する

対象の処理を実行している中で特定の例外がスローされたら場合はリトライを中止させたい、
そのような場合は、abortOnメソッドでリトライ中止対象の例外クラスを指定することで実現できます。

RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class) // リトライ対象とする例外クラス
                .abortOn(FatalException.class)         // この例外が発生したらリトライを中止する
                .withDelay(2, TimeUnit.SECONDS)         // リトライ時の間隔
                .withMaxRetries(3);                     // 最大リトライ回数

上記の例では、BusinessLogicExceptionはリトライ対象ですが、FatalExceptionが発生するとリトライが中止されます。


まとめ

RetryPolicyの内容を色々といじることで、リトライの方法を細かく制御できることがわかりました。



関連エントリ

Failsafeによるリトライ処理

業務系プログラムにおいて、リトライ処理は必須だと思いますが、
Javaで単純にリトライ処理を書こうとすると、泥臭い感じのプログラムになってしまいます。

FailSafeというライブラリを利用することで、リトライ処理を整理した形で記述することが可能です。

FailSafeは、リトライ以外のエラーハンドリング処理も提供しています)


リトライ処理のよくある要件

リトライ処理(再試行時)の要件としてあがってくることがある内容に以下のようなものがあります。

  • リトライ対象とするエラーや失敗条件を指定したい
  • リトライ回数を指定したい
  • リトライ時にディレイをかけたい(遅延させたい)
  • リトライ時の遅延時間をリトライのたびに伸ばしたい
  • リトライ時の遅延時間を一定範囲でランダムな値にしたい(ゆらぎを入れたい)
  • リトライ中であることをハンドリングしたい(例えばログに出したい)
  • 処理成功と失敗で処理分岐したい
  • リトライ処理が全て失敗した場合の戻り値を指定したい
  • リトライ処理の対象から除外するエラー条件を指定したい

etc...

このエントリで全てを紹介するわけではありませんが、FailSafeは上記の内容などを綺麗に扱ってくれます。


利用準備

Mavenの場合、pom.xml に以下の依存関係を追記してください。(今回はバージョン 1.1.0 を使用しました)

<dependency>
    <groupId>net.jodah</groupId>
    <artifactId>failsafe</artifactId>
    <version>1.1.0</version>
</dependency>


リトライのサンプル

FailSafeとは直接の関係性はありませんが、今回のサンプルコードで各所でロギング処理を行うため
以下の依存関係も追加しています

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>


基本的な使い方

基本的に、 net.jodah.failsafe.RetryPolicy でその名の通りリトライのポリシーを定めて、
net.jodah.failsafe.Failsafe で、そのポリシーとともに対象の処理を実行する。という流れになります。

いろいろ省いていますが、おおよそ以下のようなコードのイメージになります

import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;

// リトライのポリシーを定める (特定の例外がスローされたら、2秒間隔で最大3回リトライする)
RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class)  // リトライ対象とする例外クラス
                .withDelay(2, TimeUnit.SECONDS)         // リトライ時の間隔
                .withMaxRetries(3);                     // 最大リトライ回数

// 定めたリトライポリシーとともに対象の処理を実行する
Failsafe.with(retryPolicy)
                .onFailure(throwable -> logger.error("bussiness logic failed.", throwable))  // リトライ含めて全部失敗した時の処理
                .run(() -> BusinessLogic.failedExecute(time));                               // 実行したい対象の処理


例えば以下のような実行サンプルがあったとします。
BusinessLogic#failedExecuteが実行したい処理で、BusinessLogicExceptionがスローされたらリトライするようにしています。

■サンプルコード

import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class RetrySample1 {

    private static final Logger logger = LoggerFactory.getLogger(RetrySample1.class);

    public static void main(String[] args) {
        RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class) // リトライ対象とする例外クラス
                .withDelay(2, TimeUnit.SECONDS) // リトライ時の間隔
                .withMaxRetries(3);                   // 最大リトライ回数


        final long time = System.currentTimeMillis();

        try {
            retryExecute(time, retryPolicy);
        } catch (Exception e) {
            logger.error("retry over.", e);
        }


        logger.info("--------------- end ------------------");
    }

    private static void retryExecute(long time, RetryPolicy retryPolicy) {
        Failsafe.with(retryPolicy)
                .onFailure(throwable -> logger.error("bussiness logic failed.", throwable))
                .run(() -> BusinessLogic.failedExecute(time));
    }


    static final class BusinessLogic {

        static void failedExecute(long time) {
            logger.info(">>BusinessLogic call failedExecute [param: {}]", time);

            if ((System.currentTimeMillis() - time) < 3000) {
                throw new BusinessLogicException("failed");
            }

            logger.info(">>BusinessLogic failedExecute success.");
        }
    }

    static final class BusinessLogicException extends RuntimeException {

        public BusinessLogicException(String message) {
            super(message);
        }
    }

}

実行結果は以下の通りです

■実行結果

01:50:45.820 [main] INFO net.yyuki.retry.RetrySample1 - >>BusinessLogic call failedExecute [param: 1536339045746]
01:50:47.826 [main] INFO net.yyuki.retry.RetrySample1 - >>BusinessLogic call failedExecute [param: 1536339045746]
01:50:49.831 [main] INFO net.yyuki.retry.RetrySample1 - >>BusinessLogic call failedExecute [param: 1536339045746]
01:50:49.831 [main] INFO net.yyuki.retry.RetrySample1 - >>BusinessLogic failedExecute success.
01:50:49.831 [main] INFO net.yyuki.retry.RetrySample1 - --------------- end ------------------

わかりにくいですがリトライ2回目で、対象の処理の実行に成功しています


例えばBusinessLogic#failedExecuteの中身をいじって、何度リトライしても失敗するようにします。

■サンプルコード

import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class RetrySample1 {

    private static final Logger logger = LoggerFactory.getLogger(RetrySample1.class);

    public static void main(String[] args) {
        RetryPolicy retryPolicy = new RetryPolicy()
                .retryOn(BusinessLogicException.class)
                .withDelay(2, TimeUnit.SECONDS)
                .withMaxRetries(3); 


        long time = System.currentTimeMillis();

        try {
            retryExecute(time, retryPolicy);
        } catch (Exception e) {
            logger.error("retry over.", e);
        }

        logger.info("--------------- end ------------------");
    }

    private static void retryExecute(long time, RetryPolicy retryPolicy) {
        Failsafe.with(retryPolicy)
                .onFailure(throwable -> logger.error("bussiness logic failed.", throwable))
                .run(() -> BusinessLogic.failedExecute(time));
    }

    static final class BusinessLogic {

        static void failedExecute(long time) {
            logger.info(">>BusinessLogic call failedExecute [param: {}]", time);

            if ((System.currentTimeMillis() - time) >= 0) {
                throw new BusinessLogicException("failed");
            }

            logger.info(">>BusinessLogic failedExecute success.");
        }
    }

    static final class BusinessLogicException extends RuntimeException {

        public BusinessLogicException(String message) {
            super(message);
        }
    }

}

そうした場合の実行結果は以下の通りです。

■実行結果

01:54:43.116 [main] INFO net.yyuki.retry.RetrySample2 - >>BusinessLogic call failedExecute [param: 1536339283020]
01:54:45.125 [main] INFO net.yyuki.retry.RetrySample2 - >>BusinessLogic call failedExecute [param: 1536339283020]
01:54:47.129 [main] INFO net.yyuki.retry.RetrySample2 - >>BusinessLogic call failedExecute [param: 1536339283020]
01:54:49.133 [main] INFO net.yyuki.retry.RetrySample2 - >>BusinessLogic call failedExecute [param: 1536339283020]
01:54:49.137 [main] ERROR net.yyuki.retry.RetrySample2 - bussiness logic failed.
net.yyuki.retry.RetrySample2$BusinessLogicException: failed
    at net.yyuki.retry.RetrySample2$BusinessLogic.failedExecute(RetrySample2.java:47)
    at net.yyuki.retry.RetrySample2.lambda$retryExecute$1(RetrySample2.java:37)
    at net.jodah.failsafe.Functions$10.call(Functions.java:252)
    at net.jodah.failsafe.SyncFailsafe.call(SyncFailsafe.java:145)
    at net.jodah.failsafe.SyncFailsafe.run(SyncFailsafe.java:81)
    at net.yyuki.retry.RetrySample2.retryExecute(RetrySample2.java:37)
    at net.yyuki.retry.RetrySample2.main(RetrySample2.java:24)
01:54:49.137 [main] ERROR net.yyuki.retry.RetrySample2 - retry over.
net.yyuki.retry.RetrySample2$BusinessLogicException: failed
    at net.yyuki.retry.RetrySample2$BusinessLogic.failedExecute(RetrySample2.java:47)
    at net.yyuki.retry.RetrySample2.lambda$retryExecute$1(RetrySample2.java:37)
    at net.jodah.failsafe.Functions$10.call(Functions.java:252)
    at net.jodah.failsafe.SyncFailsafe.call(SyncFailsafe.java:145)
    at net.jodah.failsafe.SyncFailsafe.run(SyncFailsafe.java:81)
    at net.yyuki.retry.RetrySample2.retryExecute(RetrySample2.java:37)
    at net.yyuki.retry.RetrySample2.main(RetrySample2.java:24)
01:54:49.137 [main] INFO net.yyuki.retry.RetrySample2 - --------------- end ------------------


この結果内容からコード中の

Failsafe.with(retryPolicy)
                .onFailure(throwable -> logger.error("bussiness logic failed.", throwable))
                .run(() -> BusinessLogic.failedExecute(time));

onFailure で記述した処理が実行されていることがわかります。
それと同時に 上記の FailSafeで記述したリトライ処理の呼び出し元に BusinessLogicException がスローされていることもわかります。("retry over." というログのあたりがそれを表しています)


成功した場合に処理を実行する

処理が成功した場合に、何か処理を実行する場合は onSuccess で処理を記述します。
(ここではINFOレベルでsuccessという文字列と実行何回目だったかを出力しています)

Failsafe.with(retryPolicy)
             .onSuccess((o, ctx) -> logger.info("bussiness logic success!!!! [execCount: {}]]", ctx.getExecutions()))
             .onFailure(throwable -> logger.error("bussiness logic failed.", throwable))
             .run(() -> BusinessLogic.failedExecute(time));


こうした場合実行結果は以下のようになります

■実行結果

01:57:51.922 [main] INFO net.yyuki.retry.RetrySample3 - >>BusinessLogic call failedExecute [param: 1536339471848]
01:57:53.930 [main] INFO net.yyuki.retry.RetrySample3 - >>BusinessLogic call failedExecute [param: 1536339471848]
01:57:55.936 [main] INFO net.yyuki.retry.RetrySample3 - >>BusinessLogic call failedExecute [param: 1536339471848]
01:57:55.936 [main] INFO net.yyuki.retry.RetrySample3 - >>BusinessLogic failedExecute success.
01:57:55.936 [main] INFO net.yyuki.retry.RetrySample3 - bussiness logic success!!!! [execCount: 3]]
01:57:55.936 [main] INFO net.yyuki.retry.RetrySample3 - --------------- end ------------------

実行3回目で成功していることがわかります。


最大回数までリトライ失敗したら対象の例外はスローしない

以下のように記述すると最大回数までリトライが失敗した場合に、対象の例外を呼び出し元にスローしなくなります

Failsafe.with(retryPolicy)
                .withFallback(() -> {})
                .onFailure(throwable -> logger.error("bussiness logic failed.", throwable))
                .run(() -> BusinessLogic.failedExecute(time));

こうした場合実行結果は以下のようになります


■実行結果

02:00:04.368 [main] INFO net.yyuki.retry.RetrySample4 - >>BusinessLogic call failedExecute [param: 1536339604273]
02:00:06.377 [main] INFO net.yyuki.retry.RetrySample4 - >>BusinessLogic call failedExecute [param: 1536339604273]
02:00:08.380 [main] INFO net.yyuki.retry.RetrySample4 - >>BusinessLogic call failedExecute [param: 1536339604273]
02:00:10.385 [main] INFO net.yyuki.retry.RetrySample4 - >>BusinessLogic call failedExecute [param: 1536339604273]
02:00:10.388 [main] ERROR net.yyuki.retry.RetrySample4 - bussiness logic failed.
net.yyuki.retry.RetrySample4$BusinessLogicException: failed
    at net.yyuki.retry.RetrySample4$BusinessLogic.failedExecute(RetrySample4.java:47)
    at net.yyuki.retry.RetrySample4.lambda$retryExecute$2(RetrySample4.java:37)
    at net.jodah.failsafe.Functions$10.call(Functions.java:252)
    at net.jodah.failsafe.SyncFailsafe.call(SyncFailsafe.java:145)
    at net.jodah.failsafe.SyncFailsafe.run(SyncFailsafe.java:81)
    at net.yyuki.retry.RetrySample4.retryExecute(RetrySample4.java:37)
    at net.yyuki.retry.RetrySample4.main(RetrySample4.java:24)
02:00:10.389 [main] INFO net.yyuki.retry.RetrySample4 - --------------- end ------------------

最大回数リトライ処理を行って処理が失敗した場合でも、呼び出し元への対象例外のスローが行われなくなっています。


まとめ

FailSafe を使うことでリトライ処理をシンプルに記述できました。
今回の例では、戻り値を返さないメソッドの呼び出しのみしか試していませんが、戻り値を返すメソッドを扱うことも可能です。
また、もっと複雑な制御を行うことも可能です。(その辺はまた別の機会に・・・)



関連エントリ

OSSの頒布

「OSSライセンスの教科書」という本を読んでいます。

OSSライセンスの教科書

OSSライセンスの教科書

普段の業務上でもお世話にならないことが無い、OSS(オープンソースソフトウェア)な訳ですが、 そのOSSのライセンスについて理解を深めるのにとても良い本だと思います。


以下は「OSSライセンスの教科書」の中の頒布に関する内容の一部メモです。


頒布

OSSのライセンスについて調べたりしていると頒布(はんぷ)という言葉が付きまといます。

頒布をネットで調べると以下のような感じの定義が出てきます

不特定多数の相手に広く配る行為のこと、有償無償は問わない。

OSSを適切に扱う(OSSのライセンスの内容に適合させる)ためには、頒布が大事なタイミングとなります。
OSSライセンスでは、OSSを頒布する側に条件を課していることが良くあるためです。
(ライセンスによっては、利用許諾されたソフトウェアを頒布する際に、頒布する人がソースコードを開示する ことなどを求めています)

「頒布する人」とは、OSSの開発者だけを指すわけではなく、そのOSSを入手し、誰か他の人に渡す人(法人含む)は全て頒布する人になります。
頒布する形態も色々とあり、ソースコードを誰かに渡すこと、バイナリコードを渡すこと、OSSを製品に組み込んで渡すのも頒布になります。


頒布の事例

「OSSライセンスの教科書」では、各種事例を取り上げ、それが頒布にあたるのかどうかを紹介してくれています。
以下はその内容の簡単なメモになります。


■ソフトウェアの内製

あなたは消費者用機器の開発メーカーでソフトウェア開発を行なっている。
あなたの開発したソフトウェアにOSSが含まれている。
そのソフトウェアを組み込んだ製品が発売された。この場合、あなたの会社はOSSを頒布すると言えるか?


頒布していると言える

機器に組み込まれているからといって、バイナリ形式になっているからといって頒布では無いと主張するのは無理がある。


■開発製造受託会社に開発を委託

あなたは消費者用機器の開発メーカーに在籍している。
開発製造受託業者に委託して製品を開発してもらい受給した。
製品が発売されたが、この製品にはソフトウェアが組み込まれていて、開発元のベンダーによるとその中にはOSSが入っているとのこと。
あなたの会社はOSSを頒布していると言えるか?


頒布していると言える

開発元のベンダーがあなたの会社にOSSを頒布したが、製品とともにユーザにOSSを頒布しているのはあなたの会社です。
そのため、頒布に伴う条件を履行する必要があります。


■マーケティング部門がモバイルアプリを作成

あなたはインターネット通販企業のマーケティング部門に属しています。
外部のソフトウェアハウスに頼んで、スマートフォンアプリを開発し、拡販を開始しました。
ソフトウェアハウスの担当者は「そのアプリには実はOSSを使っているんです」と話していました。
あなたの会社がOSSを頒布したと言えるか?


頒布していると言える

あなたの会社はスマートフォンアプリに組み込まれているOSSを頒布しています。
頒布する人(法人)としてOSSライセンスが求めていることを確実に実施する必要があります。


■Webサーバ構築

あなたは Linux と Apache を使ってWebサーバを構築しました。
そのサーバ上であるWebアプリケーションを構築して運用しています。
あなたはOSSを頒布したているでしょうか?

Webブラウザなどで、このシステムにアクセスするユーザは、このサーバに対してデータを受け渡して、
サーバで処理された結果を受け取ります。
その際に、LinuxやApacheといったOSSプログラムそのものをユーザに渡されることはありません。
この観点では、

頒布していると言えない

ということになります。
しかし、Webサイトからユーザ側(ユーザの端末)で実行されるプログラムがサーバからユーザに渡されることもあります。
例えば、JavaScriptがそれに該当します。
もしも、そのJavaScriptのプログラムがOSSであった場合、

頒布していると言える

となります。


一筋縄ではいかない例となります。


■知らぬ間にOSSが製品に入っていた

あなたはソフトェアが含まれる製品を出荷しています。
ある時、全く面識のない人から連絡を受け、あなたの製品の中にOSSが含まれているのではないか
という問い合わせを受けました。
改めて調べてみると、その製品の中にOSSが含まれていることがわかりました。
あなたはOSSを頒布したと言えるか?


頒布していると言える

たとえ、あなたがOSSを頒布した意識がなくてもOSSライセンスが求めることを履行する必要があります。


まとめ

「OSSライセンスの教科書」の内容を元に、OSSの頒布につい簡単にまとめました。
OSSを扱う上で、頒布について一定の理解をしておく必要があります。

「OSSライセンスの教科書」は、OSSライセンスを理解する上で読んだ方が良いです。

JavaのString#trim

JavaのString#trimメソッドは、文字列の先頭と末尾のスペース(半角スペース)を除外した文字列を返してくれます。


例えば " sample1 " という先頭にも末尾にも半角スペースが入った文字列にtrimを実行すると、
半角スペースが削られた "sample1" という文字列が返されます。


サンプルコード

import java.util.stream.Collectors;

public class TrimSample1 {

    public static void main(String[] args) {
        String sample1 = " sample1 ";
        printStrAndTrimedStr(sample1);
    }

    private static void printStrAndTrimedStr(String str) {
        System.out.printf("[%s](%s) -> trim -> [%s](%s)\n\n", str, asciiHexDump(str),
                str.trim(), asciiHexDump(str.trim()));
    }

    private static String asciiHexDump(String ascii) {
        return ascii.chars()
                .mapToObj(b -> String.format("0x%02X", b))
                .collect(Collectors.joining(" "));
    }
}

printStrAndTrimedStr で、trim実行前後の文字列を出力して、かつ16進表記の出力もしています。
(以降のサンプルでもprintStrAndTrimedStrasciiHexDump は使いまわします)


実行結果

[ sample1 ](0x20 0x73 0x61 0x6D 0x70 0x6C 0x65 0x31 0x20) -> trim -> [sample1](0x73 0x61 0x6D 0x70 0x6C 0x65 0x31)

先頭と末尾の半角スペース(0x20)が削られています。


まぁ、ここまでは予定通りの動きな訳ですが、 Javaの String#trim って、0x20以下の文字コードを先頭・末尾から削るようです。


改行コードを含む文字列で実行してみます。

サンプルコード

public class TrimSample2 {

    public static void main(String[] args) {
        String sample2 = " sample2\r\n";
        printStrAndTrimedStr(sample2);
    }

   // さっきのサンプルと同じメソッドは省略
}


実行結果

[ sample2
](0x20 0x73 0x61 0x6D 0x70 0x6C 0x65 0x32 0x0D 0x0A) -> trim -> [sample2](0x73 0x61 0x6D 0x70 0x6C 0x65 0x32)

半角スペース(0x20)だけではなく、改行コードのCR (0x0D) と LF (0x0A) も削られています。


今度は、末尾にNUL (NULL文字)を含む文字列で実行してみます。

サンプルコード

public class TrimSample3 {

    public static void main(String[] args) {
        byte[] bytes = "sample3 _".getBytes();
        bytes[bytes.length - 1] = 0x00;
        String sample3 = new String(bytes);
        printStrAndTrimedStr(sample3);
    }

   // さっきのサンプルと同じメソッドは省略
}


実行結果

[sample3 ](0x73 0x61 0x6D 0x70 0x6C 0x65 0x33 0x20 0x00) -> trim -> [sample3](0x73 0x61 0x6D 0x70 0x6C 0x65 0x33)

この結果から、NULL文字 (0x00) も削られることが分かります。


まとめ

String#trimは、先頭と末尾の 半角スペースだけではなく 改行コードなどの 0x20以下の文字を削ります。

ASCII文字列の16進数表記を取得する

JavaでASCII文字列の16進数表記の値(Hex Dump)を取得するサンプルです。

以下のようなメソッドを用意します。

import java.util.stream.Collectors;

static String asciiHexDump(String ascii, String delimiter) {
    return ascii.chars()
            .mapToObj(b -> String.format("0x%02X", b))
            .collect(Collectors.joining(delimiter));
}


実行サンプルは以下の通りです

サンプルコード

import java.util.stream.Collectors;

public class TrimSample {

    public static void main(String[] args) {
        String sample1 = "sample1";
        System.out.printf("%s -> [%s]\n", sample1, asciiHexDump(sample1, ","));

        System.out.println();

        String sample2 = " sample2\r\n";
        System.out.printf("%s -> [%s]\n", sample2, asciiHexDump(sample2, " "));
    }

    private static String asciiHexDump(String ascii, String delimiter) {
        return ascii.chars()
                .mapToObj(b -> String.format("0x%02X", b))
                .collect(Collectors.joining(delimiter));
    }
}


実行結果

sample1 -> [0x73,0x61,0x6D,0x70,0x6C,0x65,0x31]

 sample2
 -> [0x20 0x73 0x61 0x6D 0x70 0x6C 0x65 0x32 0x0D 0x0A]