Fiverwoks Homepageへ

制約式と検索条件式

初版 2004-09-15
沼倉 均
Numakura Hitoshi

■ 制約式の定義

Xoneのクラスを定義するときには、そのインスタンスのエレメントが取る値に対して、制約を定義できます。制約とは、その値が取ることのできる条件のことです。Xoneには制約のための特別な仕掛けがあるわけではなく、Xoneのメタモデルの中で表現しています。

Xoneのクラスを表すcom.fiverworks.xone.model.XoneClassには、"_restrictionList"、"_restrictionMessageList"という2つのエレメントリスト名が定義されており、これらに制約式とメッセージをそれぞれ定義します。この2つのリスト名は、XoneClassの中でそれぞれRESTRICTION_LIST、RESTRICTION_MESSAGE_LISTという定数で定義されています。

たとえば、次のように定義されたクラスがあるとします。

PC
 価格,int
 CPU名,string,
 メモリ,int
これはクラス名がPCで、価格、CPU名、メモリというエレメントがあることを示しています。ここで、「価格は10万円以上」、「メモリは512(MB)以上でなおかつ256(MB)単位でしか増やせない」というときの制約式を考えてみると、次のようになります。

  #価格 >= 100000
  #メモリ >= 512 && (#メモリ - 512) % 256 == 0
ここで#が付いているものは、そのエレメントの実際の値を表すとします。こうしたクラスを定義するには、次の方法があります。

  1. モデルマネージャに附属のオブジェクトエディタを使う
  2. プログラムで定義する
  3. XMLファイルで定義しておき、それをモデルマネージャでインポートするか、あるいはプログラムで書き込む
このような簡単なクラスを定義するときは、オブジェクトエディタを使うのがもっとも簡単です。オブジェクトエディタならば、制約式が間違っていないかどうかをテストすることもできます。ですが、ここではプログラムで定義する方法を示します。プログラムは、次のようになります(ソースはこちら)。

 1  /*
 2   * XoneClassDef.java
 3   *
 4   */
 5  package test;
 6  
 7  import com.fiverworks.xone.model.*;
 8  import com.fiverworks.xone.mw.*;
 9  import com.fiverworks.xone.*;
10  
11  public class XoneClassDef {
12      
13      private MwMain mwMain;
14      
15      private void login() {
16          mwMain = new MwMain();
17          try {
18              mwMain.login("name", "password".toCharArray());
19          } catch (XoneException ex) {
20              ex.printStackTrace();
21          }
22      }
23      private void defineClass() {
24          login();
25          try {
26              // エレメントの定義
27              XoneElement xe1 = new XoneElement("価格", XoneModel.INT);
28              XoneElement xe2 = new XoneElement("CPU名", XoneModel.STRING);
29              XoneElement xe3 = new XoneElement("メモリ", XoneModel.INT);
30              
31              // 制約式(エレメントリスト)の定義
32              XoneElementList xel = new XoneElementList(XoneClass.RESTRICTION_LIST);
33              XoneElement r1 = new XoneElement("価格", XoneModel.STRING, 
                                        "#価格 >= 100000;");
34              XoneElement r2 = new XoneElement("メモリ", XoneModel.STRING, 
                                        "#メモリ >= 512 && (#メモリ - 512) % 256 == 0;");
35              xel.add(r1);
36              xel.add(r2);
37              
38              // クラスを生成
39              XoneClass xc = XoneModel.newXoneClass("PC", 
40                                      new XoneElement[]{xe1, xe2, xe3},
41                                      new XoneElementList[]{xel});
42              
43              // 保存
44              mwMain.save("root/classes", new XoneObject[]{xc});
45          } catch (XoneRuntimeException ex) {
46              ex.printStackTrace();
47          } finally {
48              if (mwMain.isLogin()) mwMain.logout();
49          }
50      }
51      
52      public static void main(String[] args) {
53          new XoneClassDef().defineClass();
54      }
55  }

33行では、価格の制約式を指定しています。ここに示したように、制約式を定義するエレメントと同じエレメント名で、型はstringにして式を定義します。式の中で自分自身の値を示すものは、#@で代用できます。ですから、この行は

 XoneElement r1 = new XoneElement("価格", XoneModel.STRING, "#@ >= 100000;");
