20190915のJavaに関する記事は6件です。

Web3jを使って署名付きトランザクションを送信する

記事の内容

EthereumのJavaライブラリ「Web3j」を使って署名付きトランザクションを送信する方法

環境

  • geth:1.9.0-stable
  • web3j:4.5.0

実装

自分の学習用に書いたコードなのでかなり雑ですが。。
このサンプルソースではトランザクションの送信メソッドを署名なしと署名ありでそれぞれ作成しており、mainメソッドから両方を順番に呼び出しているだけになります。

SendTransaction.java
package jp.ethereum.transaction;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Optional;

import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.admin.methods.response.PersonalUnlockAccount;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Transfer;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;

public class SendTransaction {

  // ①
  public static final Admin web3j = Admin.build(new HttpService("http://127.0.0.1:8545"));

  public static void main(String args[]) {
    try {
      // ②
      // トランザクション送信者の資格情報を取得する
      // 資格情報に公開鍵、秘密鍵が含まれている
      String password = "password";
      Credentials credentials = WalletUtils.loadCredentials(password, "秘密鍵ファイルのフルパス");

      String toAddress = "0x66b4e7be902300f9a15d900822bbd8803be87391";

      SendTransaction tx= new SendTransaction();

      // トランザクションの送信
      tx.sendTransaction(credentials, password, toAddress, 10);

      // 署名付きトランザクションの送信
      TransactionReceipt receipt = tx.sendSignedTransaction(credentials, password, toAddress);

      if (receipt != null) System.out.println(receipt.getTransactionHash()) ;

    }catch(IOException | CipherException ex) {
      ex.printStackTrace();
    }
  }

  public TransactionReceipt sendTransaction(Credentials credentials, String password, String toAddress, long value) {

    TransactionReceipt receipt = null;
      try {
        // トランザクションの生成
        // "personal_unlockAccount"のリクエストを送信し、レスポンスを受信する
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), // アドレス
            password  // パスワード
            ).send();

        // アンロックが成功していたら、Etherを送金する
        if (unlockAccountResponse.getResult()) {
          // Transactionを送信する。Blockに取り込まれるまで応答が返ってこない
          receipt = Transfer.sendFunds(web3j, credentials, toAddress, BigDecimal.valueOf(value), Unit.ETHER).send();
        }
      }catch(IOException | TransactionException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }
  public TransactionReceipt sendSignedTransaction(Credentials credentials, String password, String toAddress) {

      TransactionReceipt receipt = null;
      try {
        // トランザクションの生成
        // "personal_unlockAccount"のリクエストを送信し、レスポンスを受信する
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), // アドレス
            password  // パスワード
            ).send();

        // アンロックが成功していたら、Etherを送金する
        if (unlockAccountResponse.getResult()) {
            // "eth_sendTransaction"の引数に渡すオブジェクトを作成
          RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
            BigInteger.valueOf(10),     // from
            BigInteger.valueOf(700),     // gasPrice
            BigInteger.valueOf(4712388),   // gasLimit
            toAddress,             // to
            BigInteger.valueOf(101)     // value
            );

          // トランザクションに署名
          byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
          String hexValue = Numeric.toHexString(signedMessage);

          // トランザクションの送信
          EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
          String response = ethSendTransaction.getRawResponse();
          String transactionHash = ethSendTransaction.getTransactionHash();

          Optional<TransactionReceipt> transactionReceipt = null;
          int retry = 0;

          // トランザクションの監視
          if(transactionHash != null) {
            do {
            System.out.printf("%3d checking if transaction " + transactionHash + " is mined....\n" ,retry);
              EthGetTransactionReceipt ethGetTransactionReceiptResp = 
                  web3j.ethGetTransactionReceipt(transactionHash).send();
                transactionReceipt = ethGetTransactionReceiptResp.getTransactionReceipt();

                Thread.sleep(3000);
                retry++;
            }while(!transactionReceipt.isPresent() && retry < 100);
          } else {
            System.out.println("Transaction Send failed...");
            System.out.println("Message:" + ethSendTransaction.getError().getMessage());
            System.out.println("Data   :" + ethSendTransaction.getError().getData());
          }
        }
      }catch(IOException | InterruptedException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }
}

