月曜日, 14 3 月 2005

jcommenterでコメント雛形
この記事をクリップ!
このエントリーをはてなブックマークに追加

Javaでコードを書いてるとついて回るのがJavaDoc用のコメント書きだ。
複数人で開発するなら当然だが一人だけの場合でも以後のメンテナンス性を考えればコメントはあるに越したことはない。
だがクラスやメソッドを追加するたび、/**やら@versionやら@paramやらを書いていくのは冗長で正直面倒だ。
この場合開発環境にvimを使っていればjcommenterを使用すればこの煩わしさから解放される。
jcommenterとはJavaソース内のクラスやメソッドのコメント雛形を自動で生成してくれるvimスクリプトの一つだ。
使い方はまずjcommenterの公式サイトから落としてきたjcommenter.vimを$VIMRUNTIME/macrosに配置する。
その後~/.vimrcなどのvim設定ファイルに
autocmd FileType java source $VIMRUNTIME/macros/jcommenter.vim
autocmd FileType java map <C-c> :call JCommentWriter()<CR>
と書き加える。
上記設定が完了後、Javaソースファイルを開き
public class test
のようなクラス名記述の行に移動しCtrl-cを入力すると
/** 
 * 
 * 
 * @author 
 * @version 
 */
public class test
と自動でコメント入力のための雛形を生成してくれる。
@authorなどの後に続く名前などの情報はjcommenter.vim内の記述を変更することでデフォルト値を設定することが可能だ。
メソッドの場合も同じように
public String doSomething(String value)
のような部分でCtrl-cを入力すると
/** 
 * 
 * 
 * @param value
* @return */ public String doSomething(String value)
という形で雛形を生成してくれるのでパラメータが多い場合など非常に重宝する。
EclipseなどのIDEを使わずJDK+vimで開発する場合は割と必須なvimスクリプトだと思う。
Posted by tsujitako at 3:04 午前 in Java/

金曜日, 11 3 月 2005

JCaptchaで不正登録防止
この記事をクリップ!
このエントリーをはてなブックマークに追加

CaptchaとはCompletely Automated Public Test to tell Computers and Humans Apartの頭文字を取った造語で日本語で説明するとユーザーが人間かコンピュータか区別する自動化されたテストの事、となる。
具体的な例をあげるとYahooなどの会員登録の際に読みづらい文字が描かれた画像中の文字を判別させて入力させるフォームなどがそうだ。
言うまでもなくこれはプログラムなどによる不正な会員登録を防止するための措置でYahooに限らず最近は他のサイトでも見かけるようになってきた。
この読みづらい文字が描かれた画像を生成するJavaの実装がJCaptchaだ。
使用方法はcom.octo.captcha.engine以下のパッケージに用意されているImageCaptchaEngineの実装クラスからImageCaptchaオブジェクトを取得し文字の書かれた画像を生成する。
以下のサンプルはDeformedBaffleListGimpyEngineを使用し画像生成を行う。
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStreamReader;
import java.io.BufferedReader;

import javax.imageio.ImageIO;

import com.octo.captcha.engine.image.gimpy.DeformedBaffleListGimpyEngine;
import com.octo.captcha.engine.image.ImageCaptchaEngine;
import com.octo.captcha.image.ImageCaptcha;


public class test
{
  public static void main(String[] args)
  throws Exception
  {
    File file=new File("challenge.jpg");

    ImageCaptchaEngine engin=new DeformedBaffleListGimpyEngine();
    ImageCaptcha captcha=engin.getNextImageCaptcha();

    BufferedImage image=captcha.getImageChallenge();
    ImageIO.write(image,"JPEG",file);
    System.out.println(captcha.getQuestion());

    BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));

    String ans=reader.readLine();
    if(captcha.validateResponse(ans).booleanValue())
      System.out.println("OK");
    else
      System.out.println("NG");
  }
}
上記サンプルを実行するとカレントディレクトリにchallenge.jpgという名前で文字の描かれた画像が生成された後、標準入力から入力待ちになるので読みとった文字を入力して正解ならばOK間違いならばNGと表示される。
生成される画像は以下のような感じ。

