キャスレーコンサルティング 技術ブログ
  • HOME>
  • キャスレーコンサルティング 技術ブログ

こんにちは、キャスレーコンサルティング CSVクリエーション部:平野です。
またまた一年ぶりにブログを書きます。よろしくお願いします。
今回はcssでエレベーターを作って、動くおじさんを乗せてみたいと思います。 (続きを読む…)


こんにちは。キャスレーコンサルティング システムデザイン部の阿部です。

今回は、CSSのtransformプロパティを活用し、HTMLとCSSのみによる三次元図形の作成とその操作を行ってみました。

まずは完成図です。
各Λをクリックすることでそれぞれの方向に動き、中央の○をクリックすることで初期表示に戻ります。

以下のように手順を解説いたします。

1.transformプロパティによる要素の変形
2.変形した要素で立方体を作る

  • 2.1.面の作成
  • 2.2.面の基準を指定
  • 2.3.各面を回転させる

3.ラベルとラジオボタンで立方体を動かす

  • 3.1.ボタンの作成
  • 3.2.各ボタンに回転のアニメーションを設定する

4.おわりに

1.transformプロパティによる要素の変形

要素の変形を行うCSSのtransformプロパティは、X軸方向、Y軸方向といった二次元的な変形に加え、
Z軸方向への三次元的な変形を行うことができます。

このプロパティによって変形させた要素を面として組み合わせると、三次元的な図形を作成することができます。

作成にあたっては、以下の関数をメインに使用します。

rotateX()
→要素をX軸を中心として指定の角度に回転させる
rotateY()
→要素をY軸を中心として指定の角度に回転させる
rotateZ()
→要素をZ軸を中心として指定の角度に回転させる

translateX()
→要素をX軸方向へ指定の距離分移動させる
translateY()
→要素をY軸方向へ指定の距離分移動させる
translateZ()
→要素をZ軸方向へ指定の距離分移動させる

また、要素を入れ子にするため、以下のプロパティを使い、子孫要素に対しても三次元的な描画を摘要できるようにします。

transform-style: preserve-3d
→指定した要素の子孫要素に対しても三次元的な描画を行う

transformプロパティは3次元的な変形を行う場合、以下のブラウザに対応します。

SnapCrab_NoName_2017-9-12_19-28-27_No-00
参考:caniuse.com

今回使用するtransform-style: preserve-3dプロパティがIEに対応していないため、
それ以外の対応ブラウザでの閲覧をお願いいたします。

2.変形した要素で立方体を作る

それでは、正方形の形をとった要素を組み合わせ、立方体を作成してみましょう。
transformプロパティの、要素を回転させるrotate関数と、要素を移動させるtranslate関数を組み合わせます。

2.1.面の作成

まずは面となるdiv要素を6面分作成します。
後に各面を一括で操作できるよう、ここでは各面を.cubeの子要素として配置します。

2.2.面の基準を指定

次に、positionプロパティで位置を指定し、作成した6面を重ねます。
preserve-3dを指定し.cubeの子要素となる各面を三次元的に展開できるようにします。
ついでに、少し色を透過しておきます。

CSS(抜粋)

.cube{
  position: relative;
  transform-style: preserve-3d;
}
.cube div{
  border: 1px solid;
  position: absolute;
  opacity:0.7;
}

これで立方体作成の準備が整いました。

2.3.各面を回転させる

前面と背面は、Z軸を基準に面を前後に移動させることによって作成できます。
上面、底面、右面、左面はそれぞれY軸、X軸を中心に回転させ、それを移動させます

これで立方体が完成します。

CSS(抜粋)

.s_front{
  background: red;
  transform: translateZ(100px);
}
.s_back{
  background: orange;
  transform: translateZ(-100px);
}
.s_right{
  background: yellow;
  transform: rotateY(90deg) translateZ(100px);
}
.s_left{
  background: green;
  transform: rotateY(-90deg) translateZ(100px);
}
.s_top{
  background: blue;
  transform: rotateX(90deg) translateZ(100px);
}
.s_bottom{
  background: purple;
  transform: rotateX(-90deg) translateZ(100px);
}

そのままでは分かりづらいので、立方体に以下を指定してやや回転させた結果がこちらです。

CSS(抜粋)

.cube{
 position: relative;
 transform-style: preserve-3d;
 transform: rotateX(-20deg) rotateY(20deg) rotateZ(-20deg);
}