ポイントとなりそうなところを補足していきます。

Ethereumに接続する

Ethereumに接続する

SendTransaction.java
// ①
public static final Admin admin = Admin.build(new HttpService("http://127.0.0.1:8545"));

ここではAdminクラスのインスタンスを作成しています。
AdminクラスはWeb3jクラスを継承したクラスであり、Web3jクラスには実装されていないpersonal情報を使ったメソッドを使用することができます。
トランザクション送信ではアカウントのアンロックが必要になるので、Web3jではなくAdminクラスを使用します。

引数に指定するURLはgeth起動時に「--rpcaddr」オプションで指定したアドレスになります。
ポートは指定しなければ「8545」になります。

秘密鍵を取得する

SendTransaction.java
// トランザクション送信者の資格情報を取得する
// 資格情報に公開鍵、秘密鍵が含まれている
String password = "password";
Credentials credentials = WalletUtils.loadCredentials(password, "秘密鍵ファイルのフルパス");

WalletUtils.loadCredentialsメソッドで秘密鍵の情報を取得します。
第一引数にはユーザーアカウントのパスワード
第二引数には秘密鍵ファイル(UTCから始まるファイル)のパス(ファイル名を含む)を指定

トランザクションの送信

やっていることは単純でアカウントをアンロックして、トランザクションを送信するだけです。

SendTransaction.java
  public TransactionReceipt sendTransaction(Credentials credentials, String password, String toAddress, long value) {

    TransactionReceipt receipt = null;
      try {
        // トランザクションの生成
        // "personal_unlockAccount"のリクエストを送信し、レスポンスを受信する
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), // アドレス
            password  // パスワード
            ).send();

        // アンロックが成功していたら、Etherを送金する
        if (unlockAccountResponse.getResult()) {
          // Transactionを送信する。Blockに取り込まれるまで応答が返ってこない
          receipt = Transfer.sendFunds(web3j, credentials, toAddress, BigDecimal.valueOf(value), Unit.ETHER).send();
        }
      }catch(IOException | TransactionException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }

ポイントとなる点は、この処理がトランザクションがブロックに取り込まれるまでレスポンスが返ってこなかったことです。
Bitcoinよりはブロック生成の間隔が短いとはいえ、実際のアプリで使う場面があれば、考慮が必要になりそうです。

receipt = Transfer.sendFunds(web3j, credentials, toAddress, BigDecimal.valueOf(value), Unit.ETHER).send();

トランザクション送信の実行結果

まずはトランザクションが生成されたタイミングで「eth.getTransaction」で確認してみます。

INFO [09-15|23:28:07.213] Submitted transaction                    fullhash=0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97 recipient=0x66B4e7bE902300F9a15D900822Bbd8803Be87391

> eth.getTransaction("0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0x945cd603a6754cb13c3d61d8fe240990f86f9f8a",
  gas: 21000,
  gasPrice: 1000000000,
  hash: "0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97",
  input: "0x",
  nonce: 7,
  r: "0x3ca5a820995553d30656f2218dc10729d3e0f660c35817bbd69845ac96dc6279",
  s: "0x2e915ae47699771108f65881273f464d70db0229ad12a94ac38746f498ea7ed3",
  to: "0x66b4e7be902300f9a15d900822bbd8803be87391",
  transactionIndex: 0,
  v: "0x1c",
  value: 10000000000000000000
}

トランザクションが作成されました。マイニングしてブロックに取り込まれるか確認します。

> eth.getTransaction("0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97")
{
  blockHash: "0xc9634aec9670d312759a0e12ea5fee54948688c88e4d45a5b9cdbeef3c44c681",
  blockNumber: 2792,
  from: "0x945cd603a6754cb13c3d61d8fe240990f86f9f8a",
  gas: 21000,
  gasPrice: 1000000000,
  hash: "0xfa7ab0924c82b9e45a10acd5c6b72136a088b4dee0d4d4810a2d4f4408c3ee97",
  input: "0x",
  nonce: 7,
  r: "0x3ca5a820995553d30656f2218dc10729d3e0f660c35817bbd69845ac96dc6279",
  s: "0x2e915ae47699771108f65881273f464d70db0229ad12a94ac38746f498ea7ed3",
  to: "0x66b4e7be902300f9a15d900822bbd8803be87391",
  transactionIndex: 0,
  v: "0x1c",
  value: 10000000000000000000
}

