プログラミングモデル

旧版AgentSpaceと同様に、エージェントはコールバックメソッドの集合として定義され、そのメソッド名称などは旧版を継承していますが、その引数は異なります。また、旧版ではすべてのエージェントは特定のクラス(agentspace.Agent)のサブクラスとして定義される必要がありましたが、ver.3では、エージェントとなるクラスは下記のインターフェースの実装クラスとなる以外は制限がありません。

エージェント定義インターフェース

AgentSpaceのエージェントは、agentspace.Agentインターフェースを実装する必要があります。さらにモバイルエージェントを作るには、このインターフェースに加えて、モバイルエージェントの移動性に対応したインターフェース(agentspace.Mobile)を実装する必要があります。同様に永続可能である場合はagentspace.Preservableを、複製可能にする場合はagentspace.Duplicatableを実装する必要があります。これらのインターフェースの実装有無がエージェントの移動性や永続可能性、複製可能性を表すことになります。

種類 インターフェース 特性
エージェント agentspace.Agent 移動できないエージェント
永続化可能エージェント agentspace.Preservable ファイルなどにそれ自身をセーブできるエージェント
モバイルエージェント agentspace.Mobile コンピュータ間を移動可能なエージェント
複製可能エージェント agentspace.Duplicatable それ自身の複製できるエージェント

agentspace.Agentインターフェース

基本インターフェースであるagentspace.Agentは下記のように定義されます。

public interface Agent {
  public void create(AgentEvent evt, Context ctxt);
  public void destroy(AgentEvent evt, Context ctxt);
}

コールバックメソッド 実行タイミング
create(AgentEvent evt, Context ctxt) エージェント生成直後に呼び出される
destroy(AgentEvent evt, Congtext ctxt) エージェント終了直前に呼び出される

ここでAgentEventはコールバックメソッドが呼び出されたときの各種情報を保持したオブジェクトを定義します。Contextはエージェントが実行中に必要となる各種サービス、例えば他のコンピュータへの移動や停止などの基本APIを定義しているクラスです。なお、create()にはエージェント生成時に必要な処理を定義します。例えば必要なリソースを確保したり、変数の初期化を行います。そして、destroy()はエージェント終了の準備を行います。例えば終了前にリソースの解放が必要ならば、解放手続きをdestroy()メソッドに定義することになります。

agentspace.Mobileインターフェース

モバイルエージェント、つまりコンピュータ間を移動可能なエージェントはさらに下記のagentspace.Mobileを実装する必要があります。

public interface Mobile extends java.io.Serializable {
  public void arrive(AgentEvent evt, Context ctxt);
  public void leave(AgentEvent evt, Context ctxt);
}

コールバックメソッド 実行タイミング
leave(AgentEvent evt, Context ctxt) エージェント移動直前に呼び出される
arrive(AgentEvent evt, Congtext ctxt) エージェント移動直後に呼び出される

移動前に計算リソースなどを解放する必要がある場合は、leave()メソッドにその解放処理を定義してください。また、移動先コンピュータに到着後に計算リソースを再獲得するときはarrive()メソッドに必要な処理を定義することになります。

agentspace.Preservableインターフェース

エージェントを2時記憶装置上のファイルなどに保存する場合は下記のagentspace.Preservableを実装する必要があります。

public interface Preservable extends java.io.Serializable {
  public void suspend(AgentEvent evt, Context ctxt);
  public void resume(AgentEvent evt, Context ctxt);
}

コールバックメソッド 実行タイミング
leave(AgentEvent evt, Context ctxt) エージェント永続化直後に呼び出される
arrive(AgentEvent evt, Congtext ctxt) エージェント復活直後に呼び出される

モバイルエージェントのプログラム例

もっとも基本的なモバイルエージェントのプログラム例を示します。

package agent.simple;

import agentspace.Context;
import agentspace.Agent;
import agentspace.Mobile;
import agentspace.event.AgentEvent;
public class Sample implements Agent, Mobile {
public Sample() {}
 public void create(AgentEvent evt, Context context) {
  System.out.println("Sample: create() called");
 }
 public void destroy(AgentEvent evt, Context context) {
  System.out.println("Sample: destroy() called");
  }
 public void arrive(AgentEvent evt, Context context) {
  System.out.println("Sample: arrive() called");
 }
 public void leave(AgentEvent evt, Context context) {
  System.out.println("Sample: leave() called");
 }
}