3.ラベルとラジオボタンで立方体を動かす

3次元像の操作はjQueryなどで行うことが一般的ですが、ある程度の操作であれば、
擬似クラスのcheckedを活用することにより、HTMLとCSSのみで実現することができます。

ここでは、上下左右、正面のボタンを作成し、立方体をそれぞれの方向に動かしてみます。

3.1.ボタンの作成

まずはラジオボタンとその操作を行うそれっぽい見た目のラベルを立方体の手前に配置します。

HTML(抜粋)

<div class="base">
  <label class="label" for="top"></label>
  <label class="label" for="left"></label>
  <label class="label" for="right"></label>
  <label class="label" for="bottom"></label>
  <label class="label" for="center"></label>
  <input type="radio" name="labels" id="top">
  <input type="radio" name="labels" id="left">
  <input type="radio" name="labels" id="right">
  <input type="radio" name="labels" id="bottom">
  <input type="radio" name="labels" id="center">
  <div class="cube">

CSS(抜粋)

input{
  display: none;
}
label{
  height: 10px;
  width: 10px;
  background: none;
  position: absolute;
  z-index: 10;
  border-top: 3px solid white;
  border-left: 3px solid white;
}
.top{
  top: 0;
  left: 50%;
  margin-left: -6px;
  transform: rotate(45deg);
}
.left{
  top:50%;
  left:0;
  margin-top: -6px;
  transform: rotate(-45deg);
}
.right{
  top:50%;
  right:0;
  margin-top: -6px;
  transform: rotate(135deg);
}
.bottom{
  bottom: 0;
  left: 50%;
  margin-left: -6px;
  transform: rotate(225deg);
}
.center{
  height: 20px;
  width: 20px;
  border-radius: 10px;
  border:none;
  background: white;
  top: 50%;
  left: 50%;
  margin-top: -9px;
  margin-left: -9px;
}

3.2.各ボタンに回転のアニメーションを設定する

それぞれがチェックされた際の回転方向を指定します。
ここでは、各矢印をクリックしたらそれぞれの方向に90度回転、中央の丸をクリックすると初期表示に戻るようにします。

CSS(抜粋)

.cube{
  position: relative;
  transform-style: preserve-3d;
  transition: 1s;
}

#top:checked ~ .cube{
  transform: rotateX(90deg);
}
#bottom:checked ~ .cube{
  transform: rotateX(-90deg);
}
#left:checked ~ .cube{
  transform: rotateY(-90deg);
}
#right:checked ~ .cube{
  transform: rotateY(90deg);
}

また、.cubeにtransitionを指定し、これらの回転を滑らかに行えるようにします。

CSS(抜粋)

.cube{
  position: relative;
  transform-style: preserve-3d;
  transition: 1s;
}

これで、立方体を操作できるようになりました!
改めて完成図を貼ってみます。

4.おわりに

いかがでしたでしょうか。
全てのブラウザに対応しているわけではないので、業務レベルで使えるかどうかは微妙なところですが、
CSSだけで三次元図形を軽快に操作ができるのは驚きですね。

今回メインで使用したtransformプロパティは、使いこなすとCSSだけでも様々な表現ができます。
埋め込みに使用したCodePenにも、素晴らしい作品が多数掲載されていますので、ぜひ見てみてください!


こんにちは。
キャスレーコンサルティングのSI(システムインテグレーション)部の田中(雅)です。

Rubyを利用して、CSV,XML,JSON でのファイル出力・入力について考察していきます。
プログラムを組む時に、どの形式のファイルにするかなどを決める際の、参考にして頂ければ幸いです。

開発環境

今回は、「Cloud9」で作成しました。
以下、作成された環境のversionを確認したものです。

ruby 2.3
Rails 5.0.3
mysql 5.5.54

プログラムだけの勉強などをしたい時は、非常に簡単に環境ができるので便利ですね。

作成したプログラムファイル

railsのプロジェクトルートディレクトリ
  └lib
    └tasks
     └output_csv.rb–CSV出力プログラム
     └output_xml.rb–XML出力プログラム
     └output_json.rb–JSON出力プログラム
     └read_csv.rb–CSV入力プログラム
     └read_xml.rb–XML入力プログラム
     └read_json.rb–JSON入力プログラム

CSVファイルを出力

名前(name)、年齢(age)、性別(sex)
という簡単な構造のテーブルからSELECTを行い、CSVファイルとして出力する処理になります。
事前にこのテーブルに、10,000件のレコードを登録しておきます。