上の場合だと「coldly」で正解となる。
ただDeformedBaffleListGimpyEngineを使うと人間が見ても読めないかもしれない文字が作られる場合もあるのであまりおすすめ出来ない。
描かれる文字や背景のカスタマイズはImageCaptchaEngineを使わずダイレクトにcom.octo.captcha.image.gimpyパッケージのGimpyFactoryなどを使えば自由に変更することが可能だ。
Posted by tsujitako at 2:08 午後 in Java/

土曜日, 5 3 月 2005

Tiger -J2SE 5.0- の新JFrame
この記事をクリップ!
このエントリーをはてなブックマークに追加

5.0以前のJFrameなどではフレーム自体にadd()でコンポーネントを追加すると実行時にエラーが発生し、getContentPane()でコンテントペインを取得しそれに対してadd()を呼べと怒られてしまう。
add()に限らずsetLayout()もフレームに対して直接呼び出すと同様にエラーが発生する。
代わりにgetContentPane()で取得できるコンテントペインを使用すればいいのだが、使用する場面が多いとコードが冗長になってくるし何よりも面倒くさい。
だが5.0のJFrameではフレームに対してadd()を呼び出すとプロキシとして動作し、呼び出しがコンテントペインに転送されるためそのままコンポーネントを追加することが可能である。
5.0以前のJFrameでは
JFrame frame=new JFrame("test");
Container contentPane=frame.getContentPane();
contentPane.add(new JButton("test"));
と書いていた部分が5.0では
JFrame frame=new JFrame("test");
frame.add(new JButton("test"));
と書くことが出来る。
ただ5.0になりコンテントペインが無くなったわけではなくsetBackground()などは対象がフレームになっているのでメインコンポーネントの背景色を変更する場合はコンテントペインから呼び出す必要があるため注意が必要だ。
Posted by tsujitako at 3:12 午前 in Java/

月曜日, 28 2 月 2005

Lint4jでソース検査
この記事をクリップ!
このエントリーをはてなブックマークに追加

Lint4jFindBugsと同じようなJavaプログラムソース内のバグの温床となる記述やパフォーマンス低下につながる部分を探し出して警告してくれるツールだ。
指摘される部分の記述そのものがエラーではないため修正しなくても動作はするがメンテナンス性やパフォーマンス向上のため修正することが推奨されている。
サンプルとして以下のプログラムを用意しLint4jで検査してみた。
package test;

public class test
{
  public static void main(String[] args)
  {
    String hello=new String("hello");

    System.out.println(hello);
  }
}
で、Lint4jを実行。
$ lint4j -v 5 -sourcepath . *
./test/test.java:7: (4): Unnecessary creation of a String from another String
./test/test.java:9: (4): Possible debug call "System.out.println" was detected
問題となる記述が2箇所見つかった。
一つ目はStringインスタンスを作るのに既に""を使ってるにも関わらずさらにnew Stringしているのが問題で、二つ目はデバッグ出力として推奨されないSystem.out.printlnを使っているのが問題なようだ。System.outはともかくStringのコンストラクタは確かに無駄な処理なのでLint4jの判断は正しいと思う。
実際のプロジェクトでも定期的にソースを検査し問題となる部分がないか調べるといいかもしれない。
Posted by tsujitako at 1:39 午前 in Java/

水曜日, 23 2 月 2005

Cojenでバイトコード解析
この記事をクリップ!
このエントリーをはてなブックマークに追加

Javaで書かれたソースはコンパイルするとバイトコードと呼ばれるプラットホーム非依存の中間コードに変換される。
そのバイトコードを容易に解析、再構築するためのライブラリがCojenだ。
まずは解析するためのソースを用意する。今回用意したのは以下ような簡単なもの。
public class test{
  public static void main(String[] argv){
    test t=new test();
    t.print();
  }

  public test(){
  }