無事取り込まれました。

署名付きトランザクションの送信

SendTransaction.java
  public TransactionReceipt sendSignedTransaction(Credentials credentials, String password, String toAddress) {

      TransactionReceipt receipt = null;
      try {
        // トランザクションの生成
        // "personal_unlockAccount"のリクエストを送信し、レスポンスを受信する
        PersonalUnlockAccount unlockAccountResponse = web3j.personalUnlockAccount(
            credentials.getAddress(), // アドレス
            password  // パスワード
            ).send();

        // アンロックが成功していたら、Etherを送金する
        if (unlockAccountResponse.getResult()) {
            // "eth_sendTransaction"の引数に渡すオブジェクトを作成
          RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
            BigInteger.valueOf(10),     // from
            BigInteger.valueOf(700),     // gasPrice
            BigInteger.valueOf(4712388),   // gasLimit
            toAddress,             // to
            BigInteger.valueOf(101)     // value
            );

          // トランザクションに署名
          byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
          String hexValue = Numeric.toHexString(signedMessage);

          // トランザクションの送信
          EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
          String response = ethSendTransaction.getRawResponse();
          String transactionHash = ethSendTransaction.getTransactionHash();

          Optional<TransactionReceipt> transactionReceipt = null;
          int retry = 0;

          // トランザクションの監視
          if(transactionHash != null) {
            do {
            System.out.printf("%3d checking if transaction " + transactionHash + " is mined....\n" ,retry);
              EthGetTransactionReceipt ethGetTransactionReceiptResp = 
                  web3j.ethGetTransactionReceipt(transactionHash).send();
                transactionReceipt = ethGetTransactionReceiptResp.getTransactionReceipt();

                Thread.sleep(3000);
                retry++;
            }while(!transactionReceipt.isPresent() && retry < 100);
          } else {
            System.out.println("Transaction Send failed...");
            System.out.println("Message:" + ethSendTransaction.getError().getMessage());
            System.out.println("Data   :" + ethSendTransaction.getError().getData());
          }
        }
      }catch(IOException | InterruptedException ex) {
        ex.printStackTrace();
      }catch(Exception ex) {
        ex.printStackTrace();
      }
    return receipt;
  }
}

やっていることは
1. アカウントのアンロック
2. トランザクションオブジェクトの生成
3. トランザクションへ署名
4. トランザクションの送信
5. トランザクションの監視

という流れになります。
トランザクションの監視は指定したトランザクションのハッシュ値がブロックに取り込まれたかどうかを確認しています。

実行結果

トランザクションが作成されました。

INFO [09-15|23:29:23.752] Submitted transaction                    fullhash=0x533ff5d2635284d01d8e85015da00d8962de8b98bf5efaa6d9ceca3200243a88 recipient=0x66B4e7bE902300F9a15D900822Bbd8803Be87391

> eth.getTransaction("0x533ff5d2635284d01d8e85015da00d8962de8b98bf5efaa6d9ceca3200243a88")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0x945cd603a6754cb13c3d61d8fe240990f86f9f8a",
  gas: 4712388,
  gasPrice: 800,
  hash: "0x533ff5d2635284d01d8e85015da00d8962de8b98bf5efaa6d9ceca3200243a88",
  input: "0x",
  nonce: 10,
  r: "0xc8022528f46078b3e9a8f2a3174d147b85c23af6794fc8b07d58651931b7556f",
  s: "0x1ab35d15cf3afa6990bc6e96a593e6c2cba6432f769d0de919c770cec4a3f2c7",
  to: "0x66b4e7be902300f9a15d900822bbd8803be87391",
  transactionIndex: 0,
  v: "0x1b",
  value: 101
}

