yamicha.com's Blog - Presented by yamicha.com
Blog yamicha.com's Blog - 2018/07 の記事
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24] [25] [26] [27] [28] [29] [30] [31]

yamicha.com's Blog
 諸事情により、現在更新休止中。ご了承ください。もし今後ブログを再開することがあるとすれば、その際にはこのブログスクリプトではなく、新しく開発したものによるかもしれません。
 当ブログ管理者についてはこちらをご参照。
開発魔法(737)
社会問題(733)
お知らせ(11)
質問・バトン回答(15)
ゲスト出演(8)
経済・知的財産(150)
ゲーム開発(182)
[Ada] 勝手に補足
- Note
- 金配りの次の一手


- Endless Ultimate Diary
- 銃世界

漢字バトン
- うるる雑記帳
- 漢字接力棒

ツキアイゲノムバトン
- ブログ@うにうに画像倉庫
- あぶ内閣

縺イ縺セ縺、縺カ縺励ヰ繝医Φ
- 月夜のボヤキ
- 騎士サーラバトン
パスワードを使う
名無し (2012/02/27)


開発者解放裁判
yamicha.com (2010/03/14)
Winnyに関しては、私も「純白」とまでは考えておりませんし、使用し..

開発者解放裁判
通りすがり (2010/03/08)
winnyに関しては「ダウンロードソフト板」なんてところを拠点に開発..

新型インフルエンザの恐怖
いげ太 (2009/11/03)
> C#などの「int Some(int , int)」は、F#では「(int * int) ->..

