Ubuntu で EC2 〜 Apache Cassandra On AWS EC2 の事前準備として〜

  • ここを参考に AWS EC2 上で Apache Cassandra 1.0.5 を動かすために、前提として必要だったので以下にメモ。
  • ローカル ubuntu から ubuntu 公式 AMI をつかって EC2 でインスタンスを起動するところまで。
  • ここを見ればほぼすんなり行くはず。
ローカルの環境
  • ubuntu 11.10 (64bit)
  • $ java -version
    java version "1.6.0_23"
  • もちろん AWS のアカウントは事前に取っておく
EC2 API Tools をインストール
  • X.509 証明書を作っておいてローカルにダウンロードしておく。
    いつもどこから行くのか忘れる・・・
    このタブ
  • ec2-api-tools インストール
    $ sudo apt-get install ec2-api-tools
    
  • 環境変数
    • 東京リージョンを使うのでその指定と
    • さっき落とした X.509 証明書のパス
    $ vi ~/.bashrc
    
    export EC2_URL=https://ec2.ap-northeast-1.amazonaws.com # <- これ Asia Pacific (Tokyo)
    export EC2_PRIVATE_KEY=/path/to/aws/pk/pk-pekepekepeke.pem # X.509 証明書の
    export EC2_CERT=/path/to/aws/cert/cert-pekepekepeke.pem # X.509 証明書の
    
    $ source ~/.bashrc
    
  • 適当に ec2-xxxx を叩いて動くか確認
    $ ec2-describe-images -o self -o amazon
    
Official Ubuntu Cloud Guest AMI でインスタンス起動
  • 普段使っている公開鍵をインポートしておく
  • $ ec2-import-keypair {KEY_NAME} --public-key-file ~/.ssh/id_rsa.pub
    
  • インスタンス走らせる(ここから好きなの選んで)
  • $ ec2-run-instances ami-7c90277d -g "default" --instance-type m1.large --region ap-northeast-1 --key {KEY_NAME}
    
  • describe して running になるのを待つ
  • $ ec2-describe-instances
    
  • 行ってみる(describe したときの Public DNS をコピっておいて)
  • $ ssh ubuntu@{PUBLIC_DNS}
    
  • とりあえず無事入れたらおっk。一旦、terminate しておく。
  • $ ec2-terminate-instances {INSTANCE}
    

Apache Cassandra On AWS EC2

Apache Cassandra クラスタ環境を AWS EC2 上で
  • 公式の wiki を参考に EC2 上でクラスタ環境を構築してみた。
  • シングルノードを複数作るのとあまり変わらなかった。
  • ローカルの ubuntu から EC2 を弄るための事前準備はこっちに。

※Step.Nは公式の wiki にだいたい対応。

■ Step.1 EC2 セキュリティ・グループ「Talk To Cassandra Local Zone」の作成
$ ec2-create-group "Talk To Cassandra Local Zone" -d "Group for any machine that talks to Cassandra"
$ ec2-authorize "Talk To Cassandra Local Zone" -P tcp -p 22 -s 0.0.0.0/0

■ Step.2 あとで必要なので「Talk To Cassandra Local Zone」の {OwnerID} をメモっておく
$ ec2-describe-group "Talk To Cassandra Local Zone"
※12ケタぐらいの数字のやつ

■ Step.3 EC2 セキュリティ・グループ「Cassandra Nodes」の作成
$ ec2-create-group "Cassandra Nodes" -d "Group for any Cassandra machine"
$ ec2-authorize "Cassandra Nodes" -P tcp -p 22 -s 0.0.0.0/0

■ Step.4 あとで必要なので「Cassandra Nodes」の {OwnerID} をメモっておく
$ ec2-describe-group "Cassandra Nodes"
※12ケタぐらいの数字のやつ

■ Step.5 「Cassandra Nodes」間で以下の各ポートに接続可能なように開けておく
$ ec2-authorize "Cassandra Nodes" -P tcp -p 7000 -o "Cassandra Nodes" -u {OwnerID}
$ ec2-authorize "Cassandra Nodes" -P tcp -p 9160 -o "Cassandra Nodes" -u {OwnerID}
$ ec2-authorize "Cassandra Nodes" -P tcp -p 8080 -o "Cassandra Nodes" -u {OwnerID}
※ {OwnerID} は「Talk To Cassandra Local Zone」のほう

■ Step.6 「Talk To Cassandra Local Zone」も同様に以下の各ポートを開けておく
$ ec2-authorize "Cassandra Nodes" -P tcp -p 9160 -o "Talk To Cassandra Local Zone" -u {OwnerID}
$ ec2-authorize "Cassandra Nodes" -P tcp -p 8080 -o "Talk To Cassandra Local Zone" -u {OwnerID}
※ {OwnerID} は「Talk To Cassandra Local Zone」のほう

■ Step.7 キーペアを作成する

■ Step.8 〜 Step.10 SEED インスタンスを起動する
$ ec2-run-instances ami-7c90277d -g "Cassandra Nodes" --instance-type m1.large --region ap-northeast-1 --key {KEYPAIR_NAME}
※{KEYPAIR_NAME} は Step.7 で作ったもの
※「ami-7c90277d」という AMI は ubuntu 公式のもの。

■ Step.11 describe してパブリック DNS (={SEED_PUBLIC_DNS})とプライベート IP (={SEED_PRIVATE_IP})をコピっておく
$ ec2-describe-instances