ただ、自分の理解が追い付いていないのか、どれだけマイニングをしてもこのトランザクションがブロックに取り込まれません。
何かトランザクションに指定した値に不備があるのか?
それとも、署名したトランザクションを検証しないとブロックに取り込まれないのか?

このあたりを引き続き学習していきます。

最後に

書籍に書いてある内容を漠然とインプットしているだけの学習をしていましたが、Javaという一番得意な言語でどうやって使えるのかを調べると今まで点だったものがどんどん繋がってきました。

まだまだ情報が少ない分野なので積極的に学習した内容をアウトプットしていけたらなと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java static methodのメリット

  • インスタンス化しなくて使用できる。
  • クラス内で共用なメソッドになるため、他のメソッドからアクセスが可能になる。
class TestClass {

    private TestClass(){
    }

    // staticメソッド
    public static void printlnHelloWorld() {
        System.out.println("Hello World!");
    }
}

staticメソッドの利用

public class HelloWorld {
    public static void main(String[] args) {
        TestClass.printlnHelloWorld(); //インスタンス化不要
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlinを使いこなす 〜Convert Java File to Kotlin File 卒業への道〜 その2

はじめに

Android等でKotlinを使用している場合、Javaから『Convert Java File to Kotlin File』で自動変換している人は多い。実際にそれでも動くのだが、そこをもう1歩先に進みKotlinをより使いこなす為のメモ。

第2回目
今回はAndroidのActivityのViewの動的処理を習得する。
Kotlinバージョン:1.3

Kotlin変換前のサンプルコード

最初にJavaで書かれたサンプルを用意する。処理は。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    Button buttonA;
    TextView textViewA;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonA = (Button) findViewById(R.id.button_A);
        buttonA.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("", "buttonA");
            }
        });

        textViewA = (TextView) findViewById(R.id.text_viewA);
        textViewA.setText(R.string.text1);

        TextView textViewB = (TextView) findViewById(R.id.text_viewB);
        textViewB.setText(R.string.text2);
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        buttonA.setClickable(false);
        textViewA.setText(R.string.text2);
    }

Kotlin変換後のサンプルコード

これを自動変換して元の形に合わせるときっとこんな感じに・・・。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    var buttonA: Button? = null
    var textViewA: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        buttonA = findViewById<View>(R.id.button_A) as Button
        buttonA?.setOnClickListener { Log.d("", "buttonA") }

        textViewA = findViewById<View>(R.id.text_viewA) as TextView
        textViewA?.setText(R.string.text1)

        val textViewB = findViewById<View>(R.id.text_viewB) as TextView
        textViewB.setText(R.string.text2)
    }

    override fun onRestart() {
        super.onRestart()
        buttonA?.isClickable = false
        textViewA?.setText(R.string.text2)
    }
}

何が問題か

一応、問題なく動作できるが・・・。
まず、メンバー変数を使用するのに毎回Nullableのアンラップ(buttonA?.・・・)しないといけない。
findViewByIdのas *****
という記述もあまり意味がない。

実装方法を考える

方法が2つある
1.メンバー変数に「lateinit」を使い、findViewByIdの型を決める

MainActivity.kt
class MainActivity : AppCompatActivity() {

    lateinit var buttonA: Button
    lateinit var textViewA: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        buttonA = findViewById<Button>(R.id.button_A)
        buttonA.setOnClickListener { Log.d("", "buttonA") }

        textViewA = findViewById<TextView>(R.id.text_viewA)
        textViewA.setText(R.string.text1)

        val textViewB = findViewById<TextView>(R.id.text_viewB)
        textViewB.setText(R.string.text2)
    }

    override fun onRestart() {
        super.onRestart()
        buttonA.isClickable = false
        textViewA.setText(R.string.text2)
    }
}

2.「Kotlin Android Extensions」を使用する
1の方法でも大丈夫だがもっとシンプルに実装もできる。
それが「Kotlin Android Extensions(公式リンク)」

使用するにはgradleにclasspathの追記が必要