Context API

エージェントが利用する各種サービスのAPIを定義するクラスです。

AgentIdentifier getIdentifier();
String getName();
void setName(String name);
void terminate();
void move(URL url) throws IOException, InvalidURLException;
void store(String filename);
void duplicate() throws IllegalAgentStateException;
AgentIdentifier[] getAgents();
AgentIdentifier[] getAgents(String name);
String getName(AgentIdentifier aid) throws NoSuchAgentException;
public Object invoke(AgentIdentifier aid, Message msg) throws NoSuchAgentException, NoSuchMethodException, IllegalAgentStateException, IllegalAccessException

エージェントの終了:terminate()

そのエージェント自身を終了させます。例えば下記のように呼び出します。

public void create(AgentEvent evt, Context context) {
  ....
  context.terminate();
}

なお、このAPIを実行した時点でエージェントは終了フェーズに入り、コールバックメソッドのdestory()が呼び出されます。なお、terminate()のあとにプログラムが続いても実行されません。

エージェントの移動:move(URL url) throws IOException, InvalidURLException

そのエージェントをurlで指定されているコンピュータに移動させます。なお、コンピュータが存在しない場合やURLに不備がある場合は、例外としてInvalidURLExceptionを発行します。また、通信上の失敗があった場合はIOExceptionを返します。その後、コールバックメソッドのleave()を呼び出し、移動先に到着後にarrive()が呼び出されます。なお、ver.3では移動先のコンピュータの存在を確かめてからエージェントの移動処理に入ります。このため、移動先コンピュータと通信できない場合は移動処理には入らず、leave()やarrive()メソッドが呼び出されることはありません。

public void create(AgentEvent evt, Context context) {
  ....
  try {
    context.move(new URL("matp://some.where.com:7000"));
  } catch (InvalidURLException ex) {
     ex.printStackTrace();
  } catch (IOException ex) {
     ...
   }
}

なお、このAPIを実行した時点でエージェントは終了フェーズに入るので、APIのあとにプログラムが続いていても実行されません。エージェントの転送クラスはagentspace.protocolに再定義・追加することができますが、デフォルトではmatpと呼ぶプロトコルを利用します。このためurlのプロトコル部にはmatpと指定してください。

エージェントの保存:store(String filename) throws IOException

そのエージェントをfilenameで指定されているファイルに保存します。保存中に失敗するとIOExcpetionを介しますが、現在の実装では書き込み時の失敗した場合はエージェントを消失することがあります。

