ヘッダーをスキップ
Oracle® Fusion Middleware Oracle JDeveloper拡張機能開発者ガイド
11g リリース2(11.1.2.4.0)
B66156-04
  目次へ移動
目次

前
 
次
 

2 Oracle JDeveloperでの拡張機能の開発

この章では、JDeveloper拡張機能のライター、開発者およびユーザーが利用できる機能について紹介します。また、拡張機能の開発者からよく質問を受ける機能の例を示します。JDeveloperの固有機能について説明しています。これらの機能を使用すると、計画している拡張機能をすぐに開発できます。

この章には、次の項があります。

2.1 Oracle JDeveloperでの拡張機能の開発について

JDeveloperではオープン・アーキテクチャが使用されています。このため、特定のニーズがある場合または開発チームが使用する外部のプロセスやツールにJDeveloperを統合する必要がある場合には、独自の拡張機能を作成できます。メニュー・アイテムの追加、コンテキスト・メニューの作成、JDeveloperツールバーへの機能の組込み、データ・オブジェクトのビューを表示するドッキング可能なウィンドウの作成などを行うことができます。

2.2 拡張機能開発のユースケース

この項では一連のユースケースを説明します。拡張機能を計画するとき、または宣言トリガー・フックを使用したり遅延初期化をサポートするために以前のバージョンのJDeveloper用に作成された既存の拡張機能を変換する場合にこのようなユースケースに直面することがあります。ロードおよび実行が迅速に実行される拡張機能を作成するためには、次に示す推奨事項に従ってください。

2.2.1 ルール・ベースのメニュー秘密度について

拡張機能では、メニュー・アイテムに関連付けられたアクションの有効状態を設定することで、メニュー秘密度を制御します。これは、そのアクションに関連付けられたoracle.ide.controller.Controllersの1つによって決定されます。アクションはトリガー・フックであるため、アクションに関連付けられたoracle.ide.controller.Controller実装は、そのコントローラを所有する拡張機能が初期化されるまでは呼び出されません。単純なルールの設定例は、2.5.4.2項「単純なルールの定義方法」を参照してください。

次の一般的な状況について考えてください。メニュー・アイテムM1はアクションA1に関連付けられています。また、このアクションはいくつかのコントローラに関連付けられています。これらのコントローラは、所定のコンテキストでアクションA1を処理するかどうかを示すことができます。選択されているアクションを処理する最初のコントローラは、アクションの有効状態を設定します。

具体的な例では、EditCopyがaction IdeConstants.COPY_CMD_IDに関連付けられており、これはテキストのコピー方法を認識するコントローラと、ビジュアル・オブジェクト(UIコンポーネントなど)のコピー方法を認識する別のコントローラに関連付けられています。

以前はこのパターンのユースケースでは、メニューが表示されるときや、ツールバー・ボタンの有効状態が変化するときなど、JDeveloperによってコンテキストが変更される際に、パフォーマンスの問題が発生していました。たとえば、ユーザーが現在の選択の変更、現在のビューの変更、または現在のアクティブ・プロジェクトの変更をする場合です。

こうした状況で処理に時間がかからないようにするには、ルールに基づくアクション秘密度制御を使用して拡張機能のメニューを開発します。コントローラは拡張機能のマニフェスト・ファイルで宣言によって定義されます。詳細は、1.7項「拡張機能マニフェストの操作」を参照してください。

コントローラ定義の中で、次のようなアクションの有効状態を制御するアクション更新ルールを指定します。

  • 常に有効にする

  • アクティブなアプリケーションが存在するときに有効にする

  • アクティブなプロジェクトが存在するときに有効にする

  • コンテキストにノードがあるときに有効にする

  • コンテキストにタイプXのノードがあるときに有効にする

  • コンテキストに選択内容があるときに有効にする

  • コンテキストに1つの選択内容があるときに有効にする

  • コンテキストに複数の選択内容があるときに有効にする

コントローラでは複数のルールを定義できます。最も具体的なルールをルール定義の最初に指定する必要があります。

JDeveloperがコントローラのルールに基づく秘密度を分析した後で、そのコントローラを所有する拡張機能が初期化されます。拡張機能が初期化されると、拡張機能のController.update()メソッドがアクションの有効状態を処理します。コントローラで処理が行われない場合、拡張機能の操作は定義されたルールに従います。GUIの応答に影響しないように、すべてのController.update()メソッドがfalseを返すことをお薦めします。

2.2.1.1 複雑なController.update()の実装を回避する方法

コントローラが指定するルールでは、アクションの有効状態が宣言によって決定されます。このとき、2.2.1項「ルール・ベースのメニュー秘密度について」で説明するアクション有効化ルールが使用されます。メニューが有効でありユーザーがそのメニューを実行すると、拡張機能はメニュー実行が正しく動作するために必要な操作を実行できます。

Controller.update()の以前の実装は、アクションの有効状態を判別するために複雑で時間のかかるコードを実行していました。現在のメニュー秘密度モデルを使用すると、アクションの有効状態は宣言で決定することができ、実行する適切なアクションの選択にかかる時間が短縮されます。

コントローラの動作を制御するルールを次に示します。

拡張機能が初期化される場合:

  • メニューがまもなく表示されます。

    • ルール・ベース・コントローラの場合、ルールが評価されます。falseであれば、フレームワークによってメニューがグレー表示されます。trueの場合またはルールベース・コントローラでない場合は、次の項目を参照してください。

    • Controller.update()が呼び出されます。これでtrueが返されると、他のコントローラは処理されません。コントローラは、アクションの有効状態または無効状態をupdate()メソッド内で設定する必要があります。

  • ユーザーがアクティブ・アクションを選択します。Controller.update()が呼び出されます。これでtrueが返されると、他のコントローラは処理されません。コントローラは、アクションの有効状態または無効状態をupdate()メソッド内で設定する必要があります。

拡張機能が初期化されない場合は次のようになります。

  • メニューがまもなく表示されます。コントローラのルールが評価されます。falseであれば、フレームワークによってメニューがグレー表示されます。trueの場合、フレームワークによってアクティブ化されます。

  • ユーザーが、初期化されていない拡張機能のアクティブ・メニューを選択した場合:

    • Controller.update()が呼び出されます。update()falseを返すと例外がスローされます。コントローラのルールがtrueに評価された場合はコントローラのupdate()メソッドもtrueを返す必要があることが通知され、注意を促すログ・メッセージが表示されます。update()からtrueが返された場合は、次の項目に進みます。

    • アクションが有効か無効かが確認されます。アクションが有効な場合は、Controller.handleEvent()が呼び出されます。アクションが無効な場合は、次の項目に進みます。

    • コントローラがTriggerControllerの場合、フレームワークによって、アクションが実行されない理由を知らせるメッセージがユーザーに表示されます。TriggerControllerでない場合は、例外がスローされ、このコントローラがTriggerControllerである必要があると通知されます。

    つまり、1つ目または3つ目の条件が発生した場合は、宣言によって対処する必要があります。

図2-1 初期化されない拡張機能のフロー・ダイアグラム

初期化されない拡張機能のフロー・ダイアグラム

2.2.2 動的メニュー・ラベルおよびアイコンの使用方法

動的なメニュー・ラベルおよびアイコンは、ユーザーが選択するアプリケーション、プロジェクトまたはノードによって変化するメニュー・アイテムです。拡張機能の内部では、動的メニュー・ラベルが選択されるとコードを実行します。これは、ユーザーがメニューを選択したときに表示されるメニュー・ラベル文字列またはアイコン(あるいは両方)を変更するためです。

2.2.2.1 メニュー・ラベルに短縮名を付加する方法

ユーザーが動的メニューを選択すると、JDeveloperによって、選択された要素のショート・ラベルがメニュー・アイテム・ラベルに付加されます。これは次のように表示されます。

実行myproject.jprの実行

一般的にこれらのメニューは、選択されたアプリケーション、プロジェクト、ノードまたは要素によって異なります。拡張機能はコントローラ・フックを使用して、アクション更新ルールに基づいてラベルを更新するようにJDeveloperに要求できます。詳細は、2.2.1項「ルール・ベースのメニュー秘密度について」を参照してください。

メニュー・ラベルが更新され、ルールに指定されているように要素に関連付けられている短縮名が追加されます。この要素は、アプリケーション、プロジェクトまたはノードです。

2.2.3 動的トップ・メニューの構築方法

ソース・トップ・レベル・メニューのような、動的トップ・レベル・メニューを構成するときは、トップ・レベル・メニューと同じトリガー・フックを使用して宣言的に定義します。詳細は、2.4.2項「メニューの作成および変更方法」を参照してください。

エディタ・タイプやノード・タイプによって、メニュー・アイテムが動的トップ・レベル・メニューに含まれるかどうかが制御されます。

これら2つの条件が満たされると、アクティブ・エディタや開いているノードに対して宣言的に定義された動的メニューが、トップ・レベル・メニューとして表示されます。拡張機能のユーザーがこのメニューを選択するとき、メニュー・アイテム秘密度は、メニュー・アイテムに関連付けられたアクションの有効状態を設定することで処理されます。詳細は、2.2.1項「ルール・ベースのメニュー秘密度について」を参照してください。

2.2.4 ノード・レコグナイザについて

ノード・レコグナイザが、特定のURLによってポイントされたリソースに関連付けられているoracle.ide.model.Nodeサブクラスを認識します。

JDeveloperでは次の2つの標準レコグナイザが提供されます。

  • URLそのもので提供される情報に基づいてファイル拡張子などのノード・タイプを認識できるレコグナイザ。

  • XMLファイルの内容に基づいてXMLノード・タイプを認識できるレコグナイザ。

以前のバージョンのJDeveloperでは、カスタム・レコグナイザ実装を登録することもできました。現在、この方法は非推奨になっています。

Addin.initialize()メソッドは、次のいずれかのメソッドを使用してoracle.ide.mode.Recognizerクラスのカスタム実装を登録します。

  • Recognizer.registerRecognizer(String, Recognizer)

  • Recognizer.registerRecognizer(String, Recognizer)

  • Recognizer.registerLowPriorityRecognizer(Recognizer)

カスタム・レコグナイザを提供する拡張機能は、JDeveloperのレコグナイザが処理できるように、ノード認識を調整する必要があります。

以前のJDeveloperバージョン用に開発した拡張機能(IDE標準レコグナイザ使用)を作成し直している場合は、Addin.initialize()メソッドからレコグナイザ登録コードを削除し、レコグナイザ・トリガー・フックを使用して、拡張機能マニフェスト・ファイルextension.xmlに認識ルールを登録する必要があります。詳細は、1.7項「拡張機能マニフェストの操作」を参照してください。

2.2.5 コンテンツ・セットについて

JDeveloperはコンテンツ・セットを使用して、アプリケーション・ナビゲータにノードとして表示されるカテゴリを定義します。JDeveloperに組み込まれているカテゴリは次のとおりです。

  • アプリケーション・ソース

  • Webソース

  • リソース

コンテンツ・セットは宣言によって登録されますが、そのコンテンツ・セットを導入した拡張機能の初期化をトリガーすることはありません。コンテンツ・セットではカスタム動作は必要ないためです。拡張機能のどこにコンテンツがあるかを示すために、コンテンツ・セットのID、ラベルおよびデフォルトURLを指定する必要があります。

ContentLevelFilterのインスタンスがクライアント動作を指定し、これによりコンテンツ・セットに何が表示されるかが制御されます。

ContentLevelFilterは、ContentLevelクラスによって実装される横型トラバースをフィルタリングし、物理表現と異なる各レベルの仮想表現を提供します。ContentLevelFilterでは次の処理を行うことができます。

  • 表示に新しい要素を追加します。

  • 表示から要素を削除します。

  • 表示からサブディレクトリを削除します。

  • サブディレクトリを表示します(空の場合も)。

ContentLevelFilterのインスタンスは、クラスContentLevelの静的メソッドaddContentLevelFilter(ContentLevelFilter)によって静的に登録されます。

アプリケーション・ナビゲータで新しいアプリケーションが作成されるか開かれた後、拡張機能ユーザーがプロジェクトのトップレベル・フォルダを展開すると、そのプロジェクトのすべてのフォルダやファイルが追加される前に(BaseTreeExploreropenメソッドが使用される)、クラスContentLevelのメソッドapplyContentLevelFilters(Context, List, List)が呼び出されます。このメソッドは次の処理を実行します。

  1. すべてのソース・ルート・ディレクトリを取得します。

  2. 登録されているContentLevelFilterのインスタンスごとに、所定のContextにフィルタを適用できるかどうかをチェックします。適用できる場合は、メソッドupdateDir(URLPath, String, List, List, Context context)を呼び出します。このメソッドは、要素やサブディレクトリが表示される前に各レベルの要素やサブディレクトリのリストを変更できます。同じレベルに変更を適用するContentLevelFilterのインスタンスが複数ある場合、後のフィルタは前のフィルタの効果を認識して、適切ならば、さらにフィルタリングします。

  3. AsynchronousContentLevelFilterContentLevelのインスタンスについても前の手順と同じ処理をします。また、そのようなフィルタからCallableを取得します。これは、追加要素を取得するために非同期リクエストを実行します。


注意:

ContentLevelFilterContextに適用できるかどうかを判別するために、ContentLevelがすべてのコンテンツ・セット・キーをそのフィルタから取り出し、コンテキストにプロパティとして少なくとも1つのキーが含まれるかどうかを確認します。


ContentLevelFilterには、宣言登録は必要ありません。フィルタリングが行われる前に、NodeFactoryを使用してノードが作成されます。これは、レコグナイザを使用してURLからノードを作成します。レコグナイザはトリガー・フックであるため、フィルタリングが発生するまでには、関連する拡張機能がすでに初期化されます。

一般的に、コンテンツ・レベル・フィルタを登録する拡張機能はレコグナイザ・ルールも登録します。したがって、登録したレコグナイザ・ルールによってノード・タイプが認識されると、拡張機能は常に初期化されます。たとえば、アプリケーション・ナビゲータでは、ユーザーがナビゲータでフォルダを展開すると、そのフォルダに検出されるすべてのファイルに対してノードのインスタンスが作成されます。これにより、拡張機能の初期化がトリガーされます。その時点で、JDeveloperがコンテンツ・レベル・フィルタを呼び出します。これで、フィルタによりノードの除外や、展開するフォルダ内に表示する他のノードの追加を行えるようになります。

2.2.6 大規模な拡張機能について

多様な機能を提供し、依存ツリーの階層が深い拡張機能は、大規模拡張機能とみなされます。通常、大規模拡張機能は依存ツリーの階層が深いため、他の多くの拡張機能の事前初期化を引き起こします。多くのケースで、拡張機能ユーザーは大規模拡張機能で提供される多様な機能の一部しか利用しませんが、このタイプの拡張機能の一体化された構造のために、意図せずに必要以上の機能を得ることがあります。

大規模拡張機能のパフォーマンスを改善するためには、次のいずれかのモデルに従い大規模拡張機能をリファクタすることをお薦めします。

  • 拡張機能の主要な機能分野を識別して、特徴的な分野を個別の拡張機能としてリファクタします。

  • 概念的な機能分野を、依存ツリーの階層が浅い1つの拡張機能に含めるようにし、機能実装の詳細部分を別の拡張機能に移します。

1つ目のモデルに適しているのは、機能分野の間に単純な依存関係がある場合です。たとえば、2つの機能分野XとYを含む拡張機能E1について考えます。単純な依存関係とは、拡張機能E1を拡張機能E2とE3にリファクタしても、E2とE3の間に双方向依存関係が生じないことを意味します。依存関係が発生する場合は、サポート・モジュール(たとえばモジュールM1)を導入して解決できます。このモジュールでは、E2とE3の両方が必要な機能が提供されます。M1の依存ツリーはE1と同じでないことが重要です。同じであれば、E1の機能を実行するために事前初期化する必要がある拡張機能の数を減らすというリファクタの目的に合いません。E2とE3が互いに依存せず、拡張機能E1の元の依存ツリーが拡張機能E2とE3で均等に分割されるのが、理想的な状態です。

2つ目のモデルでは、拡張機能E1のバージョンが維持されます。これには階層が非常に浅い依存ツリーが含まれます。依存ツリーを縮小するために、リファクタ処理によって、機能分野E2とE3の実装を拡張機能E2implとE3implに移す必要があります。このパターンが機能するためには、E1が拡張機能E2implまたはE3impl (あるいは両方)に依存しないことが非常に重要です。このパターンをサポートするには、正しい実装が保証するsingleton-service-hookを使用します。つまり、拡張機能E1で機能E2がアクセスされるとE2impl拡張機能が初期化されます。singleton-service-hookを使用することにより、E1が拡張機能E2implに依存していなくてもE2implが初期化されます。詳細は、2.5項「トリガー・フックの定義および使用方法」のシングルトン登録の項を参照してください。

2.2.7 テクノロジ・スコープについて

ユーザーがテクノロジ・スコープを選択すると、そのテクノロジ・スコープを導入した拡張機能が、依存している他のすべての拡張機能と一緒に初期化されます。デプロイメント依存は、プロジェクトのデプロイメント・プロファイルに記述されます。詳細は、Oracle Fusion Middleware Oracle JDeveloperユーザー・ガイドのデプロイメント・プロファイルに関する項を参照してください。

ユーザーの選択により、そのテクノロジ・スコープを導入している拡張機能の依存ツリーの外部で拡張機能を初期化する必要が生じた場合、お薦めする対処方法としては、拡張機能の初期化をトリガーする拡張機能内の機能をユーザーが使用するのを待機します。機能セットという概念を回避できます。機能セットのすべてのメンバーの初期化が必要になると、遅延初期化の目的に反します。

2.3 JDeveloperのルック・アンド・フィールの取得

