ヘッダーをスキップ
Oracle® Fusion Middleware Oracle JDeveloper拡張機能の開発
12c (12.1.2)
E48007-01
  目次へ移動
目次

前
 
次
 

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.6項「拡張機能マニフェストの操作」を参照してください。

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

  • 常に有効にする

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

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

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

  • コンテキストにタイプ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.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.6項「拡張機能マニフェストの操作」を参照してください。

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 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要素の使用方法と開発方法について説明します。

Extension SDKには、これらの要素だけでなくその他の要素の例も用意されています。 詳細は、第3章「Extension SDKを使用した開発」を参照してください。

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

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

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

  1. 1.5.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項「コンテキスト・メニューからアドインを呼び出す方法」を参照してください。

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

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

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

    <extension xmlns="http://jcp.org/jsr/198/extension-manifest"
      ...
          <!-- 
            Define an action that will be used in various menus and toolbars.
            You can set any Swing action property on the action. The controller
            class implements oracle.ide.controller.Controller and determines
            when the action is enabled and what it does.
            
            You can use an ampersand (escaped as &amp;) in the Name property
            to specify the mnemonic for any menu items containing the action.
          -->
          <actions xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">
            <action id="oracle.ide.extsamples.first.invokeAction">
              <properties>
                <property name="Name">&amp;Extension SDK First Sample</property>
                 <!-- use an icon, stealing this one from predefined icons
                      make sure you have oracle.icon as a required bundle to
                      pick up this icon at run time from the icons module  -->
                <property name="SmallIcon">${OracleIcons.LABEL}</property>
              </properties>
            </action>
          </actions>
     
          <controllers xmlns="http://xmlns.oracle.com/ide/extension">
            <controller class="oracle.ide.extsamples.firstsample.SimpleController">
              <update-rules>
                <update-rule rule="always-enabled">
                  <action id="oracle.ide.extsamples.first.invokeAction">
                    <label>ESDK Sample Action</label>
                  </action>
                </update-rule>
              </update-rules>
            </controller>
          </controllers>
          <!--
            Install context menus in the navigator, editor, and structure pane (explorer)
            which use the same action defined above.
          -->
          <context-menu-hook rule="always-enabled">
            <site idref="navigator, editor, explorer"/>
              <menu>
                <!-- 'weight' is used to control where the section appears in
                 the menu. Weights are sorted in ascending order, each unique
                 value is its own section. -->
                <!-- section element must be in namespace
                 http://jcp.org/jsr/198/extension-manifest. -->
                <section xmlns="http://jcp.org/jsr/198/extension-manifest" id="MY_CUSTOM_MENU_SECTION_ONE" weight="1.0">
                  <!-- For menu items and submenus, weight determines the
                   order within a menu section. Items are sorted in ascending
                   order or in the order they are added if the weight is not
                   unique. -->
                 <item action-ref="oracle.ide.extsamples.first.invokeAction"></item>
               </section>
             </menu>
          </context-menu-hook>
     
          <menu-hook xmlns="http://jcp.org/jsr/198/extension-manifest">
            <!-- 
              Add the action in its own separator group at the top of the File
              menu.
            -->
            <menus>
              <menubar id="javax.ide.view.MAIN_WINDOW_MENUBAR_ID">
                <menu id="javax.ide.FILE_MENU_ID">
                  <section id="esdksample.customsection" before="javax.ide.NEW_SECTION_ID">
                    <item action-ref="oracle.ide.extsamples.first.invokeAction"></item>
                  </section>
                </menu>
              </menubar>
            </menus>
     
     
            <!--
              Add the action as toolbar button in its own separator group immediately after the
              existing "save" toolbar buttons.
            -->           
            <toolbars>
              <toolbar id="javax.ide.view.MAIN_WINDOW_TOOLBAR_ID">
                <!-- Add a new section after the first section in the toolbar. -->
                <section id="javax.ide.tck.toolbar.testsection"
                       after="javax.ide.NEW_SECTION_ID">
                  <item action-ref="oracle.ide.extsamples.first.invokeAction"></item>
                </section>
              </toolbar>
            </toolbars>
          </menu-hook>
     
       </triggers>
     </trigger-hooks>
     
     <hooks>
      <!-- 
         The feature hook controls the display of the extension on the
         Extensions page of preferences.
       -->
       <feature-hook>
         <description>Demonstrates installing menu, toolbar and gallery items.</description>
         <optional>true</optional>
         <part-of>oracle.ide.extsamples.allsamples</part-of>
       </feature-hook>
     </hooks>
    </extension>
    

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

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

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

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

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

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

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

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

  • ファイルI/O処理。

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

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

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

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

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

