UIX開発者ガイド | ![]() 目次 |
![]() 前へ |
![]() 次へ |
UIXは、そのままでも強力なフレームワークとして使用できますが、アプリケーションに必要なあらゆる機能が提供されるわけではありません。作業を進めていく段階で、UIXで提供されない追加機能が必要になります。UIXは、高度な拡張性を備えたフレームワークとして設計されており、独自のクラスおよびXML構文をほとんどの箇所で自由にプラグインできます。
UIXの次の拡張方法については、すでに説明しました。
<method>
要素を使用すると、データを提供またはイベントを処理するJavaコードをアタッチできます。
ここでは、さらに高度な拡張方法について説明します。まず、新しいユーザー・インタフェース・コンポーネントを最初から作成する、カスタムのRenderer
の記述方法を学びます。次に、ユーザー・インタフェース・コンポーネントのみでなく、UIXで使用するすべてのJava型のための、UIX解析APIを使用したXMLパーサーの記述方法を学びます。また、XMLを簡単にJavaBeansに変換できる解析APIの拡張について学びます。他のUIX機能を使用しない場合でも、この機能だけは使用する価値があります。さらに、他の開発者と簡単に作業を共有できるよう、拡張した機能を1つのUIExtension
に統合する方法を学びます。最後に、コードを変更せずに、他の開発者が定義した要素に属性と要素を追加できるParserExtension
APIについて説明します。
ここでは、次の項目について説明します。
このチュートリアルでは、UIX拡張ライブラリとして、「Project Flipper」という架空のクラス・ライブラリを開発する手順について説明します。Flipperライブラリは、現在UIXで定義されていない便利なBean、RendererおよびUIX要素のリポジトリとして使用するためのものです。このカスタム・クラス・ライブラリの開発方法を、順を追って説明していきます。カスタム・コンポーネントを作成する際に独自のクラス・ライブラリを開発する場合、この手順に従ってください。チュートリアルを終了する時点で、1つのカスタム・コンポーネントを含むクラス・ライブラリが完成します。このカスタム・コンポーネントは、UINodeツリーでカスタムUINodeとして、またはUIX文書でカスタム・タグとして使用できます。
「UIXでのページの作成」でも説明したとおり、UIX Componentsは常にネームスペースで識別されます。ネームスペースは通常URLとして定義されますが、そのURLに実際に文書が存在している必要はありません。ネームスペースは一意である必要があり、インターネットではすでにURLの一意性の維持という問題が解決されていることが、XMLでこの方法を採用した理由の1つです。
まず、Flipperコンポーネントに対して新規のネームスペースを定義します。ネームスペースは単にプロジェクト固有のURIであるため、Flipperのネームスペースはhttp://flipper.example.org/uiとします。また、Flipperプロジェクトで新しいタイプのUINodeを定義するたびに、ローカル名を定義する必要があります。不足している機能をMarlinで調べると、Flipperライブラリで実装する候補として様々なBeanがあります。このチュートリアルの目的上、便利で実装も簡単なBeanが必要です。このため、Flipperライブラリには最初にCopyrightBeanを追加します。CopyrightBeanは、法律上重要な機能を持つBeanで、オラクル社の著作権表示をレンダリングします。
Flipperのコード・ベースを単純化するため、ネームスペースやローカル名などの重要な定数を定義するFlipperConstants
インタフェースを定義します。
package org.example.flipper.ui;
public interface FlipperConstants
{
/**
* Namespace used by the Flipper implementation
*/
public static final String FLIPPER_NAMESPACE =
"http://flipper.example.org/ui";
/**
* Name of our copyright bean
*/
public static final String COPYRIGHT_NAME = "copyright";
}
Renderer
の記述Renderer
は、UIXがUINode
を出力に変換するために使用するインタフェースです。このインタフェースは作成するすべてのHTMLのベースとなる重要な機能ですが、APIとしては最も単純な部類に入ります。
public interface Renderer
{
public void render(
RenderingContext context,
UINode node) throws IOException;
}
Renderer
は、作成したノードだけでなく、ノードのすべての子に対する結果を生成します。(CopyrightBeanの場合、子ノードはありません。)次に、Renderer
の完全なコードを示します。
package org.example.flipper.ui;
import java.io.IOException;
import oracle.cabo.ui.BaseRenderer;
import oracle.cabo.ui.RenderingContext;
import oracle.cabo.ui.UINode;
import oracle.cabo.ui.io.OutputMethod;
public class CopyrightRenderer extends BaseRenderer
{
protected void renderContent(
RenderingContext context,
UINode node
) throws IOException
{
OutputMethod out = context.getOutputMethod();
out.startElement("span");
out.writeAttribute("class", "OraCopyright");
// "a9" is the Unicode copyright symbol.
out.writeText("Copyright \u00a9 Oracle Corporation. All Rights Reserved.");
out.endElement("span");
}
}
これにより、次のようなHTMLのSnippetが出力されます。
<span class="OraCopyright">
Copyright © Oracle Corporation. All Rights Reserved.
</span>
記述は多くありませんが、このコードには、Renderer
を記述する際の重要な手法であるOutputMethod
インタフェースが含まれています。OutputMethod
によってマークアップ言語が抽出されるため、出力の適切なエスケープに必要な作業を省略できます。PrintWriter
のかわりにOutputMethod
を使用することで、次の処理を自動化できます。
<form>
要素の中に別の<form>
要素を配置した場合など)を検出します。
Renderer
に機能を追加する手順に進む前に、このサンプル・コードについて注意する点がもう1つあります。このコードには、OutputMethod
に対して開始タグ<span>
を閉じるよう命令する記述はなく、要素を開始して属性を1つ記述し、要素内に配置するテキストを記述しているだけです。OutputMethod
を使用する場合、いつ要素を終了するかは実装で自動的に判断されるため、要素の終了を命令する必要はありません。
これらのメソッドをすべて無視し、HTMLを直接記述することもできます。OutputMethod
には、通常は使用しないwriteRawText()
というメソッドがあります。ただし、OutputMethod
の利点がすべて失われるため、RAWテキストの使用はお薦めしません。
次に、CopyrightBeanに2つの属性を追加します。著作権の年を示す整数のyear
属性と、リンクを提供するdestination
属性です。UIXではAttributeKey
によってノード属性を格納および取得するため、この2つの属性に対する定数を定義する必要があります。UIXではoracle.cabo.ui.UIConstants
インタフェースにDESTINATION_ATTR
定数が組み込まれていますが、ここではFlipperConstants
にYEAR_ATTR
定数を追加します。
package org.example.flipper.ui;
import oracle.cabo.ui.AttributeKey;
public interface FlipperConstants
{
/**
* Namespace used by the Flipper implementation
*/
public static final String FLIPPER_NAMESPACE =
"http://flipper.example.org/ui";
/**
* Name of our copyright bean
*/
public static final String COPYRIGHT_NAME = "copyright";
/**
* "Year" attribute key.
*/
public static final AttributeKey YEAR_ATTR =
AttributeKey.getAttributeKey("year");
}
次に、これらの属性を出力に取り込むためのコードをRenderer
に追加します。
package org.example.flipper.ui;
import java.io.IOException;
import oracle.cabo.ui.BaseRenderer;
import oracle.cabo.ui.RenderingContext;
import oracle.cabo.ui.UINode;
import oracle.cabo.ui.io.OutputMethod;
public class CopyrightRenderer extends BaseRenderer
{
protected void renderContent(
RenderingContext context,
UINode node
) throws IOException
{
OutputMethod out = context.getOutputMethod();
Object year = node.getAttributeValue(context,
FlipperConstants.YEAR_ATTR);
Object destination = node.getAttributeValue(context,
UIConstants.DESTINATION_ATTR);
if (destination != null)
{
out.startElement("a");
// URLs should be written out using writeURIAttribute(), because
// they're escaped differently than other attributes.
out.writeURIAttribute("href", destination);
}
out.startElement("span");
out.writeAttribute("class", "OraCopyright");
out.writeText("Copyright (c) ");
if (year != null)
out.writeText(year.toString());
out.writeText(" Oracle Corporation. All Rights Reserved.");
out.endElement("span");
if (destination != null)
out.endElement("a");
}
}
ここまではかなり単純な作業ですが、Renderer
のみで処理できることは限られています。まず最初に、CopyrightBean
クラスを記述する必要があります。
Renderer
のコードをすべて記述し終えた時点では、このBeanはコンポーネントのネームスペースおよびローカル名を設定し、属性を取得および設定する多くの類型的なコードの集まりにすぎません。
package org.example.flipper.ui;
import oracle.bali.share.util.IntegerUtils;
import oracle.cabo.ui.UIConstants;
import oracle.cabo.ui.beans.BaseWebBean;
public class CopyrightBean extends BaseWebBean implements FlipperConstants
{
public CopyrightBean()
{
super(FLIPPER_NAMESPACE, COPYRIGHT_NAME, null);
}
final public int getYear()
{
return BaseWebBean.resolveInteger(
(Integer) getAttributeValue(YEAR_ATTR));
}
final public void setYear(int year)
{
setAttributeValue(YEAR_ATTR, IntegerUtils.getInteger(year));
}
final public String getDestination()
{
return (String) getAttributeValue(UIConstants.DESTINATION_ATTR);
}
final public void setDestination(String destination)
{
setAttributeValue(UIConstants.DESTINATION_ATTR, destination);
}
}
このコードでは、次の点に注目してください。
Renderer
を結び付けるために使用します。単にgetRenderer()
をオーバーライドして、直接レンダラを返す方法も技術的には可能ですが、システムにより両者を結び付ける方が簡潔です。
getAttributeValue()
およびsetAttributeValue()
へと渡されています。これは重要なポイントです。値を直接インスタンス変数として格納する場合、データ・バインドが機能しないため、コンポーネントをテンプレート化できず、uiXML内に簡単に埋め込むことができません。また、Renderer
がこれらの属性を取得する方法も記述しなおすことが必要になります。
getAttributeValue()
またはsetAttributeValue
はいつでも直接コールでき、その場合オーバーライドは完全に無視されます。ここでは、これらのメソッドをfinalとマークすることで、このような問題の発生を回避しています。
IntegerUtils.getInteger()
コールでは、UINode
属性をオブジェクトとして格納する必要があるため、int
をInteger
に変換しています。通常、Integer
オブジェクトを作成する場合は、単純にnew Integer(int)
をコールします。しかしInteger
は不変オブジェクトなので、自由に共有できます。IntegerUtils
は使用頻度の高いInteger
オブジェクトのキャッシュを保持するため、オブジェクトを何度も作成する必要がなくなり、その結果時間を大幅に節約できます。この手法は、Bean以外にも幅広く応用できます。
次に、CopyrightBean
を作成します。
CopyrightBean copyright = new CopyrightBean();
copyright.setYear(2001);
copyright.setDestination("http://www.oracle.com");
CopyrightBean
は単に説明の便宜上のクラスであり、コードの最後の部分はBaseMutableUINode
を使用することで、次のようにより簡潔に記述できます。
BaseMutableUINode copyright =
new BaseMutableUINode(FlipperConstants.FLIPPER_NAMESPACE,
FlipperConstants.COPYRIGHT_NAME);
copyright.setAttributeValue(FlipperConstants.YEAR_ATTR, new Integer(2001));
copyright.setAttributeValue(UIConstants.DESTINATION_ATTR, "http://www.oracle.com");
ここまでのコードをそのままコンパイルし、これらのBeanをレンダリングしても、あまり多くの出力は得られず、かわりに次のようなメッセージがエラー・ログに送られる結果となります。
No UIX Components (Marlin) RendererFactory registered for namespace
http://flipper.example.org/ui
作成したBeanを稼働させるには、もう1つ作業が残っています。このBeanをレンダリングするには、作成したRenderer
をUIXシステムに連結する必要があり、そのためにはRendererFactory
クラスおよびUIExtension
クラスについて理解する必要があります。
レンダラを登録するには、次の手順を実行します。
UIXでは、RendererManager
内を検索してRenderer
を検出します。1つのRendererManager
がすべてのネームスペースに対するすべてのRenderer
を単独で処理する場合、その処理はかなり煩雑になります。実際には、RendererManager
はネームスペースごとにRenderer
を分類し、それぞれのネームスペースに対して1つのRendererFactory
を使用します。
したがって、まず最初にこのRendererFactory
を作成します。ここでは、作業を簡単にするためにUIXのRendererFactoryImpl
を使用します。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.RendererFactoryImpl;
public class FlipperRendererFactory extends RendererFactoryImpl
{
/**
* Return the shared instance of this factory.
*/
static public RendererFactory sharedInstance()
{
return _sInstance;
}
public FlipperRendererFactory()
{
// Register our one renderer.
registerRenderer(FlipperConstants.COPYRIGHT_NAME,
"org.example.flipper.ui.CopyrightRenderer");
}
static private final RendererFactory _sInstance =
new FlipperRendererFactory();
}
クラスそのものまたはレンダラ・クラスのインスタンスではなく、名前を使用してレンダラを登録している点に注意してください。UIXでは、実際に必要になった時点で初めてCopyrightRenderer
クラスをロードします。
これでRendererFactory
が作成されたので、次はこのファクトリを登録します。この作業には、UIExtension
インタフェースを使用します。UIExtension
には必ず2つのメソッドがあります。レンダリング・コードを登録するメソッドと、解析コードを登録するメソッドです。
package oracle.cabo.ui;
public interface UIExtension
{
public void registerSelf(LookAndFeel laf);
public void registerSelf(ParserManager manager);
}
ここでは、レンダラの登録のみを行います。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public FlipperUIExtension()
{
}
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
// For now, let's do nothing.
}
}
ここでもコードは単純ですが、さらにクラスを記述しています。簡単な1つのBeanとしてはクラスの数が多くなっていますが、今後作成するBeanでは同じRendererFactory
クラスおよびUIExtension
クラスを再利用できます。
次に、UIExtension
を登録します。登録方法は、UIX Controllerを使用している場合と、UIX Componentsに直接記述している場合とで異なります。
UIX Controllerを使用すると、簡単にUIExtension
を登録できます。登録は、プログラムによって行うことも、uix-config.xml
を使用して宣言的に行うこともできます。プログラムによって登録する場合は、BaseUIPageBroker
クラスに定義されているregisterUIExtension()
メソッドを使用します。たとえば、次のようにUIXPageBroker
のサブクラスを作成できます。
import oracle.cabo.servlet.xml.UIXPageBroker;
public class FlipperPageBroker extends UIXPageBroker
{
public FlipperPageBroker()
{
registerUIExtension(new FlipperUIExtension());
}
}
しかし、uix-config.xml
の<ui-extension>
要素を使用してUIExtension
を登録する方が、さらに簡単です。サーブレット・エンジンとしてOracle Containers For J2EE(OC4J)(またはサーブレット2.2仕様を実装したその他のエンジン)を使用している場合、これらは<web app>/WEB-INF/uix-config.xml
ファイルに置かれます。
<?xml version="1.0" encoding="ISO-8859-1"?>
<configurations xmlns="http://xmlns.oracle.com/uix/config">
<application-configuration>
<ui-extensions>
<ui-extension>org.example.flipper.FlipperUIExtension</ui-extension>
<ui-extension>org.example.someOtherPackage.AnotherUIExtension</ui-extension>
</ui-extensions>
</application-configuration>
</configurations>
uix-config.xml
の詳細は、「構成」のトピックを参照してください。
UIExtension
を登録するこのどちらの方法も、BaseUIPageBroker
クラスか、またはそのサブクラスの1つ(UIXPageBroker
など)を使用している場合にのみ有効です。これらのクラスを使用していない場合には、直接UIExtension
を登録する必要があります。
UIX Controllerを使用していない場合は、UIExtension
を自分でLookAndFeelManager
に登録する必要があります。LookAndFeelManager
は、すべてのLookAndFeel
を制御するエンティティです。特に、各ページにどのLookAndFeel
を使用するかを制御します。
最も簡単なUIExtension
の登録方法は、デフォルトのLookAndFeelManager
への登録です。
import oracle.cabo.ui.laf.LookAndFeelManager;
...
LookAndFeelManager manager =
LookAndFeelManager.getDefaultLookAndFeelManager();
manager.registerUIExtension(new FlipperUIExtension());
この処理は、最初のページをレンダリングする前に1回だけ実行します。使用するサーブレットのinit()
メソッドなどに、このようなコードを配置することをお薦めします。
デフォルトのLookAndFeelManager
にFlipperUIExtension
を登録する際の問題は、このLookAndFeelManager
はサーバー上のあらゆるWebアプリケーションで共有される可能性があるという点です。これらのWebアプリケーションのすべてが、Flipperを必要とするわけではありません。もう1つの問題は、2つのWebアプリケーションがFlipperを使用すると、Flipperが2回登録されることです。そのため、アプリケーションを追加すればするほど無駄が発生し処理速度は低下します。
この問題を解決するには、createDefaultLookAndFeelManager()
を使用して専用のLookAndFeelManager
を作成する必要があります。その後、Configuration APIを使用してこのLookAndFeelManager
を格納します。
// Instead of using the default, create a brand new manager
LookAndFeelManager manager =
LookAndFeelManager.createDefaultLookAndFeelManager();
// Register the extension just as before
manager.registerUIExtension(new FlipperUIExtension());
// And now store the LookAndFeelManager on a Configuration
ConfigurationImpl config = new ConfigurationImpl("yourConfigKey");
config.putProperty(Configuration.LOOK_AND_FEEL_MANAGER, manager);
config.register();
Configuration
API、およびレンダリングの際に特定のConfiguration
オブジェクトを使用する方法の詳細は、「構成」のトピックを参照してください。
Beanが完成しJavaで稼働できるようになったので、次は作成したBeanへのサポートをXML解析APIに追加します。
UIX解析APIは、レンダリングAPIに類似しています。解析APIでは、RendererManager
, RendererFactory
およびRenderer
のかわりに、ParserManager
, ParserFactory
およびNodeParser
を使用します。これらのAPIの詳細は後述しますが、UINode
の解析のみの場合、簡単なAPIで十分です。
UINode
を解析するには、必ずUINodeParserFactory
クラスを使用します。ほぼすべてのUINode
が同じ方法で解析されるため、Beanごとに新しいパーサーを記述する必要はありません。ただし、Beanを記述するメタデータをUINodeParserFactory
に提供する必要があります。具体的には、Beanがサポートする属性、各属性の型、およびBeanがサポートする名前の付けられた子についての情報が必要です。
UINode
のメタデータは、UINodeType
オブジェクトにより記述されます。UINodeType
では多くのことを制御できますが、ほとんどの場合はBaseUINodeType
クラスのインスタンスを作成し、これをDictionary
に配置し、そのディクショナリをUINodeParserFactory
へ渡すだけです。Project Flipperの<copyright>要素のためにParserFactory
を作成するコードは、次のようになります。
package org.example.flipper.ui;
import oracle.bali.share.collection.ArrayMap;
import oracle.cabo.share.xml.ParserFactory;
import oracle.cabo.share.xml.ParserManager;
import oracle.cabo.ui.xml.parse.UINodeParserFactory;
import oracle.cabo.ui.xml.parse.BaseUINodeType;
class FlipperUINodeParserFactory extends UINodeParserFactory
{
public FlipperUINodeParserFactory()
{
super(FlipperConstants.FLIPPER_NAMESPACE,
null,
_sFlipperTypes);
}
static private final ArrayMap _sFlipperTypes = new ArrayMap();
static
{
BaseUINodeType copyrightType =
new BaseUINodeType(FlipperConstants.FLIPPER_NAMESPACE,
null,
new Object[]{"year", Integer.class,
"destination", String.class},
BaseUINodeType.getDefaultUINodeType());
_sFlipperTypes.put(FlipperConstants.COPYRIGHT_NAME, copyrightType);
}
}
このコードを冒頭から順に確認します。
FlipperUINodeParserFactory
は、ParserFactory
であるUINodeParserFactory
を拡張します。
UINodeParserFactory
に渡します。
UINodeType
を識別します。このノード・タイプは、このネームスペース内の明示的に記述されていないすべての要素で使用されます。通常は、サポートしていない要素が解析エラーとなるようnullを渡します。
ArrayMap
を渡します。このクラスはArrayMap
という名前ですが、実際にはJava2のjava.util.Map
APIではなく、JDK 1.1のjava.util.Dictionary
APIのサブクラスです。これは、過去の経緯からUINodeParserFactory
がMap
を受け入れないためです。ArrayMap
クラスは、今回のような非常に小さなデータ・セットに対して最適化されます。
<copyright>
要素に対するUINodeType
を作成します。BaseUINodeType
コンストラクタに、次の4つのパラメータを渡します。
CopyrightBean
では名前の付けられた子をサポートしないため、これはnullです。
BaseUINodeType.getDefaultUINodeType()
を使用することで、レンダリングされた属性のみでなく、<boundAttribute>
子要素に対してもビルトイン・サポートが得られます。
UINodeType
を正しい要素名"copyright"
で格納します。この要素名および要素のネームスペースは、Renderer
の登録に使用した名前およびネームスペースと一致していることが重要です。
後は、レンダラに加えて解析コードも登録するよう、UIExtension
を変更するだけです。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public FlipperUIExtension()
{
}
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
FlipperUINodeParserFactory factory = new FlipperUINodeParserFactory();
factory.registerSelf(manager);
}
}
変更点はごく一部です。パーサー・ファクトリを作成し、UINodeParserFactory.registerSelf()
メソッドを使用して、それをParserManager
に登録しています。UIX ControllerとそのUIXPageBroker
クラスを使用している場合、パーサーは自動的に登録されます。一方、UIXファイルを直接解析している場合は、使用しているParserManager
でUIExtension.registerSelf(ParserManager)
メソッドをコールする必要があります。
これで、<flipper:copyright>
要素を直接UIXで使用できます。
<pageLayout xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:flipper="http://flipper.example.org/ui">
<copyright>
<flipper:copyright year="2001" destination="http://www.oracle.com"/>
</copyright>
</pageLayout>
名前の付けられた子の意味を正確に理解することが重要です。UINode
には、UINode
を返す特別なgetNamedChild()
メソッドがあります。通常、名前の付けられた子をサポートするBeanは、その名前の付けられた子に対するgetterメソッドとsetterメソッドを持ちます。
public UINode getStart() { ... }
public void setStart(UINode start) { ... }
これらのメソッドは、たとえば次のように、属性の取得と設定に使用されるメソッドと見た目は類似しています。
public String getWidth() { ... }
public void setWidth(String width) { ... }
public ClientValidater getOnSubmitValidater() { ... }
public void setOnSubmitValidater(ClientValideter onSubmit) { ... }
Java APIの使用者には、これらのメソッドはほとんど同じように見えます。ただし完全に同じではなく、属性はデータ・バインドできますが、名前の付けられた子はデータ・バインドできません(IncludeBeanで、名前の付けられた子のデータ・バインドをシミュレーションすることは可能です)。しかし、見た目はほとんど同じです。
XMLの場合はさらに複雑です。前の例のwidth
のような属性は、UIXでは単純属性と呼ばれています。これらの属性は、1つの文字列で簡単に記述できます。文字列、数値、真/偽などの型がこれに該当します。それ以外のonSubmitValidater
などの属性は、複雑属性と呼ばれています。複雑属性は、値の記述に単なる文字列以上の構文が必要であり、2つのレベルの子要素を使用して表されます。最初に使用する最上位のレベルは、属性と同じ名前のエンベロープ要素です。エンベロープ要素は、どの属性が解析されているかをUIXに伝えます。次の例ではこの要素内で、その型の値を定義している要素を検索します。
<yourElement>
<!-- First, an envelope element identifying the attribute name -->
<onSubmitValidater>
<!-- Then, an element describing the value; that is, what
kind of ClientValidater this is -->
<ui:decimal/>
</onSubmitValidater>
</yourElement>
ClientValidater
や、UIXに組み込まれていないカスタムの型を含むその他の型のJavaオブジェクトを記述する要素の定義方法については、このトピックの後半で説明します。
名前の付けられた子は、複雑属性と同様の構文を使用します。エンベロープ要素も同様に使用しますが、要素名には子の名前を使用します。エンベロープ要素には、UINode
を定義する要素が1つ含まれます。
<yourElement>
<!-- First, an envelope element identifying the child name -->
<start>
<!-- Then, an element describing the UINode -->
<ui:button text="Press Me"/>
</start>
</yourElement>
つまりXMLの観点で見ると、複雑属性は名前の付けられた子に類似しています。しかしUINodeType
定義では、この2つは違う方法で定義します。名前の付けられた子は名前の付けられた子のリストで定義し、複雑属性は属性リストで定義します。
UINodeType yourElementType =
new BaseUINodeType(YOUR_NAMESPACE,
new String[]{"start"},
new Object[]{"onSubmitValidater", ClientValidater.class,
"width", String.class},
BaseUINodeType.getDefaultUINodeType());
多少複雑ですが、ほとんどの場合この知識は不要です。単にuiXMLを使用している場合には、この点を理解する必要はまったくありません。width属性があり、<start>要素および<onSubmitValidater>
要素があることのみを理解してください。ただしUIXにBeanを追加する場合は、独自のXML構文を記述できるよう、これらの点を理解しておく必要があります。
UIXに追加する要素がすべて新しいUINode
型の場合については、必要な作業をすべて説明しました。しかしUIXでは、さらに広範な拡張が可能です。ここでは、CopyrightBean
にいくつかの機能を追加することによって、その拡張方法を説明します。単一年のみをサポートするのではなく、カスタムの型を持つ新しい複雑属性を使用して、ある範囲の複数年をサポートすることにします。
public class CopyrightBean()
{
// ...
public YearRange getYears()
{
return (YearRange) getAttributeValue(YEARS_ATTR);
}
public void setYears(YearRange years)
{
setAttributeValue(YEARS_ATTR, years);
}
static public class YearRange
{
public int getStart()
{
return _start;
}
public void setStart(int start)
{
_start = start;
}
public int getEnd()
{
return _end;
}
public void setEnd(int end)
{
_end = end;
}
private int _start = -1;
private int _end = -1;
}
}
次のXMLをサポートします。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999" end="2001"/>
</flipper:years>
</flipper:copyright>
(Renderer
に対する変更は練習としてユーザーが作業してください。)解析コードには、これらのYearRange
オブジェクトのいずれかを作成する方法について何も記述されていません。XMLからYearRange
オブジェクトを作成するには、APIを使用してコーディングする必要があります。
NodeParser
の基本原理は、要素とその属性、および子要素を受け取り、これらをまとめて1つのJavaオブジェクトに変えることです。SAX 2.0の標準をベースにしたイベント・ドリブンのパーサーですが、複数のノード・パーサーが共同で動作し1つのオブジェクト・ツリーを作成できるため、SAXそのものよりもはるかに強力です。解析機能は目的別の小さなクラスに整然と分解されているため、コードを全面的に変更することなく簡単に解析ロジックを拡張できます。
各NodeParser
が、XML文書の1つのサブツリーを1つのJavaオブジェクトに変換します。ただしその際、必要なオブジェクトを作成するために、別のNodeParser
に対して下位のサブツリーを処理するよう指示することができます。この手法によってコードのモジュール化が向上し、また、NodeParser
の検索および作成方法が、作成されるコードの拡張性を高くしています。
ParserFactory
はネームスペースだけでなく、生成するJavaオブジェクトの型でも登録されます。したがってUIXは、UINode
の作成用に1つ、BoundValue
の作成用に1つ、ClientValidater
用に1つなど複数のParserFactory
をUIXネームスペースに登録します。FlipperUINodeParserFactory
をFlipperUIExtension
に登録した際には、UINode
作成用のファクトリのみをFlipperネームスペースに登録しました。
NodeParser
がある要素を、たとえばBoundValue
に解析することを決定するたびに、そのNodeParser
はParseContext
に対してBoundValue
を作成できるNodeParser
を指示します。ただしその際、その要素のネームスペースおよびローカル名を渡します。
NodeParser parser = context.getParser(BoundValue.class,
namespaceOfChild,
localNameOfChild);
型(BoundValue.class
)およびネームスペースがParserFactory
を識別し、そのParserFactory
がローカル名を使用してNodeParser
を作成します。コール側では要素のネームスペースに関する情報は必要なく、そのコードと通信する必要はありません。取得するノード・パーサーが正しくBoundValue
を作成するかぎり、その要素の構造や属性を考慮する必要はありません。
つまり、UIX開発者は独自のBoundValue
要素を作成でき、作成した要素はビルトインのBoundValue
要素(<fixed>
または<concat>
など)が受け入れられる場所であればどこでも受け入れられます。また、UINode
解析コードにYearRange
オブジェクトについての情報がない場合でも、このオブジェクトは正常に作成され、years属性は正しく設定されます。
NodeParser
を記述する最も簡単な方法の1つは、LeafNodeParser
クラスのサブクラスを作成する方法です。このクラスは、リーフ要素用のパーサーの記述を簡潔にします。リーフ要素とは、属性はあるが子要素またはプレーン・テキストはない要素です。サブクラスは、1つのメソッドをオーバーライドするだけで作成できます。
abstract protected Object getNodeValue(
ParseContext context,
String namespaceURI,
String localName,
Attributes attrs) throws SAXParseException;
このメソッドには、解析時間のコンテキストをコードに提供するParseContext
、および要素のネームスペースおよびローカル名を指定します。さらに、XML要素のすべての属性の名前を列挙した属性リストを指定します。
package org.example.flipper.ui;
import org.xml.sax.Attributes;
import org.xml.sax.SAXParseException;
import oracle.cabo.share.xml.LeafNodeParser;
import oracle.cabo.share.xml.ParseContext;
class RangeNodeParser extends LeafNodeParser
{
protected Object getNodeValue(
ParseContext context,
String namespaceURI,
String localName,
Attributes attrs)
{
CopyrightBean.YearRange range = new CopyrightBean.YearRange();
String startString = attrs.getValue("start");
if (startString != null)
{
try
{
int start = Integer.parseInt(startString);
range.setStart(start);
}
catch (NumberFormatException nfe)
{
logWarning(context, "\"start\" attribute could not be parsed.");
}
}
String endString = attrs.getValue("end");
if (endString != null)
{
try
{
int end = Integer.parseInt(endString);
range.setEnd(end);
}
catch (NumberFormatException nfe)
{
logWarning(context, "\"end\" attribute could not be parsed.");
}
}
return range;
}
}
これはかなり単純なクラスです。startとendの2つの属性を取得します。どちらかが設定されていれば、それをint
に解析し、YearRange
オブジェクトに設定します。最後に、YearRange
を返します。
文字列の解析に失敗した場合、SAXParseException
をスローするかわりに警告をログに出力している点に注意してください(throws SAXParseException
宣言をすべて削除しています)。一般的には、ユーザーがすべてのエラーを同じページで一度に確認できるためエラーはログに出力することをお薦めします。例外をスローするとその時点で解析が終了するため、ユーザーには最初のエラーのみが表示されます。また、logWarning()
メソッドにより行番号と列番号が自動的にメッセージに追加されるため、ユーザーはエラーの前後関係を把握できます。発生箇所が明記されたエラー・メッセージを出力するパーサーの方が、ユーザーにとっては有用です。
次に、ParserFactory
を記述してこのNodeParser
を作成する必要があります。前述したとおり、この要素にはyearRange
という名前を付けます。
package org.example.flipper.ui;
import oracle.cabo.share.xml.NodeParser;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParserFactory;
class RangeParserFactory implements ParserFactory
{
public NodeParser getParser(
ParseContext context,
String namespaceURI,
String localName)
{
if ("yearRange".equals(localName))
return new RangeNodeParser();
return null;
}
}
これも単純なクラスです。ただしこの数行の中にも、説明の必要な点がいくつかあります。まず、ネームスペースはまったくチェックしていません。このファクトリでは、要素名のみでなくネームスペースの検証も必要であると想定できます。しかし、ファクトリ自体がネームスペースで登録されているため、ネームスペースのチェックは不要となります。次に、ここでは新規のRangeNodeParser
も返しています。NodeParser
のインスタンスは状態を持っているため、通常、共有または再利用はできません。ただし、NodeParser
は非常に軽量でもあります。最後に注意するのは、不明な要素名の場合には単純にnullを返している点です。不明な要素に対しては、中心となる解析コードが自動的にエラーをレポートするため、同じレポート・コードをここで繰り返し記述する必要はありません。
ただし、UINodeType
の定義を一部のみ変更して、この属性がレポートされるようにする必要があります。
class FlipperUINodeParserFactory extends UINodeParserFactory
{
// Skipping down to the bit that changed...
BaseUINodeType copyrightType =
new BaseUINodeType(FlipperConstants.FLIPPER_NAMESPACE,
null,
new Object[]{"year", Integer.class,
"destination", String.class,
"years", CopyrightBean.YearRange.class},
BaseUINodeType.getDefaultUINodeType());
// ...
}
最終的に、このParserFactory
をUIExtension
内に登録します。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
FlipperUINodeParserFactory factory = new FlipperUINodeParserFactory();
factory.registerSelf(manager);
RangeParserFactory rangeFactory = new RangeParserFactory();
manager.registerFactory(CopyrightBean.YearRange.class,
FlipperConstants.FLIPPER_NAMESPACE,
rangeFactory);
}
}
これで、解析コードがYearRange
オブジェクトを必要とした際に、現在の要素がFlipperネームスペースにあれば、RangeParserFactory
にパーサーの生成が要求されます。
RangeNodeParser
クラスの記述はそれほど難解ではありませんでした。しかし、YearRange
クラスに新規のプロパティを追加するたびに、パーサー・クラスの変更が必要です。さらに、システムに新しいクラスを追加するたびに、新規のパーサーを最初から記述する必要があります。作業自体は特に難しいものではありませんが、かなり煩雑です。これよりも効率的な方法があります。
oracle.cabo.share.xml.beans.BeanParser
APIです。BeanParser
を使用すると、UIXで自動的にクラスのコードを検証して属性を識別し、XMLをそのクラスのインスタンスに解析する方法を導き出すことができます。UINodeParser
と同様に、BeanParser
は別のメタデータを使用してそのクラスの型情報を取得します。このメタデータを提供するのが、BeanDef
APIです。BeanDef
は実装を伴わない完全な抽象クラスであり、直接使用することはほとんどありません。かわりに、多くの開発者は記述済の実装であるIntrospectionBeanDef
を使用します。このクラスはBeanParser
APIの重要な部分であり、自動的にBeanをスキャンして属性を検索します。
BeanParser
APIを使用するよう、ParserFactory
を次のように変更します。
package org.example.flipper.ui;
import oracle.cabo.share.xml.NodeParser;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParserFactory;
import oracle.cabo.share.xml.beans.BeanParser;
import oracle.cabo.share.xml.beans.IntrospectionBeanDef;
class RangeParserFactory implements ParserFactory
{
public NodeParser getParser(
ParseContext context,
String namespaceURI,
String localName)
{
if ("yearRange".equals(localName))
return new BeanParser(_sYearRangeDef);
return null;
}
static private final IntrospectionBeanDef _sYearRangeDef =
new IntrospectionBeanDef(CopyrightBean.YearRange.class.getName());
}
これですべてです。ノード・パーサー・クラスは不要になります。独自に記述したパーサーを使用するかわりに、BeanParser
を使用します。その際、YearRange
クラスをポイントするIntrospectionBeanDef
が指定されています。BeanDef
インスタンスをキャッシュして再利用している点に注意してください。IntrospectionBeanDef
の再作成には相当の負荷がかかるため、BeanParser
を使用する際にこれは重要なポイントになります。
解析機能はこれまでより向上します。たとえば、独自に記述したパーサーでは、不明な属性が設定されても検知されませんでした。ユーザーがstartやendのスペルを間違った場合でもそのエラーは警告なしに無視されるため、問題の追跡は非常に困難でした。BeanParser
はこのようなミスを自動的に検出し、ユーザーに対して警告します。
BeanParser
は、単純属性をサポートするだけではありません。BeanParser
では、複雑属性の処理もビルトインでサポートします。BeanParser
はエンベロープ要素を自動的に識別し、その子要素を解析します。BeanParser
は、さらに子要素の配列もサポートします。たとえば、次のようなメソッドを持つBeanがあるとします。
public class YourBean
{
public void setValidaters(ClientValidater[] validaters) { ... }
public ClientValidater[] getValidaters() { ... }
}
BeanParser
では自動的に次のXMLをサポートします。
<yourElement>
<validaters>
<ui:date/>
<ui:decimal/>
<ui:date timeStyle="short"/>
<ui:regExp/>
<oneOfYourClientValidaters/>
</validaters>
</yourElement>
BeanParser
は単にNodeParser
の一種であるため、独自に作成したノード・パーサーとシームレスに共同で稼働することができます。このAPIを使用すると複雑なJavaオブジェクトの解析が簡単になるため、XMLをJavaオブジェクトに解析する必要がある場合は、たとえ解析するオブジェクトまたはプロジェクト全体で他のUIXは使用しない場合でも検討する価値があります。
ここまでで<yearRange>
要素は正しく解析されるようになりましたが、<yearRange>
内でのデータ・バインドはサポートされていません。次のような構文のサポートが有効です。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999" data:end="today@yearSource"/>
</flipper:years>
</flipper:copyright>
または、より複雑な次の構文も考えられます。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999">
<ui:boundAttribute name="end">
<!-- Some really complex set of bound values -->
</ui:boundAttribute>
</flipper:yearRange>
</flipper:years>
</flipper:copyright>
まだLeafNodeParser
を使用している場合は、解析コードに大幅な追加が必要となります。しかしBeanParser
の場合の処理は簡単です。まずend属性がBoundValue
をサポートするよう、YearRange
クラスを編集します。
static public class YearRange
{
public int getStart()
{
return _start;
}
public void setStart(int start)
{
_start = start;
}
public int getEnd()
{
return _end;
}
public void setEnd(int end)
{
_end = end;
}
public void setEndBinding(BoundValue endBinding)
{
_endBinding = endBinding;
}
public int getEnd(RenderingContext context)
{
if (_endBinding != null)
{
Object o = _endBinding.getValue(context);
if (o instanceof Integer)
return ((Integer) o).intValue();
return -1;
}
return _end;
}
private BoundValue _endBinding;
private int _start = -1;
private int _end = -1;
}
ここでは、2つのメソッドを追加しています。パーサーが使用するのはsetEndBinding()
メソッドのみです。ここで重要なのは、このメソッドの名前が、データ・バインドしているプロパティ名にBindingという文字列を追加した名前と同じであり、パラメータとしてBoundValue
をとる点です。2つ目のメソッドは、作成したRenderer
から使用するメソッドで、end属性を取得します。
次に、解析コードを変更します。
import oracle.cabo.share.xml.NodeParser;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParserFactory;
import oracle.cabo.share.xml.beans.BeanParser;
import oracle.cabo.ui.xml.parse.UIBeanDef;
public class RangeParserFactory
{
public NodeParser getParser(
ParseContext context,
String namespaceURI,
String localName)
{
if ("yearRange".equals(localName))
return new BeanParser(_sYearRangeDef);
return null;
}
static private final UIBeanDef _sYearRangeDef =
new UIBeanDef(CopyrightBean.YearRange.class.getName());
}
太字で表示しなければ気付かない程度の変更です。IntrospectionBeanDef
のかわりにUIBeanDef
を使用しています。これですべてです。これによって、単純なデータ・バインド用のdata:構文と、複雑なデータ・バインド用の<ui:boundAttribute>構文の両方が完全にサポートされます。どちらの構文も、次のような明示的に設定された値への自動的リセットをサポートします。
<flipper:copyright>
<flipper:years>
<flipper:yearRange start="1999" end="2001"
data:end="today@yearSource"/>
</flipper:years>
</flipper:copyright>
また、不明な属性が設定された場合に警告が出されるのと同様に、データ・バインドをサポートしていないプロパティを開発者がデータ・バインドすると警告が発せられます。たとえば、YearRange
にsetStartBinding()
メソッドは追加していません。したがって、data:start
属性を追加すると、この属性はデータ・バインドできないことを知らせる警告を受け取ることになります。
LeafNodeParser
、BeanParser
またはUINodeParser
の3つの既存パーサーが、いずれも目的にあまり適さないことがあります。このような場合は、NodeParser
APIについて理解する必要があります。警告のロギング、必要な属性の取得などの多数のユーティリティ・メソッドを提供するBaseNodeParser
からサブクラスを作成することを強くお薦めしますが、NodeParser
の動作は理解しておく必要があります。
UIX解析APIは、次のようにXML要素のツリーを反復処理していきます。
UINode
(またはコール側のコードが求めるその他の型のオブジェクト)を作成するParserFactory
を使用してNodeParser
が取得されます。
NodeParser.startElement()
がコールされ、この最上位の要素の属性が処理されます。
NodeParser.startChildElement()
をコールします。このメソッドでは、子の処理に必要なNodeParser
を識別する必要があります。コールは、次のいずれかの方法で処理されます。
ParserManager
に、NodeParser
を要求します。NodeParser
は、対象となる子要素(およびその子すべて)の処理を受け持ちます。処理が終了すると、親のNodeParser.addCompletedChild()
でNodeParser
がコールされ、処理結果が組み込まれます。
NodeParser
では処理できない場合に必要となります。例としては、複雑属性で使用するエンベロープ要素や、名前の付けられた子があります。
NodeParser
の実装を返します。これは、単にthisを返す方法よりも簡単で、目的を絞った方法です。
startChildElement
がthisを返した場合、UIXではNodeParser.endChildElement()
がコールされます。
NodeParser.addCompletedChild()
がUIXでコールされます。子要素のパーサーにより作成されたオブジェクトがこのメソッドに渡されるため、パーサーは必要に応じてそのオブジェクトを格納できます。解析に失敗した場合には、このメソッドにnullが渡される場合もあるため、このメソッドの実装では、その場合への対処も必要です。
NodeParser.addText()
が1回コールされます。(これらのコールは、startChildElement()
へのコールとendChildElement()
へのコールの間に、想定される文書の順で挿入されます。)SAX APIと同様、XML文書では一続きのテキストに見えるものでも、NodeParser.addText()
が複数コールされることもあるため、開発者は、NodeParser.endElement()
、NodeParser.endChildElement()
またはNodeParser.startChildElement()
のいずれかがコールされるまでテキストを繰り返す必要があります。
NodeParser.addText()
と同じ方法でNodeParser.addWhitespace
に渡されます。ほとんどのパーサーはこのメソッドを無視しますが、空白文字を考慮するパーサーは、引数をaddWhitespace()
に渡してNodeParser.addText()
をコールします。
NodeParser.endElement()
がコールされます。このメソッドは、完全に構成されたオブジェクトを表すオブジェクトを返す必要があります。
ここまでで、UIXが拡張可能であることを説明しました。既存の型に対する新しい要素の追加や、システムへの新しい型の追加は簡単です。しかし、まだ説明していない拡張方法が1つあります。ParserExtension
APIを使用すると、別の開発者が作成したコンポーネントに、属性や子要素さえ追加できます。
このAPIについて説明するため、Project Flipper用の様々なWebサイトをポイントするすべてのリンクBean、ボタンBeanなどに、flipper:destination
属性へのサポートを追加します。受け付ける値は次の2つです。flipper:destination
をinternal
に設定した場合、リンク先はhttp://flipper.example.orgとなります。external
に設定した場合、リンク先はhttp://www.example.org/flipperとなります。したがって、たとえば次のように記述できます。
<link text="Go to the Flipper Home" flipper:destination="internal">
ParserExtension
は、デフォルトの解析コードで処理されなかった属性および要素を通知し、その値をDictionary
に集めます。親要素の処理が終了し(つまりNodeParser.endElement()
がコールされ)、Javaオブジェクトが返されると、ParserExtension
はそのJavaオブジェクトと値のDictionary
を取得します。取得したオブジェクトは変更することも、または完全に置き換えることも可能です。次に、ここで使用するParserExtension
のコードを示します。
package org.example.flipper.ui;
import java.util.Dictionary;
import oracle.cabo.share.xml.BaseParserExtension;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.ui.MutableUINode;
import oracle.cabo.ui.UIConstants;
public class FlipperParserExtension extends BaseParserExtension
{
public Object elementEnded(
ParseContext context,
String namespaceURI,
String localName,
Object parsed,
Dictionary attributes)
{
if (parsed instanceof MutableUINode)
{
_extendUINode(context, (MutableUINode) parsed, attributes);
}
else
{
logWarning(context,
"Controller extensions not supported on " +
parsed + " objects.");
}
return parsed;
}
private void _extendUINode(
ParseContext context,
MutableUINode node,
Dictionary attributes)
{
Object destination = attributes.get("destination");
if (destination != null)
{
if ("external".equals(destination))
node.setAttributeValue(UIConstants.DESTINATION_ATTR,
"http://www.example.org/flipper");
else if ("internal".equals(destination))
node.setAttributeValue(UIConstants.DESTINATION_ATTR,
"http://flipper.example.org/");
}
}
}
このコードを冒頭から順に確認します。
BaseParserExtension
を拡張している点に注意してください。このベース・クラスは、デフォルト実装のすべてのメソッドと、解析時の警告をロギングする便利なメソッドを提供します。
elementEnded()
メソッドのみです。このメソッドは、拡張している要素が終了するとコールされ、次の5つのパラメータが渡されます。
ParseContext
。
Dictionary
。各拡張属性および要素の値が、属性名または要素名をキーとしてここに格納されます。
MutableUINode
インタフェースを実装することを検証します。ParserExtension
とParserFactory
の大きな違いの1つは、拡張が型では登録されない点です。かわりに、型に関係なく、ネームスペース全体に対して1つのParserExtension
を使用します。したがって、ここで型をチェックし、正しくない場合には警告をログに出力します。
elementEnded()
は常にparsedを返します。これは、ノードを置き換えているのではなく、単に修正しているだけであることを意味します。これは最も安全な戻り値です。nullを返すことも可能ですが、その場合オブジェクト全体が削除されます。
_extendUINode()
メソッドで、destinationの値を取得およびチェックし、その内容に応じてMutableUINode
のリンク先を設定します。
ParserExtension
はParserManager
に登録されるため、再度UIExtension
を変更する必要があります。
package org.example.flipper.ui;
import oracle.cabo.ui.RendererFactory;
import oracle.cabo.ui.UIExtension;
import oracle.cabo.ui.laf.LookAndFeel;
import oracle.cabo.share.xml.ParserManager;
public class FlipperUIExtension implements UIExtension
{
public void registerSelf(LookAndFeel laf)
{
// Get the RendererFactory
RendererFactory factory = FlipperRendererFactory.sharedInstance();
// And register it on this look-and-feel.
laf.getRendererManager().registerFactory(FlipperConstants.FLIPPER_NAMESPACE,
factory);
}
public void registerSelf(ParserManager manager)
{
FlipperUINodeParserFactory factory = new FlipperUINodeParserFactory();
factory.registerSelf(manager);
RangeParserFactory rangeFactory = new RangeParserFactory();
manager.registerFactory(CopyrightBean.YearRange.class,
FlipperConstants.FLIPPER_NAMESPACE,
rangeFactory);
FlipperParserExtension extension = new FlipperParserExtension();
manager.registerExtension(FlipperConstants.FLIPPER_NAMESPACE,
extension);
}
}
ParserExtension
は、子要素もサポートできます。たとえば、現在のUIX-BC4J統合APIでは、次のような構文をサポートします。
<page xmlns="http://xmlns.oracle.com/uix/controller"
xmlns:bc4j="http://xmlns.oracle.com/uix/bc4j">
<bc4j:registryDef>
...
</bc4j:registryDef>
</page>
UIX Controllerである<page>
要素には<bc4j:registryDef>
要素に関する情報は何もなく、またその必要もありません。ParserExtension
で子要素をサポートするには、正しいNodeParser
を返すようstartExtensionElement()
メソッドを実装します。このパーサーが返すオブジェクトは、最終的にelementEnded()
へ渡されるDictionary
に格納されます。
ParserExtension
APIには、1つだけ制限があります。再コールすると、各NodeParser
はXML文書のサブツリーを解析します。拡張属性および要素は、これら各サブツリーのルートのUIX XML要素でのみサポートされます。たとえば、次のようなUIX Components XMLがあるとします。
<stackLayout>
<separator>
<spacer>
<boundAttribute name="height">
<fixed text="5"/>
</boundAttribute>
</spacer>
</separator>
<contents>
<styledText text="First"/>
<styledText text="Second"/>
</contents>
</stackLayout>
拡張属性および要素は、<stackLayout>
、<spacer>
、<styledText>
および<fixed>
の各要素へは追加できますが、<separator>
、<contents>
または<boundAttribute>
へは追加できません。概して、ParserExtension
が変更できるのはJavaオブジェクトに直接対応する要素のみであり、UINode
およびBoundValue
はこれに該当しますが、エンベロープ要素は該当しません。
UIX解析APIは、どのUIX Componentsにも、またUIX Controllerにも依存しておらず、前述したように拡張性およびビルトイン・イントロスペクション機能があるため、アプリケーション全体で他のUIXが1つも必要でない場合でもXMLを解析する優れた手段となります。XML解析APIの基本についてはほとんど説明しましたが、ここではAPIを単独で使用する場合に必要ないくつかの手順について説明します。
XML解析APIを単独で使用するためには、次のオブジェクトを準備する必要があります。
ErrorLog
: 警告またはエラーの出力先を識別します。指定しない場合、エラーはコンソールに出力されます。
ParseContext
: ParseContextImpl
のインスタンスをそのまま使用します。
ParserManager
: 必要な各ParserFactory
およびParserExtension
を登録する必要があります。
NameResolver
: このインタフェースはXML文書のソースの検索方法を定義します。単独のファイルの解析には、単純なSAXのInputSource
で十分ですが、uiXML解析APIは別のXMLファイルを含むXMLファイルをサポートする必要があり、これらのファイルを元のファイルとの関連で検索する手段が必要です。NameResolver
の実装には、次のものがあります。
DefaultNameResolver
: ベース・ファイルのディレクトリまたはURLのどちらか一方または両方を基準に、ファイルを検索できます。
ServletNameResolver
: サーブレット・コンテキストを基準にファイルを検索できます。
ClassResourceNameResolver
: (JAR内などの)クラスを基準にファイルを検索できます。
これらのオブジェクトの準備ができたら、解析するファイルの名前と、作成するオブジェクトの型も必要です。後は、XMLUtils.parseSource()
を1回コールするのみです。
import oracle.cabo.share.xml.XMLUtils;
import oracle.cabo.share.xml.ParseContext;
import oracle.cabo.share.xml.ParseContextImpl;
import oracle.cabo.share.xml.ParserManager;
import oracle.cabo.share.io.NameResolver;
import oracle.cabo.share.error.ErrorLog;
...
// Get the objects we need
ErrorLog log = ...;
ParseContext context = new ParseContextImpl(log);
ParserManager manager = ...;
NameResolver resolver = ...;
// And parse.
YourType result = (YourType)
XMLUtils.parseSource(context,
null,
manager,
resolver,
"yourFile.xml",
YourType.class);
組込みファイルの結果も必要な場合は、解析コードでXMLUtils.parseInclude()
をコールします。このメソッドに必要なパラメータは、すでに使用しているParseContext
、必要なファイルの名前および作成するオブジェクト型の3つです。その他は、解析コードが処理します。