build.gradle
buildscript {
    ext.kotlin_version = '1.3.41'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // 以下の行を追加
        classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
    }
}

これで準備OK
findViewByIdを使わなくても実装できるようになる

MainActivity.kt
// ※↓のimportの追記が必要
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button_A.setOnClickListener { Log.d("", "buttonA") }

        text_viewA.text = getText(R.string.text1)

        text_viewB.text = getText(R.string.text2)
    }

    override fun onRestart() {
        super.onRestart()
        button_A.isClickable = false
        text_viewA.text = getText(R.string.text2)
    }
}

とてもシンプル!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

数多の先輩を殺しかけてきたクソコーディング集(Java編)

はじめに

 本記事は、数多の先輩を殺しかけてきた私のクソコードを紹介し、
 笑いのネタにしつつも
 そのクソコードの有効なリファクタリング方法を記述していくものです。

1.インデント・スペースの不揃い

fizzbuzz.java
public class fizzbuzz {

    public static void main(String[] args) {
        for(int i = 0 ; i < 500 ; i ++) {
            if(i % 3 == 0 && i % 5 == 0) {
                System.out.println("FizzBuzz");
        }
        else if(i % 3 == 0) {
                System.out.println("Fizz");
            } else if (i % 5 == 0) {
                System.out.println("Buzz");
                }
            }
    }
}

このコードは正常に動作しますが、説明不要なくらいきったないコードですよね。
インデントやスペースの不揃いは意外と軽視しがちな要素ではありますが、しっかりと揃えてから質問やレビューに出しましょう。
処理ブロックに入ったら1段下げる、抜けたら1段戻る。を意識するとやりやすいです。

fizzbuzz.java
public class fizzbuzz {

    public static void main(String[] args) {
        for(int i = 0 ; i < 500 ; i ++ ) {
            if(i % 3 == 0 && i % 5 == 0 ) {
                System.out.println("FizzBuzz");
            } else if(i % 3 == 0) {
                System.out.println("Fizz");
            } else if (i % 5 == 0) {
                System.out.println("Buzz");
            }
        }
    }
}

2.変数名・定数名がやったら長い。

 これは、先輩に「名前から用途がわかる変数名にしてくれ」という指摘を受けてやらかしたものです。
※本コードの処理の内容には誤りがあります。

fizzbuzz.java
public class fizzbuzz {

    public static final boolean CONFIRM_OR_ADD_BUTTON_NAME_FLG = true;

    public static void main(String [] args) {
        String screenShowButtonNameText = "";
        if(CONFIRM_OR_ADD_BUTTON_NAME_FLG) {
            screenShowButtonNameText = "confirm";
        } else {
            buttonNameText = "confirm";
        }
        System.out.println("ButtonName : " + screenShowButtonNameText);
    }
}

定数:CONFIRM_OR_ADD_BUTTON_NAME_FLG は、おそらくボタン名がConfrimかAddかを判定するのでしょう。
そして判定した結果をおそらく画面に表示する文字列である、
screenShowButtonNameTextに格納するのでしょう。

ですが、長すぎます。具合が悪くなります。
命名規則は職場でのコーディングであれば規約等があるかもしれませんが、個人で開発するなら1単語 + 1動詞くらいに抑えておくと後々すぐわかります。

fizzbuzz.java
public class fizzbuzz {

    public static final boolean BUTTON_NAME_FLG = true;

    public static void main(String [] args) {
        String buttonNameText = "";
        if(CONFIRM_OR_ADD_BUTTON_NAME_FLG) {
            buttonNameText = "confirm";
        } else {
            buttonNameText = "confirm";
        }
        System.out.println("ButtonName : " + buttonNameText);
    }
}

3.急にテクニックに走る。

少し慣れてきたころに、その構文の有用性がわからないまま急にテクニックに走り出しました。

fizzbuzz.java
public class fizzbuzz {

    public static void main(String [] args) {
        List<String> retItemList = new ArrayList<String>();
        fizzbuzz fizzbuzz = new fizzbuzz();
        fizzbuzz.checkResultList(retItemList);
    }