時効に関する思考
yamicha.com (2009/08/31)
>いげ太さんコメントありがとうございます。手元にドキュメントが少..
Homepage
Blog Top
Normal View
List View
Search
Calendar
Comment List
Trackback List
Blog RSS
Send Trackback
Phone Mode
Administrator
yamicha.com
Blog
るううるる。
Source
法令データ提供システム
FindLaw
Development
Java2 Platform SE 6
Java EE 6 API
MySQL Developer Zone
PHP Reference
MSDN Library
Ada Reference Manual
Objective Caml
Python Documentation
Erlang
Prolog Documents
総無省
2007/07/13(Fri)20:24:56
 総務省が責任者を呼び出して「正確な当落報道」を要請。これまでは文書の郵送によって行われてきたそうですが、今回はご丁寧にも責任者を呼び出した上で文書を手渡す念の入れようです。総務省曰く、理由は「2005年度の選挙で誤報が急増したため」とのこと。
 一部マスコミは「報道への介入」などとしてこれに反発していますが、2005年度の選挙時に誤報が急増したことについては、どうやら総務省の「ねつ造」や「思い込み」ではないようです。それまではせいぜい数件に過ぎなかった誤報が、何と先の選挙においては20件以上あったといいますから、実に10倍前後まで増えていることになります。総務省の行動の是非はともかく、少なくとも誤報が大量に存在することは事実のようです。
 なお、全国紙の中では特に毎日新聞がこれに強く反発しており、社説を使ってまで総務省の姿勢を批判しています。具体的には、「国が指図すべきことではない」「報道の介入にもつながりかねない」とした上で、放送局がこれを素直に受け入れてきたことについても疑問を呈し、「国への毅然とした態度こそが国民の信頼を築く」としています。
 しかし、いくらマスコミが自己正当化論を並べ立てたところで、結局は自分でまいた種であることを忘れてはいけません。「これまで誤報は毎回数件存在したが、2005年の選挙ではゼロだった。それなのに総務省は幹部を呼び出して要請した」というのであればまだ分かりますが、「これまでは数件の誤報であったのに、2005年の選挙では20件に急増した。総務省が幹部を呼び出して正確な報道を要請するのはおかしい」では意味が通じません。総務省の姿勢を糾弾したいのであれば、まずはこの例でいうところの前者のような報道をするのが先決です。その上で「総務省はおかしい」と主張するなら、この意見は多くの人に受け入れられるでしょう。
 ただ、中には「放送局らが大量の誤報を出したことは事実としても、総務省の行動は適切ではない」と見る向きもあるでしょう。しかし、もし総務省が「2005年選挙では誤報が多かった。よって今回の選挙で1件でも誤報をした放送局からは放送免許を取り上げる」とでも言い出しているのなら、非難されるべきは間違いなく総務省ですし、私も異議を唱えますが、実際には単に「幹部を呼びつけて文書を手渡した」だけです。誤報が大幅に増加した事実を考慮すれば、総務省の行動が100%誤りであるとは認められません。
 とはいえ、総務省の行動にも疑問は多いです。「幹部を呼びつけての注意」からなし崩し的に報道への介入をもくろんでいるとの疑念を持たざるを得ないことも理由の1つですが、これだけ報道被害の現実が取りざたされている現代において、総務省のやることが「選挙についての誤報自粛要請」であるというのがまずもって釈然としません。ご丁寧にも選挙の誤報について「要請」するのであれば、もっと先にやることがあるはずです。
 例えば、大切な家族・親族・友人が凄惨な事件の犠牲になったとしましょう。それだけでも絶望的なほどの衝撃を受けることは間違いありませんが、そこへマスコミが寄ってたかって押し寄せ、被害者の名誉をズタズタに引き裂き、被害者ばかりかその関係者のプライバシーをも暴き去り、しかもタバコの吸殻や空き缶を置き土産に残していってくれます。それなのに、総務省や政府が少しでもこれをとがめようものなら「報道の自由の侵害」などと言い出して手がつけられません。はっきり言って、政府なり総務省なりが「悪者」になってでも規制を行わない限り、被害者がいわば2度殺される事態を防ぐことはできないのです。
 今回の選挙に関する要請についても、毎日新聞など一部のマスコミはすでに批判を始めています。総務省はマスコミから批判されることを承知で幹部呼び出しによる要請を行ったのです。どうせ批判されることをやむなしとしているなら、選挙の誤報よりも先に下劣な報道を規制すべきであることは論を待ちません。
 自分の散々な報道は棚に上げて総務省を批判するマスコミ連中。そして、被害者らが非常に大きな苦しみを強いられているというのに、それよりも選挙の誤報を行わないよう「要請」することを優先する総務省。私に言わせればどっちもどっちです。

 さて、その総務省やら放送やらにかかわることなのですが、残念ながら先に取り上げた「コピー9回まで」案が採用されそうです。現在のデジタル放送網に関する制限は「B-CAS」という放送局連中で作る利権団体が定めており、一般の視聴者はこの利権団体が好き勝手に定めたルールを遵守しなくてはなりません。コピーワンスについては一般視聴者及びメーカーからの批判も根強く、いくら独占利権団体といえども妥協を余儀なくされたようではありますが、これはあくまで「妥協」でしかありません。利権団体が自らの利権に食い込むような改正を認めるわけがありません。
 この案が採用された場合、DVDなどへのデータのコピーは9回まで容認されますが、10回目のコピーを行うと元のデータが抹消されます。一応「DVDに書き込み失敗してデータが消えた」という悲劇は防止できますが、利権団体がコンテンツの私的利用に関する権利を握っていることは変わりません
 また、こちらも以前に指摘した通り、現在流通しているハードウェアは9回コピーに対応していません。もし9回コピー制度を利用したければ、それに対応した新しいハードウェアを購入しなくてはなりません。ところが、既存のハードウェアは「コピー不可」「コピーワンス」「コピーフリー」に対応しているため、データをコピーフリーで配信すれば既存のハードウェアでも複数回のコピーが可能なのです。
 違法コピーの問題が深刻なのは事実ですが、現在のデジタル放送はアナログへなら(多少画質は落ちますが)無限にコピーができるため、デジタルのマスターデータさえあれば同等画質のアナログデータを大量に作ることができます。また、放送局側としてはデジタルデータがインターネット上で出回ることを恐れているようですが、いくら大容量回線が一般的になってきているとはいえ、今のところ映像は何らかの形式に圧縮されて配信されるのが普通です。生のデータをそのまま配信していては、大量のサーバー領域と大容量の回線、高いサーバー性能が必要になりますので、非常に効率が悪いのです。どうせビットレートは大幅に落とされるのですから、原本が超高画質であっても大した意味はありません。
 こうしたことから、放送局が血眼になって行っている対策はほとんど意味を成さず、一般利用者に不便を強いるだけと考えられます。さらに、「B-CAS」なるシステムが永久に世界の技術者を欺き通せるとは考えられませんので、いつしかコピー制限を完全に無力化するツールも作成されるでしょう。そうなると、一部のマニアックなユーザー、あるいは違法コピーを行う人間はこれを使って制限を無力化できるのに対し、一般ユーザーのみがコピー制限にがんじがらめにされることになります。
 実のところ、この「コピー9回」案は極めて狡猾です。著作権意識の高くない一般的な素人ユーザーの多くは「コピーワンスは不便だが、コピーが9回できるなら構わない」と考えることでしょう。しかし、これは単にコピー可能回数が違うだけで、放送局が「データを私的コピーして利用する権利」を利用者から剥奪している点は同じです。
 コピーワンスは絶対に見直されなければなりませんが、それは放送局側によるコピー制御の撤廃によるものでなければなりません

 続・JSON。JSONは基本型や配列の他、オブジェクトも扱うことができます。JavaのデータをJSONに変換する場合、JavaScriptの基本型に当たるものとしてプリミティブ及びそのラップ型を使い、配列型には配列かコレクションを使うとして、オブジェクトの場合はどうしましょうか。前回はMapを用いましたが、これでは使いづらくて仕方ありません。
 やはり、Java上のオブジェクトをそのままJSONに変換するのが最も楽でしょうか。パーサにインスタンスを渡すだけで変換できれば、楽なことこの上ありません。何といっても、JSONに変換したいクラスのインスタンスを生成し、そのインスタンスに対して必要な変更を行った後、それをパーサに渡すだけなのですから。
 そんなこんなで作ってみたのが「yamicha.com JSON Parser」です。ソースコードは後述するとして、ここで使い方を書いておきます。