  public void print(){
    System.out.println("hello");
  }
}
これを普通にコンパイルする。
$ javac test.java
生成されたtest.classをCojenのディスアセンブラに通してみる。
$ java -cp cojen-1.1.0.jar:. cojen.classfile.DisassemblyTool test
すると以下のような出力が得られる。
/**
 * Disassembled on Wed Feb 23 01:53:47 JST 2005.
 * 
 * @target 1.2
 * @source test.java
 */
public class test extends java.lang.Object {
  public static void main(java.lang.String[]) {
    // max stack: 2
    // max locals: 1
    // line 5
    new test
    dup
    invokespecial void test.()
    astore_0
    // line 6
    aload_0
    invokevirtual void test.print()
    // line 7
    return
  }

  public void () {
    // max stack: 1
    // max locals: 1
    // line 10
    aload_0
    invokespecial void java.lang.Object.()
    // line 11
    return
  }

  public void print() {
    // max stack: 2
    // max locals: 1
    // line 15
    getstatic java.io.PrintStream java.lang.System.out
    ldc "hello"
    invokevirtual void java.io.PrintStream.println(java.lang.String)
    // line 16
    return
  }
}
上記の出力だけではjavapを使用して得られたバイトコードとほとんど変わらないが、以下の-f builderオプションを付けて実行するとクラスファイルを生成するためのJavaコードが得られる。
$ java -cp cojen-1.1.0.jar:. cojen.classfile.DisassemblyTool -f builder test
出力は以下の通り。
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

import cojen.classfile.ClassFile;
import cojen.classfile.CodeBuilder;
import cojen.classfile.FieldInfo;
import cojen.classfile.Label;
import cojen.classfile.LocalVariable;
import cojen.classfile.Location;
import cojen.classfile.MethodInfo;
import cojen.classfile.Modifiers;
import cojen.classfile.Opcode;
import cojen.classfile.TypeDesc;

/**
 * Builds ClassFile for test
 *
 * @author auto-generated
 */
public class ClassFileBuilder {
  public static void main(String[] args) throws Exception {
    // public class test
    ClassFile cf = createClassFile();

    if (args.length > 0) {
      File file = new File(args[0]);
      if (file.isDirectory()) {
        writeClassFiles(cf, file);
      } else {
        OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
        cf.writeTo(out);
        out.close();
      }
    }
  }

  private static void writeClassFiles(ClassFile cf, File dir) throws Exception {
    File file = new File(dir, cf.getClassName().replace('.', '/') + ".class");
    file.getParentFile().mkdirs();
    OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
    cf.writeTo(out);
    out.close();

    ClassFile[] innerClasses = cf.getInnerClasses();
    for (int i=0; i<innerClasses.length; i++) {
      writeClassFiles(innerClasses[i], dir);
    }
  }

  public static ClassFile createClassFile() {
    ClassFile cf = new ClassFile("test", "java.lang.Object");
    cf.setTarget("1.2");
    cf.setSourceFile("test.java");

    //
    // Create constructors
    //

    // public void <init>()
    createConstructor_1(cf);

    //
    // Create methods
    //

    // public static void main(java.lang.String[])
    createMethod_1(cf);

    // public void print()
    createMethod_2(cf);

    return cf;
  }

  // public void <init>()
  private static void createConstructor_1(ClassFile cf) {
    MethodInfo mi = cf.addConstructor(Modifiers.PUBLIC, null);
    CodeBuilder b = new CodeBuilder(mi);

    b.mapLineNumber(10);
    b.loadThis();
    b.invokeSuperConstructor(null);

    b.mapLineNumber(11);
    b.returnVoid();
  }

  // public static void main(java.lang.String[])
  private static void createMethod_1(ClassFile cf) {
    MethodInfo mi = cf.addMethod(Modifiers.PUBLIC_STATIC, "main", null, new TypeDesc[] {TypeDesc.STRING.toArrayType()});
    CodeBuilder b = new CodeBuilder(mi);

    LocalVariable var_1 = b.getParameter(0);

    b.mapLineNumber(5);
    TypeDesc type_1 = TypeDesc.forClass("test");
    b.newObject(type_1);
    b.dup();
    b.invokeConstructor(null);
    b.storeLocal(var_1);

    b.mapLineNumber(6);
    b.loadLocal(var_1);
    b.invokeVirtual("print", null, null);

    b.mapLineNumber(7);
    b.returnVoid();
  }