■ Step.12 SEED になる Cassandra をセットアップ
  • インスタンスにログイン
  • $ ssh ubuntu@{SEED_PUBLIC_DNS}
    
  • JDK のインストール
  • $ sudo apt-get update
    $ sudo apt-get install openjdk-6-jdk
    $ java -version
    java version "1.6.0_23"
    
  • シングルノードのセットアップを完了する
  • SEED の Cassandra の設定
  • $ vi 1.0.5/conf/cassandra.yaml
    
    以下のとおり編集する
    • initial_token: 0
    • seeds: "{SEED_PRIVATE_IP}"
    • listen_address: {SEED_PRIVATE_IP}
    • rpc_address: 0.0.0.0
    • endpoint_snitch: org.apache.cassandra.locator.RackInferringSnitch

■ Step.13 NON SEED インスタンスを起動する
$ ec2-run-instances ami-7c90277d -g "Cassandra Nodes" --instance-type m1.large --region ap-northeast-1 --key {KEYPAIR_NAME}
describe してパブリック DNS (={NON_SEED_PUBLIC_DNS})とプライベートな IP (={NON_SEED_PRIVATE_IP})をコピっておく
$ ec2-describe-instances

■ Step.14 NON SEED の Cassandra をセットアップ
  • インスタンスにログイン
  • $ ssh ubuntu@{NON_SEED_PUBLIC_DNS}
    
  • JDK のインストール
  • $ sudo apt-get update
    $ sudo apt-get install openjdk-6-jdk
    $ java -version
    java version "1.6.0_23"
    
  • シングルノードのセットアップを完了する
  • NON SEED の Cassandra の設定
  • $ vi 1.0.5/conf/cassandra.yaml
    
    以下のとおり編集する
    • initial_token: 12345678901234567890123456789012345678
    • seeds: "{SEED_PRIVATE_IP}"
    • listen_address: {NON_SEED_PRIVATE_IP}
    • rpc_address: 0.0.0.0
    • endpoint_snitch: org.apache.cassandra.locator.RackInferringSnitch

■ 確認
  • SEED インスタンスにログイン
  • $ ssh ubuntu@{SEED_PUBLIC_DNS}
    
  • nodetool で確認する
  • $ nodetool -h localhost ring
    Address                  DC          Rack        Status State   Load            Owns    Token                                       
                                                                                   12345678901234567890123456789012345678      
    {SEED_PRIVATE_IP}        ***         ***         Up     Normal  13.41 KB        92.74%  0                                           
    {NON_SEED_PRIVATE_IP}    ***         ***         Up     Normal  6.68 KB         7.26%   12345678901234567890123456789012345678
    
  • ここまでつつがなく行っていれば、あとはcassandra-cliで遊べます。(が、keyspaceを作成する際は以下のようにしないとデータを追加する際にエラーになりました。)
  • [default@unknown] create keyspace awstest1 with placement_strategy = 'org.apache.cassandra.locator.SimpleStrategy' 
    ... and strategy_options = {replication_factor:2};
    

Apache Cassandra 1.0.5 インストール〜設定〜起動(シングルノード)

■ 環境
  • ubuntu 11.10
  • java version "1.6.0_23"(1.6の最低でもu19が必要でu21以上がベターらしい)
■ インストール
  • CASSANDRA_HOME作成
  • $ mkdir -p apache/cassandra
    $ cd apache/cassandra
    
    以下、このディレクトリで作業します
    $ pwd
    /home/ktakeda47/apache/cassandra
    
  • apache-cassandra-1.0.5-bin.tar.gzをダウンロード
  • $ wget http://www.meisei-u.ac.jp/mirror/apache/dist//cassandra/1.0.5/apache-cassandra-1.0.5-bin.tar.gz
    
  • 解凍
  • $ tar xvzf apache-cassandra-1.0.5-bin.tar.gz
    $ mv apache-cassandra-1.0.5/ 1.0.5
    
■ 設定
  • CASSANDRA_HOME/binにパスを通す
  • $ vi ~/.bashrc
    
    こんなかんじ
    export CASSANDRA_HOME=/home/ktakeda47/apache/cassandra/1.0.5
    export PATH=$PATH:$CASSANDRA_HOME/bin
    
    $ source ~/.bashrc
    
  • data_file_directories, commitlog_directory, saved_caches_directoryディレクトリ作成
  • $ mkdir data
    $ mkdir commitlog
    $ mkdir saved_caches
    
  • 設定ファイルのdata_file_directories, commitlog_directory, saved_caches_directoryをmkdirしたディレクトリへ
  • (それぞれ63行目、66行目、69行目付近)
    $ vi 1.0.5/conf/cassandra.yaml
    
  • ログディレクトリ作成
  • $ mkdir log
    
  • log4j.propertiesのログディレクトリをmkdirしたディレクトリへ
  • (35行目付近)
    $ vi 1.0.5/conf/log4j-server.properties
    
■ 起動
  • error, fatalが出なければ成功
  • $ cassandra -f
    
■ cassandra_cliからアクセス
  • 起動までつつがなく行っていれば、あとはcassandra-cliで遊べます
  • $ cassandra-cli
    

NullPointerException