output_csv.rb

require "csv"

class Tasks::OutputCsv
  def self.execute
    # DBからデータ取得
    sample_data = DataFileSample.all

    # ファイル出力
    csv_doc = CSV.open('output/test.csv', 'w')
    csv_doc.puts ["name", "age", "sex"]

    # DBから取得したデータを1件ずつCSVに追加
    for row_data in sample_data do
      csv_doc.puts [row_data.name, row_data.age, row_data.sex]
    end
  end
end

これでoutputディレクトリの配下に、test.csvというファイルが作成されます。

name,age,sex
キャスレー1,65,男
キャスレー2,81,女
キャスレー3,82,男
キャスレー4,81,女
キャスレー5,70,男
キャスレー6,42,女
キャスレー7,12,男
キャスレー8,29,女
キャスレー9,73,男

XMLファイルを出力

CSVファイルの出力と同じテーブルを使用しますが、
XML形式の場合、階層構造を作る必要がある為、若干コード量が増えます。

output_xml.rb

require 'rexml/document'
require 'rexml/formatters/pretty'

class Tasks::OutputXml
  def self.execute
    xmldoc = REXML::Document.new
    xmldoc << REXML::XMLDecl.new('1.0', 'UTF-8')
    # ルートノードを作成
    root = REXML::Element.new('datas')
    xmldoc.add_element(root)
    # 子ノードを作成
    child = REXML::Element.new('data')

    # DBからデータ取得
    sample_data = DataFileSample.all

    # DBから取得したデータを1件ずつXMLに追加
    for row_data in sample_data do
      child.add_element("name").add_text(row_data.name)
      child.add_element("age").add_text("#{row_data.age}")
      child.add_element("sex").add_text(row_data.sex)
    end
    root.add_element(child)

    # ファイル出力
    File.open('output/test.xml', 'w') do |file|
      xmldoc.write(file, 2)
    end
  end
end

こちらもoutputディレクトリ配下に、test.xmlという名前で作成されます。

<?xml version='1.0' encoding='UTF-8'?>
<datas>
  <data>
    <name>
      キャスレー1
    </name>
    <age>
      65
    </age>
    <sex>
      男
    </sex>
    <name>
      キャスレー2
    </name>
    <age>
      81
    </age>
・
・

何も指定しないと、改行してしまうんですね。。

JSONファイルを出力

CSVファイルの出力と、同じテーブルを使用します。
こちらも階層構造を作る必要はありますが、ハッシュにしてしまえば使えるので比較的簡単です。
output_json.rb

class Tasks::OutputJson
  def self.execute
    json_array = []

    # DBからデータ取得
    sample_data = DataFileSample.all

    # DBから取得したデータを1件ずつ配列に追加
    for row_data in sample_data do
      json_row = {}
      json_row[:name] = row_data.name
      json_row[:age] = row_data.age
      json_row[:sex] = row_data.sex
      json_array.push(json_row);
    end

    # 整形
    json_str = JSON.pretty_generate(json_array)

    # ファイル出力
    File.open('output/test.json', 'w') do |file|
      file.puts(json_str)
    end
  end
end

こちらもoutputディレクトリ配下に、test.jsonという名前で作成されます。