public void create(AgentEvent evt, Context context) {
  ....
  try {
    context.store("agent/sample.jar");
  catch (IOException ex) { .... }
}

なお、このAPIを実行した時点でエージェントは永続化フェーズに入るので、APIのあとにプログラムが続いていても実行されません。なお、永続化されるエージェントは、Jar形式で圧縮されることから、拡張子を.jarとすると便利かもしれません。

エージェント識別子の取得: getIdentifier()

エージェントには固有の識別子が割り当てられています。エージェント自身の識別子を取得するときは、getIdentifier()を実行すると調べられます。

public void create(AgentEvent evt, Context context) {
  ....
  AgentIdentifier aid = context.getIdentifier();
  ....
}

隣接エージェント識別子の取得: getAgents(), getAgents(String key)

同じコンピュータ(正しくは同じ実行システム上)のエージェントの識別子の配列を取得するときはgetAgents()を実行し、特定のエージェント名のエージェントの識別子を取得するときはgetAgents(String key)を呼び出す。

public void arrive(AgentEvent evt, Context context) {
  ....
  AgentIdentifier [] aids = context.getAgents();
  ....
}

public void arrive(AgentEvent evt, Context context) {
  ....
  AgentIdentifier [] aids = context.getAgents("Test");
  ....
}

エージェント名の取得: getName(), getName(AgentIdentifier aid)

エージェント自身の名前を取得するときは、getName()を実行し、他のエージェOントの名前を取得するときは、そのエージェントの識別子を引数とするgetName()を実行する。ともに文字列型(java.lang.String)で結果を返す。なお、後者において識別子が有効でない場合は例外NoSuchAgentExceptionを返す。

public void create(AgentEvent evt, Context context) {
  ....
  String name = context.getName();
  ....
}

public void arrive(AgentEvent evt, Context context) {
  ....
  AgentIdentifier aid = ...;
  String name = context.getName(aid);
  ....
}

エージェント間通信: invoke(AgentIdentifier aid, Message msg)

旧版AgentSpaceと同様にエージェントが別のエージェントの変数やメソッドを直接呼び出すことはできないようになっています。これは他のエージェントによる(不正な)呼び出しから守るためです。エージェント間で情報を交換する必要があるときには通信用APIを利用して通信をして下さい。

Object invoke(AgentIdentifier aid, Message msg) throws NoSuchAgentException, NoSuchMethodException,
IllegalAgentStateException, IllegalAccessException

第一引数は呼び出すエージェントのエージェント識別子を指定し、第二引数は呼び出すメソッドの名前や引数をMessageクラスで定義します。このMessageクラスは旧版AgentSpaceの同名クラスと同じです。

Messageクラスのインスタンスを下記のように作成し、引数に送信するメッセージの名前を与えます。メッセージの名前は文字列型の定数でなければなりません。また、大小文字を区別します。

Message(String name);

メッセージの引数は setArg() により設定します。

void setArg(Object obj);

ここで setArg() の引数が通信メッセージの引数となります。引数は0個以上を設定することができ、さらに型に制限はありません。ただし、 引数にはいるオブジェクトはJavaのスタンダードクラスまたは、通信相手がそのクラスを保持していることが条件になります。受信側では、メッセージ名と一致するメソッドに対して、setArg() で設定した順番の引数列として渡されます。このため、受信側のエージェントに送信メッセージと同じ名前のメソッドがあり、さらにそれの引数の数とそれらの型がその順番を含めて一致する必要があります。


Message msg = new Message("greeting");
msg.setArg("Hello");
msg.setArg("World");

この例は、greeting という名前のメッセージを生成するもので、その引数は、文字列型定数の "Hello" と "World" となります。つまり、呼び出されるエージェントが public greeting(String s1, String 2)というメソッドを持っている必要があります。

なお、メソッド名がない場合や引数が一致しない場合は、invoke()は例外としてNoSuchMethodExceptionを発行します。また、エージェント識別子が有効でない場合は、NoSuchAgentException例外を送ります。また、エージェントが終了や移動の準備中でメソッド呼び出しができなくなっているときは、IllegalAgentStateExceptionを例外として返します。また、この他の理由でエージェントのメソッドが呼び出せないときはIllegalAccessException例外を送ります。

次のコードはagent.Calcという名前のエージェントを探しして、該当するエージェントがもつメソッド public Integer add(Integer i, Integer j)を呼び出すものである。

AgentIdentifier[] aids = context.getAgents("agent.Calc");
for (int i = 0 ; i < aids.length ; i++) {
  try {
    Message msg = new Message("add");
    msg.setArg(new Integer(3));
    msg.setArg(new Integer(4));
    Object obj = context.invoke(aids[i], msg);
    if (obj != null && obj instanceof Integer) {
     Integer result = (Integer)obj;
    }
  }
  catch (Exception ex) {
    ex.printStackTrace();
  }
}

エージェントに到着したメッセージはすべて到着順でメッセージキューに入れられます。このため、メッセージ送信や呼び出しをしてもすぐにそれの処理が開始されるとは限りません。また、メッセージを送信する前に存在していたエージェントでも、実際にメッセージが到着したときには、移動したり終了したりしている可能性があります。このため、通信失敗を考慮したプログラミング必須となります。

現在のランタイムシステムでは、同じランタイムシステム内のエージェント間の通信しかサポートしていません。このため、異なるコンピュータ上のエージェントには通信ができないことなりますが、別のコンピュータ上のエージェントと通信をしたい場合は、エージェントをそのコンピュータに移動させ、そのコンピュータ内のローカルエージェント間通信として実現できます。なお、リモートエージェントへの通信をサポートしないのは、引数に複雑なオブジェクトをもつ場合、その実行コストが上記のエージェント移動による実現と大差がないためです。このため、今後、値や文字列などの定数インスタンスに限定したリモート通信をサポートする可能性はあります。

〒101-8430 東京都 千代田区 一ツ橋 2-1-2
国立情報学研究所 アーキテクチャ科学研究系
佐藤一郎 (E-mail: ichiro [at] nii.ac.jp)
Tel: 03-4212-2546
http://research.nii.ac.jp/~ichiro/