  // public void print()
  private static void createMethod_2(ClassFile cf) {
    MethodInfo mi = cf.addMethod(Modifiers.PUBLIC, "print", null, null);
    CodeBuilder b = new CodeBuilder(mi);

    b.mapLineNumber(15);
    TypeDesc type_1 = TypeDesc.forClass("java.io.PrintStream");
    b.loadStaticField("java.lang.System", "out", type_1);
    b.loadConstant("hello");
    TypeDesc[] params_1 = new TypeDesc[] {TypeDesc.STRING};
    b.invokeVirtual("java.io.PrintStream", "println", null, params_1);     b.mapLineNumber(16);     b.returnVoid();   } }
やたら長くて難解なコードだがよく見るとb.dup();のようなバイトコードのニーモニックそのままメソッドがあるためバイトコードの動きをそのままJavaで実装?しているようだ。
このコードをClassFileBuilder.javaという名前で保存し、コンパイルしてクラスファイルを生成してみる。
$ javac -classpath cojen-1.1.0.jar ClassFileBuilder.java
$ java -cp cojen-1.1.0.jar:. ClassFileBuilder test.class
するとtest.classが作成されるのでこれをjavaで実行する。
$ java -cp . test
hello
見事元のクラスファイルと同様の動きが再現された。
最近はAOPなどバイトコードレベルで処理を行う場合もあるためそういった低レベルな部分を解析、実装する場合は非常に役に立つのではないだろうか。
Posted by tsujitako at 2:19 午前 in Java/

月曜日, 21 2 月 2005

gjc VS jvm
この記事をクリップ!
このエントリーをはてなブックマークに追加

GNUのJavaコンパイラ、gcjがどれくらい速いのかちょっと試してみた。
以下のコードをサンプルとして使用。内容は50万個のオブジェクトを生成し、配列に順次入れていくというだけの簡単なもの。オブジェクトの生成コスト知りたかったので。
public class test{
  static final int SIZE=500000;

  public static void main(String[] args){
    long start=System.currentTimeMillis();

    testObj[] list=new testObj[SIZE];

    for(int i=0;i<SIZE;i++)
      list[i]=new testObj(i);

    long end=System.currentTimeMillis();

    System.out.println("takes "+(end-start)+" ms");
  }

  public static class testObj{
    private int _i;

    public testObj(int i){
      this._i=i;
    }
  }
}

実行環境: RH Linux 7.3(kernel 2.4.28)
Intel Celeron 1.70GHz 1GB Memory

・jvm (Sun J2SDK1.4.2_06)
$ javac test.java
$ java -cp . test
takes 313 ms

・gcj (3.3.2)
$ gcj --main=test test.java
$ gcj --main=test -o test test.java
$ ./test
takes 160 ms
これだけを見るとgcjでコンパイルしたネイティブコードの方が速いように見えるが、jvmの方はヒープの割り当てをデフォルトのままで実行しているのでこれをGCが発生しない量に設定し再度実行してみる。
$ java -Xms200m -Xmx200m -cp . test
takes 50 ms
GCさせななければjvmの方が圧倒的に速いようだ。
ただ現実的には余程簡単なプログラムでない限り無いGCの発生を無くすことはできないし、通常の込み入ったプログラムを使用した場合にも結果は変わってくるので比較結果はこの限りではないと思う。
Posted by tsujitako at 1:05 午前 in Java/

金曜日, 18 2 月 2005

Betwixtによるオブジェクトのシリアライズ/デシリアライズ
この記事をクリップ!
このエントリーをはてなブックマークに追加