[
  {
    "name": "キャスレー1",
    "age": 65,
    "sex": "男"
  },
  {
    "name": "キャスレー2",
    "age": 81,
    "sex": "女"
  },
  {
    "name": "キャスレー3",
    "age": 82,
    "sex": "男"
  },
・
・

それぞれのファイルサイズ

それぞれ、ファイル作成した時のサイズは

CSV = 0.26MB
XML = 1.1MB
JSON = 0.73MB

となりました。

CSVが1番小さく、XMLの4分の1以下ですね。

それでは、これらのファイルを読み込んだ時のメモリ使用量を見てみましょう!

CSVファイルを入力

上で作成したtest.csvを、プログラムで読み込みメモリ使用量をプリントします。
read_csv.rb

require 'csv'
require 'objspace'

class Tasks::ReadCsv
  def self.execute
    data_list = CSV.read('output/test.csv')
    # 読み込んだCSVのサイズ
    puts "#{ObjectSpace.memsize_of_all} B"
  end
end

プロジェクトルートディレクトリからコマンド実行

$ rails runner Tasks::ReadCsv.execute
Running via Spring preloader in process 2981
26317626 B

という結果が出力されました。

XMLファイルを入力

上で作成したtest.xmlをプログラムで読み込み、メモリ使用量をプリントします。
read_xml.rb

require 'rexml/document'
require 'objspace'

class Tasks::ReadXml
  def self.execute
    # XMLファイルの読み込み
    xmldoc = REXML::Document.new(open("output/test.xml"))

    # 読み込んだXMLのサイズ
    puts "#{ObjectSpace.memsize_of_all} B"
  end
end

プロジェクトルートディレクトリからコマンド実行

$ rails runner Tasks::ReadXml.execute
Running via Spring preloader in process 2994
74746687 B

という結果が出力されました。

JSONファイルを入力

上で作成したtest.jsonをプログラムで読み込み、メモリ使用量をプリントします。
read_json.rb

require 'json'
require 'objspace'

class Tasks::ReadJson
  def self.execute
    json_hash = {}
    # Jsonファイルの読み込み
    File.open('output/test.json') do |file|
      json_hash = JSON.load(file)
    end
    # 読み込んだJSONのサイズ
    puts "#{ObjectSpace.memsize_of_all} B"
  end
end

プロジェクトルートディレクトリからコマンド実行

rails runner Tasks::ReadJson.execute
Running via Spring preloader in process 3008
33209782 B

という結果が出力されました。

各ファイル形式のファイルの読み込み

こちらで、作成したファイルを読み込んだ時のメモリサイズは

CSV = 25.1MB
XML = 71.2MB
JSON = 31MB

となりました。

考察

CSVはファイル自体は、JSONの2倍以上ありますが
メモリのサイズを見ると、JSONより若干小さい程度になっており
予想と異なる結果になりました。

使用するデータ構造にもよりますが、
簡易な構造のデータであれば、JSONが使いやすく
かつ、軽く使える形式かと考えられます。

もちろん、サーバースペックも関係しますし
10MBを超えるサイズになることが想定される場合などは
範囲をきめてファイルを分けるか、データの持ち方自体検討する必要があります。

終わりに

最後までお読み頂き、ありがとうございました。


初めまして。
キャスレーコンサルティング SI(システム・インテグレーション)部の高田です。

普段Javaばかりで他の言語に触れていなかったので、
今まで触った事のない.NET Coreという環境で、ASP.NET Core の勉強をしてみようと思います。

今回はWindowsで”Hello World!”を表示するWebサイトを作成して、仮想環境のLinuxで動かす環境を作ってみました。

まず、.NET CoreとASP.NET Coreについての説明をさせていただきます。 (続きを読む…)


こんにちは。 キャスレーコンサルティング SI(システム・インテグレーション)部の満石です。

今回は、バイトコードを操作することでEnumの挙動を変える方法を簡単な例で紹介します。

予めお断りしておきますが、バイトコードを操作してEnumを書き換えることは基本的におすすめできません。
なぜなら、ソースコードから読み解けるものとは違う挙動をさせることになり、特に保守の観点でリスクが大きいからです。

しかし、大量のコード定義用のEnumが存在しているけれど、それをリリース無しで書き換える必要があるような
特殊な条件の場合に有効な手段になることもあります。

書き換える内容

以前、再帰的ジェネリクスを活用したJava8時代のコード定義Enum用インタフェースを作成してみたで作成した、F1Constructorを書き換え対象のEnumとします。

書き換え内容は2017年第4戦終了時のコンストラクターズポイントランキング順を持つ状態から
2017年第11戦終了時のコンストラクターズポイントランキング順への変更です。

とは言っても、ザウバーが9位、マクラーレンが10位から逆転して、
9位がマクラーレン、10位がザウバーに入れ替わるだけです。

実行環境

今回はバイトコード操作ライブラリとして、Javassistを使います。
バージョンは現時点での最新の安定バージョンである、3.21.0-GAです。
Javaのバージョンも最新の1.8.0_144で実行します。

バイトコードを書き換えるプログラムの作成

バイトコードを書き換えるプログラムは、書き換え対象となるプログラムとは別物(別のjarファイル)として
作成する必要があります。

なぜなら、バイトコードを書き換えるのはクラスがロードされる前に限られているからです。
クラスがロードされた後に書き換えようとすると、java.lang.LinkageErrorが発生します。

そのため、mainメソッドよりも前に実行される、premainというメソッドを実装したクラスを作成する必要があります。

premainメソッドの宣言は

public static void premain(String agentArgs, Instrumentation inst)

と決められています。

Javaにpremainなんてメソッドがあるのか!と驚く人いると思います。
実はJava SE 5から使えるようになっているので、もう使えるようになってから10年以上経っています。

このpremainメソッドの第2引数にある、Instrumentationはjava.lang.instrumentパッケージで定義されている
インタフェースです。

このInstrumentationに宣言されているメソッドの中にaddTransformerというメソッドがあり、
引数にはClassFileTransformerという、今回の目的にそのものズバリな名前のインタフェースを渡すようになっています。

ClassFileTransformerインタフェースにはただ1つtransformというメソッドが宣言されていて、
今回はこのメソッドの中身をJavassistを使用しながら実装することになります。

では、実装したコード(EnumRewritingクラス)を見てみましょう。

package com.example;

import javassist.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;

public class EnumRewriting {
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        instrumentation.addTransformer(
                (loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {
            // クラスがcom.example.F1Constructorの場合だけバイトコード書き換えを実行
            if (className.equals("com/example/F1Constructor")) {
                // デフォルトのクラスパスを持つClassPoolオブジェクトを取得
                ClassPool classPool = ClassPool.getDefault();
                // クラスファイルがバイト配列として読み込まれたものをByteArrayInputStreamにする
                ByteArrayInputStream stream = new ByteArrayInputStream(classfileBuffer);
                try {
                    // クラスを作成、つまりcom.example.F1Constructorを作成
                    CtClass ctClass = classPool.makeClass(stream);
                    // staticイニシャライザのブロックを取得
                    CtConstructor ctConstructor = ctClass.getClassInitializer();
                    // マクラーレンの順序を9に設定
                    ctConstructor.insertAfter("MCLAREN = new com.example.F1Constructor(\"MCLAREN\", 3, \"04\", \"マクラーレン\", 9);");
                    // ザウバーの順序を10に設定
                    ctConstructor.insertAfter("SAUBER = new com.example.F1Constructor(\"SAUBER\", 7, \"08\", \"ザウバー\", 10);");
                    // $VALUESを上書き
                    ctConstructor.insertAfter("$VALUES = new com.example.F1Constructor[] { FERRARI, FORCE_INDIA, HAAS, MCLAREN, MERCEDES, RED_BULL, RENAULT, SAUBER, TORO_ROSSO, WILLIAMS };");
                    // 書き換えたcom.example.F1Constructorをバイトコードに変換して、Metaspaceに読み込ませる
                    return ctClass.toBytecode();
                } catch (IOException | CannotCompileException e) {
                    // 上記例外発生時にスタックトレースを出力する。これを書いておかないと例外発生時にコンソールに何も出力されない
                    e.printStackTrace();
                    IllegalClassFormatException illegalClassFormatException = new IllegalClassFormatException();
                    illegalClassFormatException.initCause(e);
                    // 例外はIllegalClassFormatExceptionをスローするよう決められているが、挙動はnullをreturnした場合と同じで、コンソールには何も出力されない
                    throw illegalClassFormatException;
                }
            }
            // バイトコードを書き換えない場合はnullをreturnする決まりになっている
            return null;
        });
    }
}

各行がそれぞれどのような処理をしているかは上記のコード内のコメントを見ていただくとして、
どうして、このような実装になるのかをcom.example.F1Constructorを逆コンパイルしたコードで解説します。

バイトコード書き換え前後の姿

まず、書き換え対象となるcom.example.F1Constructorのソースコードがこれです。

package com.example;

/**
 * F1コンストラクター
 */
public enum F1Constructor implements CodeEnum<F1Constructor> {
    FERRARI("01", "フェラーリ", 2),
    FORCE_INDIA("02", "フォース・インディア", 4),
    HAAS("03", "ハース", 7),
    MCLAREN("04", "マクラーレン", 10),
    MERCEDES("05", "メルセデス", 1),
    RED_BULL("06", "レッドブル", 3),
    RENAULT("07", "ルノー", 8),
    SAUBER("08", "ザウバー", 9),
    TORO_ROSSO("09", "トロ・ロッソ", 6),
    WILLIAMS("10", "ウィリアムズ", 5);

    private String code;

    private String name;

    private int order;

    F1Constructor(String code, String name, int order) {
        this.code = code;
        this.name = name;
        this.order = order;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getOrder() {
        return order;
    }
}

最初に説明したとおり、ザウバーのorderが9、マクラーレンのorderには10が設定されています。

上記ソースコードをコンパイルしたら作成されるクラスファイルを逆コンパイルしたのが以下のコードになります。
逆コンパイルにはjadコマンドを利用しました。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name:   F1Constructor.java

package com.example;

// Referenced classes of package com.example:
//            CodeEnum

public final class F1Constructor extends Enum
    implements CodeEnum
{

    public static F1Constructor[] values()
    {
        return (F1Constructor[])$VALUES.clone();
    }

    public static F1Constructor valueOf(String name)
    {
        return (F1Constructor)Enum.valueOf(com/example/F1Constructor, name);
    }

    private F1Constructor(String s, int i, String code, String name, int order)
    {
        super(s, i);
        this.code = code;
        this.name = name;
        this.order = order;
    }

    public String getCode()
    {
        return code;
    }

    public String getName()
    {
        return name;
    }

    public int getOrder()
    {
        return order;
    }

    public static final F1Constructor FERRARI;
    public static final F1Constructor FORCE_INDIA;
    public static final F1Constructor HAAS;
    public static final F1Constructor MCLAREN;
    public static final F1Constructor MERCEDES;
    public static final F1Constructor RED_BULL;
    public static final F1Constructor RENAULT;
    public static final F1Constructor SAUBER;
    public static final F1Constructor TORO_ROSSO;
    public static final F1Constructor WILLIAMS;
    private String code;
    private String name;
    private int order;
    private static final F1Constructor $VALUES[];

    static
    {
        FERRARI = new F1Constructor("FERRARI", 0, "01", "\u30D5\u30A7\u30E9\u30FC\u30EA", 2);
        FORCE_INDIA = new F1Constructor("FORCE_INDIA", 1, "02", "\u30D5\u30A9\u30FC\u30B9\u30FB\u30A4\u30F3\u30C7\u30A3\u30A2", 4);
        HAAS = new F1Constructor("HAAS", 2, "03", "\u30CF\u30FC\u30B9", 7);
        MCLAREN = new F1Constructor("MCLAREN", 3, "04", "\u30DE\u30AF\u30E9\u30FC\u30EC\u30F3", 10);
        MERCEDES = new F1Constructor("MERCEDES", 4, "05", "\u30E1\u30EB\u30BB\u30C7\u30B9", 1);
        RED_BULL = new F1Constructor("RED_BULL", 5, "06", "\u30EC\u30C3\u30C9\u30D6\u30EB", 3);
        RENAULT = new F1Constructor("RENAULT", 6, "07", "\u30EB\u30CE\u30FC", 8);
        SAUBER = new F1Constructor("SAUBER", 7, "08", "\u30B6\u30A6\u30D0\u30FC", 9);
        TORO_ROSSO = new F1Constructor("TORO_ROSSO", 8, "09", "\u30C8\u30ED\u30FB\u30ED\u30C3\u30BD", 6);
        WILLIAMS = new F1Constructor("WILLIAMS", 9, "10", "\u30A6\u30A3\u30EA\u30A2\u30E0\u30BA", 5);
        $VALUES = (new F1Constructor[] {
            FERRARI, FORCE_INDIA, HAAS, MCLAREN, MERCEDES, RED_BULL, RENAULT, SAUBER, TORO_ROSSO, WILLIAMS
        });
    }
}

今回の書き換え処理でやっていることをこの逆コンパイルしたコードで表すと以下のようになります。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name:   F1Constructor.java

package com.example;

// Referenced classes of package com.example:
//            CodeEnum

public final class F1Constructor extends Enum
    implements CodeEnum
{

    public static F1Constructor[] values()
    {
        return (F1Constructor[])$VALUES.clone();
    }

    public static F1Constructor valueOf(String name)
    {
        return (F1Constructor)Enum.valueOf(com/example/F1Constructor, name);
    }

    private F1Constructor(String s, int i, String code, String name, int order)
    {
        super(s, i);
        this.code = code;
        this.name = name;
        this.order = order;
    }

    public String getCode()
    {
        return code;
    }

    public String getName()
    {
        return name;
    }

    public int getOrder()
    {
        return order;
    }

    public static final F1Constructor FERRARI;
    public static final F1Constructor FORCE_INDIA;
    public static final F1Constructor HAAS;
    public static final F1Constructor MCLAREN;
    public static final F1Constructor MERCEDES;
    public static final F1Constructor RED_BULL;
    public static final F1Constructor RENAULT;
    public static final F1Constructor SAUBER;
    public static final F1Constructor TORO_ROSSO;
    public static final F1Constructor WILLIAMS;
    private String code;
    private String name;
    private int order;
    private static final F1Constructor $VALUES[];

    static
    {
        FERRARI = new F1Constructor("FERRARI", 0, "01", "\u30D5\u30A7\u30E9\u30FC\u30EA", 2);
        FORCE_INDIA = new F1Constructor("FORCE_INDIA", 1, "02", "\u30D5\u30A9\u30FC\u30B9\u30FB\u30A4\u30F3\u30C7\u30A3\u30A2", 4);
        HAAS = new F1Constructor("HAAS", 2, "03", "\u30CF\u30FC\u30B9", 7);
        MCLAREN = new F1Constructor("MCLAREN", 3, "04", "\u30DE\u30AF\u30E9\u30FC\u30EC\u30F3", 10);
        MERCEDES = new F1Constructor("MERCEDES", 4, "05", "\u30E1\u30EB\u30BB\u30C7\u30B9", 1);
        RED_BULL = new F1Constructor("RED_BULL", 5, "06", "\u30EC\u30C3\u30C9\u30D6\u30EB", 3);
        RENAULT = new F1Constructor("RENAULT", 6, "07", "\u30EB\u30CE\u30FC", 8);
        SAUBER = new F1Constructor("SAUBER", 7, "08", "\u30B6\u30A6\u30D0\u30FC", 9);
        TORO_ROSSO = new F1Constructor("TORO_ROSSO", 8, "09", "\u30C8\u30ED\u30FB\u30ED\u30C3\u30BD", 6);
        WILLIAMS = new F1Constructor("WILLIAMS", 9, "10", "\u30A6\u30A3\u30EA\u30A2\u30E0\u30BA", 5);
        $VALUES = (new F1Constructor[] {
            FERRARI, FORCE_INDIA, HAAS, MCLAREN, MERCEDES, RED_BULL, RENAULT, SAUBER, TORO_ROSSO, WILLIAMS
        });
        MCLAREN = new F1Constructor("MCLAREN", 3, "04", "\u30DE\u30AF\u30E9\u30FC\u30EC\u30F3", 9);
        SAUBER = new F1Constructor("SAUBER", 7, "08", "\u30B6\u30A6\u30D0\u30FC", 10);
        $VALUES = new F1Constructor[] { FERRARI, FORCE_INDIA, HAAS, MCLAREN, MERCEDES, RED_BULL, RENAULT, SAUBER, TORO_ROSSO, WILLIAMS };
    }
}

staticイニシャライザブロックの最後にctConstructor.insertAfterメソッドで追加した3行が入ることで、
定数を上書きされた結果挙動が変わることがご理解いただけると思います。

バイトコードを書き換えるとは言っても、Javassitを使うと人間に簡単に理解できることコードで
あっさり実現できてしまうことに拍子抜けした方もいるのではないでしょうか?

ビルドと実行

バイトコードを書き換えるプログラム(EnumRewritingクラス)はjarファイルとしてビルドする必要があります。
今回はGradleでビルドすることにしたため、build.gradleファイルを作成しました。

group 'javassist-sample'
version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'javassist', name: 'javassist', version: '3.12.1.GA'
}

jar {
    manifest {
        attributes 'Premain-Class': 'com.example.EnumRewriting', 'Boot-Class-Path': 'javassist-3.12.1.GA.jar', 'Can-Redefine-Classes': 'true'
    }
}

dependenciesには必要なライブラリとしてjavassistを指定しています。

バイトコード書き換えを行う場合、jarファイルの中に以下の3項目を書いたマニフェストファイルが必要で、
上記jar.manifestブロック内はそのための記述です。

  • Premain-Class:この項目のみ設定必須。バイトコード操作を行うクラスを指定するので、
    今回はcom.example.EnumRewritingになる。
  • Boot-Class-Path:バイトコード操作に必要なライブラリやクラスを記述する。
    今回はjavassistのjarファイル名になる。
  • Can-Redefine-Classes:バイトコードを操作してクラスの再定義を許すかどうかを指定するため、
    今回はtrueになる。

ビルドして出来上がるjarファイルは、javassist-sample-1.0-SNAPSHOT.jarになります。

mainメソッドを実装したクラスとして以下のMainクラスを作成しました。
F1Constructorの列挙子を表示順にソートした結果を出力するだけのシンプルな実装です。

package com.example;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<F1Constructor> orderedList = CodeEnum.getOrderedList(F1Constructor.class);
        orderedList.forEach(e -> System.out.println(e.getName()));
    }
}