と書いても等価です。式の最後にはセミコロン(;)を忘れないでください。

■ 条件演算子(三項演算子)

式には、条件演算子(三項演算子)を使うことができます。先ほどの例で、価格を「メモリが1024より大きいときは価格は15万円以上、それ以下は10万円以上」という制約式にするには、さきほどの33行を

 XoneElement r1 = new XoneElement("価格", XoneModel.STRING, 
                     "#メモリ >= 1024 ? #@ >= 150000 : #@ >= 100000;");
とします。

■ 関数

Xoneには数式を処理するプロセッサが内蔵されており、制約式もこのプロセッサで処理されています。このプロセッサでは、文字列、数値(double)、ブール値を扱うことができます。また、関数として11種の文字列関数、9種のブーリアン関数、27種の数値関数を持っています。これらの関数は最初からシステムに組み込まれていますが(組み込み関数)、後から関数を追加することもできます(外部関数)。

先ほどのクラスの例で、「CPU名はAthlonXP、Athlon64、Pentium4、Celeronのいずれかである」という制約式を追加するには、

new XoneElement("CPU名", XoneModel.STRING, "equals('#@','AthlonXP') || 
        equals('#@','Athlon64') || equals('#@','Pentium4') || equals('#@','Celeron');");
というエレメントを追加します('#@'という表記に注意してください。文字列はシングルクォートで囲わなくてはなりません)。この式でももちろんかまいませんが、ここでは「列記したいずれかの値に一致するときにtrue」という関数を作ってみます(この関数は、かなり使用頻度が高そうです。Xoneに最初から組み込み関数として入れたほうが良いかもしれません)。

外部関数(ユーザ定義関数)を作るには、次のルールに従う必要があります。
  1. 1つの関数に対して、1つのJavaのクラスを作る
  2. 引数のないコンストラクタを作る
  3. com.fiverworks.xone.exp.func.IFunctionをインプリメントする
  4. 外部関数の定義ファイル(XML形式)に定義を追加する
com.fiverworks.xone.exp.func.IFunctionには、
	Object evaluate(Object[] args)
という1個のメソッドが定義されているので、これを実装します。argsというパラメータには、引数の個数分の配列が渡されます。ですから、その関数ではまず、パラメータの数が合っているかどうかを調べます。次に、この配列の要素の型はjava.lang.Double、java.lang.Boolean、java.lang.Stringのいずれかなので、その関数に型が合っているかどうか調べます。最後に関数の値を返します。関数の値もDouble、Boolean、Stringのいずれかの型で返します。

さて、先ほどの関数ですが、仕様としては次のようにします。
引数の個数が可変なのも、外部関数の特徴です。要は、その関数内で引数をどう扱うかだけでの問題です。プログラムは次のようになります(ソースはこちら)。
 1  /*
 2   * Oneof.java
 3   *
 4   */
 5  package test;
 6  import com.fiverworks.xone.XoneRuntimeException;
 7  
 8  public class Oneof implements com.fiverworks.xone.exp.func.IFunction {
 9      
10      public Oneof() {}
11      
12      public Object evaluate(Object[] args) {
13          // 引数の個数を調べる
14          if (args.length < 2) 
15              throw new XoneRuntimeException("パラメータの数が違います");
16          
17          // 最初の引数の型を調べる
18          boolean isString;
19          String str1 = null;
20          double dbl1 = 0;
21          if (args[0] instanceof String) {
22              isString = true;
23              str1 = (String)args[0];
24          } else if (args[0] instanceof Double) {
25              isString = false;
26              dbl1 = ((Double)args[0]).doubleValue();
27          } else {
28              throw new XoneRuntimeException("パラメータの型が違います");
29          }
30          
31          // 2番目以降の引数の型を調べる
32          for (int i = 1; i < args.length; i++) {
33              if (isString) {
34                  if (!(args[i] instanceof String)) 
35                      throw new XoneRuntimeException("パラメータの型が違います");
36              } else {
37                  if (!(args[i] instanceof Double)) 
38                      throw new XoneRuntimeException("パラメータの型が違います");
39              }
40          }
41          
42          // 処理
43          for (int i = 1; i < args.length; i++) {
44              if (isString) {
45                  String str = (String)args[i];
46                  if (str1.equals(str)) return new Boolean(true);
47              } else {
48                  double dbl = ((Double)args[i]).doubleValue();
49                  if (dbl1 == dbl) return new Boolean(true);
50              }   
51          }
52          // どれにも一致しない
53          return new Boolean(false);
54      }
55  }

後は、このプログラムを定義ファイル(configフォルダのouter-function.xml)に定義します。これは次のように記述します。
<root>
  <functions basePath="test">
    <function name="oneof" class="Oneof"/>
  </functions>
</root>

ここで、function要素のnameが関数名で、これは内部関数を含めてユニークな名前でなければなりません。また、そのclass属性には、Javaのクラス名を指定します。functions要素のbasePath属性には、Javaのクラスのあるパスを指定します(basePath属性の値 + "." + class属性の値でクラスが検索されます)。最後にさきほどのプログラムをコンパイルし、クラスパスに追加しておいてください。これで、さきほどの文は、

  new XoneElement("CPU名", XoneModel.STRING, 
        "oneof('#@','AthlonXP','Athlon64','Pentium4','Celeron');");
というようにコンパクトに書くことができます。

■ 制約式のメッセージ

ユーザの入力した値に対して、間違いがあるときには適切なメッセージを出す必要があります。冒頭で述べたように、メッセージは"_restrictionMessageList"というエレメントリスト(XoneClassの中ではRESTRICTION_MESSAGE_LISTと定義されている)に定義します。

メッセージは制約式と同様に、エレメント名を同じにして、string型で定義します。さきほどのXoneClassDef.javaにメッセージも定義すると、プログラムは以下のようになります(必要な部分のみ)。

 // エレメントの定義
 XoneElement xe1 = new XoneElement("価格", XoneModel.INT);
 XoneElement xe2 = new XoneElement("CPU名", XoneModel.STRING);
 XoneElement xe3 = new XoneElement("メモリ", XoneModel.INT);
 
 // 制約式(エレメントリスト)の定義
 XoneElementList xel = new XoneElementList(XoneClass.RESTRICTION_LIST);
 XoneElement r1 = new XoneElement("価格", XoneModel.STRING, 
                                  "#価格 >=  100000;");
 XoneElement r2 = new XoneElement("メモリ", XoneModel.STRING, 
                                  "#メモリ >= 512 && (#メモリ - 512) % 256 == 0;");
 xel.add(r1);
 xel.add(r2);
 
 // 制約式のメッセージ(エレメントリスト)の定義
 XoneElementList xel1 = new XoneElementList(XoneClass. RESTRICTION_MESSAGE_LIST);
 XoneElement rm1 = new XoneElement("価格", XoneModel.STRING, 
                                   "価格には 10万以上を指定してください。");
 XoneElement rm2 = new XoneElement("メモリ", XoneModel.STRING, 
                                   "メモリには512以上で、256単位で指定してください");
 xel1.add(rm1);
 xel1.add(rm2);

 // クラスを生成
 XoneClass xc = XoneModel.newXoneClass("PC", 
                          new XoneElement[]{xe1, xe2, xe3},
                          new XoneElementList[]{xel, xel1});

■ 評価

評価(Validation)は、Xoneのオブジェクトの中でインスタンスに対してのみ行うことができます。さきほど作成したクラスPCがroot/classes/PCにあるとして、評価するプログラムは次のように書けます(ソースはこちら)。

 1  /*
 2   * ValidationTest.java
 3   *
 4   */
 5  package test;
 6  
 7  import com.fiverworks.xone.model.*;
 8  import com.fiverworks.xone.mw.*;
 9  import com.fiverworks.xone.*;
10  
11  public class ValidationTest {
12      
13      private MwMain mwMain;
14      
15      private void login() {
16          mwMain = new MwMain();
17          try {
18              mwMain.login("xone", "eagle".toCharArray());
19          } catch (XoneException ex) {
20              ex.printStackTrace();
21          }
22      }
23      private void defineClass() {
24          login();
25          try {
26              // クラスの読み込み
27              XoneObject[] xos = mwMain.load(new String[]{"root/classes/PC"});
28              if (xos == null || xos.length == 0) 
                    throw new XoneRuntimeException("クラスが見つかりません");
29              XoneClass xc = (XoneClass)xos[0];
30              
31              // インスタンスの作成
32              XoneInstance pc1 = XoneModel.newXoneInstance(xc, "pc1");
33              pc1.setElementValue("価格", "120000");
34              pc1.setElementValue("メモリ", "640");
35              
36              // 評価
37              ValidateResult[] vrs = pc1.validate();
38              for (int i = 0; i < vrs.length; i++) {
39                  System.out.println(vrs[i]);
40              }
41              
42          } catch (XoneRuntimeException ex) {
43              ex.printStackTrace();
44          } finally {
45              if (mwMain.isLogin()) mwMain.logout();
46          }
47      }
48      
49      public static void main(String[] args) {
50          new ValidationTest().defineClass();
51      }
52  }

このプログラムの出力は次のようになります。
Element:#価格,int,120000
  isValid:true
  restriction:#価格 >= 100000;
  restrictionMessage:価格には10万以上を指定してください。
Element:#メモリ,int,640
  isValid:false
  restriction:#メモリ >= 512 && (#メモリ - 512) % 256 == 0;
  restrictionMessage:メモリには512以上で、256単位で指定してください

出力されているのは、エレメント(Element)、評価結果(isValid)、制約式(restriction)、制約式のメッセージ(restrictionMessage)です。これらは、ValidateResultから取り出すことができます(詳細はAPIを参照のこと)。

なお、制約式のエレメントの値がnullまたはブランクのときは、評価されません。

制約式やメッセージはクラスに定義されています。上記のプログラムのように、インスタンスの生成時にはクラスがありますから評価できますが、インスタンスをロードしただけだとクラスはありません。この場合、validateメソッドはキャッシュに該当するクラスが入っていればそれを使って評価します。

キャッシュに入っているかどうかは、XoneModelのgetXoneClassメソッドで調べるか、Cache.getInstance()でキャッシュを取得してから調べることができます。このため、利用するクラスは前もってロードしておくとよいでしょう。XoneInstanceのisValidationEnabledメソッドも同様にクラスがあるときのみ、trueを返します。

このように制約式をうまく定義してバリデーションすれば、プログラムのさまざまなところで値をチェックするようなコードは書かなくても済みます(実際のプロジェクトでもこういうコードはかなり多いのではないでしょうか)。

■ 検索式

com.fiverworks.xone.mw.MwMainには、オブジェクトをロードするメソッドを複数用意しています。そのうち、次のメソッド

 load(String parent, String mainCondition, String elementCondition)
は、mainConditionはSQLのwhere句で指定する条件式、elementConditionはエレメントの条件式で制約式と同様の式を指定できます。たとえば、
  load("root/instances", MwMain.NAME_FIELD + " LIKE 'pc%'",  "#価格 >= 150000;");

というのは、オブジェクト名がLIKE 'pc%'という条件式を満たし、価格というエレメントの値が150000以上のオブジェクトをロードします。エレメントの条件式は、さきほどの制約式を処理するプロセッサと同じものですから、ユーザ定義の関数ももちろん利用できます。ただし、クライアントとサーバのホストが異なるときは、両方でその設定が必要です。

また、com.fiverworks.xone.ut.MatchUtのselectメソッドでは、オブジェクトの配列やコレクションから、elementConditionで指定された条件を満たすオブジェクトを選択できます。

このように、制約や検索で式を処理するプロセッサを使っていますが、別にこうした用途に限らず、自由に利用することができます。また、数式を前もって登録しておき、後でパラメータだけを入れ替えて式を実行するcom.fiverworks.xone.exp.PreparedExpressionクラスというもあります(クラス名でわかるようにjava.sql.PreparedStatementと使い方が似ています)。
mailto:hnumakura@fiverworks.com

|ページTOP|ドキュメントTOP|

『Java』は米国および、その他の国における米国Sun Microsystems, Inc.の商標です。
その他本文に記載されている会社名、商品名は、各社の登録商標または商標です。
| 会社案内 | 著作権 | 免責事項 | 通信販売法規に基づく表記 | プライバシーポリシー | お問い合せ | サイトマップ |
COPYRIGHT(C) 2004 Fiverworks Ltd. ALL RIGHTS RESEREVED.