Coomons BetwixtはBeanオブジェクトからXMLへのシリアライズや逆のXMLからBeanオブジェクトへのデシリアライズを行うライブラリだ。
通常のObjectOutputStreamを使用するシリアライズと違って出力されるファイルがXML形式なため可読性が高く、その上XMLの編集によりデシリアライズ後のオブジェクトの状態まで変更することが可能である。
使用方法はとてもシンプルでBeanReaderによりXMLを読み込んでオブジェクトを生成し、BeanWriterによりオブジェクトをXMLに出力するというものだ。
XMLの要素名はそのままBeanのアクセッサメソッドからgetやadd等の接頭語が取られた状態の名前になるので直感的に理解できると思う。
以下のサンプルは本を表したBookとそのBookをリストで管理するLibraryをXMLから読み込み、一つBookを追加して書き出すというものである。
//Bookオブジェクト
public class Book{
  private String _name;

  public Book(String name){
    _name=name;
  }

  public void setName(String name){
    _name=name;
  }

  public String getName(){
    return _name;
  }
}
//Libraryオブジェクト
import java.util.List;
import java.util.ArrayList;

public class Library{
  List _books=new ArrayList();

  public void addBook(Book book){
    _books.add(book);
  }

  public Book[] getBooks(){
    Book[] resultBooks=new Book[_books.size()];
    _books.toArray(resultBooks);

    return resultBooks;
  }
}
//実行
File file=new File("library.xml");
BeanReader reader=new BeanReader();
reader.registerBeanClass(Library.class);
Library library=(Library)reader.parse(file);

//Book[] books=library.getBooks();
//for(int i=0;i<books.length;i++)
//  System.out.println("book "+books[i].getName());

library.addBook(new Book("new book"));

OutputStreamWriter owriter = new OutputStreamWriter(new FileOutputStream(file),"utf-8");
owriter.write("<?xml version='1.0' encoding='utf-8'?>");
BeanWriter writer = new BeanWriter(owriter);
writer.setWriteIDs(false);
writer.enablePrettyPrint();
writer.write(library);
writer.close();
このサンプルに次のXMLを処理させる。
//library.xml
<?xml version='1.0' encoding='utf-8'?><br>
<Library>
  <books>
    <book>
      <name>book</name>
    </book>
  </books>
</Library>
するとオブジェクトに変換された後、以下のようにbookの要素が一つ追加されて書き出される。
<?xml version='1.0' encoding='utf-8'?>
<Library>
  <books>
    <book>
      <name>book 1</name>
    </book>
    <book>
      <name>new book</name>
    </book>
  </books>
</Library>
複雑なオブジェクトのシリアライズには向かないがちょっとしたBeanを永続化させるにはちょうどいいのではないだろうか。
Posted by tsujitako at 12:45 午前 in Java/

木曜日, 17 2 月 2005

Java Computing 2005 Spring
この記事をクリップ!
このエントリーをはてなブックマークに追加

2005年3月10日、11日の日程でJava Computing 2005 Springが開催されるそうだ。
今年がJava生誕10周年ということで参加費は無料、これはお得。是非行きたいところだけど今年は無理だろうなぁ。
思えば去年Java Technology Conference 2004は良かった、何がってJava nightで出された料理、うまかった。
でもSunのイベント公式サイトを見るとどうもJava nightについてまで言及されていないのが気になる。今年は開催されないのだろうか。
去年のJava nightに参加して「あぁ自分もあの舞台に立ちたい」と思って早一年、結局思っただけで何も作らなかったんだけども。
もし作っていたとしても今年のお祭りでJava nightが開催されないのであれば作り損になってたわけで、よし結果オーライ。
来年開催されるかもしれないJava nightに向けてネタを考え始めることにしよ。
Posted by tsujitako at 2:22 午前 in Java/

火曜日, 15 2 月 2005

javax.cryptoパッケージによる暗号化/複合化
この記事をクリップ!
このエントリーをはてなブックマークに追加