上記Mainクラスをまずは単純に以下のコマンドで実行してみます。

java com.example.Main

結果は

メルセデス
フェラーリ
レッドブル
フォース・インディア
ウィリアムズ
トロ・ロッソ
ハース
ルノー
ザウバー
マクラーレン

になり、実装どおりマクラーレンよりザウバーが上に来ています。

今度はEnumを書き換えるため、javaコマンドを実行するディレクトリに先ほどビルドした
javassist-sample-1.0-SNAPSHOT.jarと、javassist-3.12.1.GA.jarも置いて、
以下のコマンドを実行します。

バイトコード書き換えはmainメソッドが動く前に動作させる必要があるため、作成したjarファイルは
-javaagentオプションで指定する必要があります。

java -javaagent:javassist-sample-1.0-SNAPSHOT.jar com.example.Main

結果は、

メルセデス
フェラーリ
レッドブル
フォース・インディア
ウィリアムズ
トロ・ロッソ
ハース
ルノー
マクラーレン
ザウバー

となり、無事にマクラーレンとザウバーの順序が入れ替わりました!

まとめ

果たしてこの記事が誰かの役に立つことはあるのか!?と書いている本人が思ってしまうくらい
マニアックな内容になりましたが、Javaってこんなこともできるんだ・・・!と楽しんでいただけたなら幸いです。

