Java AWT: データ転送


最終更新日:1997 年 8 月 13 日

目的

典型的な GUI ユーザは、カット、コピー、ペースト、およびドラッグ&ドロップのような操作を使用して、アプリケーション間でデータが転送できることを期待しています。現在の Java 環境で、このための唯一の機構は、AWT ネイティブウィジェット (TextField、TextArea) がデフォルトで提供している機能を介するものです。しかし、これらのネイティブウィジェットが対応していない所で、そのような操作を有効にする必要のあるケースが多くあります。そのため、Java プラットフォームは、基本的なデータ転送機能を使用可能にするために、API を提供する必要があります。このドキュメントでは、様々な転送プロトコルをその上に構築できる Java オブジェクトに対するデータ転送機能の基準線となるものを定義します。このドキュメントではまた、これら上位レベル転送プロトコルの 2 つの API、クリップボードおよびドラッグ&ドロップについて説明します。

注: クリップボード API は JDK 1.1 に含まれていますが、ドラッグ&ドロップ API は (1.1 の時間的制約により) 次のバージョンに含まれる予定です 。ここでは、JavaBeans ドキュメントから参照でき、できるだけ早くフィードバックが得られるように、ドラッグ&ドロップに関するドキュメントが含まれています。

Java データ転送

転送 API の設計目標

データ転送 API の設計目標は次のとおりです。

転送 API の概要

Transferable オブジェクト

API は、転送される次のオブジェクトが実装するインタフェースを軸にしています。
	java.awt.datatransfer.Transferable
Transferable オブジェクトは、書式 (データフレーバと呼ばれる) の並びを提供し、この並びに対して、もっとも詳細に記述するフレーバからもっとも簡素なものまで、順序付けられたデータを提供できなければなりません。また、特定のフレーバで要求されたとき、(オブジェクト参照の形式で) データを返すことができなければなりません。あるいは、そのフレーバがサポートされていない場合、またはそのデータがもはや使用できない場合には、例外をスローします。

よく用いられるデータ型に Transferable インタフェースを実装した簡易クラスが、開発者がこれらの共通型の転送を容易にできるように提供されています。例を示します。

	java.awt.datatransfer.StringSelection

この API の目的は、特定の要素またはデータ型を転送可能にする作業に入ったとき、上位レベル転送プロトコル (クリップボード、ドラッグ&ドロップなど) のどれかを使用して、簡単にそれを渡せることを保証することです。

データフレーバ

共通データ転送操作 (クリップボート、ドラッグ&ドロップ) の典型的様相とは、データを転送するフレーバをめぐっての、提供者と要求者の間の交渉です。たとえば、html テキストがブラウザで選択され、ほかのワードプロセッサアプリケーションにコピーされ、ペーストされるとき、ペースト操作の可能な対象アプリケーションの数を最大にするために、ブラウザは通常データを複数のフレーバ (通常、html フォーマットのテキストおよびプレーンな ASCII) で提供します。

この交渉には、個別のアプリケーションでこれら様々なフレーバとデータ型が一意に定義され認識できる、データ型分類用名前空間の定義が必要です。「フォーマット」などの使用頻度の高い用語との混同を避けるために、「フレーバ」という用語を使ってこの概念を表します。

データフレーバは、アプリケーション間のフレーバ交渉と転送を可能にするために、特定のフレーバに関する必要な全情報をカプセル化したオブジェクトによって表します。

	java.awt.datatransfer.DataFlavor

この情報には、フレーバの論理名 (プログラムからの識別を可能にする)、ユーザに表示する名前 (ユーザに示すために使用し、各言語対応が可能なものもある)、データを実際に転送するオブジェクトクラスを定義するために使用する表現クラスなどが含まれます。

論理名としての MIME 形式

データフレーバの論理名を表すために、MIME 形式/サブ形式のパラメータ表記の使用を計画しています (MIME 形式の仕様については、RFC 1521 を参照)。Java 環境に対しては、「他のデータ型分類用名前空間」を作成するのではなく、既存のインターネット標準を採用する方が好ましいと考えます。

MIME 形式の登録は、現在 Internet Assigned Numbers Authority (IANA) というサードパーティが行なっており、公開データフォーマット用に使用する標準形式/サブ形式名を簡単に検索できるようになっています。あまり一般的でないフォーマットに対して新しい MIME 形式/サブ形式名を定義するためには、公式の登録は必要ありません (このような公式の要件が、基本的な Java データ転送では必要ないのは好ましい)。新しい形式名は公式な登録はせずに、名前の前に "x-" を追加して作成できます。

表現クラス

データを実際に返す Transferable メソッド
	Object getTransferData(DataFlavor flavor)