Effective Java 第2版 第2章オブジェクトの生成と消滅 項目6廃れたオブジェクト参照を取り除く」に、廃れた参照に null を設定する必要があるプログラムで、廃れた参照に null を設定することで得られる派生的な利点に、
参照が間違って使用された場合に、プログラムは何も言わずに間違った処理を行うのではなく、 NullPointerException ですぐにエラーとなることです。プログラミングのエラーをできるだけ早い段階で発見することは常に有益です。
とあります。

たしかに。
好き嫌いや時と場合にもよりますが、個人的には落ちてくれたほうが良いです。


see also: Javaプログラマであるかを見分ける10の質問 7.NullPointerExceptionが発生するのは主にどういう状況か?

Mozilla Thunderbird 5.0 バックアップ 移行(旧マシン→新マシンとか)

  • ubuntu 11.04
  • thunderbird 5.0

バックアップ対象のアカウントのプロファイルを確認
thunderbird の「メニュー>編集>アカウント設定...」の中に「メッセージの保存先:」という項目があって、そこのパスが普通に使っていれば「/home/{user}/.thunderbird/gonyogonyo.default/」以下になってるので、その「gonyogonyo.default」を全部まるっとバックアップしておけば良い。
一応、設定ファイルの「profiles.ini」を見て確認。
$ cd $HOME/.thunderbird
$ cat profiles.ini
[General]
StartWithLastProfile=1

[Profile0]
Name=default
IsRelative=1
Path=gonyogonyo.default

バックアップ
$ tar cvzf gonyogonyo.tar.gz gonyogonyo.default

移行先で thunderbird インストール
インストールするだけしてとりあえず起動しても何もしない。
移行
移行元のバックアップを移行先のどっかに展開。「/home/{user}/.thunderbird」の下で良いと思うけど。
で移行先の「profiles.ini」の「Path」を編集。
$ cd $HOME/.thunderbird
$ vi profiles.ini
[General]
StartWithLastProfile=1

[Profile0]
Name=default
IsRelative=1
Path=gonyogonyo.default

確認
起動すると移行元で最後に thunderbird を使っていた状態が復元される。

android org.apache.http.client.HttpClient user-agent

  • Android 2.2 (API Level 8)
  • リクエスト
    final HttpClient client = new DefaultHttpClient();
    final HttpParams params = client.getParams();
    params.setParameter(CoreProtocolPNames.USER_AGENT, "android user-agent hoge"); // !
    final HttpGet request = new HttpGet("http://hogehoge.com/");
    try {
    	final HttpResponse response = client.execute(request);
    	// ・・・
    } catch (ClientProtocolException e) {
    	// TODO Auto-generated catch block
    } catch (IOException e) {
    	// TODO Auto-generated catch block
    }
    
    サーバ側ログ
    192.168.100.101 - - [01/Aug/2011:01:23:45 +0900] "GET / HTTP/1.1" 200 175 "-" "android user-agent hoge"
    

WebView の Scrollbar 部分の余白

WebView の Scrollbar が表示される部分の余白みたいなのが気になって。
WebView#setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET)の場合
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mWebView = new WebView(this);
        mWebView.loadUrl(URL);
        mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); // !
        mWebView.setWebViewClient(new WebViewClient());
        setContentView(mWebView);
    }
Scrollbarを常時表示させているなら違和感ないのかな。

WebView#setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY)にしてみる
   mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
あーこれだわ。標準でインストールされているブラウザとかの表示。

WebView#setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET)は?
mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
ん?SCROLLBARS_INSIDE_INSETとの違いは?

んじゃWebView#setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY)は?
ん?SCROLLBARS_INSIDE_OVERLAYとの違いは?

WebView と WebViewClient を使って HTML のソースを LogCat に流す

public class HogeActivity extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final WebView webView = new WebView(this);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new HogeWebViewClinet());
        webView.addJavascriptInterface(new WebViewLogger(), "webViewLogger");
        webView.loadUrl("http://www.yahoo.co.jp/");
        setContentView(webView);
    }
    
    class HogeWebViewClinet extends WebViewClient {
        @Override public void onPageFinished(WebView view, String url) {
            view.loadUrl("javascript:window.webViewLogger.log(document.documentElement.outerHTML);");
        }
    }
    
    class WebViewLogger {
        public void log(String str) {
            Log.d("webViewLogger", str);
        }
    }
    
}

eclair ではスローされて froyo ではスルーされる(JSONException (Unterminated string at character...))

改行コードを含む JSON を返す Servlet
@Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse)
            throws ServletException, IOException {
        servletResponse.setContentType("application/json");
        servletResponse.setCharacterEncoding("UTF-8");
        PrintWriter writer = servletResponse.getWriter();
        writer.write("{\"str\" : \"HO\nGE\"}"); // !!!
        writer.flush();
        writer.close();
    }

文字列「HOGE」のど真ん中に改行コードを含む、 JSON 的によろしくない JSON を返す Servlet を作ります。

android クライアント側
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onResume() {
        super.onResume();
        new Thread(new Runnable() {
            @Override public void run() {
                HttpClient httpClient = new DefaultHttpClient();
                HttpGet httpGet = new HttpGet("http://192.168.0.5:8080/json");
                HttpResponse httpResponse = null;
                try {
                    httpResponse = httpClient.execute(httpGet);
                } catch(ClientProtocolException e) {
                    Log.e("AndroidClient", "ClientProtocolException! " + e.getMessage());
                } catch(IOException e) {
                    Log.e("AndroidClient", "IOException! " + e.getMessage());
                }
                if(httpResponse != null) {
                    try {
                        JSONObject json = new JSONObject(EntityUtils.toString(httpResponse.getEntity()));
                        String str = json.getString("str");
                        Log.d("AndroidClient", "str : " + str);
                    } catch(ParseException e) {
                        Log.e("AndroidClient", "ParseException! " + e.getMessage());
                    } catch(JSONException e) {
                        Log.e("AndroidClient", "JSONException! " + e.getMessage());
                    } catch(IOException e) {
                        Log.e("AndroidClient", "IOException! " + e.getMessage());
                    }
                }
            }
        }).start();
    }