再度、念押ししておきますが、このようなバイトコード書き換えはやらずに済むならその方がいいです。
通常のプログラムよりかなりデバッグしづらく、思ったように動かない場合は相当難航します。
バイトコード書き換えは慎重に。


Hi, This is Hara from System Inegration Department.

I’ve recently found myself very interested in the Vue.js and wanted to learn more about it.
Today, I’m going to write about creating simple webpage using Vue.js

What is Vue.js?

Vue.js is a progressive framework for building interactive web interfaces using MVVM.

To use Vue.js you can include or to use CLI, I recommend using a CLI since this is more realistic to build Vue.js application. So let’s use the recommended way this time. (続きを読む…)


みなさん、こんにちは!
キャスレーコンサルティング、システムデザイン部の石井です。

はじめに

Javaで開発を行なうに当たり、様々なフレームワークを使用する機会があると思います。
私自身、今まで主にseasar2での開発経験のみのため、
他のフレームワークには、どのような特徴があるのかと疑問に思い調べ初めました。

そんな中で、特に興味を持った「Apache Wicket」(以降Wicketと表現します。)について、
実際に開発環境を作成しましたので、ご紹介します。 (続きを読む…)


こんにちは。キャスレーコンサルティングCSVクリエーション部の秦です。

今回は、コミュニケーションツールのひとつであるSlackを使って
チャットボットを作る手順をご紹介します。 (続きを読む…)


