広告

■□■□■□■□■□■□■□■□■□■□■□■□■□■□■
                      2011年03月09日

    Java総合講座 - 初心者から達人へのパスポート
                  2009年11月開講コース 042号

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■□■□■□■□■□■□■□■□■□■□■□■□■□■□■


-------------------------------------------------------
・現在、このメールマガジンは以下の2部構成になっています。
[1] 当初からのコース:vol.xxx(xxxは番号)が振られています。
   これは現在、中級レベルになっています。
[2] 2009年11月開講コース:xxx号(xxxは番号)が振られています。
   これは現在、初心者向けのレベルになっています。
・このメールマガジンは、画面を最大化して見てください。
小さな画面で見ていると、不適切な位置で行が切れてしまう
など、問題を起すことがあります。
・このメールマガジンに掲載されているソース・コード及び
文章は特に断らない限り、すべて筆者が著作権を所有してい
ます。また、これらのソース・コードは学習用のためだけに
提供しているものです。
-------------------------------------------------------


========================================================
◆ 01.データベースを使用するアプリケーションの開発(続き)
========================================================

では、ソース・コードを説明していきましょう。
(自力で理解できている人は読み飛ばしてください。)


まず、Employeeクラスについては、これまでの説明でじゅうぶん
理解できると思うので省略します。
わからない点がある人は、個別に質問をお寄せください。


次に、EmployeeDbManagerを説明します。
このクラスは、データベースのEMPLOYEEテーブルにデータ(行)を
追加する作業を行うべく用意しました。

以下にソース・コードを再度提示します。

--------------------------------------------------------
package jp.co.flsi.lecture.db;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import jp.co.flsi.lecture.entity.Employee;

public class EmployeeDbManager extends DbManager {
   private String selectSql = "SELECT * FROM EMPLOYEE WHERE EMP_NUM = ?";
   private String insertSql = "INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)";

   public boolean insertData(Employee anEmployee) {
      try {
         connect();
         PreparedStatement selectPs = conn.prepareStatement(selectSql);
         selectPs.setInt(1, anEmployee.getEmployeeNumber());
         ResultSet rs = selectPs.executeQuery();
         if (rs.next()) return false;
         selectPs.close();
         conn.setAutoCommit(false);
         PreparedStatement insertPs = conn.prepareStatement(insertSql);
         insertPs.setInt(1, anEmployee.getEmployeeNumber());
         insertPs.setString(2, anEmployee.getName());
         insertPs.setDate(3, new java.sql.Date(anEmployee.getDateOfBirth().getTimeInMillis()));
         insertPs.setInt(4, anEmployee.getDepartmentNumber());
         insertPs.executeUpdate();
         insertPs.close();
         conn.commit();
         disconnect();
      }
      catch (SQLException exception) {
         exception.printStackTrace();
      }
      return true;
   }

}
--------------------------------------------------------

このクラスにはフィールドが2つ、つまり
9行目
--------------------------------------------------------
private String selectSql = "SELECT * FROM EMPLOYEE WHERE EMP_NUM = ?";
--------------------------------------------------------
というのと
10行目
--------------------------------------------------------
private String insertSql = "INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)";
--------------------------------------------------------
というのがありますが、これらはそれぞれ
15行目
--------------------------------------------------------
PreparedStatement selectPs = conn.prepareStatement(selectSql);
--------------------------------------------------------

21行目
--------------------------------------------------------
PreparedStatement insertPs = conn.prepareStatement(insertSql);
--------------------------------------------------------
の中で使われているだけです。

たとえば、15行目を
--------------------------------------------------------
PreparedStatement selectPs = conn.prepareStatement("SELECT * FROM EMPLOYEE WHERE EMP_NUM = ?");
--------------------------------------------------------
というように、9行目を使わずに一まとめに記述してもいいのですが、
あまりにも文が長ったらしくなるので9行目と15行目に分けただけです。
(これでソース・コードが見やすくなります。)

10行目と21行目についても同様です。

ところで10行目のSQL文はEMPLOYEEテーブルにデータを追加(INSERT)
するためのSQL文ですから、まさに必要なSQL文であることはわかりま
すが、9行目は何のためにあるのでしょうか。