拡張機能について、1つのアイテムまたはアイテムのグループを既存のJDeveloperメニューまたはツールバーに追加するだけでよい場合もあります。

メニューを追加するには:

  1. ウィンドウ、エディタまたはエクスプローラで新しいコンテキスト・メニューを追加するには(「構造」ウィンドウなど)、例2-2に示すように、リスナーをインストールします。

    例2-2 コンテキスト・メニューの追加

     <context-menu-hook rule="always-enabled">
            <site idref="navigator, editor, explorer"/>
              <menu>
                <!-- 'weight' is used to control where the section appears in
                 the menu. Weights are sorted in ascending order, each unique
                 value is its own section. -->
                <!-- section element must be in namespace
                 http://jcp.org/jsr/198/extension-manifest. -->
                <section xmlns="http://jcp.org/jsr/198/extension-manifest" id="MY_CUSTOM_MENU_SECTION_ONE" weight="1.0">
                  <!-- For menu items and submenus, weight determines the
                   order within a menu section. Items are sorted in ascending
                   order or in the order they are added if the weight is not
                   unique. -->
                 <item action-ref="oracle.ide.extsamples.first.invokeAction"></item>
               </section>
             </menu>
          </context-menu-hook>
    
  2. メニューバーを追加するには、例2-3を参照します。

    例2-3 メニューバーの追加

      <menu-hook xmlns="http://jcp.org/jsr/198/extension-manifest">
            <!-- 
              Add the action in its own separator group at the top of the File           menu. 
            -->
            <menus>
              <menubar id="javax.ide.view.MAIN_WINDOW_MENUBAR_ID">
                <menu id="javax.ide.FILE_MENU_ID">
                  <section id="esdksample.customsection" before="javax.ide.NEW_SECTION_ID">
                    <item action-ref="oracle.ide.extsamples.first.invokeAction"></item>              </section>            </menu>          </menubar>        </menus>
    
  3. ツールバーにメニューを追加するには、例2-4を参照します。

    例2-4 ツールバーへのメニューの追加

    <menu-hook xmlns="http://jcp.org/jsr/198/extension-manifest">
            <!-- 
              Add the action in its own separator group at the top of the File 
              menu. 
            -->
            <menus>
              <menubar id="javax.ide.view.MAIN_WINDOW_MENUBAR_ID">
                <menu id="javax.ide.FILE_MENU_ID">
                  <section id="esdksample.customsection" before="javax.ide.NEW_SECTION_ID">
                    <item action-ref="oracle.ide.extsamples.first.invokeAction"></item>
                  </section>
                </menu>
              </menubar>
            </menus>
    
    

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

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

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

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

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

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-6に示すようにDockableFactoryを作成します。

    例2-6 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-7に示すようにDockableWindowを作成します。

    例2-7 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-8に示すように、アドイン初期化の際にファクトリをインストールします。

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

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

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

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

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

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-10に基づいてコードを作成します。

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

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-11に基づいてコードを作成します。

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

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-12に基づいてコードを作成します。

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

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

Extension SDKの詳細は、第3章「Extension SDKを使用した開発」を参照してください。

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

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

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

  • invokeメソッドの定義

  • getIconメソッドの定義

  • 拡張機能マニフェストでのウィザードの定義

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