んで、 Servlet にリクエスト飛ばしてレスポンスを org.json.JSONObject に変換するような android クライアントアプリを作ります。

それを eclair(2.1-update1) エミュレーター上で動かす
JSONException! Unterminated string at character 13 of {"str" : "HO
GE"}

「Unterminated string at character 13 of ...」と言われて落ちます。「android クライアント側」の 23 行目で落ちて、 29 行目のエラーログが吐かれます。

今度は froyo(2.2) エミュレーター上で動かす
str : HO
GE

あら不思議。 25 行目のログが出力されちゃうじゃありませんの!

eclair と froyo のソースを見比べる

「dalvik/libcore/json/src/main/java/org/json」以下です。

eclipse 上で「 compare with 」とかしてもあまり意味ありません。インターフェースというか public なメソッドのシグニチャーだけ同じで実装は随分違っている雰囲気です。

JSONObject に食わせた String をパースする org.json.JSONTokener を見比べてみると・・・

public class JSONTokener {
// (中略)
    public String nextString(char quote) throws JSONException {
        char c;
        StringBuilder sb = new StringBuilder();
        for (;;) {
            c = next();
            switch (c) {
            case 0:
            case '\n':
            case '\r':
                throw syntaxError("Unterminated string");

上は eclair

下が froyo (private な深いメソッドまで行ってますが、まあこの辺だと思います)

public class JSONTokener {
// (中略)
    private int nextCleanInternal() throws JSONException {
        while (pos < in.length()) {
            int c = in.charAt(pos++);
            switch (c) {
                case '\t':
                case ' ':
                case '\n':
                case '\r':
                    continue;

スルーとスローは随分違うと思いますが、 JSON を返す側(ここでは Servlet 側)がちゃんとエスケープすれば、どちらもなんの問題も起きません。

アクセサ( getter / setter)・不変クラス( immutable )・カプセル化( encapsulation )

public interface Radioactivity {
 /**
  * 年間放射線量を取得する。
  * @return ミリシーベルト
  */
 double getRadiation();
 /**
  * 設定されている線量が安全かどうか
  * @return true の場合、安全
  */
 boolean isSafe();
}
/**
 * 平常時
 */
public class Normal implements Radioactivity {
 private double radiation;
 public Normal(double radiation) {
  this.radiation = radiation;
 }
 /**
  * @see Radioactivity#getRadiation()
  */
 public double getRadiation() {
  return this.radiation;
 }
 /**
  * @see Radioactivity#isSafe()
  */
 public boolean isSafe() {
  return this.radiation < 1.0d;// ふつうはこのぐらい
 }
}
/**
 * 緊急時
 */
public class Emergency implements Radioactivity {
 private double radiation;
 public Emergency(double radiation) {
  this.radiation = radiation;
 }
 /**
  * @see Radioactivity#getRadiation()
  */
 public double getRadiation() {
  return this.radiation;
 }
 /**
  * @see Radioactivity#isSafe()
  */
 public boolean isSafe() {
  return this.radiation < 20.0d;// 緊急なので致し方なく
 }
}
  • アクセサを getter のみにすることで一度設定した年間の被曝線量を変更できなくする(不変クラス)( immutable )
  • #isSafe() メソッドは時の政府の都合により内部の処理を変更することができる(カプセル化)( encapsulation )
see also: Javaプログラマであるかを見分ける10の質問 5.フィールドのアクセス修飾子をprivateにしgetter/setterメソッドを提供する事でフィールドを参照する設計方針を取る主な理由を説明せよ

チェック例外( checked exception )と非チェック例外( unchecked exception )

Java の例外
  • コンストラクタやメソッドが実行された時に、戻り値とは別の例外的な値を呼び出し元に戻したい時に、Throwable クラスのサブクラスを生成して呼び出し元に戻すことが出来る
  • コンストラクタやメソッドを実行した結果、本来の正常な処理とは別の例外的な値がコンストラクタやメソッドから返ってきた場合に、メソッドの呼び出し元は catch 節で正常処理とは別のエラー処理のコードを書くことが出来る
チェック例外
  • 投げる側はコンストラクタやメソッドに throws 宣言しなければならない
  • 呼び出し元は try-catch ブロックで囲むか、自分のメソッドシグニチャーで throws 宣言しなければならない
非チェック例外
  • Error クラスまたは RuntimeExceptoin クラス、もしくはそのサブクラス
  • 投げる側はコンストラクタやメソッドに throws 宣言しなくても良い(
  • 呼び出し元も try-catch で囲む必要もないし、自分のメソッドシグニチャーで throws 宣言する必要もない(
see also: Javaプログラマであるかを見分ける10の質問 5.チェック例外と非チェック例外の違いを型と例外処理の観点で説明せよ。

自分の手でやりました ( Nexus S android 2.3.2 -> android 2.3.3 )

待てど暮らせど空から降って来なかったので、手持ちの Nexus S の android 2.3.2 から android 2.3.3 へのバージョンアップを、自力でやりました。簡単でした。
GRH78C → GRI40
手順はこちらのとおりやれば出来ます。 Nexus One も同じみたいです。
  1. こちらから更新ファイルをダウンロード
  2. Nexus S を USB ケーブルで PC と接続して、 android の SD カード( /sdcard )にダウンロードした ZIP ファイルをコピー
  3. Nexus S をシャットダウン
  4. Nexus S から USB ケーブルを抜く
  5. ボリュームアップ+電源ボタンを同時押しで Nexus S を起動
    FASTBOOT MODE (撮影者が写りこんでるけど(・ε・)キニシナイ!!)

  6. ボリュームボタンで RECOVERY を選択して、電源ボタンを押す
  7. この画面でボリュームアップ+電源ボタンを同時押し
    一発でうまくいかなくて、 5. からやり直すこと2回ぐらい

  8. ボリュームボタンで apply update from /sdcard を選択して、電源ボタンを押す

  9. SD カード内のファイル一覧からダウンロードした ZIP ファイルを選択して更新開始
  10. Install from sdcard complete. と表示されたら、ボリュームボタンで reboot system now を選択して電源ボタンを押す

see also: Nexus One、Nexus Sを手動でAndroid 2.3.3(GRI40)にアップデートする手順

GC(ガーベジコレクション)

オブジェクトに最初に割り当てられたメモリ空間を、そのオブジェクトが使われなくなった時(そのオブジェクトへの参照がなくなった時)に、JavaVM がそのオブジェクトが占有していたメモリ空間を自動的に解放する。



GCされない例/される例

GCされない例
class Dept { // 部署クラス
    private Emp[] emps; // 所属する従業員
    private int size;
    public Dept() {
        emps = new Emp[10]; // 従業員の席を用意
    }
    public void employ(Emp emp) { // 従業員を雇う
        if(emps.length == size) {
            emps = Arrays.copyOf(emps, 2 * size + 1); // 定員オーバーになったら、席を追加
        }
        emps[size++] = emp;
    }
    public void fire(int index) { // 従業員を解雇する
        Emp emp = emps[index];
        emp.empNo = -1;
    }
    public int getEmpsSize() {
        return size;
    }
}




OutOfMemoryErrorを発生させる
public static final void main(String[] args) {
        Dept[] depts = new Dept[3456]; // 3456個部署作って・・・
        int deptNo = 0;
        int empNo = 0;
        try {
            while(deptNo < depts.length) {
                Dept dept = new Dept();
                depts[deptNo++] = dept;
                for(int i = 0; i < 3456; i++) { // 3456人部署に雇って・・・
                    Emp emp = new Emp(deptNo, empNo++);
                    dept.employ(emp);
                }
                int empsSize = dept.getEmpsSize();
                for(int i = 0; i < empsSize; i++) {
                    if((i % 2) == 0) { // 内、半分を解雇する
                        dept.fire(i);
                    }
                }
            }
        } catch(OutOfMemoryError e) {
            System.err.println("OutOfMemoryError");
        } finally {
            System.out.println("deptNo : " + deptNo + " empNo : " + empNo); // deptNo : 3332 empNo : 11514569
        }
    }
自分の環境だと、 3332個目の部署・11514569人目の従業員でOutOfMemoryErrorが発生する。
GCされる例
部署クラスのfire()メソッド内で、解雇された従業員にnullを設定するように変更する。
public void fire(int index) {
        emps[index] = null; // nullを設定する
        size--;
    }
もう一度mainを実行すると、 「deptNo : 3456 empNo : 11943936」が出力されて、OutOfMemoryErrorはスローされずにプログラムが終了する。 そもそもこの部署クラスのように、プログラマーが独自にメモリー管理をしなれけばならないようなプログラムは、例外的であるべきだ、とEffective Javaには書いてあります。 see also: Javaプログラマであるかを見分ける10の質問 4.オブジェクトがガベージコレクション(GC)される主たる条件は何か?
see also: Effective Java(第2版)第2章オブジェクトの生成と消滅 項目6 廃れたオブジェクトの参照を取り除く

Generics(ジェネリクス(総称))・Type Safe(タイプセーフ(型の安全性))

1.5以降

interface EmpService {
    List<Emp> getEmps(int deptNo);
}
List<Emp> emps = empService.getEmps(10);
for(int i = 0; i < emps.size(); i++) {
    Emp emp = emps.get(i); // キャストの必要なし・コード量減る
    // 処理
}
for(Emp emp : emps) { // 拡張forループ使えばもっとコード量減る
    // 処理
}

1.4以前

interface EmpService {
    List getEmps(int deptNo);
}
List emps = empService.getEmps(10);
for(int i = 0; i < emps.size(); i++) {
    Emp emp = (Emp) emps.get(i); // キャストが必要・ClassCastExceptionになるかどうかは実行時までわからない
    // 処理
}
Object obj = emps.get(i);
if(obj instanceof Emp) { // なのでこんな感じでチェックする必要があり・ロジック増える
    Emp emp = (Emp) obj;
    // 処理
}
see also: Javaプログラマであるかを見分ける10の質問 3.List<Integer>のようにジェネリクス型を使う主たる目的は何か

文字列連結、java.lang.StringBuilder、java.lang.StringBuffer

public static final void main(String[] args) {
        int i = 0;
        String str = String.valueOf(i);
        long start = System.currentTimeMillis();
        while(i < 100000) {
            str = str + "/" + (i++);
        }
        long end = System.currentTimeMillis();
        System.out.println("[+]=" + (end - start));// #1=114719, #2=108078, #3=114188
    }
public static final void main(String[] args) {
        int i = 0;
        StringBuilder builder = new StringBuilder(i);
        long start = System.currentTimeMillis();
        while(i < 100000) {
            builder.append("/").append(i++);
        }
        long end = System.currentTimeMillis();
        System.out.println("[StringBuilder]=" + (end - start));// #1=15, #2=15, #3=16
    }
public static final void main(String[] args) {
        int i = 0;
        StringBuffer buffer = new StringBuffer(i);
        long start = System.currentTimeMillis();
        while(i < 100000) {
            buffer.append("/").append(i++);
        }
        long end = System.currentTimeMillis();
        System.out.println("[StringBuffer]=" + (end - start));// #1=16, #2=47, #3=15
    }
see also: Javaプログラマであるかを見分ける10の質問 「文字列の+演算子による連結とStringBuilderを使った連結の違いを説明せよ。」

==演算子とjava.lang.Object#equals(Object)メソッド

public class Equals {
    public static final void main(String[] args) {
        final String aaa0 = "aaa";
        final String aaa1 = "aaa";
        System.out.println("aaa0 == aaa1 : " + (aaa0 == aaa1));// -> true
        final String aaa2 = new String("aaa");
        final String aaa3 = new String("aaa");
        System.out.println("aaa2 == aaa3 : " + (aaa2 == aaa3));// -> false
        System.out.println("aaa2 equals aaa3 : " + (aaa2.equals(aaa3)));// -> true
        
        final Integer one0 = 1;
        final Integer one1 = 1;
        System.out.println("one0 == one1 : " + (one0 == one1));// -> true
        final Integer one2 = new Integer(1);
        final Integer one3 = new Integer(1);
        System.out.println("one2 == one3 : " + (one2 == one3));// -> false
        System.out.println("one2 equals one3 : " + (one2.equals(one3)));// -> true
        
        final Hoge hoge0 = new Hoge(0);
        final Hoge hoge1 = new Hoge(0);
        System.out.println("hoge0 == hoge1 : " + (hoge0 == hoge1));// -> false
        System.out.println("hoge0 equals hoge1 : " + (hoge0.equals(hoge1)));// -> true
    }
}
class Hoge {
    private int i;
    public Hoge(int i) {
        this.i = i;
    }
    public int getI() {
        return i;
    }
    @Override public boolean equals(Object obj) {
        return obj instanceof Hoge ? ((Hoge) obj).getI() == i : false;
    }
    @Override public int hashCode() {
        return i;
    }
}
see also: Javaプログラマであるかを見分ける10の質問 「==演算子とequalsメソッドの違いは何か?」

Yahoo!地図 SDK for Android

SDKっていうからもっと重厚なやつを想像してましたが、ちっちゃいjar一個とpng六個が同梱されたセットで、単に地図をぺろっと表示するだけならすごく短いコードで出来ました。
Yahoo!地図 SDK for iPhone & Androidを公開しました
Yahoo!地図 SDK for Android

  1. 上のリンクからZIPをダウンロードしておいて適当なところに解凍しておく。
  2. (持ってなければ)Yahoo!JapanデベロッパーネットワークでアプリケーションIDを取得する。
  3. 普通にeclipseでandroidプロジェクトを作成する。
  4. ymap.jarをassetsに置いてbuild path通す。
  5. res/drawable-hdpi/に同梱の画像を置く。
  6. jp.co.yahoo.android.maps.MapActivityをextendsしてActivityを作成する。
public class YMapActivity extends MapActivity {
    private static final String Y_APP_ID = "アプリケーションID";
    private MapView mMapView;
    private GeoPoint mGeoPoint;
    private MapController mMapController;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMapView = new MapView(this, Y_APP_ID);
        mGeoPoint = new GeoPoint(35677695, 139771460);
        mMapController = mMapView.getMapController();
        mMapController.setCenter(mGeoPoint);
        mMapController.setZoom(1);
        setContentView(mMapView);
    }
標準

サテライト

地下街

シンプル

路線図

ミッドナイト

もっといろいろ出来るっぽいので試してみたいですね。

Apache Cassandra 0.7.0 インストール・設定・起動・Javaからインサート(備忘録)

インストール

$ mkdir -p ~/apache/cassandra
$ cd ~/apache/cassandra/
$ wget http://www.meisei-u.ac.jp/mirror/apache/dist//cassandra/0.7.0/apache-cassandra-0.7.0-bin.tar.gz
$ tar xvzf apache-cassandra-0.7.0-bin.tar.gz
$ mv apache-cassandra-0.7.0/ 0.7.0

設定

CASSANDRA_HOME
$ vi /home/ktakeda47/.bashrc
export CASSANDRA_HOME=/home/ktakeda47/apache/cassandra/0.7.0
export CASSANDRA_CONF=$CASSANDRA_HOME/conf
export CASSANDRA_MAIN=org.apache.cassandra.thrift.CassandraDaemon
$ source ~/.bashrc
Java1.6が必要
$ java -version
java version "1.6.0_20"
各種ディレクトリの場所指定
$ cd 0.7.0/
$ vi conf/cassandra.yaml
# directories where Cassandra should store data on disk.
#data_file_directories:
#    - /var/lib/cassandra/data
data_file_directories:
    - /home/ktakeda47/apache/cassandra/0.7.0/data
# commit log
#commitlog_directory: /var/lib/cassandra/commitlog
commitlog_directory: /home/ktakeda47/apache/cassandra/0.7.0/commitlog
# saved caches
#saved_caches_directory: /var/lib/cassandra/saved_caches
saved_caches_directory: /home/ktakeda47/apache/cassandra/0.7.0/saved_caches
$ mkdir data
$ mkdir commitlog
$ mkdir saved_caches
log4jログファイルの出力先指定
$ vi conf/log4j-server.properties
#log4j.appender.R.File=/var/log/cassandra/system.log
log4j.appender.R.File=/home/ktakeda47/apache/cassandra/0.7.0/log/system.log
$ mkdir log
JMXのポート変更
$ vi conf/cassandra-env.sh
# Specifies the default port over which Cassandra will be available for
# JMX connections.
#JMX_PORT="8080"
JMX_PORT="9090"

起動

$ ./bin/cassandra -f
cliでアクセス
$ ./bin/cassandra-cli
Welcome to cassandra CLI.
Type 'help;' or '?' for help. Type 'quit;' or 'exit;' to quit.
[default@unknown] 
[default@unknown] connect localhost/9160;
Connected to: "Test Cluster" on localhost/9160
キースペース作成
[default@unknown] create keyspace Sample1;
[default@unknown] use Sample1;
Authenticated to keyspace: Sample1
カラムファミリー作成
[default@Sample1] create column family Standard1; 
[default@Sample1] create column family Super1 with COLUMN_TYPE=Super and COMPARATOR=BytesType and SUBCOMPARATOR=BytesType;
[default@Sample1] create column family utf8Order with COMPARATOR=UTF8Type;
[default@Sample1] create column family time_utf8Order with COLUMN_TYPE=Super and COMPARATOR=LongType and SUBCOMPARATOR=UTF8Type;
データモデル確認
[default@Sample1] show keyspaces;
       WARNING: Could not connect to the JMX on localhost:8080, information won't be shown.
 Keyspace: Sample1:
  Replication Strategy: org.apache.cassandra.locator.SimpleStrategy
    Replication Factor: 1
  Column Families:
    ColumnFamily: Standard1
      Columns sorted by: org.apache.cassandra.db.marshal.BytesType
      Row cache size / save period: 0.0/0
      Key cache size / save period: 200000.0/3600
      Memtable thresholds: 0.4359375/93/60
      GC grace seconds: 864000
      Compaction min/max thresholds: 4/32
      Read repair chance: 1.0
    ColumnFamily: Super1 (Super)
      Columns sorted by: org.apache.cassandra.db.marshal.BytesType/org.apache.cassandra.db.marshal.BytesType
      Row cache size / save period: 0.0/0
      Key cache size / save period: 200000.0/3600
      Memtable thresholds: 0.4359375/93/60
      GC grace seconds: 864000
      Compaction min/max thresholds: 4/32
      Read repair chance: 1.0
    ColumnFamily: time_utf8Order (Super)
      Columns sorted by: org.apache.cassandra.db.marshal.LongType/org.apache.cassandra.db.marshal.UTF8Type
      Row cache size / save period: 0.0/0
      Key cache size / save period: 200000.0/3600
      Memtable thresholds: 0.4359375/93/60
      GC grace seconds: 864000
      Compaction min/max thresholds: 4/32
      Read repair chance: 1.0
    ColumnFamily: utf8Order
      Columns sorted by: org.apache.cassandra.db.marshal.UTF8Type
      Row cache size / save period: 0.0/0
      Key cache size / save period: 200000.0/3600
      Memtable thresholds: 0.4359375/93/60
      GC grace seconds: 864000
      Compaction min/max thresholds: 4/32
      Read repair chance: 1.0
なんかwarn出てますよorz

Javaからアクセス

package sample;

import java.nio.ByteBuffer;

import org.apache.cassandra.thrift.Cassandra.Client;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.thrift.TimedOutException;
import org.apache.cassandra.thrift.UnavailableException;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

public class SimpleInsert {

 public SimpleInsert() {
 }

 public static final void main(String[] args) {
  TTransport tTransport = new TFramedTransport(new TSocket("localhost", 9160));
  TProtocol tProtocol = new TBinaryProtocol(tTransport);
  try {
   tTransport.open();
  } catch (TTransportException e) {
   throw new RuntimeException(e);
  }
  
  Client client = new Client(tProtocol);
  try {
   client.set_keyspace("Sample1");
   ByteBuffer key = ByteBufferUtil.bytes("sample1");
   ColumnParent columnParent = new ColumnParent("Standard1")
    .setSuper_column((ByteBuffer) null);
   Column column = new Column(
     ByteBufferUtil.bytes("hoge"),
     ByteBufferUtil.bytes("sample_value"),
     System.currentTimeMillis());
   client.insert(key, columnParent, column, ConsistencyLevel.ONE);
   tTransport.flush();
  } catch (TTransportException e) {
   throw new RuntimeException(e);
  } catch (InvalidRequestException e) {
   throw new RuntimeException(e);
  } catch (UnavailableException e) {
   throw new RuntimeException(e);
  } catch (TimedOutException e) {
   throw new RuntimeException(e);
  } catch (TException e) {
   throw new RuntimeException(e);
  } finally {
   tTransport.close();
  }
 }

}
確認
$ ./bin/cassandra_cli
なんか入ってる?
[default@Sample1] connect localhost/9160;
[default@Sample1] use Sample1;
[default@Sample1] get Standard1['sample1'];
       => (column=686f6765, value=73616d706c655f76616c7565, timestamp=1296460094273)Returned 1 results.
see also: http://gihyo.jp/dev/serial/01/cassandra/0001
see also: http://d.hatena.ne.jp/amanar/20110109/1294570753

too many SQL variables: , while compiling: ・・・

too many SQL variables: , while compiling: DELETE FROM sample_tbl WHERE rowid IN (?,?,?,...

そもそもこんなエラーには滅多にブチ当たらないだろうし、もしブチ当たったならアプリケーションの設計から見直したほうが良いと思います。はい。
最初このエラーをLogCatで見たときはandroidの問題かなと思いましたが、結論はsqlite3の制限でしたというお話。

例えば、以下のメソッドをアプリケーション側から使うとき。
package android.database.sqlite;

public class SQLiteDatabase extends SQLiteClosable {
    public void execSQL(String sql, Object[] bindArgs) throws SQLException {
        // (中略)
    }
}
第一引数sqlに下記のような"?"プレースフォルダが1000個あるSQL文を、
DELETE FROM sample_tbl WHERE rowid IN (?,?,?,...延々1000個...,?);
第二引数に"?"プレースフォルダにバインドしてもらうパラメータを1000個渡して実行します。JDBCのPreparedStatementを使うときのやり方と同じですね。
するとLogCatには以下のようなログが吐かれて、SQLは失敗してしまいます。
ERROR/hoge.fuga.SampleDao(123): too many SQL variables: , while compiling: DELETE FROM sample_tbl WHERE rowid IN (?,?,?,........
androidのソースを辿っていくと、
public void execSQL(String sql, Object[] bindArgs) throws SQLException {
        // (中略)
        DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]);
    }

package android.database;

public class DatabaseUtils {
    public static void bindObjectToProgram(SQLiteProgram prog, int index,
            Object value) {
        if (value == null) {
            prog.bindNull(index);
        } else if (value instanceof Double || value instanceof Float) {
            prog.bindDouble(index, ((Number)value).doubleValue());
        } else if (value instanceof Number) {
            prog.bindLong(index, ((Number)value).longValue());
        } else if (value instanceof Boolean) {
            Boolean bool = (Boolean)value;
            if (bool) {
                prog.bindLong(index, 1);
            } else {
                prog.bindLong(index, 0);
            }
        } else if (value instanceof byte[]){
            prog.bindBlob(index, (byte[]) value);
        } else {
            prog.bindString(index, value.toString());
        }
    }
}
prog.bindString(int, String)に行ってみます↓
package android.database.sqlite;

public abstract class SQLiteProgram extends SQLiteClosable {
    public void bindString(int index, String value) {
        if (value == null) {
            throw new IllegalArgumentException("the bind value at index " + index + " is null");
        }
        if (!mDatabase.isOpen()) {
            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
        }
        acquireReference();
        try {
            native_bind_string(index, value);
        } finally {
            releaseReference();
        }
    }
}
native_bind_string(int, String)にジャンプ↓
package android.database.sqlite;

public abstract class SQLiteProgram extends SQLiteClosable {
    protected final native void native_bind_string(int index, String value);
}
frameworks/base/core/jni/android_database_SQLiteProgram.cppまで行き、
static void native_bind_string(JNIEnv* env, jobject object,
                               jint index, jstring sqlString)
{
    int err;
    jchar const * sql;
    jsize sqlLen;
    sqlite3_stmt * statement= GET_STATEMENT(env, object);

    sql = env->GetStringChars(sqlString, NULL);
    sqlLen = env->GetStringLength(sqlString);
    err = sqlite3_bind_text16(statement, index, sql, sqlLen * 2, SQLITE_TRANSIENT);
    env->ReleaseStringChars(sqlString, sql);
    if (err != SQLITE_OK) {
        char buf[32];
        sprintf(buf, "handle %p", statement);
        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
        return;
    }
}
sqlite3_bind_XXX()関数が「too many SQL variables」と言っているようです。

要はsqlite3としては以下のような制限がありますよと。
http://www.sqlite.org/limits.html#max_variable_number
9.Maximum Number Of Host Parameters In A Single SQL Statement
...(中略)...
the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, which defaults to 999.

SQL文のIN句に渡せる値の数とかWHERE句の条件の数の問題ではなく、sqlite3に渡せるバンドパラメータの上限がデフォルト999個までということ。それが証拠にパラメータを渡す方ではなく、SQL文だけを引数に取る以下のメソッドの第一引数に、IN句の条件を文字列として連結して渡すと失敗しません。
package android.database.sqlite;

public class SQLiteDatabase extends SQLiteClosable {
    public void execSQL(String sql) throws SQLException {
        // (中略)
    }
}

パラメータが1000個もあるSQLを投げつけてくるアプリケーションに問題があると思います。

How about you?

public class Male extends Person {
    private boolean ikemen;
    public Male(boolean ikemen) {
        this.ikemen = ikemen;
    }
    public boolean isIkemen() {
        return ikemen;
    }
    /* No setter... */
}
BloggerでのSyntax Highlighter(ソースコードハイライト)の方法を試したかっただけ・・・