この項のガイドラインに従うと、JDeveloperにシームレスに溶け込む拡張機能を開発できます。たとえば、oracle.javatools.ui.LayoutConstantsで公開されているUI定数を使用すると、共通のコンポーネントに関して標準の間隔や位置合せを使用でき、推奨の間隔を実現できます。間隔は、コンポーネントがダイアログ、ウィザードまたはモードレス概要エディタのどれに含まれるかにかかわらず、すべてのコンポーネントで使用できます。

図2-2に、JDeveloperダイアログの要素で使用される標準の間隔を示します。

図2-2 JDeveloperダイアログでの間隔表示

間隔を表示するJDeveloperダイアログ

図2-3は、JDeveloperの概要エディタで使用される間隔を示します。

図2-3 概要エディタでの間隔表示

間隔を表示する概要エディタ

2.4 JDeveloperの要素の作成方法

この項では、様々なJDeveloper要素の使用方法と開発方法について説明します。

2.4.1 JDeveloperの要素を迅速に作成する方法

次のJDeveloper要素は「新規ギャラリ」ですぐに作成できます。

「新規ギャラリ」ですぐにJDeveloper要素を作成するには、次の手順を実行します。

  1. 1.6.1項「拡張機能開発用のアプリケーションおよびプロジェクトの作成方法」の手順を実行して、アプリケーションおよび拡張機能プロジェクトを作成します。

  2. 「ファイル」→「新規」を選択して「新規ギャラリ」を開きます。

  3. 「新規ギャラリ」の「カテゴリ」ツリーで、「クライアント層」の下の「拡張機能の開発」を選択します。

  4. 「アイテム」リストで、作成するJDeveloper要素をダブルクリックします。対応するダイアログが開きます。手順を確認するには、[F1]を押すか、ダイアログの「ヘルプ」をクリックします。

2.4.2 メニューの作成および変更方法

JDeveloperに対するユーザーの操作の多くはメニューを介して行われるため、既存のメニューを変更して新しいメニューを作成できます。JDeveloperのメニュー・バーのアイテムに選択したアイテムを追加するだけではなく、コンテキスト・メニューも追加でき、拡張機能に含まれる機能の操作を細かく制御できます。

メニューが対話するコマンド拡張機能は、Addinインタフェースに依存します。詳細は、2.4.5項「コマンドの開発方法」を参照してください。

2.4.2.1 メニューについて

メニューは、拡張機能をJDeveloperに統合する重要な手段です。バージョニング・システムの選択や内部コード・スニペット・データベースへのアクセスなど、主要な機能をJDeveloperのメニュー・システムに追加して、メニューまたはツールバーで選択することで、拡張機能を利用または制御できるようにします。一方、拡張機能の一部の機能(社内のバージョニング・ソフトウェアでのファイルのチェックアウトや、社内でよく使用されるツールでのコードの確認)は、コンテキスト・メニューに含めることをお薦めします。コンテキスト・メニューは、該当する場所でユーザーがマウスの右ボタンをクリックすると表示されます。JDeveloperの拡張機能システムではどちらの方法も可能です。詳細は、2.4.2.2項「ポップアップ・メニューの作成方法」を参照してください。

JDeveloperには、コンテキスト・メニューの宣言的作成をサポートするクラスが含まれています。作成されたコンテキスト・メニューは、一連のリスナーやコントローラによってJDeveloperのメイン機能にフックできます。この方法で、JDeveloperの構造とユーザー・インタフェースに適合するメニューベースの拡張機能を作成すると同時に、基本的なIDEを超えた機能を追加できます。詳細は、2.4.5.5項「コンテキスト・メニューからアドインを呼び出す方法」を参照してください。

あるいは、拡張機能に含まれる機能が、すでにJDeveloperの既存のメニュー構造に組み込まれていても、現在の各メニューに固有の機能を付け加えることがあります。この場合は、拡張機能によって、既存のメニューに固有の新オプションを追加する必要があります。詳細は、2.4.2.4項「既存のJDeveloperメニューにメニュー・アイテムを追加する方法」を参照してください。

これに似た方法を使用して、ツールバーに新しい選択肢を追加することもできます。詳細は、2.4.2.5項「ツールバーにドロップダウン・ボタンを追加する方法」を参照してください。

2.4.2.2 ポップアップ・メニューの作成方法

コンテキスト・メニュー・リスナー・クラスを使用したメニューの宣言的な作成では、標準クラスを使用できます。これにより、拡張機能の更新が容易になり、すべてのメニューに一貫性が加わり、拡張機能を迅速にロードできるようになります。また、カスタム・コードを開発する必要はありません。既存のコンテキスト・メニューのリスナー・クラスを使用できます。リスナー・クラスを登録し、拡張機能のメニューを追加できるコールバック・インタフェースを使用し、メニューが作成される特定のビューに関連付けられているコントローラを最後に使用します。詳細は、2.4.5.5項「コンテキスト・メニューからアドインを呼び出す方法」を参照してください。

複数の選択肢を含むメニューを作成するには、次の手順を実行します。

  1. 例2-1のサンプル・コードを使用して、コンテキスト・メニューのリスナー・クラスを拡張機能マニフェストに登録します。

    例2-1 コンテキスト・メニューのリスナー・クラスの登録

    <extension xmlns="http://jcp.org/jsr/198/extension-manifest" ...>
     ...
      <hooks>
       <jdeveloper-hook xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">
        <actions>
          <!--  Action to show the File List --> 
          <action id="MY_CMD_ID">
           <properties>
             <property name="Name">My Action</property> 
           </properties>
             <controller-class>oracle.ide.extsamples.basic.SimpleController
             </controller-class>
          </action>
        </actions>
          <context-menu-listeners>
            <site idref="navigator"> <!-- or "editor", or "explorer" -->
                <listener-class>oracle.ide.extsamples.basic.
                  SimpleContextMenuListener</listener-class>
           </site>
        </context-menu-listeners>
       </jdeveloper-hook>
      </hooks>
     ...
    </extension>
    
  2. 例2-2に示すようにContextMenuListenerを実装します。

    これはコールバック・インタフェースです。これを使用して、拡張機能がメニュー・アイテムおよびサブメニューをコンテキスト・メニューに追加できます。

    例2-2 ContextMenuListener

    package oracle.ide.extsamples.basic;
    import oracle.ide.Context;
    import oracle.ide.controller.ContextMenu;
    import oracle.ide.controller.ContextMenuListener;
    import oracle.ide.controller.IdeAction;
    /**
      * ContextMenuListeners add items to context menus.
    */
    public final class SimpleContextMenuListener 
     implements ContextMenuListener
    {
      public void menuWillShow(ContextMenu contextMenu)
      {
        // Add my menu to the context menu only when user clicked on SomeNode class.
        if (contextMenu.getContext().getNode() instanceof   oracle.ide.extsamples.basic.SomeNode.class)
        {
           IdeAction action = IdeAction.find( SimpleController.SAMPLE_CMD_ID );
           contextMenu.add( contextMenu.createMenuItem( action ) );
        }
      }
      public void menuWillHide(ContextMenu contextMenu)
      {
          // Most context menu listeners will do nothing in this method. In
          // particular, you should *not* remove menu items in this method.
       }
      public boolean handleDefaultAction(Context context)
      {
          // You can implement this method if you want to handle the default
          // action (usually double click) for some context.   
          return false;      
       }
    }
    
  3. 例2-3に示すようにoracle.ide.controller.Controllerを実装します。

    Viewには関連するControllerがあります。コントローラは、ユーザー・アクションに関連付けられているコマンドを処理することを求めるリクエストを受け取ります。コントローラのhandleEventメソッドは、指定された適切なコマンドで呼び出されます。リクエストされたコマンドをコントローラが処理しない場合、そのコマンドを管理コントローラに代行させます。また、コントローラは、updateメソッドを呼び出して特定のコマンドの可用性も判別します。

    例2-3 oracle.ide.controller.Controller

    package oracle.ide.extsamples.basic;
    import oracle.ide.Context;
    import oracle.ide.controller.Controller;
    import oracle.ide.controller.IdeAction;
    /**
      * ContextMenuListeners add items to context menus.
     */
    public final class SimpleController 
      implements Controller
    {
       public static final int MY_CMD_ID = Ide.findCmdID( "MY_CMD_ID" );
       public boolean handleEvent( IdeAction action, Context context )
        {
          if (IdeAction.getCommandId() == MY_CMD_ID )
           {
             // Do some action
             return true;
             } 
            return false;
           }
        public boolean update( IdeAction action, Context context )
         {  
           if (IdeAction.getCommandId() == MY_CMD_ID )
            {
             // Enable action
             action.setEnabled(true);
             return true;
            }
            action.setEnabled(false);
            return false;
           }
     }     
    

2.4.2.3 コンテキスト・メニュー・リスナーを登録してパフォーマンスを改善する方法

コンテキスト・メニュー・リスナーを登録すると、コンテキスト・メニューが迅速にポップアップ表示されるようになります。

コンテキスト・メニュー・リスナーは、コンテキスト・メニューのポップアップを遅延させないように最小限の処理を実行する必要があります。現在、アプリケーション・ナビゲータのコンテキスト・メニューには250近いメニュー・リスナーがあります。したがって、コンテキスト・メニューのポップアップに1秒かからないようにするには、リスナーが平均してかけられる時間は4ミリ秒未満です。

パフォーマンスの向上に関するヒントを次に示します。

  • 拡張機能が特定のノードだけに関係する場合は、その特定のノード・タイプに対してコンテキスト・メニュー・リスナーを登録する必要があります。拡張機能のユーザーがそのタイプのノードをクリックしたときだけ、リスナーが呼び出されるようになります。

  • リスナーのmenuWillShowメソッドが呼び出されるとき、アクションはメニュー・アイテムの追加のみにする必要があります。メニュー・アイテムのアクションが実行されるときに、該当しないコンテキストから保護します。

  • リスナーのmenuWillHideメソッドが処理を行わないようにする必要があります。

コンテキスト・メニューのパフォーマンスが悪影響を受けるものは次のとおりです。

  • ファイルI/O処理。

  • ファイルの内容の解析。

  • 検索のためのリストの繰返し処理。

  • ファイル・システムでの検索。

  • コンテキスト・メニューに追加済のメニューの繰返し処理およびメニュー名変更やメニュー削除の試行。

2.4.2.4 既存のJDeveloperメニューにメニュー・アイテムを追加する方法

拡張機能について、1つのアイテムまたはアイテムのグループを既存のJDeveloperメニューまたはツールバーに追加するだけでよい場合もあります。詳細は、2.4.5.4項「メイン・ウィンドウ・メニューからアドインを呼び出す方法」を参照してください。

新しいメニュー・アイテムを「ツール」メニューに追加するには、次の手順を実行します。

  1. 例2-4に示すように、oracle.ide.controller.*をインポートし、メニューで呼び出すアクション(_myActionID)を作成します。

    例2-4 oracle.ide.controller.*

    import oracle.ide.controller.*;
      // Create the action
      _myActionID = Ide.createCmdID("FirstAction");
      IdeAction firstAction = IdeAction.get(
          _myActionID,
          null,
          "My First Action",
          IdeMainWindow.ACTION_CATEGORY_TOOLS,
          new Integer('F'),
          null, null, true);
    
  2. 例2-5に示すように、現在のアイテムをコントローラとして設定します。

    例2-5 コントローラ

    // Set ourselves as the controller
     firstAction.addController(this);
    
  3. 例2-6に示すように、firstActionに関連付けられているメニュー・アイテムをメニューに追加します。

    例2-6 firstActionメニュー・アイテム

    // Add a menu entry in the Tools menu
    final Menubar menubar = Ide.getMenubar();
    final JMenuItem firstActionMenu = menubar.createMenuItem
     firstAction,MenuConstants.WEIGHT_UNDEFINED);
     menubar.add
     (firstActionMenu,MenuManager.getJMenu(IdeMainWindow.MENU_TOOLS));
    

既存のメニューに複数のメニュー・アイテムを追加する手順は、次のとおりです。

例2-7では、3つのメニュー・アイテムを既存のメニューに追加しています。

例2-7 3つのメニュー・アイテムをメニューに追加するサンプル・コード

//  First menu of the first section
JMenuItem menu1 = contextMenu.createMenuItem(myAction1, 1f); 
contextMenu.add(menu1, 1f); 

//  Second menu of the first section
final JMenuItem menu2 = contextMenu.createMenuItem(myAction2, 2f);   
contextMenu.add(menu2, 1f); 

// <- First menu of the second section
final JMenuItem menu3 = contextMenu.createMenuItem(menu3, 1f);
contextMenu.add(menu3, 2f);

2.4.2.5 ツールバーにドロップダウン・ボタンを追加する方法

場合によっては、拡張機能がウィンドウで作動し、拡張機能に固有のオプションまたは拡張機能のウィンドウに表示される特定のデータ・オブジェクトに固有のオプションで構成されるツールバーが、そのウィンドウ自体に含まれることがあります。詳細は、2.4.5.4項「メイン・ウィンドウ・メニューからアドインを呼び出す方法」を参照してください。

ツールバーにドロップダウン・ボタンを追加するには、次の手順を実行します。

拡張機能に固有のウィンドウに、例2-8のコードをメソッドとして組み込むと、3つのアクションを含むドロップダウン・ボタンがツールバーに追加されます。ユーザーがこのボタンをクリックすると、表示されるメニューには3つのアクションが含まれます。

例2-8 ツールバーへのドロップダウン・ボタンの追加

void doit(Toolbar toolbar, IdeAction dropDownIdeAction, IdeAction ideAction1, IdeAction ideAction2, IdeAction ideAction3) {
   ActionMenuToolButton actionMenuToolButton = toolbar.addActionMenuButton(dropDownIdeAction);
   Action[] actions = new Action[] {
      ideAction1,
      ideAction2,
      ideAction3,
   };
   actionMenuToolButton.setMenuActions(actions);
}

2.4.3 ウィンドウとビューの操作

JDeveloperデータ・モデルでは、扱っている情報の内容を表示および監視するために2つの重要な概念、ウィンドウとビューを使用します。ウィンドウ(多くの場合、IDEフレームワークの特定の場所に表示されるドッキング可能ウィンドウ)は、情報を表示し、コマンド入力を受け取ります。ビューでは、拡張機能によって生成、処理または表示される情報オブジェクト(ファイルまたはデータベース・レコードなど)にJDeveloperがアクセスする方法が定義されます。

2.4.3.1 ドッキング可能ウィンドウについて

ドッキング可能ウィンドウを使用すると、JDeveloperの指定の場所に拡張機能専用の領域を作成できます。ドッキング可能ウィンドウを使用するときは主に次の2つの手順を実行します。

  1. ドッキング可能ウィンドウを作成します。

  2. ドッキング可能ウィンドウを配置します。

拡張機能で使用するウィンドウを作成して配置したら、拡張機能がそのウィンドウを認識するようにします。特に、ユーザーが入力のためにウィンドウを選択したときに認識されるようにする必要があります。JDeveloperが使用するメカニズムはIDEリスナーです。ウィンドウにIDEリスナーを追加すると、拡張機能と作成したウィンドウが結び付けられます。詳細は、2.4.3.4項「IDEリスナーをビューに追加する方法」を参照してください。

ドッキング可能ウィンドウを作成して配置し、IDEリスナーの割当てを行ったところで、ユーザーによるウィンドウの選択を拡張機能が認識するように設定できます。ウィンドウは、選択されるとアクティブ・ビューを保持します。アクティブ・ビューをリスニングするように追加したIDEリスナーに指示できます。詳細は、2.4.3.5項「アクティブ・ビューのリスニング方法」を参照してください。

2.4.3.2 単純なドッキング可能ウィンドウの作成方法

ドッキング可能ウィンドウの作成は3つの手順で行います。DockableFactoryを作成し、クラスMyDockableWindowを使用してドッキング可能ウィンドウを作成し、アドインの初期化時にDockableFactoryをインストールします。