は、(柔軟性を最大にするために) 「Object」クラスのインスタンスを返すように緩やかに定義されているために、DataFlavor が定義した表現クラスは転送操作の終端では重要になります。これにより、返されたオブジェクトがあいまいさなしにデコードされるようになるからです。

現在の DataFlavor クラスは、次の 2 つの一般的なデータフレーバを定義します。

	     MIME-type="application/x-java-serialized-object; class=<implemenation class>"
	     RepresentationClass=<implemenation class>

        たとえば、AWT GUI コンポーネントを表す DataFlavor を次に示します。

	     MIME-type="application/x-java-serialized-object; class=java.awt.Component"
	     RepresentationClass=java.awt.Component
	 転送操作の要求側がこのフレーバのデータを求める場合、
         Component クラスのインスタンスが戻されます。
	     MIME-type="application/<mime-subtype>"
	     RepresentationClass=java.io.InputStream

        たとえば、RTF テキストを表す DataFlavor を次に示します。

	     MIME-type="application/rtf"
	     RepresentationClass=java.io.InputStream
	 転送操作の要求側がこのフレーバのデータを求める場合、
         RTF 形式のテキストを読み込み
         および解析できる InputStream のインスタンスが戻されます。

与えられた MIME 形式 (上の 2 つ目のタイプ) については、Java プログラムでは異なる表現クラスを持つ複数のフレーバを自由に作成できます。たとえば、上記の MIME 形式 application/rtf にフレーバを提供することに加えて、プログラムは別のフレーバを指定することもできます。

        たとえば、RTF テキストを表す DataFlavor を次に示します。

	     MIME-type="application/rtf"
	     RepresentationClass=foobar.fooRTF

カプセル化の原理

単なる MIME 形式文字列ではなく、データフレーバを定義するためにクラスを使用することは、過剰であると思えます。しかし、このカプセル化は次の理由から利点があるといえます。
  1. 使用性 - データフォーマットに対するユーザ表示名のように、データフォーマットに関連付けるべき属性があります。これは、論理文字列にだけ関連付けることができます。DataFlavor オブジェクトについては、これらの属性は直接カプセル化している DataFlavor オブジェクトに関連付けられています。
  2. 利便性 - DataFlavor オブジェクトを使用することにより、データフォーマットの比較処理が容易になります。isMimeTypeEqual() メソッドはプログラマの負担を軽減します。そうでない場合、プログラマが MIME 形式を正規の形式に、必ず変換しなくてはなりません (MIME 形式、サブ形式、パラメータ名には大文字小文字の区別がなく、パラメータはどんな順序でも表示されるため、問題になる)。
  3. 拡張性 - 抽象化によって、必要なときにいつでもフレーバ属性およびフレーバメソッドを拡張できるようになります。
  4. 性能 - Motif および Windows は、データフォーマットを識別するために、文字列ではなくアトムを使用します。各データフォーマットを識別するためにオブジェクトを使用することで、必要なアトムの収容数を減らすとともに、MIME 形式名に対応するアトムをキャッシュする手頃な場所が与えられます。同様に、text/plain などの MIME 形式を、Windows の CF_TEXT や Macintoshの TEXT などのプラットフォーム固有のクリップボードフォーマットに割り当てた結果は、キャッシュが可能です。

DataFlavor の概念は、複雑で混乱を引き起こすように思えます。しかし、意図しているのは、共通に使用するデータフレーバのセットを定義して、これを開発者ができるだけ使用しやすくすることです。

複数項目の転送

転送プロトコルが、単一の転送操作で、複数の別個のデータの転送をサポートすること (すなわち、ファイルマネージャアプリケーションから、複数のファイルのアイコンをドラッグ&ドロップすること) は、一般的ではありません。転送 API は、複数のデータ項目の同時転送をある形式でサポートする必要があります。現在の提案は、個別データオブジェクトの集まりを扱うことができる Transferable を実装して、この機能をカプセル化することです。この計画は現在綿密に調査されており、この提案を将来改訂するときに詳細に検討されます。

Transferable オブジェクト作成のコード例

次のコードは StringSelection クラスソースを示します。これはプレーンな Unicode テキストを転送できるクラスを作成するための API の使用方法の例です。

package java.awt.datatransfer;

import java.io.*;

/**
 * A class which implements the capability required to transfer a
 * simple java String in plain text format.
 */
public class StringSelection implements Transferable, ClipboardOwner {

    final static int STRING = 0;
    final static int PLAIN_TEXT = 1;

    DataFlavor flavors[] = {DataFlavor.stringFlavor, DataFlavor.plainTextFlavor};

    private String data;
						   