    public List<String> checkResultList(List<String> retItemList) {
        List<String> checkList =  retItemList == null ? new ArrayList<>() : retItemList.isEmpty() ? new ArrayList<>() : retItemList;
        return checkList;
    }
}

三項演算子の使用そのものは悪ではないです。(私の中では)しかし、上記のようにネストをさせると可読性が落ちます。
三項演算子の使用は、短い条件かつ1個の条件のみとし、それ以外はif -elseで判定するとするのが有効でしょうか。

fizzbuzz.java
import java.util.ArrayList;
import java.util.List;

public class fizzbuzz {

    public static void main(String [] args) {
        List<String> retItemList = new ArrayList<String>();
        fizzbuzz fizzbuzz = new fizzbuzz();
        fizzbuzz.checkResultList(retItemList);
    }

    public List<String> checkResultList(List<String> retItemList) {
        List<String> checkList = new ArrayList<String>();
        if(checkList != null && !(checkList.isEmpty())) {
            checkList = retItemList;
        }
        return checkList;
    }
}

4.不必要すぎるコメント

fizzbuzz.java
import java.util.ArrayList;
import java.util.List;
 // クラス名の「fizzbuzz」は嘘。本当はリストの中身をチェックする処理。
public class fizzbuzz {
       // mainメソッドはJavaのエントリーポイントで、ここから処理が開始される。
       // コマンドライン引数は今回は渡さない。
    public static void main(String [] args) {
        // String型を格納する空のListを生成する。
        List<String> retItemList = new ArrayList<String>();
        // TODO:「fizzbuzz」は正しくない名前、修正が必要。
        fizzbuzz fizzbuzz = new fizzbuzz();
        // checkResultListメソッドを呼び出す。判定を行う。
        fizzbuzz.checkResultList(retItemList);
    }
        // リストのnullチェックをする。
    public List<String> checkResultList(List<String> retItemList) {
        // チェック用の空のリストを宣言する。
        List<String> checkList = new ArrayList<String>();
        // checkListがnull又は空の場合は、空のリストを返却する。
        if(retItemList != null && !(retItemList.isEmpty())) {
            checkList = retItemList;
        }

        return checkList;
    }
}

・・・はい。ごめんなさい。
コメントの書き方のコツはいくつかありますが、今回の例でいくと、
・見ればわかるものは書かない。
・不要なTODOは削除しておく。
この二点を意識すると、まだ意味のあるコメントが残ります。

fizzbuzz.java
import java.util.ArrayList;
import java.util.List;
public class fizzbuzz {
    public static void main(String [] args) {
        List<String> retItemList = new ArrayList<String>();
        fizzbuzz fizzbuzz = new fizzbuzz();
        fizzbuzz.checkResultList(retItemList);
    }
        // 引数で渡されてきたリストのnullチェックをする。
    public List<String> checkResultList(List<String> retItemList) {
        List<String> checkList = new ArrayList<String>();
        // checkListがnull又は空の場合は、処理を行わない。
        if(checkList != null && !(checkList.isEmpty())) {
            checkList = retItemList;
        }

        return checkList;
    }
}

条件分岐のコメント、「checkListがnull又は空の場合は、処理を行わない。」は、
処理の変更によってnull、空以外をチェックするようになった時に、コメントを変更しないと処理とコメントが乖離する危険性をはらんでいますが、
一目見て条件分岐の内容がわかることの利点もあります。
今回は利点の方を重視しました。

おわりに

 以上です。ここで挙げたことは、基本的な内容ですが、意識するのは最初の内は難しいです。
可読性を上げるメリットは数多くあるので、処理の内容がわからなくて物が作れなくても可読性だけは意識しましょう。
それだけでも先輩エンジニアからのアドバイスの受けやすさや、余計なダメージ、話の流れが飛ぶといったことが減り、成長につながるはずです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java 11+でCORBAサービスを呼び出してみる