1.最もシンプルな使い方
 publicフィールドに@JSONMemberをつけるだけです。
public class Test{
	@JSONMember public String value;
}
 フィールドがpublic以外の場合、このフィールドは無視されます。@JSONMemberは必ずpublicフィールドにつけるようにしましょう。

2.JavaBeanとして使う
 フィールド名(publicでなくても構わない)の先頭を大文字にしたset〜及びget〜という名前のpublicメソッドを作成し、getまたはsetメソッドのどちらかに@JSONMemberをつけるだけです。
public class Test{
	private String value;
	@JSONMember public String getValue(){
		return value;
	}
	public void setValue(String v){
		value = v;
	}
}
 なお、フィールドとメソッドの両方に、あるいはgetterとsetterの両方に@JSONMemberがついていると、例外が発生します。命名規則は基本的にJavaBeanに従いますが、かなり厳密です。例えば、urlという名前のフィールドのgetterメソッド名はgetUrl()でなければならず、getURL()では動作しません。これは様々な表記が混在した場合に厳密にフィールドを解決するための仕様です。
 getterメソッドの返り値はフィールドの型と同じでなければならず、引数は持てません。setterメソッドはvoidでなければならず、引数はフィールドの型と同じでなければなりません。また、このパーサでは「JavaBeanはJavaScriptのプロパティに相当」という考え方に基づいているため、フィールドがなくgetterとsetterだけがあるJavaBeanメソッドは処理されません。JavaBeanメソッドはあくまでフィールドとセットであると考えてください。
 注意点としては、仮にgetter/setterメソッドのいずれか片方でもpublicではない場合、そのプロパティは無視されます。言うまでもありませんが、getter/setterのどちらか片方が存在しなかったり、返り値の型や引数が適切でない場合においても、やはりそのプロパティは無視されます。

3.フィールド名と異なる名称のgetter/setterを使う
 (2)の規則によれば、メソッドに対して@JSONMemberを付加して処理を行う場合、urlというフィールドには「getUrl()」「setUrl()」というJavaBeanメソッドを用意しなければなりません。しかし、@JSONPropertyメソッドでJavaBeanメソッド名を指定してやれば、任意の名前のJavaBeanメソッドを使うことができます。
public class Test{
	@JSONProperty("URL") private String url;
	@JSONMember public String getURL(){
		return url;
	}
	public void setURL(String vu){
		url = u;
	}
}
 上記の例では「URL」をJavaBeanメソッド名としていますが、全く異なる名前(「Value」など)でも構いません。ただし、@JSONPropertyには先頭が大文字の文字列を渡すようにしましょう。先頭が小文字の文字列を渡しても動作はしますが、例えば「value」を渡した場合、JavaBeanメソッド名はそれぞれ「getvalue()」「setvalue()」となってしまいます。これが「Value」であれば「getValue()」「setValue()」になります。

4.全フィールドをJSON変換対象にする
 フィールドが大量にあり、しかもそれらすべてをJSONにしたい場合、全フィールドまたはメソッドに@JSONMemberを付加するのは手間がかかります。そこで@JSONContentアノテーションを使えば、全フィールド・メソッドをJSON化することができます。ただし、defaultEnable(デフォルト値はfalse)をtrueに設定しなければなりません。
@JSONContent(defaultEnable=true) public class Test{
	private String value;
	public String getValue(){
		return value;
	}
	public void setValue(String v){
		value = v;
	}
}
 @JSONContentは以下のような仕様になっています。

I.フィールド、あるいはフィールドとJavaBeanメソッドの両方があるプロパティのみが変換対象とみなされます。すなわち、JavaBeanメソッドしかないプロパティは変換対象になりません。
II.publicフィールドとpublic JavaBeanメソッドの両方が存在する場合、フィールドよりもJavaBeanメソッド経由でのデータ取得が優先されます。ただし、フィールドが@JSONMemberアノテートされている場合においては、JavaBeanメソッドがあってもフィールドを使います。
III.(2)の規則の通り、フィールド名の先頭を大文字にしたものがJavaBeanメソッドである(value -> getValue,setValue)とみなされますが、(3)で述べたようにフィールドに@JSONPropertyが指定されている場合には、@JSONPropertyの値がJavaBeanメソッドのプロパティ名であると解釈されます。
IV.上位クラスから継承されたフィールドは一切処理されません。@JSONContent(defaultEnable=true)において処理されるのは、当該クラス内で定義されたフィールドのみです。