    /**
     * Creates a transferable object capable of transferring the
     * specified string in plain text format.
     */
    public StringSelection(String data) {
        this.data = data;
    }

    /**
     * Returns the array of flavors in which it can provide the data.
     */
    public synchronized DataFlavor[] getTransferDataFlavors() {
	return flavors;
    }

    /**
     * Returns whether the requested flavor is supported by this object.
     * @param flavor the requested flavor for the data
     */
    public boolean isDataFlavorSupported(DataFlavor flavor) {
	return (flavor.equals(flavors[STRING]) || flavor.equals(flavors[PLAIN_TEXT]));
    }

    /**
     * If the data was requested in the "java.lang.String" flavor, return the
     * String representing the selection, else throw an UnsupportedFlavorException.
     * @param flavor the requested flavor for the data
     */
    public synchronized Object getTransferData(DataFlavor flavor) 
			throws UnsupportedFlavorException, IOException {
	if (flavor.equals(flavors[STRING])) {
	    return (Object)data;
	} else if (flavor.equals(flavors[PLAIN_TEXT])) {
	    return new StringReader(data);
	} else {
	    throw new UnsupportedFlavorException(flavor);
	}
    }

    public void lostOwnership(Clipboard clipboard, Transferable contents) {
    }
}


クリップボード

アプリケーション内部およびアプリケーション間の両方で、データ転送の手段として、カット、コピー、ペースト操作を提供することは、現在ほとんどのアプリケーションに一般的に期待される機能です。JDK 1.0 の AWT API では、現在 (ネイティブテキストウィジェットでデフォルトとして存在する以外)、この基本的なクリップボード操作の機能を何も提供していません。そしてこの機能は、Java プログラムをユーザ環境にうまく統合するためには欠かすことができません。

クリップボード API の設計目標

この API の設計目標は次のとおりです。

クリップボード API の概要

クリップボード API は、Java プログラム内で、カット、コピー、ペースト操作を実装する標準機能を提供します。異なるクリップボードを異なる目的で作成し名前付けできます (プログラムは自分自身の私有クリップボードの作成を希望する可能性がある)。しかし、(System と名付けられた) 単一のクリップボードインスタンスがあり、これは Java プログラムが Java 以外のアプリケーションとのデータ転送を可能にするために、プラットフォームのネイティブ機能とのインタフェースを持つものです。

クリップボードアーキテクチャは、Java データ転送 API が定義するデータ転送機構に依存しています。クリップボード API には、標準クリップボード用のデータ転送モデルを実装する次の単一のクラスがあります。

	java.awt.datatransfer.Clipboard 

データをクリップボードに書き込んでいるクラスが実装する次のインタフェースがあります。

	java.awt.datatransfer.ClipboardOwner

Clipboard クラスは、クリップボードとの読み込みおよび書き込みのための次の 2 つの基本的メソッドを提供します。

	void setContents(Transferable content, ClipboardOwner owner)
	Transferable getContents(Object requestor)

ClipboardOwner インタフェースは次の単一のメソッドから成り、別のオブジェクトがクリップボードの所有権を主張した場合に呼び出されます。

	void lostOwnership(Clipboard clipboard)

開発者が一般的なデータ型にクリップボード操作を実装する作業を簡単に行えるように、標準的な方法で ClipboardOwner インタフェースを実装する簡易クラスが提供されます。

	java.awt.datatransfer.StringSelection

java.awt.Toolkit 内の次のメソッドは、ネイティブプラットフォーム機能とのインタフェースを持つクリップボードのインスタンスへのアクセスを提供します。

	Clipboard getSystemClipboard();

クリップボード API を使用したカット、コピー、ペーストの実装

「カット」および「コピー」を実装するプログラムの一般的操作手順を次に示します。
  1. カットあるいはコピーするデータの、Transferable インタフェースを実装するオブジェクトのインスタンスを生成します。
  2. ClipboardOwner インタフェースを実装するオブジェクトのインスタンスを生成します (Transferable を実装するオブジェクトと同じ可能性がある)。
  3. この Transferable および ClipboardOwner オブジェクトを、クリップボードの setContents() メソッドに渡します。これでクリップボードの所有権が確立します。
  4. 別のオブジェクトがクリップボードの所有権を主張した場合に呼び出される、クリップボード所有者の lostOwnership() メソッドへの呼び出しを処理します。ただし、所有権を失うときに何もする必要がない場合、このメソッドは空でかまいません。

「ペースト」を実装するプログラムの一般的操作手順を次に示します。

  1. Transferable インタフェースを実装するオブジェクトにハンドルを返す、getContents() メソッドを使用してクリップボードの内容を要求します。
  2. getTransferDataFlavors() メソッドを使用して、transferable オブジェクトからデータを使用できるフレーバのリストを要求します。
  3. transferable オブジェクトの getTransferData() メソッドを使用して、希望する使用可能なフレーバのデータを取得します。