以前「Spring(Java)からCORBAサービスを呼び出してみる」にて、Java 8上でSpringの機能を使ってCORBAサービスを呼び出す方法を紹介してみましたが、今回はJava 11+にて、代替ライブラリを利用してCORBAサービスを呼び出す方法について紹介してみようかと思います(なぜこの時代にCORBAなのか!?については・・・聞かないでくださいw)。

検証バージョン

JDK

  • AdoptOpenJDK 11.0.4+11
  • OpenJDK 12.0.2+10
  • OpenJDK 13+33
  • OpenJDK 14-ea+14-570
  • JDK 8 (orbdを使ってネーミングサービス経由でのアクセスをできるようにするため)

NOTE:

本エントリではCORBAサービスもJavaで作成しているのですが・・・CORBAオブジェクトをネーミングサービス経由で取得できるようにするためにはJDK 8に含まれていたorbd相当の機能も必要になります(ネーミングサービスを使わずにIOR文字列を使ってCORBAオブジェクトを取得する場合はorbd相当の機能は不要です)。JDK 11以降にorbdコマンドは含まれていないので・・・本エントリーではorbdコマンドを使うためだけにJDK 8を使います・・・(完全に妥協ですが、本エントリーはあくまでクライアント側にフォーカスを当てているのでご了承ください)

Spring Boot

  • Spring Boot 2.1.8.RELEASE

代替ライブラリ

Eclipse ORBとは

すごーくざっくり言うと・・GlassFishで使用しているORB実装を、Eclipse EE4Jの独立したサブプロジェクトとして開発が行われているライブラリのようです。このライブラリからは、ORB実装だけではなく、idljやrmicなどのツール群も合わせて提供が行われています。なお、このライブラリは、「JEP 320: Remove the Java EE and CORBA Modules」の中で代替ライブラリとして紹介されています。

JBoss repackaging of the OpenJDK ORBとは

JBossがOpenJDK 8のORB実装を再配布してくれているライブラリのようです。1stリリースが2015年なので・・・JDK 9で非推奨、JDK 11でモジュールが消えることに関係しているかは定かではありません。

JBoss repackaging of the OpenJDK9 ORB additionsとは

JBossがOpenJDK 9で消えた?ORB関連のクラスを再配布してくれているライブラリのようです。ここには、「Spring(Java)からCORBAサービスを呼び出してみる」で使っていた「com.sun.jndi.cosnaming.CNCtxFactory」が含まれています。

JavaでCORBAサービスを作ってみる

こちら」をご覧ください。IDLファイルからJavaコードを生成する部分については、JDK 11+にはidljコマンドが存在しないので、Eclipse ORBから提供されているidlj(executable jar)を使うことで同等のクラスを生成することができます。

$ java -jar idlj-4.2.1.jar -fall -td src/main/java src/main/resources/Greeting.idl

JavaでCORBAサービスを公開してみる

代替ライブラリの有効化

JDK 11+にはORB実装が存在しないため、何もしないと必要なクラスが見つからずコンパイルができないので、まずは・・・代替ライブラリを使えるようにする必要があります。

Eclipse ORB編

pom.xml
<dependency>
  <groupId>org.glassfish.corba</groupId>
  <artifactId>glassfish-corba-orb</artifactId>
  <version>4.2.1</version>
</dependency>
<dependency> <!-- Spring Boot編で「com.sun.jndi.cosnaming.CNCtxFactory」を使うため -->
  <groupId>org.jboss.openjdk-orb</groupId>
  <artifactId>openjdk-orb-jdk9-supplement</artifactId>
  <version>1.0.3.Final</version>
  <exclusions>
  <exclusion> <!-- ORB実装がEclipse ORBと被ってしまうため除外 -->
    <groupId>org.jboss.openjdk-orb</groupId>
    <artifactId>openjdk-orb</artifactId>
    </exclusion>
  </exclusions>
</dependency>

JBoss repackaging of the OpenJDK ORB編

pom.xml
<dependency>
  <groupId>org.jboss.openjdk-orb</groupId>
  <artifactId>openjdk-orb</artifactId>
  <version>8.1.4.Final</version>
</dependency>

CORBAサービスの公開