このメソッドが呼び出されるのは、「新規ギャラリ」または「ツール」メニューでウィザードのUI要素がユーザーによって選択されたときです。ウィザードが影響を与える可能性がある、現在選択されているオブジェクトが、contextパラメータによって識別されます。

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

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

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

    public boolean invoke(Context ctx) {
        if (!isAvailable(ctx))
            return false;
        String greetee = null;
        greetee =
                JOptionPane.showInputDialog(Ide.getMainWindow(), "Enter your name:",
                                            WIZARD_NAME,
                                            JOptionPane.OK_CANCEL_OPTION);]
        if (greetee == null) // User canceled
            return false;
        JOptionPane.showMessageDialog(Ide.getMainWindow(),
                                      "Hello " + greetee + "!", WIZARD_NAME,
                                      JOptionPane.INFORMATION_MESSAGE);
        return true;
    }

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

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

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

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

    public Icon getIcon() {        if (image == null) {            image = new ImageIcon(HelloX.class.getResource(ICON_NAME));        }        return image;    }

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

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

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

拡張機能マニフェストでは、定義することが多数あります。例2-15を参照してください。

例2-15 拡張機能マニフェストでのウィザードの定義

<trigger-hooks xmlns="http://xmlns.oracle.com/ide/extension">
    <triggers>
      <gallery xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">
        <folders>
          <name label="ESDK-Gallery Example">ESDK-Gallery Example</name>
          <category>General</category>
        </folders>
        <item rule="always-enabled">
          <!-- name is the fully-qualified name of an oracle.ide.wizard.Invokable  -->
          <name>oracle.ide.extsamples.hellox.HelloX</name>
          <!-- id is optional, and if specified will be stored in the Context
               so it can be retrieved when the wizard is invoked. -->
          <id>oracle.ide.extsamples.hellox</id>
          <!-- description is the short label of the gallery item -->
          <description>Say Hello Wizard</description>
          <!-- help is the long label for the gallery item -->
          <help>Prompts the user to enter his name, and repeats it. Part of the Extension SDK, this is a trivial example.</help>  
          <icon>/oracle/ide/extsamples/hellox/HelloX.gif</icon>
          <category>General</category>
          <folder>ESDK-Gallery Example</folder>
        </item>
      </gallery>
    </triggers>
  </trigger-hooks>

<gallery>には、「新規ギャラリ」のウィザードに関する情報が含まれています。

  • <name>は、ウィザードの解読可能な名前を提供します。

  • <category>は、ウィザードの「新規ギャラリ」カテゴリを指定します。カテゴリ内のエントリは、アルファベット順になっています。

  • <folder>は、カテゴリ内のフォルダを指定します。

  • ruleは、ウィザードが使用可能な時期を指定します。

  • ウィザード・クラスは、<id>で識別されます。

  • <description>は、「新規ギャラリ」のウィザードの短い説明です。

    <help>は、アイテムを選択した場合、または「新規ギャラリ」の「すべての説明を表示」を選択した場合に表示されるエントリの長い説明です。

  • <icon>は、「新規ギャラリ」内のアイコンを指定します。

拡張機能アプリケーションextensionsdkを実行することで、例2-15のコードが、「新規ギャラリ」に表示される内容にどのように影響を与えるかを確認することができます。

図2-4 「新規ギャラリ」のHelloXウィザード

「新規ギャラリ」のウィザードの例

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-16のコードは、コマンドを呼び出したときにトリガーされるイベントの処理方法を示します。

例2-16 イベントの処理

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

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

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

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

例2-17は、「アプリケーション」ウィンドウなどのウィンドウ内にコンテキスト・メニューをインストールする方法を示しています。このコードは、Oracle IDE Extension SDKの一部としてインストールされる拡張機能サンプル内のClassBrowserプロジェクトのものです。

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

 <!--
        Install context menus in the navigator, editor
        which use the same action defined above. -->
      
      <context-menu-hook rule="always-enabled">
        <site idref="navigator,editor"/>
          <menu>
            <!-- 'weight' is used to control where the section appears in
             the menu. Weights are sorted in ascending order, each unique
             value is its own section. -->
            <!-- section element must be in namespace
             http://jcp.org/jsr/198/extension-manifest.  -->
            <section xmlns="http://jcp.org/jsr/198/extension-manifest" id="MY_CUSTOM_MENU_SECTION_ONE" weight="1.0">
              <!-- For menu items and submenus, weight determines the
               order within a menu section. Items are sorted in ascending
               order or in the order they are added if the weight is not
               unique.  -->
             <item action-ref="esdksample.showClassBrowser"></item>
           </section>
         </menu>
      </context-menu-hook>