実は、10行目のSQL文を使ってこれから追加しようとしているデータと
同じ社員番号(EMP_NUM)のデータが既にデータベース上にある場合は、
10行目のSQL文を実行する段階でエラーになります。
(EMP_NUMが主キー(PRIMARY KEY)になっているからです。)

これを防ぐために、予め同じ社員番号のデータが存在しないことを確認
するために9行目のSQL文を使います。


ではinsertData()メソッドの中を見ていきましょう。
このメソッドはデータベースのEMPLOYEEテーブルに行を追加するための
メソッドです。

12行目
--------------------------------------------------------
public boolean insertData(Employee anEmployee) {
--------------------------------------------------------
を見ると、このメソッドの戻り値はbooleanになっていますが、
この戻り値には以下のような意味を持たせています。

戻り値がfalseのとき: 同じ社員番号のデータが既にあるのでデータを追加できない。
戻り値がtrueのとき:  データを追加できた。
(なお、それ以外のエラーでデータが追加できなかった場合には
SQLExceptionが発生してコンソールに表示される。)

このような意味は、ソース・コードを見ただけではわかりにくいので、
本来はソース・コードの中にコメントとして説明書きしておくべきものです。
(が、面倒くさかったのでコメントを割愛しました。あしからず。)

また、このメソッドの引数がEmployee型になっていることにも注目して
ください。
EMPLOYEEテーブルは社員というエンティティーを表すものですから、
Javaのプログラムの中で同じエンティティーを表すEmployee型を引数
にしているのです。

13行目
--------------------------------------------------------
try {
--------------------------------------------------------

30行目
--------------------------------------------------------
}
--------------------------------------------------------
は、SQLExceptionを検出するために実行内容全体をtry文の
ブロック({ })で囲んでいます。

14行目
--------------------------------------------------------
connect();
--------------------------------------------------------
は、スーパークラス(DbManager)のconnect()メソッドを呼び出して
データベースに接続しています。

15行目
--------------------------------------------------------
PreparedStatement selectPs = conn.prepareStatement(selectSql);
--------------------------------------------------------
のPreparedStatementについては既に023号や024号などで説明しま
したので、それを見てください。

16行目
--------------------------------------------------------
selectPs.setInt(1, anEmployee.getEmployeeNumber());
--------------------------------------------------------
のsetInt()メソッドについても既にvol.024で説明したので、
それを見てください。

ただし、16行目のコードでは第二番目の引数がanEmployee.getEmployeeNumber()
になっており、insertData()メソッドの引数として取り込んだEmployee型オブ
ジェクトの社員番号(employeeNumber)を取り出してそれをsetInt()メソッド
の第二番目の引数として設定していますね。

17行目
--------------------------------------------------------
ResultSet rs = selectPs.executeQuery();
--------------------------------------------------------
も既に024号で説明しましたが、このexecuteQuery()を実行することに
よって、anEmployee.getEmployeeNumber()で取り出された社員番号の
データが既にデータベース内に存在するとrsにデータが返ってきます。

したがって、このrsにデータが入っているかどうかを調べることによって、
同じ社員番号のデータが既にデータベース内に存在しているかどうかが
わかることになります。

18行目
--------------------------------------------------------
if (rs.next()) return false;
--------------------------------------------------------
では、このrsの中にデータが入っているかどうかを調べています。

024号ではrsに複数のデータが返ってくる可能性があるのでwhile文
を使っていますが、ここでは同じ社員番号のデータはあっても一つしか
ありえないのでif文を使っています。

つまり、rsの中は先頭の一行としてデータが存在するか、さもなければ
rsがまったくの空っぽかのどちらかです。

そしてデータが存在すればrs.next()の結果がtrueになりますので、
「return false」が実行されます。つまり、このメソッドはここで
終了し、戻り値としてfalseを返します。

データが存在しなければ、そのまま次の行へと実行が移ります。

19行目
--------------------------------------------------------
selectPs.close();
--------------------------------------------------------
このPreparedStatementを閉じます。

20行目
--------------------------------------------------------
conn.setAutoCommit(false);
--------------------------------------------------------
自動コミットしないように、つまり意図的にコミットの命令を出す
までは勝手にコミットしないように設定します。

21行目
--------------------------------------------------------
PreparedStatement insertPs = conn.prepareStatement(insertSql);
--------------------------------------------------------
18行目でデータベースに同じ社員番号のデータがまだ存在していない
ことが確認されているので、ここでSQLのINSERT文を用意してデータの
追加作業にはいります。

22行目
--------------------------------------------------------
insertPs.setInt(1, anEmployee.getEmployeeNumber());
--------------------------------------------------------
このsetInt()メソッドも既に023号で説明しました。
ただし、ここでは第二番目の引数がanEmployee.getEmployeeNumber()
になっており、insertData()メソッドの引数として取り込んだEmployee型
オブジェクトの社員番号(employeeNumber)を取り出してそれをsetInt()メ
ソッドの第二番目の引数として設定していますね。

23行目
--------------------------------------------------------
insertPs.setString(2, anEmployee.getName());
--------------------------------------------------------
このsetString()メソッドも既に023号で説明しました。
ただし、ここでは第二番目の引数がanEmployee.getName()になって
おり、insertData()メソッドの引数として取り込んだEmployee型オ
ブジェクトの氏名(name)を取り出してそれをsetString()メソッド
の第二番目の引数として設定していますね。

24行目
--------------------------------------------------------
insertPs.setDate(3, new java.sql.Date(anEmployee.getDateOfBirth().getTimeInMillis()));
--------------------------------------------------------
setDate()メソッドは初めて出てきましたね。
これはデータベースのDATE型のカラムに値を設定するためのメソッド
ですが、このメソッドの第二引数はjava.sql.Date型(java.sqlパッ
ケージに入っているDateクラスの型)になっています。

そこで、insertData()メソッドの引数として取り込んだEmployee型
オブジェクトの生年月日(dateOfBirth)をそのまま使うことはでき
ず、いったんjava.sql.Date型に変換しておく必要があります。

anEmployee.getDateOfBirth()で取り出した生年月日はGregorianCalendar型
ですが、GregorianCalendarのgetTimeInMillis()というメソッドは
この日付をミリ秒(1000分の1秒)単位の数値(long型)に変換します。

一方、java.sql.Date型のコンストラクターでlong型の数値を引数に持つ
ものは、引数に指定された数値をjava.sql.Date型の日付のオブジェクト
に変換します。

したがって、24行目の中の
new java.sql.Date(anEmployee.getDateOfBirth().getTimeInMillis())
というコードは、insertData()メソッドの引数として取り込んだEmployee型
オブジェクトの生年月日をjava.sql.Date型のインスタンスにしてくれるわけ
です。

25行目
--------------------------------------------------------
insertPs.setInt(4, anEmployee.getDepartmentNumber());
--------------------------------------------------------
このsetInt()メソッドでは第二番目の引数がanEmployee.getDepartmentNumber()
になっており、insertData()メソッドの引数として取り込んだEmployee型オブ
ジェクトの部門コード(departmentNumber)を取り出してそれをsetInt()メ
ソッドの第二番目の引数として設定していますね。

こうして組み立てたSQL文を
26行目
--------------------------------------------------------
insertPs.executeUpdate();
--------------------------------------------------------
で実行しています。

以下、27〜29行目
--------------------------------------------------------
insertPs.close();
conn.commit();
disconnect();
--------------------------------------------------------
はいつもの後始末です。

31〜33行目
--------------------------------------------------------
catch (SQLException exception) {
   exception.printStackTrace();
}
--------------------------------------------------------
は、このメソッドの実行中にSQLExceptionが発生した場合にそれを捕らえて
コンソールに書き出すためのいつものコードです。

34行目
--------------------------------------------------------
return true;
--------------------------------------------------------
は、このメソッドの実行が成功したときにメソッドを終了してtrueを返す
ようにしています。


では、次にHumanResourceEntryPaneクラスのgetJButton()メソッドの中の
無名クラスの定義の中にあるactionPerformed()メソッドのソース・コード
を見てみましょう。
下にソース・コードを再提示します。
--------------------------------------------------------
public void actionPerformed(ActionEvent e) {
    Employee anEmployee = new Employee();
    anEmployee.setEmployeeNumber(Integer.parseInt(getJTextField().getText()));
    anEmployee.setName(getJTextField1().getText());
    CalendarConverter aCalConverter = new CalendarConverter();
    aCalConverter.setDay(Integer.parseInt(getTextField().getText()));
    aCalConverter.setMonth(Integer.parseInt(getTextField_1().getText()));
    aCalConverter.setJaNameOfEra(getComboBox().getSelectedIndex());
    switch (aCalConverter.getJaNameOfEra()) {
    case 0:
    case 1:
    case 2:
    case 3:
        aCalConverter.setJaYear(Integer.parseInt(getJTextField2().getText()));
        aCalConverter.convertJaCal2GreCal();
        break;
    default:
        aCalConverter.setGreYear(Integer.parseInt(getJTextField2().getText()));
        break;
    }
    anEmployee.setDateOfBirth(aCalConverter.getGreYear(), aCalConverter.getMonth(), aCalConverter.getDay());
    ComboBoxModelOfDepartment aBoxModelOfDepartment = (ComboBoxModelOfDepartment) getComboBox_1().getModel();
    Department aDepartment = (Department)aBoxModelOfDepartment.getDepartmentList().get(getComboBox_1().getSelectedIndex());
    anEmployee.setDepartmentNumber(aDepartment.getDepartmentNumber());
    EmployeeDbManager empDbManager = new EmployeeDbManager();
    empDbManager.insertData(anEmployee);
}
--------------------------------------------------------

このactionPerformed()メソッドは「登録」ボタンをクリックしたときに
呼び出されるActionListenerのメソッドでしたね。

まず、先頭の307行目(この行番号は人によって違う可能性があるので、あくまで
大雑把な目安と考えて下さい。以下、特に明記しませんが同様です。)
--------------------------------------------------------
Employee anEmployee = new Employee();
--------------------------------------------------------
は、Employee型のインスタンスを生成し、変数anEmployeeに代入しています。
最終的には、このEmployeeオブジェクト(anEmployee)を先ほどのEmployeeDbManager
のinsertData()メソッドの引数として渡したいので、このanEmployeeにこれから
必要なデータを設定していきます。

308行目
--------------------------------------------------------
anEmployee.setEmployeeNumber(Integer.parseInt(getJTextField().getText()));
--------------------------------------------------------
は、HumanResourceEntryPaneの画面のテキスト・フィールド(JTextField)
に入力された社員番号をanEmployeeのemployeeNumber属性(フィールド)に
設定しています。

JTextFieldを貼り付けるときにjTextFieldというBean Name(変数名)にして
いたのに対応して、このテキスト・フィールドを取り出すためのgetメソッド
(getter)が自動的にgetJTextField()というメソッド名になっています。

こういった自動的に振られたメソッド名(および変数名)ではわかりにくいです
から、それらをわかりやすい名前に一挙に変更する方法を後ほど(別の号で)
お話しします。

このgetJTextField()メソッドで取り出したJTextFieldオブジェクトのgetText()
メソッドを呼び出すとそのテキスト・フィールドに入力されている文字列を返し
てくれます。

しかしEmployeeのsetEmployeeNumber()メソッドは引数にint型を指定しなければ
なりませんから文字列の型を指定するわけにはいきません。

そこで、IntegerのparseInt()メソッドを使ってString型からint型に変換して
いるのです。

309行目
--------------------------------------------------------
anEmployee.setName(getJTextField1().getText());
--------------------------------------------------------
は、HumanResourceEntryPaneの画面のテキスト・フィールド(JTextField)
に入力された氏名をanEmployeeのname属性(フィールド)に設定しています。

氏名を入力するためのJTextFieldを貼り付けるときにjTextField1というBean Name
(変数名)にしていたのに対応して、このテキスト・フィールドを取り出すための
getメソッド(getter)は自動的にgetJTextField1()というメソッド名になって
います。

次は生年月日の入力データをanEmployeeに設定するためのコードなんですが、
問題になるのは、「明治」「大正」「昭和」「平成」「西暦」のいずれが指定
されているかによって年の扱いが違ってくるということです。

この問題については、以前、和暦と西暦の間の変換を行うCalendarConverter
というクラスを作っていましたから、これを利用することにします。

そのために、310行目
--------------------------------------------------------
CalendarConverter aCalConverter = new CalendarConverter();
--------------------------------------------------------
でCalendarConverterのインスタンス(変数aCalConverter)を生成しておき、

311〜312行目
--------------------------------------------------------
aCalConverter.setDay(Integer.parseInt(getTextField().getText()));
aCalConverter.setMonth(Integer.parseInt(getTextField_1().getText()));
--------------------------------------------------------
でHumanresourceEntryPaneの画面のテキスト・フィールドに入力された
生年月日の月と日をaCalConverterに設定しておき、

313行目
--------------------------------------------------------
aCalConverter.setJaNameOfEra(getComboBox().getSelectedIndex());
--------------------------------------------------------
でHumanresourceEntryPaneの画面のJComboBoxの年号(「明治」「大正」
「昭和」「平成」「西暦」)のリストのうち何番目の項目が選択されて
いるのか、そのインデックスを取り出してaCalConverterに設定し、

314〜325行目
--------------------------------------------------------
switch (aCalConverter.getJaNameOfEra()) {
 case 0:
 case 1:
 case 2:
 case 3:
   aCalConverter.setJaYear(Integer.parseInt(getJTextField2().getText()));
   aCalConverter.convertJaCal2GreCal();
   break;
 default:
   aCalConverter.setGreYear(Integer.parseInt(getJTextField2().getText()));
   break;
}
--------------------------------------------------------
で、その年号が和暦の場合は西暦の年に変換し、年号が西暦の場合は
そのまま年を設定しています。

ここではJComboBoxで選択されたものが「明治」(インデックス=0)「大正」
(インデックス=1)「昭和」(インデックス=2)「平成」(インデックス=3)の
いずれの場合も和暦なので、これら0,1,2,3のいずれの場合にも和暦から
西暦への変換が必要になります。

このような場合は
--------------------------------------------------------
 case 0:
 case 1:
 case 2:
 case 3:
--------------------------------------------------------
というようにcaseを複数連続指定しておき、その下に実行したいコードを
書いておけば、0,1,2,3のいずれの場合にもそのコードが実行されます。

なお011号でも説明したように、コードはbreak;のところで実行が打ち止め
になり、switchのブロックから抜け出ます。

なお、和暦から西暦へ変換するにはCalendarConverterのconvertJaCal2GreCal()
メソッド(011号参照)を呼び出しますが、このメソッドを呼び出す前に
jaYear属性(フィールド)に値を設定しておく必要があります。
したがって、その前にsetJaYear()メソッドを実行しています。

convertJaCal2GreCal()メソッドはjaYearの値を西暦の年に変換してgreYear
属性に設定しますが、JComboBoxで選択されたものが「西暦」の場合は変換
の必要はありませんからsetGreYear()メソッドを使って直接greYear属性に
設定しています。

326行目
--------------------------------------------------------
anEmployee.setDateOfBirth(aCalConverter.getGreYear(), aCalConverter.getMonth(), aCalConverter.getDay());
--------------------------------------------------------
は、CalendarConverterを使って変換した後の年月日を使ってanEmployeeの
生年月日(dateOfBirth)属性の値を設定しています。


あと残っているのはanEmployeeの部門コード(departmentNumber)属性です。

HumanResourceEntryPaneの画面上では、DEPARTMENTテーブルから取ってきた
データのうち部門名(DEPART_NAME)の値のみをJComboBoxに表示し選択させ
るようにしていますが、実際にanEmployeeに設定したいのは部門コード
つまり数値ですから、JComboBoxの値を直接取り出すというやり方は使えま
せん。

それで、327〜329行目
--------------------------------------------------------
ComboBoxModelOfDepartment aBoxModelOfDepartment = (ComboBoxModelOfDepartment) getComboBox_1().getModel();
Department aDepartment = (Department)aBoxModelOfDepartment.getDepartmentList().get(getComboBox_1().getSelectedIndex());
anEmployee.setDepartmentNumber(aDepartment.getDepartmentNumber());
--------------------------------------------------------
のように回りくどいことをしています。

まず
327行目
--------------------------------------------------------
ComboBoxModelOfDepartment aBoxModelOfDepartment = (ComboBoxModelOfDepartment) getComboBox_1().getModel();
--------------------------------------------------------
は、このJComboBoxにつないであるComboBoxModelオブジェクト(037号に書い
ている通りComboBoxModelOfDepartmentクラスのインスタンスとして生成して
あるもの)を取り出して、変数aBoxModelOfDepartmentに代入しています。

なお、JComboBoxにつないであるComboBoxModelオブジェクトを取り出すため
には上記のようにJComboBoxのgetModel()というメソッドを使います。

ここで取り出したComboBoxModelオブジェクトはComboBoxModelOfDepartmentの
インスタンスであり、DEPARTMENTテーブルのデータから作られたDepartmentオ
ブジェクトのリスト(Vector型)を持っています(037号参照)。

328行目
--------------------------------------------------------
Department aDepartment = (Department)aBoxModelOfDepartment.getDepartmentList().get(getComboBox_1().getSelectedIndex());
--------------------------------------------------------
は、そのComboBoxModelOfDepartmentインスタンスのgetDepartmentList()メソッ
ドを呼び出してDepartmentオブジェクトのリスト(Vector型のオブジェクト)を
取り出しておき、そのVectorのget()メソッドで、対象となる要素(Departmentオ
ブジェクト)を取り出しています。

このVectorのget()メソッドは、引数に指定したインデックスの要素を取り出す
メソッドです。

上記の引数に指定されているgetSelectedIndex()というメソッドは、JComboBoxの
リストの中で選択状態になっている項目のインデックスを返すメソッドです。

これをget()メソッドの引数として指定しているのでJComboBoxの選択されてい
る項目のインデックスの要素(Departmentオブジェクト)が取り出されること
になります。

このVectorの中に入っている要素の順番はDEPARTMENTテーブルから取り出した
順番であり、これがそのままJComboBoxに表示される順番にもなっているので、
これで対象となる要素(Departmentオブジェクト)が取り出されることになり
ます。

329行目
--------------------------------------------------------
anEmployee.setDepartmentNumber(aDepartment.getDepartmentNumber());
--------------------------------------------------------
は、こうして取り出したDepartmentオブジェクトのdepartmentNumber(部門コード)
属性の値をanEmployeeに設定しています。


以上で、anEmployeeにデータが一通り設定されましたから、いよいよデータベース
への書き込みにはいります。

すなわち、330〜331行目
--------------------------------------------------------
EmployeeDbManager empDbManager = new EmployeeDbManager();
empDbManager.insertData(anEmployee);
--------------------------------------------------------
で、EmployeeDbManagerのインスタンスを生成して、そのinsertData()メソッ
ドの引数にanEmployeeを指定して呼び出しています。
あとはinsertData()メソッドがデータの追加をやってくれます。


(次回に続く。)


では、また来週。



================================================
◆ 02.前回の演習問題の答
================================================

前回のソース・コードの入力が一通り終わった段階で、HumanResourceJFrame
を実行して、動作確認してみましたか。

動作確認してみた人は、いくつか物足りないところ(不十分な点)に気が
ついたことと思います。

たとえば、「登録」ボタンをクリックしても、何も変化が起きないので、
ちゃんと動作したのか(データベースへのデータの挿入がちゃんと行われ
たのか)どうか、はっきりしません。

これでは気持ちが悪いですから、データの挿入が完了したのならその旨
のメッセージを返すべきです。


また、社員番号を空白にしたまま「登録」ボタンをクリックしたり、ある
いは社員番号に数値以外の文字列を入力して「登録」ボタンをクリックす
るとコンソールにエラー(Exception=例外)が表示されてしまいますね。
これでは何を間違えたのか非常にわかりにくいです。

このように入力すべきデータを空白にしていたり間違った型のデータを
入力した場合にも、わかりやすいメッセージを返すべきです。


次回はここらへんの対処方法を説明しましょう。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2011 Future Lifestyle Inc. 不許無断複製