ORBが利用可能な状態になったところでCORBAサービスを公開します。詳しくは「こちら」をご覧ください。 JDK 8のorbdコマンドにパスが通っている環境であればソースコードを修正する必要はないのですが、パスが通っていない場合は・・・・以下のような修正が必要になります。

    List<String> orbdStartupCommands = new ArrayList<>();
//    orbdStartupCommands.add("orbd");
    orbdStartupCommands.add("/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/bin/orbd"); // フルパスでコマンドを指定
    orbdStartupCommands.addAll(Arrays.asList(ORB_OPTIONS));

JavaからCORBAサービスを呼び出してみる

こちら」をご覧ください。修正が必要なところはありません。

Springの機能を使ってCORBAサービスを呼び出してみる

こちら」をご覧ください。修正が必要なところはありません。

デモアプリケーション

本エントリで紹介した内容と完全に一致するわけではありませんが、同等の内容のデモアプリケーションをGitHubに公開してあります。

まとめ

JDK 11でモジュールが消えてしまったCORBAですが、代替ライブラリを使うことでJDK 11+の環境でも使うことができることがわかりました。が・・・代替ライブラリが商用アプリケーションでも利用できるのか?と言う部分が気になりライセンスを調べたところ、

Eclipse ORBは・・・

ホームページの抜粋
Eclipse Distribution License 1.0 (BSD)
Eclipse Public License 2.0
一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception

JBoss repackaging of the OpenJDK ORBは、

ライセンスファイルより
GNU General Public License, version 2 with the GNU Classpath Exception

のようなので、jarファイルをクラスパス上に追加して使う分には、ソースコードを公開することができない非OSSの商用アプリケーションでも使うことができそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

java.nio.file.PathMatcher::matchesでjava.lang.IllegalArgumentExceptionが発生する

事象

いきなり個人的な事情で申し訳ないのですが、一連のファイルやディレクトリに対して、globを使ってフィルタリングしたいという場合、Files::newDirectoryStream(Path, String)を利用してきました。というより、その方法しか知りませんでした。

このAPIでは第2引数にglobを指定します。以下がその例になります。

var dir = Paths.get(".");
try (var paths = Files.newDirectoryStream(dir, "**.py")) {
    for (var path : paths) {
        doSomething(path);
    }
} catch (IOException e) {
    e.printStackTrace();
}

正直なところDirectoryStreamは使いにくいAPIで、とくに「java.util.Streamにつなげづらい」というのが、個人的にはもっともつらい。要するに「globによるフィルタリングをStream処理に組み込みたい」わけです。そこで、いろいろと調べてまわったところ、java.nio.file.PathMatcherを利用すればよいということが判明。

var dir = Paths.get(".");
var matcher = FileSystems.getDefault().getPathMatcher("**.py");
Files.walk(dir).filter(matcher::matches).forEach(path -> doSomething(path));

さっそくこれを実行したところ、以下のようにIllegalArgumentExceptionが発生し、想定通りに動作しませんでした。

Exception in thread "main" java.lang.IllegalArgumentException
    at java.base/sun.nio.fs.WindowsFileSystem.getPathMatcher(WindowsFileSystem.java:262)
    at Main.main(Main.java:19)

原因と対処

今回は「JavaDocをちゃんと読みましょう」案件でした…。FileSystem::getPathMatcher(String)のJavaDocには次の通り記述されています。

syntaxAndPattern パラメータは、構文とパターンを識別し、次の形式をとります。

syntax:pattern

ここでの ':' はそれ自体を表します。
FileSystem 実装では、「glob」および「regex」構文をサポートしますが、その他をサポートすることもできます。構文コンポーネントの値は大文字小文字に関係なく比較されます。

今回のようにglobパターンを利用したい場合はglob:**.pyというような書き方をする必要があるということです。それを使って上記の例を書き換えたものが以下になり、これを実行するとIllegalArgumentExceptionが発生しなくなりました。

var dir = Paths.get(".");
var matcher = FileSystems.getDefault().getPathMatcher("glob:**.py");
Files.walk(dir).filter(matcher::matches).forEach(path -> doSomething(path));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む