5.JSONContentアノテートされたクラス内の特定のプロパティを処理しない
 @JSONMemberのenableにfalseを渡すだけです。
@JSONContent(defaultEnable=true) public class Test{
	private String value;
	@JSONMember(enable=false) public String getValue(){
		return value;
	}
	public void setValue(String v){
		value = v;
	}
}
 これで「value」プロパティはないものとみなされます。

6.JavaScript上でのプロパティ名を任意の名称に設定する
 例えば「value」というフィールドがあれば、JSON上でのプロパティ名も「value」であるものとみなされますので、JavaScriptでは「object_var.value」という記述でそれを取得することになります。これを任意の名前に変更するには、@JSONMemberのnameに任意の名前を指定します。
public class Test{
	private String value;
	@JSONMember(name="data") public String getValue(){
		return value;
	}
	public void setValue(String v){
		value = v;
	}
}
 JavaScriptでは「object_var.data」としてアクセスすることになります。

 この通り、割と色々なことができます。ただし、パース対象のクラスが何らかのクラスの子に当たる場合、上位クラスのフィールドは(たとえ@JSONMemberアノテートされていても)一切無視されますので、継承は考えない方が賢明です。無論、@JSONMemberを付加してフィールドをオーバーライドすれば、そのフィールドは処理されるようになります。
 上記では色々と細かなケースについて言及しましたが、単に使うだけなら「@JSONMember」か「@JSONContent」をつけるだけという非常にお手軽なものです。
 それでは、これを使って何かを作成するとしましょうか。以下のようなディレクトリ構成になります。
/js
 /js_jar
  /com
   /yamicha
    /json
     JSONContent.java
     JSONMember.java
     JSONProperty.java
     JSONException.java
     JSONParser.java
    /js
     @Remote JSONAccess.java
     @Stateless JSONAccessBean.java
     @Entity Member.java
     @Entity Equipment.java
  /META-INF
   persistence.xml
 /js_war
  /WEB-INF
   web.xml
  register.jsp
  json.jsp
 /META-INF
  application.xml
 このうち、com.yamicha.jsonに属するものが「yamicha.com JSON Parser」のクラスになります。言うまでもありませんが、この部分だけ切り取ってパッケージ化すれば、ライブラリとして利用することも可能です。
 以下にソースを示します。
// JSONMember.java
package com.yamicha.json;

import java.lang.annotation.*;
import static java.lang.annotation.RetentionPolicy.*;
import static java.lang.annotation.ElementType.*;

@Retention(value=RUNTIME) @Target(value={FIELD , METHOD})
	public @interface JSONMember{
	boolean enable() default true;
	String name() default "";
}

// JSONContent.java
package com.yamicha.json;

import java.lang.annotation.*;
import static java.lang.annotation.RetentionPolicy.*;
import static java.lang.annotation.ElementType.*;

@Retention(value=RUNTIME) @Target(value={TYPE})
	public @interface JSONContent{
	boolean defaultEnable() default false;
}

// JSONProperty.java
package com.yamicha.json;

import java.lang.annotation.*;
import static java.lang.annotation.RetentionPolicy.*;
import static java.lang.annotation.ElementType.*;

@Retention(value=RUNTIME) @Target(value={FIELD})
	public @interface JSONProperty{
	String value() default "";
}

// JSONException.java
package com.yamicha.json;

public class JSONException extends Exception{
	public JSONException(){
	}
	public JSONException(String msg){
		super(msg);
	}
}

// JSONParser.java
package com.yamicha.json;

import java.util.*;
import java.lang.reflect.*;