こんにちは。
キャスレーコンサルティングのSI(システム・インテグレーション)部の高橋です。

これまでも技術ブログでは、
Google Apps Script入門
Google Apps Scriptで、心温まる年賀状を作ろう!
Google Apps Scriptとラベルで実現する業務効率化のすゝめ
Google Apps Scriptでカレンダー連動リマインドのすゝめ

と過去に取り扱っていますが、
今回は、「未回答のイベントをリマインド」する方法をご紹介させていただきます! (続きを読む…)


こんにちは。
キャスレーコンサルティングのSI部(システム・インテグレーション部)所属の原です。

今回はJavaScriptフレームワークのVue.jsを使って、簡単なウェブページを作成する方法をご紹介します。

Vue.jsとは?

ユーザーインターフェイスを構築するいわゆる、MVVMでつくるためのフレームワークです。

Vue.jsを使えるようにするには、直接組み込む方法とCLIを使う方法がありますが
アプリを構築するにはCLIを使う方が現実的なので、今回はCLIを使う方法でウェブアプリを作っていきます。 (続きを読む…)


12345...1020...

  • Profile
    キャスレーコンサルティングの技術ブログです。
    当社エンジニアが技術面でのTips、技術系イベント等についてご紹介いたします。
  • CSV社長ブログ
  • チーム・キャスレーブログ