2.4.6 エディタの開発方法

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

Extension SDK内のCustomEditorプロジェクトは、oracle.ide.editor.Editorの拡張およびjava.beans.PropertyChangeListenerの実装を行う、カスタムのXMLQueryEditorを実装することでこれを実行する方法を示します。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

2.4.6.1 非同期エディタの使用

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

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

  • 「コンポーネント」ウィンドウがUIをロードおよびブロックをしないようにします。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

図2-6 エディタの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-18に示すようにアドイン・クラスのinitializeメソッドで実行する必要があります。このコード例は、Extension SDKに含まれるサンプル・プロジェクトの1つ、StructurePaneプロジェクトのものです。詳細は、第3章「Extension SDKを使用した開発」を参照してください。

例2-18 登録処理

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 Extension SDK Java APIリファレンスPaletteManagerに関する項を参照してください。

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

2.4.8.1 「コンポーネント」ウィンドウのページについて

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

「コンポーネント」ウィンドウを表示するには、「表示」「コンポーネント」を選択します。

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

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

  • 「コンポーネント」ウィンドウで静的コンポーネントを宣言します。

  • 動的コンポーネントを宣言し、「コンポーネント」ウィンドウがロードされたときに組み込みます。

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

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

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

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

2.4.8.2 「コンポーネント」ウィンドウの静的コンテンツを宣言する方法

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

「コンポーネント」ウィンドウの形状は、単純な4層の分類として定義されます。

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

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

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

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

例2-19は、Extension SDKのCPPageProviderプロジェクトのものであり、1つのコンポーネントを含む「コンポーネント」ウィンドウ・ページを宣言する方法を示しています。Extension SDKの詳細は、第3章「Extension SDKを使用した開発」を参照してください。

例2-19 1つのコンポーネントを含む「コンポーネント」ウィンドウ・ページの宣言

<?xml version="1.0" encoding="UTF-8" ?>
<extension xmlns="http://jcp.org/jsr/198/extension-manifest"
           id="oracle.ide.extsamples.cppageprovider"
           version="11.1.1"
           esdk-version="1.0">
  <name>ESDK Sample - Component Palette Page Provider</name>
  <owner>Oracle</owner>
  <feature-member id="esdk-samples"
 xmlns="http://xmlns.oracle.com/ide/extension"/>
  <hooks>
    <palette-hook xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">
      <pageProvider>
        <providerClassName>oracle.ide.extsamples.pageprovider.SamplePageProvider</provider
ClassName>
      </pageProvider>
      <page>
        <name>Sample Components (ESDK Sample)</name>
        <pageId>SampleStatic</pageId>
        <showForTypes>
          <type>java</type>
        </showForTypes>
        <technologyScopes>
          <technologyScope>Java</technologyScope>
        </technologyScopes>
        <type>java</type>
        <group>
         <name>Components</name>
         <groupId>SampleStatic-Components</groupId>
          <showForTypes>
            <type>java</type>
          </showForTypes>
          <technologyScopes>
            <technologyScope>Java</technologyScope>
          </technologyScopes>
          <type>java</type>
          <section>
             <sectionId>SampleStatic-Components-Section1</sectionId>
             <name/>
             <item>
               <name>Table</name>
               <description>Sample Table</description>
               <icon>${OracleIcons.TABLE}</icon>
               <info/>
               <type>JavaBean</type>
               <itemId>SampleStatic-Components-Section1-Item1</itemId>
               <technologyScopes>
                 <technologyScope>Java</technologyScope>
               </technologyScopes>
             </item>
           </section>
         </group>
      </page>
    </palette-hook>
    <feature-hook>
      <part-of>oracle.ide.extsamples.allsamples</part-of>
      <description>Demonstrates how to integrate a component palette page provider into the IDE.</description>
      <optional>true</optional>
    </feature-hook>
  </hooks>
</extension>

2.4.8.3 「コンポーネント」ウィンドウ・ページの動的コンポーネントを宣言する方法

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