public class JSONParser{
 public JSONParser(){
 }
 public String parse(Object o) throws JSONException{
  return parseData(o , new ArrayList());
 }
 private String parseData(Object o , List nest) throws JSONException{
  // 型によって処理を変える
  if(o == null){
   return null;
  }else if(o instanceof Integer || o instanceof Short || 
   o instanceof Long || o instanceof Float || 
   o instanceof Double || o instanceof Boolean){
   // プリミティブ型のラップの場合
   return o.toString();
  }else if(o instanceof String){
   // 文字列の場合
   return "\"" + ((String)o).replace("\"" , "\\\\\"") + "\"";
  }else{
   // 配列/リスト・オブジェクトの場合
   return parseNest(o , nest);
  }
 }
 private String parseNest(Object o , List nest) throws JSONException{
  for(Object n : nest){
   if(o == n)
    throw new JSONException("オブジェクト " + o.toString() + 
     " が循環参照されています。");
  }
  List newnest = new ArrayList(nest);
  newnest.add(o);

  if(o instanceof List || o.getClass().isArray()){
   // リスト・配列の場合
   return parseArray(o , newnest);
  }else{
   // オブジェクトの場合
   return parseObject(o , newnest);
  }
 }
 private String parseArray(Object o , List nest) throws JSONException{
  Class c = o.getClass();

  Object array[] = null;
  if(c.isArray()){
   array = (Object[])o;
  }else{
   array = ((List)o).toArray();
  }

  // 配列をパース
  String str = "[";
  for(int i = 0; i < array.length; i++){
   if(i > 0)
    str += ",";

   str += parseData(array[i] , nest);
  }
  str += "]";
  return str;
 }
 private String parseObject(Object o , List nest) throws JSONException{
  Class c = o.getClass();

  // JSONMember.enable() 指定のないフィールド・メソッドの挙動を設定
  // とりあえず無効とみなす(パーサのオプションで設定できるようにしてもよい)
  boolean enable = false;
  JSONContent jc = (JSONContent)c.getAnnotation(JSONContent.class);
  if(jc != null)
   enable = jc.defaultEnable();

  // フィールドを探索
  Field fields[] = c.getDeclaredFields();
  // フィールド名の重複チェック用
  List<String> names = new ArrayList<String>();
  String str = "{";

  int regist = 0;
  for(Field field : fields){

   // フィールドに @JSONProperty 注釈があれば、指定された Bean メソッドを探す
   String property = "";
   if(field.isAnnotationPresent(JSONProperty.class))
    property = field.getAnnotation(JSONProperty.class).value();

   // 注釈なし、あるいは注釈で名前が指定されていなかったら名称を生成
   if(property.length() == 0){
    property = field.getName();
    property = property.substring(0 , 1).toUpperCase(Locale.ENGLISH) + 
     property.substring(1 , property.length());
   }

   // それを使って get Bean メソッドがあるかを調査
   Class type = field.getType();
   String get = "get" + property;
   String set = "set" + property;
   boolean method_call = false;
   boolean method_annotation = false;
   boolean annotation_is_getter = true; // アノテーションが getter にある

   Method getter = null;
   Method setter = null;
   try{
    getter = c.getMethod(get);

    // 戻り値がフィールドの型と一致するかを確かめる
    if(!type.equals(getter.getReturnType()))
     getter = null;
   }catch(NoSuchMethodException e){
   }
   try{
    setter = c.getMethod(set , type);
   }catch(NoSuchMethodException e){
   }

   Method accessor[] = {getter , setter};
   int method_call_value = 0;
   for(int i = 0; i < accessor.length; i++){
    Method m = accessor[i];

    if(m != null){ // 存在するらしい
     // アクセスできるかを確かめる
     if(Modifier.isPublic(m.getModifiers())){
      method_call_value ++; // アクセス可能

      // アクセスできるので、JSONMember アノテーションを調べる
      if(m.isAnnotationPresent(JSONMember.class)){
       if(method_annotation){
        // すでに getter にアノテーションがあるのに、setter にもあった場合
        throw new JSONException("@JSONMember アノテーションが重複しています");
       }
       method_annotation = true;
       annotation_is_getter = (i == 0);
      }
     }
    }
   }

   if(method_call_value >= 2) // getter/setter を両方使える場合のみ
    method_call = true;

   boolean field_call = false;
   boolean field_annotation = false;

   // フィールドへの直接アクセスについて調べる
   if(Modifier.isPublic(field.getModifiers())){
    field_call = true; // アクセス可能

    // JSONMember アノテーションを確かめる
    if(field.isAnnotationPresent(JSONMember.class)){
     if(method_annotation){
      // @JSONMember が getter/setter のいずれかにあって、フィールドにもある
      throw new JSONException("@JSONMember アノテーションが重複しています");
     }
     field_annotation = true;
    }
   }

   // メソッドとフィールドのどちらもコールできないなら continue
   if(!method_call && !field_call)
    continue;

   // メソッドとフィールドのどちらを使うかを判定
   // @JSONMember が一方になされている場合はそちらを使う
   // これが両方にないならとりあえずメソッドを使う
   boolean use_method = true;
   if((method_annotation || !field_annotation) && method_call){
    use_method = true;
   }else{
    use_method = false;
   }

   boolean json_enable = enable; // このプロパティを JSON で使用するか

   // JSONMember があるならプロパティの有効性(JSONMember が true か)を調査
   JSONMember jm = null;
   if(use_method){
    if(annotation_is_getter)
     jm = getter.getAnnotation(JSONMember.class);
    else
     jm = setter.getAnnotation(JSONMember.class);
   }else{
    jm = field.getAnnotation(JSONMember.class);
   }
   if(jm != null)
    json_enable = jm.enable();

   // このプロパティが有効でないなら continue
   if(!json_enable)
    continue;

   // フィールド名を定める
   String name = field.getName(); // 通常はそのままフィールド名だが・・・
   if(jm != null && jm.name().length() != 0){ // フィールド名指定があるなら
    name = jm.name(); // それを使用する
   }
   // フィールド名の重複をチェック
   if(names.indexOf(name) != -1){
    throw new JSONException("フィールド名 " + name + " が重複しています。");
   }

   // プロパティの値を処理
   String data = null; // JSON 化した値データ
   if(use_method){ // getter メソッドから取得
    try{
     data = parseData(getter.invoke(o) , nest);
    }catch(IllegalAccessException e){
    }catch(IllegalArgumentException e){
    }catch(InvocationTargetException e){
    }
   }else{ // フィールドから取得
    try{
     data = parseData(field.get(o) , nest);
    }catch(IllegalAccessException e){
    }catch(IllegalArgumentException e){
    }catch(ExceptionInInitializerError e){
    }
   }

   // フィールドとプロパティを登録
   if(regist > 0)
    str += ",";

   str += parse(name) + ":" + data;

   regist ++;
  }
  str += "}";
  return str;
 }
}
 ところで、肝心のJSONParserの使い方ですが、