遅延データモデル

クリップボードモデルは、クリップボードの内容があるレベルで持続することを示しますが、カットおよびコピー時に不要な性能の低下を避けるために、データ消費者が要求するまで、データが実際には所有者から転送されない可能性があります (これは「遅延」データモデルと呼ばれる)。クリップボードの所有者が破壊された場合、提供者が終了したあとにデータの使用可能性を保証するために、そのデータを取得しクリップボード内に格納しようと試みることが必要です。しかし、クリップボードの所有者は、転送がいつ起こるかという仮定をしてはなりません。そして lostOwnership() メソッドが呼び出されるまで、データを使用可能にするように試みなければなりません。無期限にデータを使用可能にしておくことができない場合、データが要求され、それが使用可能でない場合は、IOException をスローする必要があります。

セキュリティ

ダウンロードされたアプレットがネイティブシステムのクリップボードへアクセスできるようにすることに関して、その内容に対する読み込み可能 (これは微妙な問題になりえる)、および書き込みという点から、明確なセキュリティ上の問題があります。初期には、信頼されていないアプレットは、システムのクリップボードに直接アクセスすることは認められません (クリップボードアクセス用の SecurityManager メソッドがある)。操作の初期化がユーザ生成イベント (「カット」または「ペースト」キーボードイベントなど) から行われた場合、信頼されていないアプレットがクリップボード操作に参加できるようにする機構を、現在調査中です。

クリップボード API を使用したサンプルコード

単なるテキストのカット、コピー、ペーストを実装しているプログラムの簡単な例を次に示します。

注:簡単にするために、コピーとペーストの操作元および操作先として TextArea を使用します。ほとんどのプラットフォームで、カット、コピー、ペーストはすでにネイティブピア内の TextArea および TextField 用に実装されています。

								       

import java.awt.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;

public class ClipboardTest extends Frame 
                           implements ClipboardOwner, ActionListener {

    TextArea srcText, dstText;
    Button copyButton, pasteButton;

    Clipboard clipboard = getToolkit().getSystemClipboard();

    public ClipboardTest() {
        super("Clipboard Test");
        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        setLayout(gridbag);

        srcText = new TextArea(8, 32);
        c.gridwidth = 2;
        c.anchor = GridBagConstraints.CENTER;
        gridbag.setConstraints(srcText, c);
        add(srcText);

        copyButton = new Button("Copy Above");
        copyButton.setActionCommand("copy");
        copyButton.addActionListener(this);
        c.gridy = 1;
        c.gridwidth = 1;
        gridbag.setConstraints(copyButton, c);
        add(copyButton);

        pasteButton = new Button("Paste Below");
        pasteButton.setActionCommand("paste");
        pasteButton.addActionListener(this);
        pasteButton.setEnabled(false);
        c.gridx = 1;
        gridbag.setConstraints(pasteButton, c);
        add(pasteButton);

        dstText = new TextArea(8, 32);
        c.gridx = 0;
        c.gridy = 2;
        c.gridwidth = 2;
        gridbag.setConstraints(dstText, c);
        add(dstText); 

        pack();
    }

    public void actionPerformed(ActionEvent evt) {
        String cmd = evt.getActionCommand();

        if (cmd.equals("copy")) { 
           // Implement Copy operation
           String srcData = srcText.getText();
           if (srcData != null) {
                StringSelection contents = new StringSelection(srcData);
                clipboard.setContents(contents, this);
                pasteButton.setEnabled(true);
            }
        } else if (cmd.equals("paste")) {
            // Implement Paste operation
            Transferable content = clipboard.getContents(this);
            if (content != null) {
                try {
                    String dstData = (String)content.getTransferData(
                                                DataFlavor.stringFlavor);
                    dstText.append(dstData);
                } catch (Exception e) {
                    System.out.println("Couldn't get contents in format: "+
                           DataFlavor.stringFlavor.getHumanPresentableName()); 
                }
             }
        }
    }
    public void lostOwnership(Clipboard clipboard, Transferable contents) {
       System.out.println("Clipboard contents replaced");
    }
     public static void main(String[] args) {
        ClipboardTest test = new ClipboardTest();
        test.show();
     }
}

ドラッグ&ドロップ

以前このドキュメントに含まれていたドラッグ&ドロップ仕様は、さらに発展して Java 2 SDK ドキュメント内の ドラッグ&ドロップドキュメント となりました。


コメントの送付先: java-awt@java.sun.com
Copyright © 1996, 1999, Sun Microsystems, Inc. All rights reserved.