Web系のシステムを作っていてよくあるのが乱数をキーとして個人情報を特定する場面。
ユニークな乱数を発行してそれを個人情報と一緒に保存するのが一般的だが、既存項目の情報を暗号化してそれをキーとして扱うことも出来る。
それを可能にするのがjavax.cryptoパッケージだ。
このパッケージ以下には秘密鍵の実装や暗号化/複合化を行うクラスが含まれる。
使用可能なアルゴリズムにはAES,Blowfish,DES,RSAなどがあるが以下のサンプルではDESede(トリプルDES)を使った。
またバイト配列を16進文字列に変換するためCommons Codecを使用した。
//暗号化
String value="sample"; //暗号対象文字列
String key="ABCDEFGHIJKLMNOPQRSTUVWX"; //キー(24バイト)

SecretKeyFactory keyFac = SecretKeyFactory.getInstance("DESede");
DESedeKeySpec keySpec = new DESedeKeySpec(key.getBytes());
SecretKey secKey = keyFac.generateSecret(keySpec);

Cipher encoder = Cipher.getInstance("DESede");
encoder.init(Cipher.ENCRYPT_MODE,secKey);
byte[] b = encoder.doFinal(value.getBytes());
System.out.println(new String(Hex.encodeHex(b)));


//複合化
String value="2aee0c344a792f57"; //複合化対象文字列
String key="ABCDEFGHIJKLMNOPQRSTUVWX"; //キー(24バイト)

SecretKeyFactory keyFac = SecretKeyFactory.getInstance("DESede");
DESedeKeySpec keySpec = new DESedeKeySpec(key.getBytes());
SecretKey secKey = keyFac.generateSecret(keySpec);

Cipher decoder = Cipher.getInstance("DESede");
decoder.init(Cipher.DECRYPT_MODE,secKey);
byte[] b = Hex.decodeHex(value.toCharArray());
System.out.println(new String(decoder.doFinal(b)));
上記サンプルの暗号化を行うと"sample"という文字列が暗号化され"2aee0c344a792f57"となる。
これを同じキーで複合化すると"sample"という文字列に戻すことが可能だ。
Posted by tsujitako at 2:11 午前 in Java/

月曜日, 14 2 月 2005

JAIによる画像縮小
この記事をクリップ!
このエントリーをはてなブックマークに追加

Javaを利用してサムネイルなどの画像縮小を行う際に一番メジャーなのはjava.awt.GraphicsのdrawImageメソッドではないだろうか。
確かにJ2SE標準のAPIなので手っ取り早いのだが、いかんせんニアレストによる補完が行われるため画質が粗くなってしまう。
こういう場合はJava Advanced Imaging (JAI)を利用すれば高画質で縮小させることができる。
javax.media.jai.Interpolationのスタティックフィールドに宣言されているINTERP_BICUBIC,INTERP_BICUBIC_2,INTERP_BILINEAR,INTERP_NEARESTがそれぞれバイキュービック、バイキュービック2、バイリニア、ニアレストによる補完方法になっていて、スケール変更の際のパラメータとして指定すればその方法で補完される。
以下はバイリニアにより元の画像の縦横1/2のサイズに縮小させる例。
String readFileName = "sample.jpg";
String writeFileName = "sample2.jpg";

RenderedOp image = JAI.create("stream",new FileSeekableStream(new File(readFileName)));
Interpolation interp = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);

ParameterBlock params = new ParameterBlock();
params.addSource(image);
params.add(0.5F);
params.add(0.5F);
params.add(0.0F);
params.add(0.0F);
params.add(interp);

RenderedOp image2 = JAI.create("scale", params);

JPEGEncodeParam encParam = new JPEGEncodeParam();
encParam.setQuality(0.95f);
JAI.create("filestore",writeFileName,"JPEG",encParam);

元の画像


java.awt.GraphicsのdrawImageによる縮小


javax.media.jai.Interpolation.INTERP_BILINEARによる縮小



縮小された画像の画質にこだわる場合はJAIを使う方が賢明かと。
Posted by tsujitako at 1:47 午前 in Java/
前のページ   1 > 2   次のページ