// class Test : @JSONMember、@JSONContent などで注釈されたクラス
JSONParser p = new JSONParser();
String json = p.parse(new Test());
 これだけです。parse()メソッドにはクラスの他、プリミティブ(のラップ)型、配列型、List型、String型など様々なものを渡すことができます。プリミティブ(のラップ)型はJavaScriptの基本型に、配列及びListをインプリメントした型はJavaScriptの配列に、String型はJavaScriptの文字列に、@JSONMemberや@JSONContentアノテートされたクラスのインスタンスは上記で述べた規則に沿って、それぞれJSONに変換されます。循環参照は検出可能になっており、検出された場合には例外を投げてきます。
 後はこれをどう使うかですが、せっかくJSON変換をオブジェクトにマッピングできるようになったのです。アレしかないでしょう
// js_jar\com\yamicha\js\Member.java
package com.yamicha.js;

import javax.persistence.*;
import java.util.*;
import static javax.persistence.CascadeType.*;
import static javax.persistence.FetchType.*;
import com.yamicha.json.*;

@Entity @Table(name="ee_js_member" , schema="yamicha") 
	@JSONContent(defaultEnable=true) 
	public class Member implements java.io.Serializable{
	private int number;
	private String name;
	private String prefix;
	private List<Equipment> equipments;

	public Member(){
		equipments = new ArrayList<Equipment>();
	}
	public Member(String name , String prefix){
		this();
		this.name = name;
		this.prefix = prefix;
	}
	public Member(String name , String prefix , 
		List<Equipment> equipments){
		this(name , prefix);
		this.equipments = equipments;
	}

	@Id @GeneratedValue @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@Column(name="prefix") public String getPrefix(){
		return prefix;
	}
	@OneToMany(cascade=ALL , fetch=EAGER , mappedBy="member") 
		public List<Equipment> getEquipments(){
		return equipments;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setName(String n){
		name = n;
	}
	public void setPrefix(String p){
		prefix = p;
	}
	public void setEquipments(List<Equipment> e){
		equipments = e;
	}

	public void reverse(){
		for(Equipment e : equipments)
			e.setMember(this);
	}
}

// js_jar\com\yamicha\js\Equipment.java
package com.yamicha.js;

import javax.persistence.*;
import java.util.*;
import static javax.persistence.CascadeType.*;
import static javax.persistence.FetchType.*;
import com.yamicha.json.*;

@Entity @Table(name="ee_js_equipment" , schema="yamicha") 
	@JSONContent(defaultEnable=true) 
	public class Equipment implements java.io.Serializable{
	private int number;
	private String name;
	private Member member;

	public Equipment(){
	}
	public Equipment(String name){
		this.name = name;
	}
	public Equipment(String name , Member member){
		this(name);
		this.member = member;
	}

	@Id @GeneratedValue @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@ManyToOne @JoinColumn(name="member") @JSONMember(enable=false) 
		public Member getMember(){
		return member;
	}

	public void setNumber(int n){
		number = n;
	}
	public void setName(String n){
		name = n;
	}
	public void setMember(Member m){
		member = m;
	}
}
 開発魔法・Entity Bean。OneToManyが装備品のマッピングに適することは(当ブログでは)周知の事実です。本当は全く違ったものにしようとも考えましたが、あえて前回のJSONと似せた構造にしてみましたので、比べてみるのも面白いでしょう。
 上記クラスはどちらもJSONContentを使ってJSONを適用しているのですが、このままでは循環参照が生じてしまうため、当該部分に@JSONMember(enable=false)を用いて回避しています。原理はSOAの@XmlTransientと同じです。それにしても、JPA以後のEntity Beanは柔軟性が高くて助かります。それ自体がPOJOな上にXMLも不要とあって、同じくアノテーションで何とでもなるJAX-WSと組み合わせたり、今回のJSON ParserのようなPOJOベースの自作ライブラリと組み合わせたり。
 次に、このEntityを扱うStateless Beanを作成します。別に色々と処理を書いても構わないのですが、今回はシンプルにまとめるとしましょう。
// js_jar\com\yamicha\js\JSONAccess.java
package com.yamicha.js;

import javax.ejb.*;
import java.util.List;

@Remote public interface JSONAccess{
	public void addMember(Member m);
	public Member getMember(int id);
	public List<Member> getMembers();
	public String getJSONMember(int id);
	public String getJSONMembers();
}

// js_jar\com\yamicha\js\JSONAccessBean.java
package com.yamicha.js;

import javax.annotation.*;
import javax.ejb.*;
import javax.persistence.*;
import java.util.List;
import com.yamicha.json.*;

@Stateless(name="JSONAccess" , mappedName="ejb/JSONAccess") 
	public class JSONAccessBean 
	implements JSONAccess , java.io.Serializable{
	@PersistenceContext(unitName="persist") private EntityManager em;

	public void addMember(Member m){
		em.persist(m);
	}
	public Member getMember(int id){
		return em.find(Member.class , id);
	}
	public List<Member> getMembers(){
		return (List<Member>)em.createQuery(
			"SELECT m FROM Member m").getResultList();
	}
	public String getJSONMember(int id){
		try{
			JSONParser p = new JSONParser();
			return p.parse(getMember(id));
		}catch(JSONException e){
			return null;
		}
	}
	public String getJSONMembers(){
		try{
			JSONParser p = new JSONParser();
			return p.parse(getMembers());
		}catch(JSONException e){
			return null;
		}
	}
}
 別にJSP側でJSON変換を行ってもかまわないのですが、せっかくですからStateless Beanに変換結果を返すメソッドを用意してみました。やはりアノテーションの力は大きく、いちいち面倒な記述を行わなくても、上記の記述のみでJSON変換を行うことが可能です。
 残るはWeb層の実装のみです。
// register.jsp
<%@ page import="javax.servlet.http.* , javax.annotation.* , 
javax.ejb.* , javax.naming.* , java.rmi.* , javax.rmi.* , 
java.io.* , java.util.* , com.yamicha.json.* , com.yamicha.js.*" 
contentType="text/plain" pageEncoding="Shift_JIS" %>

<%!
private JSONAccess getJSONAccess() throws Exception{
	Context ct = new InitialContext();
	Object ejb = ct.lookup("ejb/JSONAccess");
	return (JSONAccess)PortableRemoteObject.narrow(
		ejb , JSONAccess.class);
}
%>

<%
List<Equipment> sara_equips = new ArrayList<Equipment>();
sara_equips.add(new Equipment("ハーバード"));
sara_equips.add(new Equipment("6.28ラジアンシールド"));
sara_equips.add(new Equipment("スパムメイル"));
sara_equips.add(new Equipment("議決権未満かぶと"));
Member sara = new Member("サーラ" , "騎士" , sara_equips);
sara.reverse();

List<Equipment> ilias_equips = new ArrayList<Equipment>();
ilias_equips.add(new Equipment("有価小剣"));
ilias_equips.add(new Equipment("ハイパーリング"));
ilias_equips.add(new Equipment("ジャバの豆"));
ilias_equips.add(new Equipment("紡糸の帽子"));
Member ilias = new Member("イリアス" , "魔道士" , ilias_equips);
ilias.reverse();

List<Equipment> harden_equips = new ArrayList<Equipment>();
harden_equips.add(new Equipment("イントソード"));
harden_equips.add(new Equipment("アンシグネッドショートソード"));
harden_equips.add(new Equipment("リフレクション"));
Member harden = new Member("ハードゥン" , "剣聖" , harden_equips);
harden.reverse();

JSONAccess ja = getJSONAccess();
ja.addMember(sara);
ja.addMember(ilias);
ja.addMember(harden);
%>

// json.jsp
<%@ page import="javax.servlet.http.* , javax.annotation.* , 
javax.ejb.* , javax.naming.* , java.rmi.* , javax.rmi.* , 
java.io.* , java.util.* , com.yamicha.json.* , com.yamicha.js.*" 
contentType="text/html;charset=UTF-8" pageEncoding="Shift_JIS" %>

<%!
private JSONAccess getJSONAccess() throws Exception{
 Context ct = new InitialContext();
 Object ejb = ct.lookup("ejb/JSONAccess");
 return (JSONAccess)PortableRemoteObject.narrow(ejb , JSONAccess.class);
}
%>

<%
JSONAccess ja = getJSONAccess();
String json = ja.getJSONMembers();
%>
<html>
<head>
<title>JSON</title>
<script language="JavaScript">
function start(){
 var members = eval("(<%=json.replace("\"" , "\\\"")%>)");

 var str = "<table border=\"1\">";
 str += "<tr><td bgcolor=\"#CCDDFF\">number</td>";
 str += "<td bgcolor=\"#CCDDFF\">name</td>";
 str += "<td bgcolor=\"#CCDDFF\">equipments</td></tr>";
 for(var i = 0; i < members.length; i++){
  var member = members[i];

  member.getEquipmentLength = function(){
   return this.equipments.length;
  }
  member.joinEquipment = function(text){
   var str = "";
   for(var i = 0; i < this.getEquipmentLength(); i++){
    if(i != 0)
     str += text;
    str += this.equipments[i].name;
   }
   return str;
  }
  member.getName = function(){
   return this.prefix + this.name;
  }

  str += "<tr><td valign=\"top\">" + member.number + "</td>";
  str += "<td valign=\"top\">" + member.getName() + "</td>";
  str += "<td valign=\"top\">" + member.joinEquipment("<br>") + "</td></tr>";
 }
 str += "</table>";

 document.getElementById("data").innerHTML = str;
}
</script>
</head>
<body onload="start()" 
link="#0000FF" alink="#FF0000" vlink="#0000FF">