ドッキング可能ウィンドウを作成するには、次の手順を実行します。

  1. 例2-9に示すようにDockableFactoryを作成します。

    例2-9 DockableFactory

    public class MyDockableFactory implements DockableFactory
    {
     static final String VIEW_TYPE = "MY_VIEW_TYPE";
     public void install()
     {
       final DockingParam dockingParam = new DockingParam();
       dockingParam.setPosition(IdeConstants.SOUTH);
       DockStation.getDockStation().dock(
             new MyDockableWindow(),
             dockingParam);
    }
     public Dockable getDockable(ViewId viewId)
    {
       if (viewId.getName().equals(MyDockableWindow.VIEW_ID))
          return new MyDockableWindow(); 
      {
        return null;
      }
    }
    
  2. 例2-10に示すようにDockableWindowを作成します。

    例2-10 DockableWindow

    public class MyDockableWindow extends DockableWindow
    {
      static final String VIEW_ID = "MY_VIEW_ID";
       private JLabel _ui;
       public MyDockableWindow()
      {
         super(MyDockableFactory.VIEW_TYPE + "." + VIEW_ID);
      }
      public String getTabName()
      {
      return "ShortName";
      }
      public String getTitleName()
      {
      return "The Long Name Comes Here";
      }
      public String getTitleName()
      {
      return "The Long Name Comes Here";
      }
     public Component getGUI()
     {
       if (_ui == null)
       {
        _ui = new JLabel("The UI is here");
       }
       return _ui;
       }
     public int getDefaultVisibility(Layout layout)
      {
        return DEFAULT_VISIBILITY_VISIBLE;  
      }
    }
    
  3. 例2-11に示すように、アドイン初期化の際にファクトリをインストールします。

    例2-11 ファクトリのインストール

    DockStation.getDockStation().registerDockableFactory(
       MyDockableFactory.VIEW_TYPE,
       new MyDockableFactory()
    );
    

2.4.3.3 ドッキング可能ウィンドウの配置方法

例2-12は、ドッキング可能ウィンドウをアプリケーション・ナビゲータの中央に配置する方法を示します。アプリケーション・ナビゲータ拡張機能がロードされない場合、ウィンドウは左側(WEST)にドッキングされます。

例2-12 ドッキング可能ウィンドウの中央配置

dockingParam = new DockingParam();
final NavigatorManager applicationNavigatorManager = NavigatorManager.getApplicationNavigatorManager();
final NavigatorWindow navigatorWindow = applicationNavigatorManager.getNavigatorWindow();
dockingParam.setPosition(
   navigatorWindow,
   IdeConstants.CENTER,
   IdeConstants.WEST
);

2.4.3.4 IDEリスナーをビューに追加する方法

選択リスナーをアクティブ・ビューに追加するには、アクティブ・ビューの変更をリスニングする必要があります。リスナーを追加したビューがアクティブでなくなると、リスナーをそのビューから削除する必要があります。

アクティブ・リスナーを追加するには、次の手順を実行します。

例2-13に基づいてコードを作成します。

例2-13 アクティブ・リスナーの追加

import oracle.ide.view.ActiveViewListener;
import oracle.ide.view.ActiveViewEvent;
import oracle.ide.view.ViewSelectionListener;
class MyActiveViewListener implements ActiveViewListener
 {
    private ViewSelectionListener _selectionListener = new ViewSelectionListener()
    {
       public void viewSelectionChanged(ViewSelectionEvent e)
        {
         //Your code responding to view selection changes goes here.
        {
    };

アクティブ・リスナーを変更するには、次の手順を実行します。

例2-14に基づいてコードを作成します。

例2-14 アクティブ・リスナーの変更

public void activeViewChanged(ActiveViewEvent e)
 {
   View view = e.getOldView();
    
   if (view != null) view.removeViewListener(_selectionListener);
   view = e.getNewView();
   //While this example adds a ViewSelectionListener to any active view,
   //it is strongly recommended that you add your view selection listener
   //to views your extension is interested in only.
   view.addViewListener(_selectionListener);
  }
}

2.4.3.5 アクティブ・ビューのリスニング方法

JDeveloper IDEアーキテクチャのモデル/ビュー/コントローラ・モデルでは、どのビューがアクティブか(つまり、所定のウィンドウにどのデータ表現が表示されているか)が拡張機能によって把握されていることが必要です。(ウィンドウ・システムでは入力フォーカスがあるアクティブ・ビューが参照されます。)JDeveloper IDEアーキテクチャでは、拡張機能がアクティブ・ビューをリスニングする必要があります。これは、ビューによって実行されるコマンドが適切なデータに確実に適用されるようにする仕組みです。

アクティブ・ビューをリスニングするには、次の手順を実行します。

例2-15に基づいてコードを作成します。

例2-15 アクティブ・ビューのリスニング

Ide.getMainWindow().addActiveViewListener(new ActiveViewListener()
  {
     public void activeViewChanged(ActiveViewEvent e)
    {
       final View view = e.getNewView();
       System.out.println(view.getId() + " has been activated");
     }
});

2.4.4 ウィザードの開発方法

ウィザードは、タスクを実行するために呼び出される拡張機能です。典型的なウィザードはUIから呼び出され、1つまたは一連のダイアログ・ボックスで構成されるユーザー・インタフェースを開きます。ユーザーがこのインタフェースでタスクのパラメータを指定します。一般的なタスクは、ドキュメントまたは他のデータ・オブジェクトの作成です。「新規ギャラリ」またはツール・メニューにインストールされるウィザードのために、特別な起動メカニズムが提供されています。このようなケースでは、ウィザード・マネージャが起動の詳細を管理します。

2.4.4.1 ウィザード・プロジェクトの設定方法

ウィザード・プロジェクトのプロパティによって、ウィザード・プロジェクトのパス、ライブラリおよび他の設定が指定されます。プロジェクトをすでに設定している場合は、ソース・ファイルを追加してから、ウィザードをデバッグおよびデプロイできます。

ウィザード・プロジェクトには通常3つの主要コンポーネントがあります。

  • ウィザード・クラス。このクラスは、ユーザー・インタフェースでのウィザードの外観および起動方法を処理します。このクラスはWizardインタフェースを実装する必要があります。

  • モーダル・ダイアログ。このダイアログはユーザーと対話して、ウィザードの機能に必要なデータを収集します。

  • データ・オブジェクト。ウィザードは、データ・オブジェクトを作成または変更するために、ダイアログによって収集されたデータを適用します。

この項で示すコード例は、Extension SDKに含まれるHelloXサンプル・プロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

HelloXはウィザードを直接実装し、ユーザー・インタフェースとしてJDialogを使用します。JDeveloperのルック・アンド・フィールに準拠するウィザードはJEWTウィザード・フレームワークを使用します。

2.4.4.2 ウィザード・インタフェースの実装方法

モーダル・タスクを実行するためにユーザー・インタフェースから呼び出される拡張機能は、oracle.ide.wizard.Wizardインタフェースを実装する必要があります。このインタフェースは拡張機能のインストールに対応し、JDeveloperのユーザー・インタフェースにインストールを統合します。

ウィザード・インタフェースを実装するには、次の手順を実行します。

  • コンストラクタの定義

  • invokeメソッドの定義

  • getMenuSpecificationメソッドの定義

  • isAvailableメソッドの定義

  • getIconメソッドの定義

  • getNameメソッドの定義

2.4.4.2.1 コンストラクタの定義方法

ウィザードのコンストラクタは、ウィザードがロードされるときに1回だけ呼び出されます。このコンストラクタは軽量であることが必要です。つまり、オブジェクト参照を作成または保持しないでください。そのような処理はinitializeメソッドで行う必要があります。

2.4.4.2.2 invokeメソッドの定義方法

このメソッドによってウィザードの機能が具体化されます。通常、ウィザードはダイアログを開いてユーザーからパラメータを取得します。その後、それらのパラメータを使用してドキュメントや他のデータ・オブジェクトを作成または変更します。

このメソッドが呼び出されるのは、「新規ギャラリ」または「ツール」メニューでウィザードのUI要素がユーザーによって選択されたときです。ウィザードが影響を与える可能性がある、現在選択されているオブジェクトが、コンテキスト・パラメータによって識別されます。ウィザードが「ツール」メニューから呼び出された場合、paramsパラメータは空です。extension.xmlファイルにあるウィザードのwizardParametersタグの値セットを含みます。

invokeメソッドを定義するには、次の手順を実行します。

例2-16に基づいてコードを作成します。

例2-16 invokeメソッドの定義

public boolean invoke(oracle.ide.addin.Context context, java.lang.String[] params)
{
  if ( !this.isAvailable(context) )
    return false;
  String greetee = null;
  JProject project = (JProject) context.getProject();
  // Get the parameter from the user.
  greetee = JOptionPane.showInputDialog
    (new JDialog(), prompt, wizName, JOptionPane.OK_CANCEL_OPTION);
  if ( greetee == null ) 
    return false;
  // Create the document and the node that represents it.
  if ( !createNode(project, greetee) )
    return false;
  return true;
}
2.4.4.2.3 getMenuSpecificationメソッドの定義方法

このメソッドは、「ツール」メニューに表示されるウィザードのアイテムの外観を決定するために呼び出されます。ウィザードが「ツール」メニューにインストールされていない場合、このメソッドはnullを返します。

getMethodSpecificationメソッドを定義するには、次の手順を実行します。

例2-17に基づいてコードを作成します。

例2-17 getMethodSpecificationメソッドの定義

public MenuSpec getMenuSpecification()
 {
   if ( menuSpec == null)
     {
     Icon icon = getIcon();  
     menuSpec = new MenuSpec(wizName, new Integer((int)'X'), (KeyStroke)null,   icon);
     }
    return menuSpec;
  }
2.4.4.2.4 isAvailableメソッドの定義方法

このメソッドは、ウィザードの「新規ギャラリ」でのエントリまたは「ツール」メニューでのアイテムを、現在のコンテキストに応じて有効にするか無効にするかを決定するために呼び出されます。たとえば、プロジェクト・ノードに対して処理を行うウィザードは、現在のノードがプロジェクト・ノードである場合のみ有効にする必要があります。

isAvailableメソッドを定義するには、次の手順を実行します。

例2-18に基づいてコードを作成します。

例2-18 isAvailableメソッドの定義

public  boolean isAvailable(oracle.ide.addin.Context context)
 {
   Project p = context.getProject();
   if ( (p != null) && (p instanceof JProject) )
     return true;
   return false;
}
2.4.4.2.5 getIconメソッドの定義方法

このメソッドが呼び出されるのは、ウィザードの「新規ギャラリ」または「ツール」メニュー・アイコンを取得するためです。ウィザードでアイコンが必要ない場合、このメソッドはnullを返します。

getIconメソッドを定義するには、次の手順を実行します。

例2-18に基づいてコードを作成します。

例2-19 getIconメソッドの定義

public Icon getIcon() 
{
 if (image == null) 
 {
   image = GraphicsUtils.createImageIcon(
   GraphicsUtils.loadFromResource(imageName, this.getClass()));   
 }
 return image;
}
2.4.4.2.6 getNameメソッドの定義方法

このメソッドは、ウィザードの解読可能な名前を提供します。適切な文字列を入力してください。

2.4.4.3 新規ギャラリにウィザードを追加する方法

ウィザードは、拡張機能マニフェスト・ファイルにギャラリ・ウィザードの説明を指定すると、「新規ギャラリ」にインストールできます。ウィザードの登録やイベント処理の詳細すべては、ウィザード・マネージャによって処理されます。

ウィザードが呼び出されるのが「新規ギャラリ」だけの場合、ウィザード・クラスのコンストラクタは、拡張機能ユーザーがそのカテゴリを最初に開くまで呼び出されません。ギャラリ・マネージャは、カテゴリが最初に開かれるときに説明ファイルを読み取ります。次に、ウィザードをインスタンス化し、インスタンスから導出したアイコンとラベルを使用してアイテムを構成します。

2.4.4.4 「ツール」メニューにウィザードを追加する方法

ウィザード・マネージャは、「ツール」メニューにインストールされたウィザードを特別にサポートします。ウィザード・マネージャが、メニュー・アイテムの追加やユーザーによる選択の細かい部分を処理します。

ウィザードはユーザー・インタフェースの別の場所(他のメニュー、コンテキスト・メニューまたはツールバーなど)にインストールすることもできます。そのようなケースでは、コマンドを定義し、明示的にインストールして処理する必要があります。拡張機能マニフェスト・ファイルに、ウィザードのアドイン説明を含めてください。

「ツール」メニューにウィザードをインストールするには、次の手順を実行します。

  1. 例2-20に示すようにウィザードはgetMenuSpecification()を実装する必要があります。このメソッドで、oracle.ide.util.MenuSpecの新しいインスタンスを作成し、必要なメニュー・アイテムのラベル、ニーモニックおよびアイコンを渡します。

    コード例は、Extension SDKに含まれるHelloXおよびConfigPanelサンプル・プロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

    例2-20 「ツール」メニューへのウィザードのインストール

    public MenuSpec getMenuSpecification()
    {
      if ( menuSpec == null)
        {
        Icon icon = getIcon();
        menuSpec = new MenuSpec(wizName, new Integer((int)'X'),         (KeyStroke)null, icon);
        }
     return menuSpec;
    }
    
  2. アイコンとラベルを返す、ウィザードのgetMenuSpecificationメソッドを定義します。(ウィザードが「新規ギャラリ」のみで呼び出される場合、このメソッドはnullを返すことがあります)。

  3. 拡張機能をデプロイしてインストールします。詳細は、第5章「拡張機能のパッケージ化およびデプロイ」を参照してください。

2.4.5 コマンドの開発方法

メニュー・アイテムまたはツールバー・アイコンとしてユーザー・インタフェース要素を追加する拡張機能や、新たな目的のために既存の要素をカスタマイズする拡張機能は、機能をコマンド拡張機能にカプセル化する必要があります。

2.4.5.1 Addinインタフェースの実装方法

ほとんどの拡張機能はAddinインタフェースを実装する必要があります。このインタフェースは、JDeveloperの起動時に拡張機能のインストールに対応します。

Addinインタフェースを実装するためには、最初にコンストラクタを定義します。コンストラクタはできるだけ何もしないようにしてください。初期化タスクはinitializeメソッドで実行します。

次に、initializeメソッドを定義します。このメソッドは、インスタンスが作成された後でアドイン・マネージャによって呼び出されます。初期化時に実行する必要があるタスクは次のとおりです。

  • UI要素とコントローラを作成します。

  • マネージャへ登録します。

アドインが呼び出されるまで必要のない他のタスク(データ構造の作成など)は延期して、JDeveloperの起動が不必要に遅くならないようにします。

2.4.5.2 コマンドの実装方法

拡張機能がメニュー・アイテムまたはツールバー・アイコンを追加する場合は、その機能をコマンドとして実装する必要があります。

既存のコントロールに特別な動作を定義する拡張機能は、カスタム・アクションを定義したり、カスタム・コマンド・クラスを実装したりするのではなく、そのコントロールにすでに用意されているアクションとコマンド・クラスを使用する必要があります。Ideクラスに定義されているフィールドは、IDEの標準コマンドのクラス名とIDを提供します。

拡張機能のコマンドには、次のタスクが必要です。

  • イベントの処理

  • undoメソッドの定義

  • その他のメソッドの定義

この項のコード例は、Extension SDKに含まれるFirstSampleサンプル・プロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

2.4.5.2.1 イベントの処理

例2-21のコードは、コマンドを呼び出したときにトリガーされるイベントの処理方法を示します。

例2-21 イベントの処理

/**
  * ContextMenuListeners add items to context menus.
  */
public final class SimpleContextMenuListener implements ContextMenuListener {
    public void menuWillShow(ContextMenu contextMenu) {
        // First, retrieve our action using the ID we specified in the
        // extension manifest.
        IdeAction action = IdeAction.find(SimpleController.SAMPLE_CMD_ID);
        // Then add it to the context menu.
        contextMenu.add(contextMenu.createMenuItem(action));
     }
      public boolean handleDefaultAction(Context context) {
        // You can implement this method if you want to handle the default
        // action (usually double click) for some context.
        return false;
     }
}

コードのブロックが長くなる場合は、doItメソッドを使用できます。

2.4.5.2.2 undoメソッドの定義方法

このメソッドを定義する必要があるのは、コンストラクタによってコントローラがNORMALタイプと指定される場合のみです。undoメソッドには通常次の2つのタスクが含まれます。

  • doitメソッドの処理結果を元に戻します。getDataメソッドから取得した値でチェックポイントの状態に復元するか、doitメソッドの処理を逆に実行します。

  • 変更が行われたことをオブザーバに通知します。

コマンドが正常に終了するとOKを返します。それ以外の場合は、CANCELまたは0以外の値を返します。デフォルト実装はCANCELを返し、悪影響はありません。

2.4.5.2.3 その他のメソッドの定義方法

次のメソッドのデフォルト実装はオーバーライドできます。

  • getIdは、コンストラクタに渡されるコマンドIDを返します。

  • getTypeは、コンストラクタに渡されるコマンド・タイプ定数を返します。または、この引数が指定されない場合はNO_CHANGEを返します。

  • getNameは、コンストラクタに渡される名前文字列を返します。または、この引数が指定されない場合は空の文字列を返します。

  • getAffectedNodesはデフォルトではnullを返します。

  • setContextgetContextは、保護されたContext変数の書込みと読取りをそれぞれ行います。デフォルト値はnullです。

  • setDatagetDataは、プライベートObject変数の書込みと読取りをそれぞれ行います。デフォルト値はnullです。

2.4.5.3 アクションの定義方法

新しいコマンド・クラスを実装する拡張機能は、それらを含むアクションを定義する必要があります。アクションは、メニュー・アイテム(または他のユーザー・インタフェース・コントロール)と、そのメニュー・アイテムが選択されたときに実行されるコマンドのリンクになります。アクションはIdeActionのインスタンスです。

既存のUI要素に特別な動作を定義する拡張機能は、カスタム・アクションを定義するのではなく、その要素にすでに用意されているアクションを使用する必要があります。Ideクラスに定義されているフィールドは、標準コマンドのクラス名とIDを提供します。たとえば、保存処理を提供するすべてのエディタは、事前定義済のIde.SAVE_CMD値およびIde.CUT_SAVE_ID値と、それらに関連付けられたアクションを使用する必要があります。

通常、アクションは、アクションに関連するメニュー・アイテムまたはツールバー・アイコンをインストールするAddinクラスのフィールドとして定義されます。拡張機能の初期化においてアクションを作成して構成します。

この項のコード例は、Extension SDKに含まれるFirstSampleサンプル・プロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

2.4.5.3.1 アクションの取得方法

アクションはコマンドID別にキャッシュされます。必要なコマンドのアクションがすでに存在する場合、通常は、新しいアクションを作成するかわりに既存のアクションを使用する必要があります。

アクションを作成するためにIdeActionコンストラクタを使用しないでください。かわりに、次の静的メソッドを使用して、キャッシュされたアクションを取り出すか、新しいアクションを作成します。

  • findメソッドは、該当するアクションがキャッシュ済の場合、指定されたコマンドIDを含むアクションを返します。

  • 様々なgetメソッドは、該当するアクションがキャッシュ済の場合、指定されたコマンドIDと一致するアクションを返します。ただし、そのアクションのプロパティは、指定されたパラメータと一致する場合としない場合があります。コマンドIDに対応するアクションが見つからない場合は、指定されたパラメータに基づいて新しいアクションが作成され、キャッシュ、そして返されます。

  • createメソッドは、新しいアクションを作成し、それをキャッシュせずに返します。createで取得されるアクションは、後でロードされる拡張機能では使用できません。

2.4.5.3.2 アクション値の設定方法

アクションには、外観を決定するものなど様々なプロパティが含まれることがあります。これらには、putValueメソッドやgetValueメソッドで文字列キーを使用してアクセスします。一部のプロパティは、アクションが静的メソッドcreateまたはgetによって作成されるときに設定されます。IDEによって認識されるキーはToggleActionスーパークラスに定義されます。詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスToggleActionに関する項を参照してください。

2.4.5.3.3 アクションのコントローラの拡張方法

ビューに依存しないアクション(openなど)は、アクションを更新してイベントを処理するためにコントローラを指定する必要があります。ビューのコンテキストで呼び出されるアクションには、アクション・コントローラは必要ありません。

コマンドの動作を拡張すると、そのコマンドを使用するすべてのIDE機能で、カスタム操作を実行できます。これには、コマンドのアクションのコントローラを置き換えるか、アクションにまだコントローラがない場合にはコントローラを設定します。ただし、デフォルトの動作を損ねないように注意する必要があります。

既存のコントローラを置き換えるには、次の手順を実行します。

  1. 古いクラスを拡張して、新しいコントローラ・クラスを実装します。

  2. 新しいコントローラが拡張するコマンドを処理する際に、custom処理を実行し、そのコマンドに対して古いコントローラのhandleEventメソッドを呼び出します。こうすることで、元の動作が保持されます。

コントローラのないアクションにコントローラを追加するには、次の手順を実行します。

  1. 新しいコントローラ・クラスを実装します。

  2. 新しいコントローラが拡張するコマンドを処理する際に、custom処理を実行し、そのコマンドに対してスーパーバイザのhandleEventメソッドを呼び出します。こうすることで、元の動作が保持されます。

getControllerメソッドとaddControllerメソッドを使用してアクションのコントローラにアクセスします。

2.4.5.3.4 アクションのコマンド・クラスの拡張

getCommandメソッドは、アクションのコマンド・クラスの名前を返します。これを置き換えるためにsetCommandメソッドを使用できます。ただし、そうするとグローバルに置き換えられるため、そのアクションを処理するすべてのIDE機能に影響します。悪影響が生じないようにするには、元のコマンド・クラスだけを、それを拡張するクラスで置き換えます。

2.4.5.4 メイン・ウィンドウ・メニューからアドインを呼び出す方法

メイン・ウィンドウ・メニューからアドインを呼び出せるようにするには、アドインのメニュー・アイテムをメイン・ウィンドウ・メニューのいずれかに追加します。アイテムはどのメニューにも追加できますが、拡張機能を「ツール」メニューから呼び出す場合は、アドインとしてインストールするかわりに(または、アドインとしてのインストールに加えて)ウィザードとしてインストールする必要があります。「ツール」メニューでのメニュー・アイテムの追加と処理は、ウィザード・マネージャによって行われます。

IDEメニューは、Menubarのシングルトン・インスタンスによって表示されます。これはgetMenubarメソッドを使用してアクセスできます。

拡張機能は、IDEメニューにメニュー、サブメニューおよびメニュー・アイテムを追加できます。呼び出される拡張機能(ウィザードなど)は、インストール時に独自のアイテムをメニューに追加します。または、拡張機能が標準メニュー・アイテムの独自の動作を定義できます。たとえば、「編集」メニューのアイテムの処理内容は使用されるエディタによって異なります。

メニュー・アイテムにはIdeActionオブジェクトが関連付けられています。

IDEの状態が変化すると、アクティブ・ビューのコントローラの指示によって、すべてのメニュー・アイテムの有効/無効状態がリセットされます。次にユーザーがメニュー・アイテムを選択するか、アイテムのキーボード・ショートカットを入力すると、アイテムのアクションで指定されたコマンドが現在のコンテキストに対して実行されます。

メニュー・アイテムを定義するには、次のコンポーネントを指定します。

  • コマンドIDは、メニュー・アイテムが選択されたときに実行されるコマンドを表します。

  • コマンドIDに関連付けられているアクションは、ユーザー・アクションとコントローラの間のリンクとして動作します。詳細は、2.4.5.3項「アクションの定義方法」を参照してください。

  • コントローラは、メニュー・アイテムの有効化と無効化を行い、イベントを処理します。アドインによってインストールされた機能を最終的に呼び出します。詳細は、2.4.6.3項「コントローラの実装方法」を参照してください。

  • メニュー・アイテムは、ユーザー・アクションとコントローラの間のリンクとして使用されます。

メニュー・バーのcreateMenuItemメソッドを呼び出して、メニュー・アイテムを作成します。メニュー・バーはIDEのコンポーネントです。

return Ide.getMenubar().createMenuItem(action);

メニューはメイン・ウィンドウの静的なメンバーです。メニューのaddメソッドを呼び出して、メニュー・アイテムを追加します。メニュー・アイテムはJDeveloperが起動された直後から使用できることが必要です。このため、アドインのinitializeメソッドで作成してインストールします。次のコードでは、メニュー・アイテムが「ナビゲート」メニューに追加されます。

public void addMenuItem() 
 {
    Environment.getJMenu(IdeMainWindow.MENU_EDIT).      add(createMenuItem(contextInfoAction));
}

2.4.5.5 コンテキスト・メニューからアドインを呼び出す方法

アプリケーション・ナビゲータやソース・エディタなどの一部のビューには、ユーザーがウィンドウを右クリックするとポップアップ表示されるコンテキスト・メニューがあります。拡張機能はコンテキスト・メニューにアイテムを追加できます。

ContextMenuListenerを実装するすべてのクラスのオブジェクトによって表されるユーザー・インタフェース要素は、コンテキスト・メニューを含むことができます。コンテキスト・メニューは、JDeveloperのほぼすべての場所で使用できます。最終的にユーザー・インタフェース要素のほとんどは、このクラスを実装するか、インプリメンタのサブコンポーネントになります。

ユーザーが右クリックすると、コンテキスト・メニューが再構成されます。選択されたコンテキスト・メニュー・リスナー(右クリックの対象に関連付けられているリスナー)がポーリングされ、コンテキスト・メニューにアイテムまたはサブメニューを付加する機会が与えられます。たとえば、ドキュメントを表すノードが右クリックされると、そのドキュメント・タイプのビューアとして登録されているエディタやデザイナのメニュー・アイテムをコンテキスト・メニューに追加できます。

コンテキスト・メニュー・リスナーは、次のメソッドでポーリングされます。

  • poppingUp。コンテキスト・メニューが構成されているときに呼び出されます。リスナーは、このときにメニュー・アイテムを付加する必要があります。

  • poppingDown。コンテキスト・メニューが終了されるときに呼び出されます。

  • handleDefaultAction。ダブルクリックで呼び出されます。この場合、ポーリングされたリスナーのうち1つのみtrueを返して、メニュー・アイテムに関連するアクションが呼び出されることを示す必要があります。

コンテキスト・メニューから拡張機能を呼び出せるようにするには、ビューのコンテキスト・メニューにメニュー・リスナーを追加します。メニュー・リスナーがメニュー・アイテムをインストールする機会を得るのは、コンテキスト・メニューが再作成されるときです(ポップアップ表示されるときすべて)。

コンテキスト・メニュー・アイテムを定義するには、次のコンポーネントを指定します。

  • コマンドIDは、メニュー・アイテムが選択されたときに実行されるコマンドを表します。

  • コマンドIDに関連付けられているアクションは、ユーザー・アクションとコントローラの間のリンクとして動作します。詳細は、2.4.5.3項「アクションの定義方法」を参照してください。

  • コントローラは、メニュー・アイテムの有効化と無効化を行い、イベントを処理します。アドインによってインストールされた機能を最終的に呼び出します。詳細は、2.4.6.3項「コントローラの実装方法」を参照してください。

  • コンテキスト・メニュー・リスナーは、アイテムを追加できる各コンテキスト・メニューのリスナー・インスタンスを提供します。様々なビューそれぞれが、独自のコンテキスト・メニューを管理します。

例2-22では、アプリケーション・ナビゲータのコンテキスト・メニューにリスナーが追加されます。

例2-22 コンテキスト・ウィンドウへのリスナーの追加

public void createCtxMenuListeners(ContextInfoController controller) {
     ContextMenu menu;
     // Add a listener to the Explorer's context menu.  
     // This form will work for any manager or view that defines getContextMenu.
     menu = EditorManager.getEditorManager().getContextMenu();
     menu.addContextMenuListener(new ContextInfoMenuListener(controller));
     // Add a listener to the Navigator's context menu.  
     NavigatorManager.getWorkspaceNavigatorManager().addContextMenuListener    (new ContextInfoMenuListener(controller), null);
}

アドインがロードされるときにコンテキスト・メニュー・リスナーを追加する必要があります。このため、このタスクはアドインのinitializeメソッドで実行してください。コントローラとアクションの作成は、コンテキスト・メニューが開くまで(つまりリスナーのメソッドが最初に呼び出されるまで)延期できます。

2.4.6 エディタの開発方法

エディタは、ユーザーが変更できるようにオブジェクトを表示するビューです。通常、エディタにはテキストが表示されます。デザイナはテキスト以外のエディタです。エディタは、ドキュメントのノードのコンテキスト・メニューまたは「表示」メニューを介して開かれます。ドキュメントで使用できるエディタは、そのドキュメントのタイプについてIDEのエディタ・マネージャに登録されたエディタです。エディタは、通常は構造エクスプローラと組み合せて使用されますが、これは必須ではありません。

2.4.6.1 EditorAddinクラスの実装方法

エディタの拡張機能はEditorAddinクラスを実装します。このクラスは、拡張機能をエディタ・マネージャに統合し、JDeveloperの起動時の拡張機能のインストールに対応します。

EditorAddinクラスを実装するには、次の手順を実行します。

  • コンストラクタを定義します。

  • initializeメソッドを定義します。

  • getEditorClassメソッドを定義します。

  • isDefaultメソッドを定義します。

  • getMenuSpecificationメソッドを定義します。

ここで示すコード例は、Extension SDKに含まれるCustomEditorサンプル・プロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

2.4.6.1.1 コンストラクタの定義方法

コンストラクタはできるだけ何も実行しないようにします。初期化タスクはinitializeメソッドで実行します。

2.4.6.1.2 initializeメソッドの定義方法

このメソッドは、インスタンスが作成された後でアドイン・マネージャによって呼び出されます。ここでエディタをエディタ・マネージャに登録します。

エディタはIDEのエディタ・マネージャに登録する必要があります。登録によって、エディタと1つ以上のノード・クラスが関連付けられます。ユーザーがコンテキスト・メニューまたは「表示」メニューから、あるノードについてエディタを開こうとすると、そのノードのクラスに登録されているエディタだけが有効になります。

2.4.6.1.3 getEditorClassメソッドの定義方法

このメソッドではクラスが指定されます。編集可能なビューとして作動するエディタの実装です。

2.4.6.1.4 isDefaultメソッドの定義方法

ノード・クラスにはデフォルト・エディタを設定できます。ユーザーがノードをダブルクリックすると、デフォルト・エディタでノードが開きます。エディタを登録済ノード・タイプのデフォルトとして宣言するには、このメソッドを実装してtrueを返します。

2.4.6.1.5 getMenuSpecificationメソッドの定義方法

エディタ・マネージャは、ナビゲータを識別するために各ナビゲータの上部に表示されるアイコンとテキストのメニュー仕様を追加します。メニュー仕様は、エディタのメニュー・アイテムの外観を指定します。

2.4.6.2 エディタ・クラスの定義方法

エディタは、ユーザーが変更できるようにオブジェクトを表示するビューです。Editorを拡張して独自のエディタを作成します。エディタ・クラスがインスタンス化されるのは、そのエディタに登録されているノード・タイプが開いて表示されるときです。

エディタは、次のような他のコンポーネントに関連付けられています。

  • エディタ・マネージャ(IDEのコンポーネント)は、ユーザーがノードを開くと、エディタ・クラスを選択してインスタンス化します。

  • EditorAddinは、エディタとエディタ・マネージャの仲介役として使用されます。JDeveloperが起動すると、アドインがエディタ・クラスを登録します。

  • 編集されるデータを表すオブジェクトは、通常はNodeの実装です。

  • エディタ・コンポーネント(JEditorPaneの実装)は、データの実際の変更を実行します。

  • Controllerは、ユーザー・インタフェースの編集コマンドを解釈し、エディタ・コンポーネントのメソッドを呼び出します。

  • Explorerは、データ・オブジェクトの構造を表示します。エクスプローラはエディタ・メソッドを呼び出して、ユーザー・アクションに応答します。

エディタ・クラスは次の処理を行う必要があります。

  • エディタのインスタンス化

  • エディタの初期化

  • コントローラへのアクセス

  • ルートGUIコンポーネントの取得

  • エクスプローラ・イベントへの応答

  • エディタ・コンポーネント・イベントへの応答

  • 更新メッセージの生成

ここで示すコード例は、Extension SDKに含まれるCustomEditorサンプル・プロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

2.4.6.2.1 エディタのインスタンス化方法

ユーザーがアプリケーション・ナビゲータでノードを開くと、エディタ・マネージャがエディタ・クラスをインスタンス化します。例2-23に示すように、エディタ・マネージャはデフォルト・コンストラクタを呼び出します。このためコンテキスト固有のパラメータはありません。

例2-23 デフォルト・コンストラクタを呼び出すエディタ・マネージャ

public PropFileEditor()
 {
     systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
} 
2.4.6.2.2 エディタの初期化方法

エディタ・マネージャはエディタ・インスタンスを初期化するためにsetContextメソッドを呼び出します。例2-24に示すように、このメソッドは、編集されるドキュメントをコンテキストから抽出し、エディタ・コンポーネントを初期化します。

例2-24 エディタ・コンポーネントの初期化

public synchronized void setContext( Context context )
{
   if ( context != null )
   {
      Element element = context.getElement();
      // A sanity check: if the context is bad the editor pane will be empty.
      if ( ( element != null ) &&
           ( element instanceof TextDocument ) )
      {
       super.setContext( context );
       document = (TextDocument) context.getElement();
       initializeGraphics( context );
      }
   }
}
2.4.6.2.3 コントローラのアクセス方法

エディタにはコントローラ・インスタンスが関連付けられており、ユーザー・イベントを捕捉して解釈します。例2-25に示すように、IDEはエディタのgetControllerメソッドを呼び出して、コントローラを取得します。このメソッドは最初に呼び出されたときにコントローラを作成します。

例2-25 エディタのgetControllerメソッドの呼出し

public Controller getController()
{
  if ( editorController == null )
  {
    editorController = new PropFileEditorController( this );
  }
  return editorController;
} 
2.4.6.2.4 ルートGUIコンポーネントの取得方法

エディタには関連付けられているルート・グラフィカル・ユーザー・インタフェース・コンポーネントがあります。例2-26に示すように、このエディタ・インスタンスのパネルを返します。

例2-26 rootGUIコンポーネントの取得

public Component getGUI()
{
  return mainPanel;
}
2.4.6.2.5 エクスプローラ・イベントへの応答方法

データ・オブジェクトのエクスプローラ・ビューは、エディタ内でドキュメントをナビゲートする手段を提供します。ユーザーがエクスプローラで要素を選択すると、データの対応する部分を表示するようにエクスプローラがエディタに指示します。

エディタをエクスプローラに統合する予定がある場合は、1つ以上のgotoメソッドを提供する必要があります。(この機能はエディタ・インタフェースの必須ではありません。)例2-27は、エディタのカーソルをテキスト・ファイルの指定行に移動するメソッドです。

例2-27 カーソルを移動するメソッド

public void setCaretPosition( int offset )
{
   editorComponent.setCaretPosition( offset );
}
2.4.6.2.6 エディタ・コンポーネント・イベントへの応答方法

エディタ・コンポーネント(JEditorPaneの実装)は、データが変更されるとアクション・イベントを生成します。エディタは、KeyListenerのようなインタフェースを実装する必要があります。初期化の際には、エディタ・コンポーネントのaddKeyListenerメソッドを呼び出して、イベントを受け取るように自らを登録する必要があります。例2-28は、KeyListenerメソッドの例です。keyTypedが、変更に関するアラートを他のIDEオブジェクト(レコグナイザなど)に通知するメソッドをトリガーします。

例2-28 KeyListenerメソッド

public void keyTyped( KeyEvent e )
{
 timer.restart();
}
2.4.6.2.7 更新メッセージの生成方法

データ・オブジェクトの状態が変化したときは、他のIDEコンポーネント(ドキュメントのエクスプローラなど)に知らせる必要があります。これは通知メカニズムによって実行されます。例2-29に示すように、エディタがUpdateMessageインスタンスを作成し、ドキュメントのオブザーバにブロードキャストします。

例2-29 UpdateMessageインスタンス

public void keyTyped( KeyEvent e )
{
  timer.restart();
}

2.4.6.3 コントローラの実装方法

拡張機能がビュー・クラスを実装する場合、ビューのイベントを処理するコントローラ・クラスも実装する必要があります。コントローラを実装するには、Controllerインタフェースを使用します。

コントローラ・クラスを実装するには、次のメンバーの定義を指定します。

  • handleEventメソッドの定義

  • updateメソッドの定義

  • コマンド定数の定義

ここに示すコード例は、FirstSampleプロジェクトとClassSpyプロジェクトのものです。この2つのサンプル・プロジェクトはExtension SDKに含まれています。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

2.4.6.3.1 handleEventメソッドの定義方法

handleEventメソッドは、選択されたコマンドを参照してアクションを処理するswitch文です。他の部分はスーパーバイザで処理します。例2-30に示すように、拡張機能に定義されたコマンドを処理するためにこのメソッドを定義し、拡張機能でオーバーライドする必要がある他のメソッドを定義します。

例2-30 handleEventメソッドの定義

public boolean handleEvent(IdeAction action, Context context) {
 int cmdId = action.getCommandId();
 // Handle actions containing this command:
 if (cmdId == CONTEXT_INFO_CMD_ID) {
     CommandProcessor cmdProc = CommandProcessor.getInstance(); 
     String commandName = action.getCommand();
     Command command = cmdProc.createCommand(commandName, context); 
     // Use command processor to execute command. 
     try { 
       cmdProc.invoke(command); 
       }
     catch (Exception e) { 
       System.err.println(e.toString()); 
       }
     finally { 
       return true; 
       }
     }
 // Let the IDE try to find another Controller to handle this action.
 return false;
}

データに対して実行するメソッドを呼び出して、イベントを直接処理できます。または、コマンド・オブジェクトを作成してコマンド・プロセッサから呼び出すことで、間接的にイベントを処理できます。後の方法が好ましいケースを次に示します。

  • イベントに適用可能なデフォルト動作がある場合。アクションのコマンド・クラスにデータとの互換性がある場合、コマンドを再実装する必要はありません。たとえば、Ide.SAVE_CMDはSaveアクションのデフォルト・コマンド・クラスです。Documentインタフェースを実装するすべてのデータ・クラスで定義されます。

  • イベントがUNDO可能な場合。コマンド・プロセッサがUNDOスタックを管理します。

  • イベントがシステムと対話する場合。あるいは、コンピュータ集約型の場合。コマンド・プロセスが独自のスレッドでコマンドを呼び出します。

2.4.6.3.2 updateメソッドの定義方法

updateメソッドは本質的にはswitch文です。選択したアクションの有効と無効を切り替えて、残りの部分はスーパーバイザで代行します。例2-31に示すように、拡張機能に定義されたアクションのためにこのメソッドを定義し、拡張機能でオーバーライドする必要がある他のメソッドを定義します。

例2-31 updateメソッドの定義

public boolean update(IdeAction action, Context context) {
  int cmdId = action.getCommandId();
  // Set the enabled status for relevant actions.
  if ( cmdId == CONTEXT_INFO_CMD_ID ) {
     action.setEnabled(enableContextInfo(context));
     return true;
  }
  // Let the IDE try to find another Controller to update this action. 
  return false;
}
2.4.6.3.3 コマンド定数の定義方法

拡張機能で必要なコマンドを定義し、コマンドIDを指定します。例2-32に示すように、Ide.findOrCreateCmdID静的メソッドが一意のコマンドIDを割り当てます。

例2-32 Ide.findOrCreateCmdIDメソッド

public static final int CONTEXT_INFO_CMD_ID =
    Ide.findOrCreateCmdID("ContextInfoController.CONTEXT_INFO_CMD_ID");

2.4.6.4 エディタ・レイアウトの指定方法

レイアウトは、特定のタスクに対応するようにドッキング可能ウィンドウを調整するJDeveloperの機能です。レイアウトは、ユーザー・アクションによって変更され、ユーザーが別のタスクに切り替えるときに保存され、ユーザーが戻ると復元されます。

通常、ユーザーは、「プリファレンス」ダイアログの環境ページ(「ツール」メニューから使用可能)で、またはJavaビジュアル・エディタのレイアウト・マネージャを使用して、カスタム・レイアウトを定義します。ただし、エディタの拡張機能は、そのエディタのためにレイアウトを指定できます。

レイアウトはEditorのサブクラスであるLayoutSelectorのメソッドによって指定されます。エディタは次のメソッドをオーバーライドする必要があります。

  • getPreferredLayoutURLは、セッションの間にレイアウトの状態が保存されるプロパティ・ファイルを指定します。レイアウトは、エディタがセッションで最初に使用されるときにこのファイルから抽出され、JDeveloperが終了するときにファイルに書き込まれます。

  • onPreferredLayoutActivateは、レイアウト・ファイルが見つからないときにレイアウトを初期化するために呼び出されます。このメソッドは、ドッキング可能ウィンドウのオープン、クローズおよび調整します。

2.4.6.5 非同期エディタの使用

非同期エディタは、UIスレッド(Swingのイベント・ディスパッチ・スレッド)の外部のワーカー・スレッドにコンテンツをロードするため、ユーザーが操作する際の応答性が向上します。非同期エディタのコンテンツをロードするとき、エディタ・フレームワークでは次の処理が行われます。

  • 「エディタのロード中」というメッセージとアニメーションを含むパネルを表示します。両方とも構成できます。

  • コンポーネント・パレットがUIをロードおよびブロックをしないようにします。

エディタのロードが開始するのはContextが設定されたときです。このアクションにより、エディタのUIがUIスレッドで必要とするモデルのロードがトリガーされます。このために、非同期エディタはワーカー・スレッドを作成してロードを実行します。モデルのロードが行われている間に、IDEはエディタのGUIコンポーネントを確認します。エディタの実際のUIがまだロードされていない場合、メッセージ(たとえば、「エディタのロード中」)を含むパネルが表示され、コンポーネント・パレットはロードされません。エディタの実際のUIが作成されると、非同期エディタが自動的に入れ替わります。

エディタ・フレームワークにより、エディタの実装方法の柔軟性が高くなります。詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスAsynchronousEditorに関する項を参照してください。

opencloseおよびactivateのようなライフサイクル・メソッドはfinalです。非同期エディタでは、類似しているが新しいライフサイクル・メソッドが導入されます。これらのメソッドには追加の引数を指定できます。引数は、エディタのUIがロードされたかどうかを示すフラグです。

2.4.6.5.1 非同期エディタの仕組み

最初の手順としては、たとえばユーザーがアプリケーション・ナビゲータでノードをダブルクリックすると、EditorManagerがエディタを作成して開きます。

  1. EditorStateが新しい非同期エディタ(oracle.ide.editor.AsynchronousEditorのサブクラス)を作成します。

  2. 非同期エディタは作成されるとプロパティを設定します。そのプロパティによって、エディタが非同期であり、実際のエディタはまだロードされていないことを、IDEの他のビューが認識します。このプロパティに関心を持つビューの例としては、コンポーネント・パレットがあります。コンポーネント・パレットがコンテンツをロードするのは、エディタが完全にロードされた場合のみです。

  3. EditorStateによって、新しい非同期エディタ内のコンテキストが設定されます。

  4. 非同期エディタは、コンテンツがまだロードされていないことが適切なフラグによって示されるのを待機し、その後で非同期エディタ・コンテンツ・モデルをロードします。これは非同期に実行されます(UIイベント・スレッド外)。

  5. EditorManagerは、エディタの優先レイアウトを設定します。

  6. EditorStateがエディタを開きます。これによって、getGUIの呼出しが行われます。

これは、2.4.6.5.1項の「エディタのオープン」で説明しています。

図2-4 エディタのオープン

非同期エディタを開く方法

エディタがいったん開くと、エディタのメソッドgetGUIが呼び出されます。このとき、非同期エディタで「エディタのロード中」というメッセージが表示され、2.4.6.5.1項の「エディタのGUIの取得」に示すように、コンテンツ・モデルを非同期にロードするタスクがUIイベント・スレッド外でスケジュールされます。

図2-5 エディタのGUIの取得

エディタのGUIの取得

この時点でコンテンツ・モデルは非同期にロードされており、次の処理が行われます。

  • AsynchronousEditorは、メソッドopenImplshowImplおよびactivateImplを呼び出して、エディタのGUIのロード、表示およびアクティブ化します。

  • AsynchronousEditorは待機中ページを実際のエディタGUIと切り替えます。

  • AsynchronousEditorは、完全にロードされたことをコンポーネント・パレットなどのリスナーに通知します。リスナーはそれぞれのコンテンツをロードできるようになります。

通常のエディタを非同期エディタにするには、次の手順を実行します。

  1. oracle.ide.editor.Editorではなくoracle.ide.editor.AsynchronousEditorを拡張します。

  2. 抽象メソッドgetEditorContext(Context)を実装します。このメソッドは、エディタのUIが所定のContextで必要とするモデルをロードします。

  3. 抽象メソッドdoSetContext(Context)を実装します。このメソッドは、Editorで元のメソッドsetContext(Context)と同じ機能を持つと想定されます。setContext(Context)はfinalになります(非同期機能の正しい動作を保証します)。

  4. 抽象メソッドisContentModelLoaded()を実装します。このメソッドは、エディタによって使用されるモデルがロードされるかどうかにかかわらず、非同期エディタ・フレームワークを指定します。

  5. 抽象メソッドopenImpl(boolean)を実装します。非同期エディタの正しい動作を保証するために、Editorの抽象メソッドopen()がfinalになります。openImplは、エディタのUIのモデルがロードされたかどうかを示すブール引数を渡します。ほとんどのケースで、与えられた引数がtrueの場合、必要となるのは既存のopen()の実装をopenImplに移すことだけです。

  6. Editorライフサイクル・イベントのすべてのコードを新しいメソッドに移動します。非同期コンテンツのロードが正しく作動することを保証するために、AsynchronousEditorはエディタのライフサイクルをfinalにして、implメソッドでそれらを実装するサブクラスを提供します。

    finalにするライフサイクル・メソッドは次のとおりです。

    • open

    • close

    • editorShown

    • editorHidden

    • activate

    • deactivate

    AsynchronousEditorのサブクラスは次のメソッドを実装する必要があります。

    • openImpl(boolean)

    • closeImpl(boolean)

    • editorShownImpl(boolean)

    • editorHiddenImpl(boolean)

    • activateImpl(boolean)

    • deactivateImpl(boolean)

    この場合、ブール引数はエディタのコンテンツ・モデルがロードされたかどうかを示します。

    このタスクの複雑さは、特定のエディタのクラス階層の数のレベルに比例します(特にライフサイクル・メソッドをオーバーライドする場合)。一連のクラスがclose()をオーバーライドする場合、それらすべてがかわりにcloseImpl(boolean)をオーバーライドする必要があります。

2.4.7 エクスプローラの開発方法

構造エクスプローラでは、ドキュメントの編成が通常はツリーとして表示されます。JDeveloperでは、一般的な様々なドキュメント・タイプに対してエクスプローラが提供されます。カスタム・エクスプローラをインストールして、エクスプローラ・マネージャに登録することもできます。

2.4.7.1 エクスプローラの作成方法

構造エクスプローラでは、ドキュメントの編成が通常はツリーとして表示されます。

構造エクスプローラは、DocumentViewです。エディタまたはデザイナにフォーカスが移ると、そのエクスプローラが構造ウィンドウに表示されます。エクスプローラは、本質的にはドキュメントの索引または目次です。ユーザーはエクスプローラを使用して、ドキュメント・ビューに表示されるコンテンツをナビゲートします。ドキュメントの階層は「構造」ウィンドウに表示され、ドキュメントが編集されると更新されます。

JDeveloperでは、一般的な様々なドキュメント・タイプに対してエクスプローラが提供されます。カスタム・エクスプローラをインストールして、エクスプローラ・マネージャに登録することもできます。ドキュメントがJDeveloperで開かれると、対応するエクスプローラがエクスプローラ・マネージャによって提供されます。エクスプローラはドキュメントを解析して、生成される構造を表示します。

エクスプローラ・アドインには次のコンポーネントが含まれます。

  • 登録を実行するクラス。このクラスがAddinを実装します。JDeveloperが起動されると、このクラスの1つのインスタンスがアドイン・マネージャによって作成され、このインスタンスが登録を実行します。アドイン・クラスには他の目的は必要ありません。

  • エクスプローラのユーザー・インタフェースを提供するクラス。このクラスは特別なビューアです。ドキュメントの構造がツリーで表示されるとき、必要な機能のほとんどは、TreeExplorerまたはそのサブクラスの1つを拡張することで提供されます。このクラスは、要素でのマウスのクリックを処理し、エディタでの変更を追跡し、必要な場合にツリーを再構築するパーサーを呼び出します。

  • Elementの1つ以上のインスタンスを含む要素モデル。このモデルは、ドキュメントのエクスプローラ・ビューの構造表現です。要素は、ドキュメントのコンテンツの特定の場所に関連付けられています。選択されると、ビューアがその場所までスクロールします。

  • ドキュメントの要素の構造を生成するパーサー。パーサーは、ドキュメントを、ビューア座標にマッピングされる要素の階層に単純化します。

エクスプローラを設計するときは次の点に注意してください。

  • Explorerは、Documentに含まれるデータの構造化された視覚的表現を提供するビューです。

  • 1つのドキュメントに対して複数のエクスプローラの実装が存在することが可能です。

  • Explorerインタフェースを実装して、構造エクスプローラを作成します。

  • ChildFilterは、TreeExplorerと組み合せて使用するオプション・インタフェースです。これは、ユーザーがコンテナ・ノードを展開したときに表示される子をフィルタリングします。

2.4.7.2 構造エクスプローラの登録および初期化方法

エクスプローラは、IDEのExplorerManagerに登録する必要があります。登録すると、エクスプローラがノード・クラスまたはノードとビューアのペアに関連付けられます。

ユーザーがノードについてビューアを選択するとき、IDEは、ノードとビューアに最適なエクスプローラのリクエストをExplorerManagerに送ります。ビューアは、そのノードとビューアについて登録されているものか、ノードのみについて登録されているものです。該当する登録が行われていない場合、ExplorerManagerは、リクエストのノード・クラスに互換されるノード・クラスに登録されているビューアを見つけようとします。

登録処理は、例2-33に示すようにアドイン・クラスのinitializeメソッドで実行する必要があります。このコード例は、Extension SDKに含まれるサンプル・プロジェクトの1つ、StructurePaneプロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

例2-33 登録処理

public void initialize()
{
  Class editorClass = PropFileEditor.class;
  ExplorerManager.getExplorerManager().register(PropFileSourceNode.class, 
                     PropFileExplorer.class, 
                     PropFileEditor.class );
}

2.4.7.3 構造エクスプローラ要素モデルの作成方法

エクスプローラの要素モデルは、所定のドキュメントのデータを視覚的に示す構造表現です。

各モデルは、Elementの1つ以上のインスタンスで構成されます。通常、モデルはエクスプローラ内でツリーとしてレンダリングされますが、これは必須ではありません。要素モデルはビジュアル・オブジェクトではありませんが、グラフィカル・コントロールを使用して視覚的にレンダリングされます。たとえば、javax.swing.JTreeによってレンダリングされるjavax.swing.tree.TreeModelです。

エクスプローラの実装により、そのモデルと、さらにはそのモデルの視覚表現が、任意の方法でドキュメントのデータに加えられた変更を確実に受け取ります(たとえば、ソース・エディタ、プロパティ・インスペクタでは、JDeveloperの外部でドキュメントを編集します)。これはJDeveloperのイベント・メッセージング・メカニズムを使用して実現されます。

2.4.7.4 構造エクスプローラの更新方法

関連するドキュメントのコンテンツが変更されるときは常に、状態を更新できるように構造エクスプローラが通知を受ける必要があります。通知メカニズムを使用して、ビューアでの変更内容に関する情報をエクスプローラに送信します。構造エクスプローラは、updateメソッドを提供するObserverを実装して、ビューアのドキュメントを表すオブジェクトに登録する必要があります。

2.4.8 新しいコンポーネント・パレット・ページの追加方法

コンポーネント・マネージャを使用すると、個々のページをコンポーネント・パレットに追加できます。拡張機能のユーザーは、一貫性のあるユーザー・インタフェースの構築や、他の標準化されたタスクの実行のために、これらのページを利用できます。コンポーネント・パレットでは、コンポーネントのページが提供されます。ユーザーはここからコンポーネントを選択して、拡張機能を使用して構築しているコンテンツまたは拡張機能のために構築しているコンテンツに追加できます。

コンポーネント・ページの作成と管理の機能は、PaletteManagerインスタンスのメソッドを使用して、プログラミングで実行することもできます。詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスPaletteManagerに関する項を参照してください。

この項では、静的および動的コンポーネントをコンポーネント・パレットに宣言的に追加する例、追加のヘルプを提供する例、個々のコンポーネント・ページ内でユーザーが特定のアイテムを検索する機能を補強する例を示します。

2.4.8.1 コンポーネント・パレット・ページについて

コンポーネント・パレットでは、ユーザーは、よく使用されるデータ構造をJDeveloperのプロジェクト、アプリケーションまたは他のコンテンツに追加できます。コンポーネント・パレットではページが提供されます。ユーザーはここから個別のコンポーネントを選択して、拡張機能を使用して構築しているコンテンツまたは拡張機能のために構築しているコンテンツに追加できます。具体的なコンポーネントには、著作権宣言文のように単純なものもあれば、リモート・リポジトリに接続するための設定のように複雑なものもあります。さらに、JDeveloperでは、所定のパレット・ページから使用できるコンポーネントは、選択されているファイルのタイプによって異なります。たとえば、HTMLファイルを編集している場合、コンポーネント・パレットのページには、アンカー、電子メール・リンク、他のよく使用されるHTMLコンポーネントなど、使用可能なHTMLコンポーネントのリストが表示されます。Javaページを編集している場合は、これとはまったく異なるコンポーネントが表示されます。

コンポーネント・パレットを表示するには、「表示」「コンポーネント・パレット」を選択します。

拡張機能開発者のレベルでは、コンポーネント・マネージャは、ページを宣言的にコンポーネント・パレットに追加する方法を提供します。これにより、拡張機能のユーザーが特定のコンポーネントを使用できるようになります。そのようなページで、提供を予定している標準コンポーネント(静的と動的の両方)をすべて表示できます。たとえば、一貫性のあるユーザー・インタフェースのユーザーによる構築を支援するために、またはJDeveloperのパレット・マネージャで管理されるコンポーネントを利用する標準化タスクを実行するために、コンポーネント・マネージャを使用して、コンポーネントの1つ以上のページをコンポーネント・パレットに追加できます。

拡張機能を開発するとき、コンポーネント・ページ、静的コンポーネントと動的コンポーネントおよびそれらのサポートを、拡張機能に宣言によって組み込むことができます。次の操作を実行できます。

  • コンポーネント・パレット・ページで静的コンポーネントを宣言します。

  • 動的コンポーネントを宣言し、コンポーネント・パレット・ページがロードされたときに組み込みます。

  • ユーザーがコンポーネントを使用できるようにします。

  • ユーザーがコンポーネントを簡単に検索できるようにします。

このような宣言ツールを使用すると、拡張機能ユーザーが、よく使用される要素を拡張機能を使用してユーザー自身のプロジェクトやアプリケーションに追加できます。さらに、ヘルプや検索の機能により、ユーザーがそのようなパレット・コンポーネントを簡単に見つけて適用できるようになります。

パレット・マネージャを使用すると、コンポーネント・ページをプログラミングで追加することもできます。

2.4.8.2 コンポーネント・ページの静的コンテンツを宣言する方法

多くのケースで、拡張機能のパレットのコンテンツは変化しません。ソース・リポジトリに対する一連の接続パラメータまたは拡張機能での使用場所にかかわらず変化しない他の固定データです。このようなケースでは、例2-34に示すようにpalette-hookを使用して拡張機能マニフェスト・ファイルに静的コンテンツを定義できます。

コンポーネント・パレットの形状は、単純な4層の分類として定義されます。

  • 最も上のレベルはページです。Swing、AWT、ADF Swing、ADF Faces、Java Server Faces (JSF)、JavaServer Pages (JSP)など、テクノロジに応じてコンポーネントをグループ化します。UIでページが表示されるのは、「コンポーネント・パレット」ウィンドウの最も上にあるコンボ・ボックスの選択肢です。

  • 各ページにはグループがあります。グループには少数の機能カテゴリが含まれます。たとえば、ページSwingには、Common ControlsとLayoutというグループが含まれることがあります。グループは、「コンポーネント・パレット」ウィンドウのドッキング可能ウィンドウです。

  • 各グループにはセクションがあります。セクションは、共通のコンポーネントをまとめて、メニュー・セパレータに似た区切り線をUIに表示し、コンポーネントを名前のアルファベット順に並べます。

  • 最後は、各セクションにアイテム(またはコンポーネント)があります。アイテムは、コンポーネント・パレットにコンポーネントを表示するための属性(たとえば、名前、説明、アイコンなど)で構成されます。

例2-34は、1つのコンポーネントがあるパレット・ページを宣言する方法を示します。

例2-34 1つのコンポーネントを含むパレット・ページの宣言

<?xml version="1.0" encoding="windows-1252" ?>
<extension xmlns="http://jcp.org/jsr/198/extension-manifest"
           id="oracle.ide.samples.pageprovider"
           version="1.0"
           esdk-version="1.0">
  <name>Component Palette Page Provider Sample</name>
  <owner>Oracle</owner>
  <dependencies>
    <import>oracle.ide.palette2</import>
 </dependencies>
 <hooks>
   <palette-hook xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">
     <page>
      <name>My Sample Components</name>
      <pageId>SampleStatic</pageId>
      <showForTypes>
        <type>java</type>
      </showForTypes>
      <technologyScopes>
         <technologyScope>Java</technologyScope>
         <technologyScope>JavaBeans</technologyScope>
      </technologyScopes>
      <type>java</type>
      <group>
       <name>Components</name>
        <groupId>SampleStatic-Components</groupId>
         <showForTypes>
           <type>java</type>
         </showForTypes>
         <technologyScopes>
           <technologyScope>Java</technologyScope>
           <technologyScope>JavaBeans</technologyScope>
         </technologyScopes>
         <type>java</type>
         <section>
            <sectionId>SampleStatic-Components-Section1</sectionId>
            <name/>
            <item>
               (name>Table</name>
               <description>Sample Table</description>
               <icon>/oracle/ide/samples/pageprovider/table.png</icon>
               <info/>
               <type>JavaBean</type>
               <itemId>SampleStatic-Components-Section1-Item1</itemId>
               <technologyScopes>
                 <technologyScope>Java</technologyScope>
                 <technologyScope>JavaBeans</technologyScope>
               </technologyScopes>
             </item>
           </section>
         </group>
      </page>
   </palette-hook>
  </hooks>
</extension>

2.4.8.3 パレット・ページの動的コンポーネントを宣言する方法

ロードする前にコンポーネント・パレットのコンテンツを識別できないことがあります。コンポーネント・パレットのコンテンツを事前に定義できない場合は、パレット・ページ・プロバイダを定義する必要があります。この方法によりクライアント開発の柔軟性が高くなります。つまり、コンポーネント・パレットが表示コンテキストをプロバイダに提供すると、プロバイダが、コンポーネント・パレットに表示するコンポーネントを返します。

パレット・ページ・プロバイダはクライアント開発者が公開済パレットAPIを使用して開発します。ページ・プロバイダ・クラス名は、拡張マニフェスト・ファイルでコンポーネント・パレットに提供されます。

パレットAPIの詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスoracle.ide.palette2に関する項を参照してください。

拡張機能マニフェストのエントリはパレットフックのelement pageProviderを使用します。

例2-35は、パレット・ページ・プロバイダのクラス名を指定する拡張機能マニフェストです。

例2-35 パレット・ページ・プロバイダのクラス名を指定する拡張機能マニフェスト

<?xml version="1.0" encoding="windows-1252" ?>
<extension xmlns="http://jcp.org/jsr/198/extension-manifest"
           id="oracle.ide.samples.pageprovider"
           version="1.0"
           esdk-version="1.0">
  <name>Component Palette Page Provider Sample</name>
  <owner>Oracle</owner>
  <dependencies>
    <import>oracle.ide.palette2
  </dependencies>
  <hooks>
    <palette-hook xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">
      <pageProvider>
        <providerClassName>oracle.ide.samples.pageprovider.SamplePageProvider             </providerClassName>
      </pageProvider>
    </palette-hook>
  </hooks>
</extension>

例2-36は、パレット・ページをコンポーネント・パレットに追加するパレット・ページ・プロバイダです。このクラスはPalettePageProviderを拡張します。

extension.xmlの要素pageProvider.providerClassNameは、このクラスをページ・プロバイダとしてコンポーネント・パレットに登録します。コンポーネント・パレットは、現在のコンテキストでcreatePalettePages()を呼び出します。

PalettePagesを拡張するクラスSamplePagesは、現在のコンテキストで構成されます。つまり、このクラスがパレット・ページのリストを編成するために十分な情報を得るのは、getPages()がコンポーネント・パレットから呼び出されるときです。

例2-36 パレット・ページ・プロバイダ

package oracle.ide.samples.pageprovider;
SamplePageProvider.java
import oracle.ide.Context;
import oracle.ide.palette2.PalettePageProvider;
import oracle.ide.palette2.PalettePages;
/**
 * SamplePageProvider
 * <p>
 * This is an example of a PalettePageProvider that adds a palette page to the
 * Component Palette(CP).  As required this class extends PalettePageProvider.
 * </p>
 * <p>
 * Element pageProvider.providerClassName in extension.xml registers
 * this class with the CP as a page provider.
 * </p>
 * <p>
 * The CP will call method createPalettePages() with the current Context. 
 * Class SamplePages which extends PalettePages is constructed with the current context.
 * Using the current context SamplePages should have sufficient information to
 * assemble a list of palette pages when method getPages() is called by the CP.
 * </p>
 * 
 * @see SamplePages
 * @see SamplePalettePage
 * @see SamplePaletteGroup
 * @see SamplePaletteItem
 */
public class SamplePageProvider extends PalettePageProvider  {
   /**
     * Default constructor.
     * 
     */
    public SamplePageProvider() {
    }
    /**
     * Override the default, returns SamplePages if context applies.
     * 
     * @param context
     * @return PalettePages if context is relevant, otherwise null.
     */
    public PalettePages createPalettePages(Context context) {
        if ( checkRelevantContext( context ) )  {
          SamplePages pages = SamplePages.getInstance();
          pages.initialize(context);
          return pages;
        }
        else
          return null; // no pages to provide for this context.
    }
}

例2-37SamplePages.javaの例を示します。このクラスは、pageTypejavaの場合に、1つのアイテムと1つのセクションを含む1つのグループでパレット・ページを作成します。必要に応じてこのクラスはPalettePagesを拡張します。

例2-37 SamplePages.java

package oracle.ide.samples.pageprovider;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import oracle.ide.Context;
import oracle.ide.net.URLFileSystem;
import oracle.ide.palette2.DefaultPaletteSection;
import oracle.ide.palette2.PaletteItem;
import oracle.ide.palette2.PalettePage;
import oracle.ide.palette2.PalettePages;
import oracle.ide.palette2.PalettePagesListener;
/**
 * SamplePages
 * <p>
 * This class creates a palette page with a single group that contains a
 * single section that contains a single item when the pageType is "java".
 * As required this class extends PalettePages.
 * </p>
 * <p>
 * TODO: palettePagesListener
 * </p>
 */
public class SamplePages extends PalettePages {
    private final static String SAMPLEPROVIDER_ID =SamplePageProvider.class.getName();
     /**
     * Singleton
     */
    private static SamplePages _singleton = new SamplePages();
   /**
     * Composite for PalettePagesListener
     */
    protected List<PalettePagesListener> palettePagesListeners;
    /**
    * Composite for PalettePage
    */
    protected List<PalettePage> palettePages;
    // Default constructor
    public SamplePages() {
    }
    /**
     * Returns the singleton instance of SamplePages.
     * @return the singleton instance of SamplePages
     */
    public static SamplePages getInstance() {
       return _singleton; 
    }
    /**
     * Initialize palettePages.
     * @param context
     */
    public void initialize(Context context) {
        URL url = context.getNode().getURL();
        final String pageType = getSuffix( url ); 
    // Only interested in java.
      if( pageType.equals("java") ) {
          if( palettePages != null ) {
             palettePages.clear();
        }
      // create a PalettePage
      SamplePalettePage page = new SamplePalettePage "oracle.ide.samples.pageprovider.SampPage01" // pageId
             , "My Sample Page Component"  // description
             , null                   // icon
             , "java"                 // type
             , "java"                 // showForTypes
             , "Java;JavaBeans" );    // technologyScope
      // create a PaletteGroup  
      SamplePaletteGroup group = new SamplePaletteGroup          "oracle.ide.samples.pageprovider.SampGroup01" // groupId
            , "My Sample Group"         // name
            , "My Sample Group Component"  // description
            , "java" );                // type
      // add group to page
          page.addGroup(group); 
      // create a section. Make the name null since a separator is not neeeded.
       DefaultPaletteSection section = new DefaultPaletteSection "oracle.ide.samples.pageprovider.SampSection01"  // sectionId
      // add section to group
       group.addSection(section); 
      // create an item. TODO: use SampleBean.java
       SamplePaletteItem item = new SamplePaletteItem
("oracle.ide.samples.pageprovider.SampItem01" // itemId , SAMPLEPROVIDER_ID // provider id , "My Sample Bean" // name , "My Sample Bean Description" // description , "/oracle/ide/samples/pageprovider/snapshot.png" // icon , "java"); // type // add item to section section.addItem(item); // add item to section // add page addPage(page); } else { palettePages.clear(); } } /** * getPages */ public Collection<PalettePage> getPages() { return Collections.unmodifiableList(palettePages); } /** * Returns the PaletteItem identified by itemId. providerId is used to * determine whether this item is owned by this page provider. * The provider returns the matching PaletteItem only if the PaletteItem * is within the current context. Null is returned if the PaletteItem is * within the current context or not recognized by this provider. * </p> * @return PaletteItem */ public PaletteItem getItem( String providerId, String itemId ) { if( providerId == null || providerId.length() == 0 || itemId == null || itemId.length() == 0 ) { return null; } PaletteItem paletteItem = null; if( providerId.equals(SAMPLEPROVIDER_ID)) { for( PalettePage palettePage : palettePages ) { SamplePalettePage sampPage = (SamplePalettePage) palettePag paletteItem = sampPage.getItem(itemId); if( paletteItem != null ) { break; } } // end of for } return paletteItem; } /** * addPalettePagesListener public void addPalettePagesListener(PalettePagesListener listener) { if( palettePagesListeners == null ) { palettePagesListeners = new ArrayList<PalettePagesListener>(); } palettePagesListeners.add(listener) } /* * add pages to palettePages. /* private void addPage( SamplePalettePage sampPage ) { if (palettePages == null ) { palettePages = new ArrayList<PalettePage>(); } palettePages.add(sampPage); } */ * Return suffix * @param title Title of EditorFrame */ private String getSuffix( URL url ) ( final String suffix = URLFileSystem.getSuffix( url ); int period = suffix.lastIndexOf( "." ); if( period != -1 ) { // Is a selected file return suffix.substring( period + 1 ); } return ""; } }

例2-38SamplePalettePage.javaの例を示します。

例2-38 SamplePalettePage.java

package oracle.ide.samples.pageprovider;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import oracle.ide.palette2.DefaultPalettePage;
/**
 * SamplePalettePage
 * <p>
 * This class extends DefaultPalettePage. DefaultPalettePage comprises 
 * the attributes and methods that this example requires. A constructor is added
 * to accomodate the data values for this sample.
 * </p>
 */
public class SamplePalettePage extends DefaultPalettePage {
    public SamplePalettePage(String pageId, String pageName, String pageDescription
             , String pageIcon, String pageType, String pageShowForTypes
             , String pageTechnologyScope) {
      setName( pageName );
      setDescription( pageDescription );
      setIcon( pageIcon );
      setData(PAGE_PAGEID, pageId );
      setData(PAGE_TYPE, pageType);
     // make delimited string into a List of strings
     List<String> showForTypes = new ArrayList<String>();
     final StringTokenizer tknTypes = new StringTokenizer pageShowForTypes, ";"    ); //NOTRANS
      while( tknTypes.hasMoreTokens() )
      {
         String token = (String)tknScopes.nextToken();
         technologyScope.add(token);
      }
    setData(PAGE_TECHNOLOGYSCOPES, technologyScope ); 
  }
}

例2-39 SamplePaletteGroup.java

package oracle.ide.samples.pageprovider;
import oracle.ide.palette2.DefaultPaletteGroup;
/**
 * SamplePaletteGroup
 * <p>
 * This sample extends DefaultPaletteGroup.DefaultPaletteGroup comprises the
 * attributes and methods that this example requires. A constructor is added to
 * accomodate the data values for this sample.
 * </p>
 *
 */
public class SamplePaletteGroup extends DefaultPaletteGroup {
    public SamplePaletteGroup(String groupId, String groupName, String groupDescription , String groupType) {
       setName( groupName );
       setDescription( groupDescription );
       setData(GROUP_GROUPID, groupId);
       setData(GROUP_TYPE, groupType);
     }
}

例2-40SamplePaletteItem.javaの例を示します。

例2-40 SamplePalette Item.java

package oracle.ide.samples.pageprovider;
import oracle.ide.palette2.DefaultPaletteItem;
 * SamplePaletteItem
 * <p>
 * This example extends DefaultPaletteItem. DefaultPaletteItem comprises 
 * the attributes  and methods that this example requires. A constructor is
 * added to accommodate the data values for this sample.
 * </p>
 *
 */
public class SamplePaletteItem extends DefaultPaletteItem
 {
   public SamplePaletteItem(String itemId, String itemProviderId, String itemName, String itemDescription 
 , String itemIcon, String itemType) {
    setName( itemName );
    setDescription( itemDescription ); 
    setIcon( itemIcon );
    setItemId( itemId );
    setProviderId(itemProviderId);
    setData(ITEM_TYPE, itemType);
  )
)

2.4.8.4 コンポーネント・ページにヘルプを提供する方法

コンポーネントにヘルプを用意する方法は2つあります。

  • パレット・アイテムの説明は、コンポーネントのツール・ヒントとして表示されます。ユーザーがコンポーネント・パレットのコンポーネント上にマウスを重ねると表示されます。説明を複数の行に分けるには改行文字\nを使用します。

  • ユーザーはパレット・コンポーネントを右クリックして、コンテキスト・メニューからヘルプを選択できます。「ヘルプ」オプションがコンポーネントのコンテキスト・メニューに表示されるのは、そのコンポーネントにHelpableがある場合のみです。helpableクラスをインスタンス化できない場合は、その文字列がIDEヘルプ・システムのhelpIdとして使用されます。

例2-41は、この方法を示します。

例2-41 コンポーネント・ページのヘルプの設定

  <item>
    <name>Table</name>
    <description>Sample Table</description>
    <icon>/oracle/ide/samples/pageprovider/table.png</icon
    <info/>
    <type>JavaBean</type>
    <itemId>SampleStatic-Components-Section1-Item1</itemId>
    <technologyScopes>
      <technologyScopes>
      <technologyScope>JavaBeans</technologyScope> 
     </technologyScopes>
    <helpable>oracle.samples.SampleHelpable</helpable>
 </item>

または、例2-42のようにhelpidを直接指定できます。

例2-42 helpidの直接指定

 <item>
   <name>Table</name>
   <description>Sample Table</description>
   <icon>/oracle/ide/samples/pageprovider/table.png</icon> 
   <info/>
   <type>JavaBean</type>
   <itemId>SampleStatic-Components-Section1-Item1</itemId>
   <technologyScopes>
     <technologyScope>Java</technologyScope>
     <technologyScope>JavaBeans</technologyScope>
   </technologyScopes>
  <helpable>help_topic_id</helpable>
</item>

これと同じ処理をプログラミングで行うには、getHelpable()PaletteItemインタフェースに実装します。

2.4.8.5 パレット・アイテムの検索を拡張する方法

デフォルトでは、ユーザーがコンポーネント・パレットの検索ボックスに語または語句を入力すると、JDeveloperは、名前か説明に検索文字列が含まれるアイテムを検索します。

ユーザーに対して表示されない他の埋込みデータ(タグ属性など)をコンポーネント・パレットによって検索するには、アイテムと一緒にsearchTextContextを指定できます。

例2-43に示すように、コンポーネント・パレットの拡張機能フックでこれを宣言的に指定できます。

例2-43 コンポーネント・パレットの拡張機能フック

  <item>
    <name>Table</name>
    <description>Sample Table</description> 
    <icon>/oracle/ide/samples/pageprovider/table.png</icon>
    <info/>
    <type>JavaBean</type>
    <itemId>SampleStatic-Components-Section1-Item1</itemId>
    <technologyScopes>
       <technologyScope>Java</technologyScope>
       <technologyScope>JavaBeans</technologyScope>
    </technologyScopes>
    <searchTextContext>MySearchContext</searchTextContext>
 </item>

例2-44に示すように、SearchContextが次にPaletteSearchインタフェースを実装します。

例2-44 PaletteSearch

class MySearchContext implements PaletteSearch
 {
   boolean searchItemContainsText(String itemId, String searchString)
   {
     boolean found = false; 
    // Search internal data
    return found;
   }
}

2.4.9 プリファレンスについて

ユーザーが自らのプリファレンスを変更および保存できるようにすると、JDeveloperの拡張機能を使用するプロセスに生産性と柔軟性が付加されます。

2.4.9.1 プリファレンスの実装方法

製品レベルのプリファレンスはPreferencesクラスで管理します。詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスPreferencesに関する項を参照してください。

プリファレンスを保存するために使用されるデータ構造はHashStructureです。これもOracle Fusion Middleware Oracle Extension SDK Java APIリファレンスで説明されています。Preferencesクラスは、1つの拡張機能ディレクトリ(製品全体を表す拡張機能)のみにプリファレンスを保存します。たとえば、JDeveloperでは製品ディレクトリの形式はjdev-user-directory/system/oracle.JDeveloper.11.2.0.x.yとなります。11.2.0はバージョン番号、x.yはビルド番号です。プリファレンスを含むファイルの名前はpreferences.xmlです。

一連の新しいプリファレンスをIDEに組み込むには、次の手順を実行します。

  1. プリファレンスを保存するためのデータ・モデルを実装します。詳細は、2.4.9.2項「データ・モデルの実装方法」を参照してください。

  2. プリファレンスを保存するためのデータ・モデルを実装します。詳細は、2.4.9.3項「UIパネルの実装方法」を参照してください。

  3. UIパネルを「プリファレンス」ダイアログに登録します。これは「ツール」メニューから選択できます。詳細は、2.4.9.4項「UIパネルの登録方法」を参照してください。

  4. プリファレンス・モデルを取得します。詳細は、2.4.9.5項「プリファレンス・モデルの取得方法」を参照してください。

  5. プリファレンスの変更をリスニングして、変更を保存できるようにします。詳細は、2.4.9.6項「変更のリスニング方法」を参照してください。

2.4.9.2 データ・モデルの実装方法

プリファレンス・データを表すクラスは、HashStructureAdapterのサブクラスである必要があります。詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスHashStructureAdapterに関する項を参照してください。

例2-45には、詳細のコメントが付いた典型的な実装パターンが含まれます。

例2-45 実装パターン

package oracle.killerapp.coolfeature;
import oracle.javatools.data.HashStructure;
import oracle.javatools.data.;
import oracle.javatools.data.PropertyStorage;
// Start with class being final.  You can always remove final if subclassing ever
// proves useful.  In many cases, subclassing is actually unnecessary and may get
// you into an instanceof/typecast mess.  Consider defining a separate (not
// subclass) adapter class instead.
public final class CoolFeaturePrefs extends 
{
// The DATA_KEY should be a hard-coded String to guarantee that its value stays
 // constant across releases.  Specifically, do NOT  
 // constant across releases.  Specifically, do NOT use CoolFeaturePrefs.class.getName().
 // The reason is that if CoolFeaturePrefs is ever renamed or moved,
 // CoolFeaturePrefs.class.getName() will cause the DATA_KEY String to        change,which
 // introduces a preferences migration issue (since this key is used in the persisted
 // XML) that will require more code and testing to accommodate and open up your code to
 // annoying little bugs.  Unknowing developers have been trapped by this problem before,
 // so eliminate this cause of bugs by using a hard-coded String for DATA_KEY.
 //
 // By convention, DATA_KEY should be the fully qualified class name of the
 // .  This helps ensure against name collisions. This also makes it
 // easier to identify what piece of code is responsible for a preference when you're
 // looking at the XML in the product-preferences.xml file.  Of course, that only works
 // as long as the adapter class itself is never renamed or moved, so avoid renaming or
 // moving this class once it's been released in production. 
  private static final String DATA_KEY = "oracle.killerapp.coolfeature.CoolFeaturePrefs";
 // Private constructor enforces use of the public factory method below.
 private CoolFeaturePrefs(HashStructure hash)
{
   super(hash);
}

// Factory method should take a PropertyStorage (instead of HashStructure directly).
// This decouples the origin of the HashStructure and allows the future possibility 
// of resolving preferences through multiple layers of HashStructure. Classes/methods
// that currently implement/return PropertyStorage:
// - oracle.ide.config.Preferences
// - oracle.ide.model.Project
// - oracle.ide.model.Workspace
// - oracle.ide.panels.TraversableContext.getPropertyStorage()
 public static CoolFeaturePrefs getInstance(PropertyStorage prefs)
{
  // findOrCreate makes sure the HashStructure is not null.  If it is null, a
  // new empty HashStructure is created and the default property values will
  // be determined by the getters below.
   return new CoolFeaturePrefs(findOrCreate(prefs, DATA_KEY));
}
-----------------------
// Like DATA_KEY, all other keys also appear in the XML, so they should not be
// changed once released into production, or else you'll have some migration  issues
// to fix
private static final String MAX_NUMBER_OF_THINGIES = "maxNumberOfThingies";  //NOTRANS
private static final int DEFAULT_MAX_NUMBER_OF_THINGIES = 17;

public int getMaxNumberOfThingies()
{
  // Specify default in the getInt call to take advantage of HashStructure's
  // placeholder mechanism.  See HashStructure javadoc for details on placeholders.
  return _hash.getInt(MAX_NUMBER_OF_THINGIES, DEFAULT_MAX_NUMBER_OF_THINGIES);
}
 public void setMaxNumberOfThingies(int maxNumberOfThingies)
{
 _hash.putInt(MAX_NUMBER_OF_THINGIES, maxNumberOfThingies);
}
//---------------------------------------------
 private static final String THINGIE_NAME = "thingieName";   //NOTRANS
 private static final String DEFAULT_THINGIE_NAME = "widget";  //NOTRANS
 public String getThingieName()
{
 return _hash.getString(THINGIE_NAME, DEFAULT_THINGIE_NAME);
}
 public void setThingieName(String thingieName)
{
 return _hash.putString(THINGIE_NAME, thingieName);
}
 // etc..
}

2.4.9.3 UIパネルの実装方法

プリファレンス・パネルを実装するクラスは、DefaultTraversablePanelのサブクラスである必要があります。詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスDefaultTraversablePanelに関する項を参照してください。

例2-46は典型的な実装パターンを示します。

例2-46 DefaultTraversalPanel

package oracle.killerapp.coolfeature;
import oracle.ide.panels.DefaultTraversablePanel;
// You should keep the panel class package-private and final unless there
// is a good reason to open it up.  In general, preferences panels are not
// supposed to be part of a published API, so the class modifiers should
// enforce that.
final class CoolFeaturePrefsPanel extends DefaultTraversablePanel
{
 // But, the no-arg constructor still needs to be public.
 public CoolFeaturePrefsPanel()
  {
   // Layout the controls on this panel.
  }
  public void onEntry(TraversableContext tc)
 {
   final CoolFeaturePrefs prefs = getCoolFeaturePrefs(tc);
  // Load prefs into the panel controls' states.
 }
   public void onExit(TraversableContext tc)
 {
  final CoolFeaturePrefs prefs = getCoolFeaturePrefs(tc);
 // Save the panel controls' states to prefs.
}
  private static CoolFeaturePrefs getCoolFeaturePrefs(TraversableContext tc)
{
  // If you've implemented CoolFeaturePrefs according to the typica
  // implementation pattern given above, this is how you attach the
  // adapter class to the defensive copy of the preferences being
  // edited by the Tools->Preferences dialog.
  return CoolFeaturePrefs.getInstance(tc.getPropertyStorage());
 }
}

2.4.9.4 UIパネルの登録方法

例2-47は、拡張機能マニフェスト(extension.xml)のXMLフラグメントです。これは、前に説明したパネルを「プリファレンス」ダイアログ(「ツール」メニューから選択可能)に登録します。

例2-47 拡張機能マニフェストのXMLフラグメント

<extension ...>
  <hooks>
    <settings-ui-hook xmlns="http://xmlns.oracle.com/ide/extension">
      <page id="CoolFeaturePrefs" parent-idref="/preferences">
         <label>${SOME_RES_KEY}</label>
           <traversable-class>oracle.killerapp.coolfeature.CoolFeaturePrefsPanel                </traversable-class> 
         </page> 
     </settings-ui-hook> 
   </hooks>
</extension>

2.4.9.5 プリファレンス・モデルの取得方法

プリファレンス・ダイアログ・コード以外のコードでプリファレンスの読取りと書込みを行う必要がある場合は、oracle.ide.config.Preferencesクラスを使用してプリファレンスを取得します。この方法はプリファレンス・ページでは使用しないでください。このコードを使用してプリファレンス・オブジェクトに変更を加えると、すぐに有効になります。このため、常に取り消せるようにする必要がある「プリファレンス」ダイアログには適していません。

Preferences p = oracle.ide.config.Preferences.getPreferences();
CoolFeaturePrefs myPrefs = CoolFeaturePrefs.getInstance( p );

2.4.9.6 変更のリスニング方法

プリファレンス・モデル・オブジェクトの基礎になっているハッシュ構造にoracle.javatools.data.StructureChangeListenerをアタッチすると、プリファレンスの変更をリスニングできます。通常、適切な方法は、例2-48に示すようにリスナーをモデル・オブジェクトにアタッチするためのメソッドを公開することです。

例2-48 モデル・オブジェクトにアタッチするメソッドの公開

public final class CoolFeaturePrefs extends 
{
 //...
 public void addStructureChangeListener( StructureChangeListener l )
  {
    _hash.addStructureChangeListener( l );
  }
   public void removeStructureChangeListener( StructureChangeListener l )
  {
    _hash.removeStructureChangeListener( l );
   }
 //...
}

2.4.10 プロジェクト・プロパティについて

プロジェクト・プロパティはプロジェクト・プリファレンスに似ています。2.4.9項「プリファレンスについて」を参照してください。

プロジェクト・プロパティはProjectクラスを使用して管理します。詳細は、Oracle Fusion Middleware Oracle Extension SDK Java APIリファレンスProjectに関する項を参照してください。

2.4.11 変更をUNDO可能にする方法

急速に繰り返されるモデルに基づいてアプリケーション開発を進めるユーザーにとっては、ソリューションからソリューションへと移動する際に、変更を簡単に取り消すための機能が重要です。変更内容をUNDO可能にする機能を追加すると、ユーザーにこの機能が提供されます。

2.4.11.1 テキスト変更をUNDO可能にする方法

拡張機能でなんらかの形式のテキスト入力が使用される場合、たとえば、会社で使用される特定の機能を備えたカスタム・テキスト・エディタを実装する場合、ユーザーは、テキストを入力しているときに行った変更を元に戻せることを期待します。例2-49に示すように、UndoableEditを使用すると、ユーザーがテキストに対して加えた変更が拡張機能による実行または取消しが可能になります。

例2-49 UNDO可能な編集

import javax.swing.undo.UndoableEdit;
import oracle.ide.Context;
import oracle.ide.controller.Command;
import oracle.ide.controller.CommandProcessor;
import oracle.ide.model.TextNode;
import oracle.javatools.buffer.TextBuffer;
public class MyCommand extends Command
{
   private UndoableEdit _undoableEdit;
   public MyCommand(Context context)
  {
    super(-1, Command.NORMAL, "Insert Hello");
    setContext(context);
  }
  public int doit() throws Exception
  {
    if (_undoableEdit == null)
    {
       final TextNode textNode = (TextNode) context.getNode();
       final TextBuffer textBuffer = textNode.acquireTextBuffer();
         textBuffer.beginEdit();
         textBuffer.insert(0, "Hello World".toCharArray());
         _undoableEdit = textBuffer.endEdit();
         textNode.releaseTextBuffer();
      } else
      {
        _undoableEdit.redo();
      }   
      return OK;
      }
      public int undo() throws Exception
      {
       _undoableEdit.undo();
       return OK;
   }
}

2.4.11.2 コマンドをUNDO可能にする方法

UNDO可能な変更をドキュメントに加える場合は、例2-50に示すように、変更内容を実行する方法と元に戻す方法を認識しているコマンドを実装する必要があります。

例2-50 変更内容の実行と取消し

import oracle.ide.controller.Command;
import oracle.ide.Context;
import oracle.ide.model.Node;
public class MyCommand extends Command
{
  public MyCommand(Context context, Node affectedNode)
  {
    super(-1, Command.NORMAL, "My Changes");
   // The context usually already contains the node so this would not be necessary
   final Context contextCopy = new Context(context);
   contextCopy.setNode(affectedNode);
   setContext(contextCopy);
 }
   public int doit() throws Exception
{
    final Node affectedNode = context.getNode();
    // Do the changes to the node here
    return OK;
 }
public int undo() throws Exception
   final Node affectedNode = context.getNode();
   // Undo the changes to the node here
  return OK;
 }
}

2.5 トリガー・フックの定義および使用方法

<trigger-hooks>要素はhttp://xmlns.oracle.com/ide/extensionネームスペースにあります。

<trigger-hooks>要素には次の3つの子要素が含まれます。

IDEで提供されるトリガー・フックを次に示します。

2.5.1 <trigger-hook-handler>の登録方法

独自のトリガー・フックを定義するには、例2-51「トリガー・フック・ハンドラの登録」に示すようにトリガー・フック・ハンドラを登録します。

例2-51 トリガー・フック・ハンドラの登録

<trigger-hooks xmlns="http//xmlns.oracle.com/ide/extension/myExtension">
    <registry>
      <trigger-hook-handler
        tag-name="my-hook"
        handler-class="oracle.ide.extension.HashStructureHook"
        namespace="http://xmlns.oracle.com/ide/extension"
        schema-location="my-hook.xsd"
        register-as-hook="true"/>
    </registry>
  </trigger-hooks>

register-as-hook属性は、拡張機能のフックを<hooks>セクションにも指定できるかどうかを制御します。デフォルト値はfalseです。同じ要素を<hooks>でサポートする意味がある場合だけtrueに設定します。

既存の宣言フックをトリガー・フックにする場合は、現在のフック・ハンドラの登録を解除して、かわりにtrigger-hook-handlerを例2-51「トリガー・フック・ハンドラの登録」に示すように登録し、register-as-hook属性をtrueに設定します。

JDeveloperで使用される以前の標準に対して構築された既存の拡張機能を更新している場合、下位互換性のために、使用したのと同じネームスペースを維持できます。既存のフックが古いjdeveloper-hookの子で、これもトリガー・フックとしてサポートする場合は、属性register-as-jdeveloper-hooktrueに設定します。

コアIDEの外部にある拡張機能は、コアIDEに含まれるhook-handlerクラスを使用する必要があります。次の2つの方法があります。

  • ハンドラ・クラスとしてのoracle.ide.extension.HashStructureHook。トリガー・フックも<hooks>にある場合またはトリガー・フックが条件トリガー・セクションで使用できる場合、HashStructureを監視した後で新しい要素はHashStructureHookによって処理されます。この状況では、コードはHashStructureHookイベントをリスニングする必要があります。

  • DeferredElementVisitorHookを使用すると、フックのXMLデータを処理するためにカスタムElementVisitorクラスを使用できます。したがって、すでにカスタム・フック・ハンドラを作成した場合は、コードを再利用できます。

2.5.2 拡張機能のトリガー・フックを定義する方法

トリガー・フックを使用するには、例2-52「トリガー・フックの定義」のような構文を使用します。

例2-52 トリガー・フックの定義

    <triggers>
      <singleton-provider-hook>
        <singleton base-class="oracle.bali.xml.addin.JDevXmlContextFactory"
                   impl-class="oracle.bali.xml.addin.JDevJavaXmlContextFactory" />
      </singleton-provider-hook>
    </triggers>
  </trigger-hooks>

2.5.3 ExtensionRegistryから解析済情報を取得する方法

要素名にtrigger-hook-handlerを定義すると、そのExtensionHookハンドラ・クラスの1つのインスタンスが作成され、そのトリガー・フックが使用されるときは常に処理します。

ExtensionHookインスタンスを取得するには、ExtensionRegistry.getHook(elementName)を呼び出します。以前のバージョンでのJDeveloper拡張機能開発では、通常の宣言フックのExtensionHookを取得するためにこのAPIが使用されました。処理されたすべてのトリガー・フックと、現在ロードされているすべてのフック・セクションは、ExtensionHookインスタンスの同じバケットに加えられます。

trigger-hook-handlerHashStructureHookの場合、返されるHashStructureHookインスタンスからHashStructureを取得できます。これには、HashStructureでの要素名のすべての使用箇所のXML情報や、HashStructureの異なるセクションがどの拡張機能のものかを示す格納情報が含まれます。HashStructureAdapterサブクラスを使用すると、HashStructureから情報を抽出できます。

2.5.4 ルールおよび条件トリガー・セクションの定義方法

この項では、拡張機能マニフェストでルールや条件を定義する方法について説明します。

2.5.4.1 ルールの定義方法

ルールやルールのタイプが、拡張機能マニフェストextension.xml<trigger-hooks><rules>セクションで定義されます。

rule-typeは、Javaに実装されるルール関数を表し、関数が受け取るパラメータを指定します(メソッドのシグネチャなど)。IDEによって提供される組込みのルール・タイプのセットがあります。

ルールは、パラメータの特定の値を渡す、ルール関数の呼出しを表します。各ルールにはグローバルに一意のIDが付けられ、ルールをサポートするフックからIDで参照されます。

ルール・フレームワークは、IDEモジュールのoracle.ide.extension.rulesです。

ルール・タイプを定義するには、次の項目を指定する必要があります。

  • 参照のためのID

  • oracle.ide.extension.rules.RuleFunctionのサブクラスであり、サポートされるパラメータをリストする実装クラス。パラメータがオプションと必須のどちらかを指定できます。

IDEモジュールのextension.xmlによって、例2-53に示すようにいくつかの組込みルール・タイプが定義されます。

例2-53 組込みルール・タイプ

<trigger-hooks xmlns="http://xmlns.oracle.com/ide/extension">
  <rules>
    <rule-type id="always-enabled" class="oracle.ide.extension.rules.functions.AlwaysEnabled" />
    
    <rule-type id="any-selection-has-attribute" class="oracle.ide.extension.rules.functions.AnySelectionHasAttribute">
      <supported-parameters>
        <param name="element-attribute" required="true"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="context-has-element" class="oracle.ide.extension.rules.functions.ContextHasElement">
      <supported-parameters>
        <param name="element-class" required="true"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="context-has-node" class="oracle.ide.extension.rules.functions.ContextHasNode">
      <supported-parameters>
        <param name="node-class" required="true"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="context-has-project" class="oracle.ide.extension.rules.functions.ContextHasProject" />
   <rule-type id="context-has-view" class="oracle.ide.extension.rules.functions.ContextHasView">
      <supported-parameters>
        <param name="view-class" required="true"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="context-has-workspace" class="oracle.ide.extension.rules.functions.ContextHasWorkspace" />
    <rule-type id="element-has-attribute" class="oracle.ide.extension.rules.functions.ElementHasAttribute">
      <supported-parameters>
        <param name="element-attribute" required="true"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="on-extension-init" class="oracle.ide.extension.rules.functions.ExtensionInitialized">
      <supported-parameters>
        <param name="extension-id" required="true"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="extension-is-enabled" class="oracle.ide.extension.rules.functions.ExtensionEnabled">
      <supported-parameters>
        <param name="extension-id" required="true"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="on-single-selection" class="oracle.ide.extension.rules.functions.SingleSelection">
      <supported-parameters>
        <param name="element-class" required="false"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="on-multiple-selection" class="oracle.ide.extension.rules.functions.MultipleSelection">
      <supported-parameters>
        <param name="element-class" required="false"/>
      </supported-parameters>
    </rule-type>
    <rule-type id="node-is-dirty" class="oracle.ide.extension.rules.functions.NodeIsDirty" />
    <rule-type id="project-has-techscope" class="oracle.ide.extension.rules.functions.ProjectHasTechScope">
      <supported-parameters>
        <!-- Comma-separated list of technology keys -->
        <param name="technology-keys" required="true" />
        <!-- Specify 'all' or 'any' for match, to specify if all keys should exist or any one key suffices -->
        <param name="match" required="false" />
      </supported-parameters>
    </rule-type>
  </rules>
</trigger-hooks>

<rule-type>のハンドラは、<rule>の解析中に使用するために、またルールの実行時評価のためにこの情報を保存します。ルール・クラスは、ルールを評価する必要が生じる最後の瞬間までロードされません。

独自のルール・タイプを導入できますが、いくつかの制限事項を理解することが重要です。

  • IDEは、完全にロードされていない拡張機能からはルール・タイプ・クラスをロードしません。

  • ルール評価は、拡張期のロードをトリガーしません。

拡張機能E1がR1-rule-typeとT1-trigger-hookを導入するケースについて考えてみます。T1-trigger-hookではルールの使用がサポートされるとします。拡張機能E1が完全にロードされたときだけ、T1-trigger-hookのデータを使用して、そこで参照されているルールを評価します。したがって、R1-rule-typeのルールをT1-trigger-hookで使用するのは適切です。

ただし、R1-rule-typeのルールをide-coreトリガー・フック(galleryなど)で使用しようとすると、そのルールが評価されるときに拡張機能E1がロードされている保証はありません。その場合にはエラーが記録されます。

2.5.4.2 単純なルールの定義方法

ルールは、<trigger-hooks><rules>セクションで定義されます。ルール・タイプを定義するには、参照のためのID、ルール・タイプを特定するタイプ属性およびルール・タイプで必要なパラメータの値を指定する必要があります。

例2-54に示すように、<rule>のハンドラによって次の項目が確認されます。

  • IDが一意かどうか。

  • タイプの値が、extension.xml(または依存関係のextension.xml)に定義されたルール・タイプのIDと一致するか。

  • 必須パラメータのすべてに値があるか。

  • 指定されているすべてのパラメータが、ルール・タイプに定義されたパラメータ名と一致するか。

例2-54 単純なルール

  <rules>
    <rule id="context-has-text-node" type="context-has-node">
      <parameters>
        <param name="node-class" value="oracle.ide.model.TextNode" />
      </parameters>
    </rule>
    <rule id="context-has-source-node-1" type="context-has-node">
      <parameters>
        <param name="node-class" value="org.product.SourceNode1" />
      </parameters>
    </rule>
   <rule id="context-has-source-node-2" type="context-has-node">
      <parameters>
        <param name="node-class" value="org.product.SourceNode2" />
      </parameters>
    </rule> 
    <rule id="on-xxx-init" type="on-extension-init">
      <parameters>
        <param name="extension-id" value="org.product.MyXxxExtension"/>
      </parameters>
    </rule>
   <rule id="on-yyy-init" type="on-extension-init">
      <parameters>
        <param name="extension-id" value="org.product.MyYyyExtension"/>
      </parameters>
   </rule>
   <rule id="on-text-node-single-selection" type="on-single-selection">
     <parameters>
       <param name="element-class" value="oracle.ide.model.TextNode" />
     </parameters>
   </rule>
 </rules>
</trigger-hooks>

2.5.4.3 暗黙に使用できるルール

必須パラメータのない各ルール・タイプは、自動的にルールとして登録されます(ルール・タイプIDがルールIDとして使用されます)。たとえば、context-has-projectnode-is-dirtyは、必須パラメータのないルール・タイプの例です。これらのIDは、ルールが参照される場所であればどこでも使用できます。<rule>extension.xmlに明示的に追加する必要はありません。

2.5.4.4 ルールのガイドライン

ルールを含めるextension.xmlとルール名を注意深く選択することにより、IDの重複を回避して、最大限に再利用できます。

経験則としては、パラメータとして渡すクラス名を含む拡張機能を見つけて、ルールをその拡張機能のextension.xmlに含めます。たとえば、例2-54context-has-source-node-1ルールを定義するには、SourceNode1クラスを含む拡張機能を探して、ニーズを満たす既存のルールがあるかどうかを確認します。存在しない場合は、ルールをそのextension.xmlに追加します。

IDは、次のように、わかりやすく、ルール・タイプIDに基づくように設定する必要があります。

  • context-has-xxx-node

  • context-has-xxx-node

  • context-has-xxx-element

  • context-has-xxx-view

  • on-xxx-single-selection

  • on-xxx-init

2.5.4.5 複合ルールの定義方法

他のルールとブール演算子で構成されるルールを定義できます。サポートされるブール演算子は、<or>、<and>および<not>です。<and>および<or>ブール演算子要素は、任意の数の子を受け入れます。<not>要素は1つの子を受け入れます。ブール演算子の各子は、例2-55に示すようにルールの参照か別のブール演算子である必要があります。

例2-55 複合ルール

<trigger-hooks xmlns="http://xmlns.oracle.com/ide/extension">
  <rules>
    <composite-rule id="context-has-xxx-or-yyy-node">
      <or>
        <rule-reference id="context-has-jsp-node" />
        <rule-reference id="context-has-zzz-node" />
      </or>
    </composite-rule>
   <composite-rule id="on-aaa-and-bbb-init">
      <and>
        <rule-reference id="on-aaa-init" />
        <rule-reference id="on-bbb-init /">
      </and>
    </composite-rule> 
   <composite-rule id="more-complicated-composite-rule">
      <or>
        <rule-reference id="rule-a" />
        <and>
          <rule-reference id="rule-b" />
          <not>
            <rule-reference id="rule-c" />
          </not>
       </and>
     </or>
    </composite-rule>
  </rules>
</trigger-hooks>

2.5.4.6 フックからルールを参照する方法

すべてのルールはextension.xml<rules>セクションに定義され、ルールをサポートするフックでIDによって参照されます。

トリガー・フックでルールをサポートする場合に必要となるのは、ルールのIDを受け入れるための構文の変更だけです。規約では、ルールIDをruleという名前の属性に指定する必要があります。これは、例2-56例2-57および例2-58で説明しています。

例2-56 ギャラリ・アイテム

  ...
  <item rule="always-enabled">
    <name>oracle.jdeveloper.template.wizard.TemplateWizard</name>
    <id>Application</id>
    <description>${NEW_APPLICATION_TEMPLATE_GALLERY_ITEM}</description>
    <help>${MANAGE_TEMPLATES_WIZARD_DESCRIPTION}</help>
    <folder>Applications</folder>
    <technologyKey>General</technologyKey>
    <icon>/oracle/javatools/icons/apptemplate.jpg</icon>
  </item>
</gallery>

例2-57 コントローラ

<controllers xmlns="http://xmlns.oracle.com/ide/extension">
  <controller class="com.random.FooController">
    <update-rules>
      <update-rule rule="on-text-node-selected">
        <action id="some.action.id1">
          <label>Perform Action on {0}</label>
          <label-param>${node.name}</label-param>
        </action>
      </update-rule>
    </update-rules>
  </controller>
  <controller class="com.random.BarController">
    <update-rules>
      <update-rule rule="on-jsp-node-selected">
        <action id="some.action.id3" />
        <action id="some.action.id4" />
        <action id="some.action.id5" />
      </update-rule>
    </update-rules>
  </controller>
</controllers>

例2-58 コンテキスト・メニュー

<context-menu-hook rule="context-has-xxx-or-yyy-node">
  <site ref-id="navigator"/>
  <menu>
    <section xmlns="http://jcp.org/jsr/198/extension-manifest" id="MY_CUSTOM_MENU_SECTION_ONE" weight="1.0">
      <item action-ref="myTriggerActionId" weight="1.0" />
      <item action-ref="myOtherTriggerActionId" weight="2.0" />
     </section>
   </menu>
 </context-menu-hook>

2.5.4.7 ルール参照の評価方法とルールの評価方法

トリガー・フックを実装しているときに、フックでルールをサポートする場合は、ルールのIDを受け入れるルール属性をXML構文に追加します(ルール属性のXMLスキーマ・タイプはxsd:NCNameにする必要があります)。

フック・ハンドラの実装で、ルール属性の値を取得するときは、次のメソッドをRuleEngineで呼び出して、ルール参照の解析時評価を実行します。

public static boolean validateRuleReference(String id, Set<String> expectedRuleTypes, ElementContext referenceContext

このメソッドは、そのIDの既知のルールが(同一extension.xmlまたは依存関係に)存在することを検証します。また、オプションのexpectedRuleTypesを渡すと、ルールがそれらのタイプの1つかどうかを検証します(複合ルールの場合は、すべてのパーティクルが予期されるタイプであることが必要です)。オプションのreferenceContextパラメータが渡されると、すべての問題はElementContextのロガーでログをとります。

ルールを実際に評価するときには、ルール・エンジンでルールIDをIDEコンテキスト(ある場合)と一緒にメソッドに渡します。ルール・エンジンは、ルール・タイプ・クラスをインスタンス化し、ルール定義のパラメータをコンテキストと一緒に渡して、trueまたはfalseを返します。

public static boolean evaluateRule(String id, Context ideContext)

最初のメソッドは、ルール評価時に発生する例外のログをとり、ルールの評価に問題がある場合はfalseを返します。後のメソッドは、すべての例外のログをとりますが、呼出し元に例外をスローします(呼出し元が別のアクションを実行する場合)。

2.6 オンライン・ヘルプ・サポートの追加方法

JDeveloperによって使用されるヘルプ・システムでは、コンテキスト依存の[F1]キーのトピックをIDEの構造に統合して提供しています。ユーザーがヘルプにアクセスするには、ウィザード・パネルまたはダイアログの「ヘルプ」ボタンをクリックするか、[F1]キーを押します。

拡張機能でもコンテキスト依存の[F1]ヘルプ・トピックを提供すると、拡張機能のユーザーが拡張機能を理解して効果的に使用できるようになります。

2.6.1 ヘルプ・システムの作成方法

ヘルプ・システムを作成するにはhelpsetを用意します。これは多数のHTMLヘルプ・トピックを含むJavaアーカイブです。

ヘルプ・システムの作成の詳細は、Oracle Fusion Middleware Oracle Help開発者ガイドを参照してください。その他の資料およびOracle Help for Java Developer's Kitのダウンロードは、http://www.oracle.com/technetwork/developer-tools/help/index-083946.htmlを参照してください。

ヘルプ・システムを作成するには、次の手順を実行します。

  1. ウィザードの各パネルまたは各ウィンドウや各エディタで、パネルについて説明するHTMLトピック・ファイルを作成します。

  2. helpsetのマップ制御ファイルを作成します。

  3. helpset (.HS)制御ファイルを作成します。

  4. トピックと制御ファイルをJavaアーカイブにアセンブルします。アーカイブのルート名はHSファイルと同じにして、HSファイルはアーカイブのルート・ディレクトリに配置する必要があります。

  5. コンポーネントとヘルプ・トピックを関連付けます。各パネルに、registerTopicへの呼出しを指定して、トピックIDを登録します。たとえば、panelがウィザード・パネルの名前で、topicがドキュメントのトピックIDの場合、呼出しは次のようになります。

    HelpSystem.getHelpSystem().registerTopic((JComponent)panel, topic);
    
  6. helpsetのJavaアーカイブをデプロイのために拡張機能に組み込みます。これで、拡張機能がロードされるとユーザーがヘルプ・トピックを使用できるようになります。

2.6.2 ヘルプ・システムの登録方法

拡張機能のヘルプ・システムは、拡張機能がロードされる前にユーザーが使用する必要があります。これは、ユーザーが機能分野を把握できるようにするためです。このため、例2-59に示すようにヘルプ・システムを登録する必要があります。

例2-59 ヘルプの<trigger-hooks>への登録

<trigger-hooks xmlns="http://xmlns.oracle.com/ide/extension">
  <triggers>
    <help xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">
      <item>
          <helpName>ejb</helpName>
          <helpURL>../doc/$edition/ohj/helpset.jar!/helpset.hs</helpURL>
          <relativeTo>using_diagrams</relativeTo>
        <relativePosition>after</relativePosition>
      </item>
    </help>
  </triggers>
</trigger-hooks>

2.7 印刷サポートの追加方法

JDeveloperでは、ComponentまたはTextNodeに基づいてPageableオブジェクトを作成できるDocumentPrintFactoryのデフォルト実装が提供されます。

2.7.1 DocumentPrintFactoryの登録方法

例2-60に示すように、DocumentPrintFactoryをextension.xmlに宣言的に登録することで印刷がサポートされます。

例2-60 DocumentPrintFactoryの登録

<hooks>
 <print-hook xmlns="http://xmlns.oracle.com/ide/extension">
     <documentPrintFactory id="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory"
                           class="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory" />
 </print-hook>
</hooks>

2.7.2 ビュー・クラスを登録して印刷を有効化する方法

ビューから印刷できるようにするには、ビュー・クラスと、印刷がビューで呼び出されたときにPageableオブジェクトを作成するDocumentPrintFactoryを登録する必要があります。

例2-61は、oracle.ide.navigator.NavigatorWindowprintHelperを登録する方法を示し、oracle.ide.print.DocumentPrintFactoryというIDで登録されたDocumentPrintFactoryを、Pageableオブジェクトの作成で使用することを指定します。

例2-61 ビュー・クラスの登録

<hooks>
    <print-hook xmlns="http://xmlns.oracle.com/ide/extension">
      <printHelper documentPrintFactoryId="oracle.ideri.navigator.NavigatorPrintFactory" 
                   view-class="oracle.ide.navigator.NavigatorWindow" />
    </print-hook>
</hooks>

別のものを印刷するか、デフォルトの印刷機能を拡張する場合は、DocumentPrintFactoryを拡張して、ビューのPageableオブジェクトを作成し、DocumentPrintFactoryの実装を登録します。例2-62は、複数のビューで同じDocumentPrintFactoryを使用する方法を示します。

例2-62 複数のビューでの同じDocumentPrintFactoryの使用

<print-hook xmlns="http://xmlns.oracle.com/ide/extension">
     <documentPrintFactory id="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory"
                           class="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory" />
     <printHelper documentPrintFactoryId="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory"
                  view-class="oracle.jdevimpl.help.HelpTopicEditor" />
     <printHelper documentPrintFactoryId="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory"
                   view-class="oracle.jdevimpl.help.HelpWindow" />
     <printHelper documentPrintFactoryId="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory"
                  view-class="oracle.jdevimpl.help.HelpContentPanel" />
    <printHelper documentPrintFactoryId="oracle.jdevimpl.help.HelpTopicDocumentPrintFactory"
                 view-class="oracle.jdevimpl.help.HelpCenterWindow$HelpCenterView" />
    </print-hook>
</hooks>

2.7.2.1 ノード処理のためのPageableFactory実装を登録する方法

JDeveloperでは、印刷するTextNodeデータを扱うためにデフォルトのPageableFactory実装が提供されます。

場合によっては、必要な出力が提供される実装で標準のPageableFactory実装をオーバーライドします。たとえば、NavigatorWindowに登録されているDocumentPrintFactoryNavigatorPrintFactoryです。印刷するためにPageableオブジェクトを作成する必要がある場合は、PrintManager.createPageableForObject()を呼び出します。これが、選択された要素に最適なPageableFactoryを見つけようとします。ノード・クラスの処理に使用されるPageableFactoryを登録できます。

例2-63 ノード・クラスを処理するPageableFactoryの登録

<print-hook xmlns="http://xmlns.oracle.com/ide/extension">
      <textNodePageableFactory node-class="oracle.mypackage.MyNodeClass"
               weight="0.5">oracle.mypackage.MyPageableFactory</textNodePageableFactory>
</print-hook>

例2-63では、ノード・クラスは、指定されたPageableFactoryで処理する必要があるノード(この場合はoracle.mypackage.MyPageableFactory)を指定します。現在の要素との一致をチェックするとき、登録されたPageableFactory実装のリストを順序付けるために重みが使用されます。