「コンポーネント」ウィンドウ・ページ・プロバイダは、公開されている「コンポーネント」ウィンドウAPIを使用してクライアントの開発者によって開発され、ページ・プロバイダのクラス名は、拡張機能マニフェスト・ファイルの「コンポーネント」ウィンドウに提供されます。

「コンポーネント」ウィンドウAPIの詳細は、Oracle Extension SDK Java APIリファレンスoracle.ide.palette2に関する項を参照してください。

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

例2-20は、「コンポーネント」ウィンドウ・ページ・プロバイダのクラス名を指定する拡張機能マニフェストです。

例2-20 「コンポーネント」ウィンドウ・ページ・プロバイダのクラス名を指定する拡張機能マニフェスト

<?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-21は、「コンポーネント」ウィンドウに「コンポーネント」ウィンドウ・ページを追加する、「コンポーネント」ウィンドウ・ページ・プロバイダを示しています。このクラスはPalettePageProviderを拡張します。

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

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

例2-21 「コンポーネント」ウィンドウ・ページ・プロバイダ

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-22SamplePages.javaの例を示します。このクラスは、pageTypejavaの場合に、1つのアイテムと1つのセクションを含む1つのグループで「コンポーネント」ウィンドウ・ページを作成します。必要に応じてこのクラスはPalettePagesを拡張します。

例2-22 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-23SamplePalettePage.javaの例を示します。

例2-23 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-24 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-25SamplePaletteItem.javaの例を示します。

例2-25 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-26は、この方法を示します。

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

  <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-27のようにhelpidを直接指定できます。

例2-27 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-28に示すように、「コンポーネント」ウィンドウの拡張機能フックでこれを宣言的に指定できます。

例2-28 「コンポーネント」ウィンドウの拡張機能フック

  <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-29に示すように、SearchContextが次にPaletteSearchインタフェースを実装します。

例2-29 「コンポーネント」ウィンドウの検索

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 Extension SDK Java APIリファレンスPreferencesに関する項を参照してください。

プリファレンスを保存するために使用されるデータ構造はHashStructureです。これもOracle Extension SDK Java APIリファレンスで説明されています。Preferencesクラスは、1つの拡張機能ディレクトリ(製品全体を表す拡張機能)のみにプリファレンスを保存します。たとえば、JDeveloperでは製品ディレクトリの形式はjdev-user-directory/system/oracle.JDeveloper.12.1.2.x.yとなります。12.1.2はバージョン番号、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 Extension SDK Java APIリファレンスHashStructureAdapterに関する項を参照してください。

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

例2-30 実装パターン

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 Extension SDK Java APIリファレンスDefaultTraversablePanelに関する項を参照してください。

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

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

例2-32 拡張機能マニフェストの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-33に示すようにリスナーをモデル・オブジェクトにアタッチするためのメソッドを公開することです。

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

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 Extension SDK Java APIリファレンスProjectに関する項を参照してください。

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

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

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

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

例2-34 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-35に示すように、変更内容を実行する方法と元に戻す方法を認識しているコマンドを実装する必要があります。

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

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-36「トリガー・フック・ハンドラの登録」に示すようにトリガー・フック・ハンドラを登録します。

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

<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-36「トリガー・フック・ハンドラの登録」に示すように登録し、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-37「トリガー・フックの定義」のような構文を使用します。

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

    <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-38に示すようにいくつかの組込みルール・タイプが定義されます。

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

<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-39に示すように、<rule>のハンドラによって次の項目が確認されます。

  • IDが一意かどうか。

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

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

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

例2-39 単純なルール

  <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-39context-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-40に示すようにルールの参照か別のブール演算子である必要があります。

例2-40 複合ルール

<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-41例2-42および例2-43で説明しています。

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

  ...
  <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-42 コントローラ

<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-43 コンテキスト・メニュー

<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 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-44に示すようにヘルプ・システムを登録する必要があります。

例2-44 ヘルプの<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-45に示すように、DocumentPrintFactoryをextension.xmlに宣言的に登録することで印刷がサポートされます。

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

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

<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-47は、複数のビューで同じDocumentPrintFactoryを使用する方法を示します。

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