<b>yamicha.com JSON</b><br>
<br>

<span id="data"></span>

</body>
</html>
 ご注目いただきたいのは、JSONから生成したオブジェクト(の配列要素)に対して関数を代入している点です。JavaScriptではこのようなことが可能なのです。後はこれらの関数を使用し、好きなように処理をして結果を返すのみです。
 さて、後はデプロイするのみなのですが、忘れてはいけないのが配備記述子です。ディレクトリデプロイする場合は以下の3つの配備記述子が必要ですが、EAR化したものをデプロイするのであれば、persistence.xml以外の配備記述子は必要ありません。
// js_jar\META-INF\persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
 version="1.0">
<persistence-unit name="persist" transaction-type="JTA">
<provider>
oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider
</provider>
 <jta-data-source>jdbc/MySQL</jta-data-source>
 <properties>
  <property name="toplink.jdbc.driver" 
   value="com.mysql.jdbc.Driver" />
  <property name="toplink.ddl-generation" value="create-tables" />
  <property name="toplink.target-database" value="MySQL4" />
 </properties>
</persistence-unit>
</persistence>

// META-INF\application.xml
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee">
	<description>yamicha.com JSON Parser Sample</description>
	<module>
		<web>
			<web-uri>js.war</web-uri>
			<context-root>js</context-root>
		</web>
	</module>

	<module>
		<ejb>js.jar</ejb>
	</module>
</application>

// js_war\WEB-INF\web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
	<display-name>yamicha.com JSON Parser Sample</display-name>
	<description>yamicha.com JSON Parser Sample</description>
</web-app>
 後はこれをデプロイ(ディレクトリデプロイを行っても、EAR化したものをデプロイしても良い)し、最初にregister.jspにアクセスします。ブラウザには何も表示されませんが、これでデータが登録されます。本当に登録されたのか不安であれば、SQLを打って確認してみましょう。
SELECT m.prefix , m.name , COUNT(e.number) FROM ee_js_member m 
INNER JOIN ee_js_equipment e ON m.number = e.member 
GROUP BY m.number;

騎士	サーラ	4
魔道士	イリアス	4
剣聖	ハードゥン	3
 無事に登録されていることを確認できたら、json.jspを呼び出してみましょう。JavaScriptがJSONを解釈し、データをテーブル化して出力してくれることでしょう。
 今回のポイントは、データベースの読み書きにせよ、JSONへの変換にせよ、開発者がその処理に介在する必要がほとんどない点です。余計な処理を書く必要がなく、データの処理・整形・表示といった必要部分のみをコーディングすれば良いため、非常に楽ができます。
 POJOとアノテーションの威力、侮れません。また、JavaScriptの柔軟性(「いい加減」ともいいますが)を利用し、生成したオブジェクトに関数を登録するなどして処理をすることができる点も重要です。今回のコーディングは各種手法の長所の集大成と考えることができるのではないでしょうか。
カテゴリ [開発魔法][社会問題] [トラックバック 0][コメント 0]
<- 前の記事を参照 次の記事を参照 ->

- Blog by yamicha.com -