パスの有効な文字の制限を取り除く
概要:
org.eclipse.core.runtime.IPathのデータ構造とその標準実装、 org.eclipse.core.runtime.Pathでは基本的なファイルシステムのファイル名に制限があります。これらの制限により、有効なファイルをEclipse Workspaceにて追加できなくなることがあります。このドキュメントはEclipse3.0の制限を述べ、これらの制限を改善する提案を行ないます。最終更新日: 2004年10月18日
背景
IPathは、org.eclipse.core.runtimeプラグインによって抽象的なデータ構造として提供され、以下の部分からなります。
- オプションのデバイス(文字列)
- 0もしくはそれ以上のセグメント (Stringで表されます)
- 前方、後方のオプションと、セパレータのUNC
IPathとStringインスタンスの間の変換を容易にするために、 IPathは、コロン(:)をデバイスのデリミタとして、そして、スラッシュとバックスラッシュ('/'と’\')を文字セグメントのデリミタとして予約語にしています。
IPath.isValidSegmentのJavaDocAPIは、全ての制限の概要を記述しています。
- 空の文字列は有効ではありません
- コロン(":")文字を含む任意の文字列は有効ではありません
- スラッシュ("/")文字を含む任意の文字列は有効ではありません
- バックスラッシュ("\")文字を含む任意文字列は有効ではありません
- 最初か最後に空白文字がある任意の文字列は有効ではありません
- 他のすべての文字列が有効である
これとUnixのファイル名の制限と比較してください。(IEEE Std. 1003.1-2001, section 3.169 Filenameの”Base Definitions”に定義されています。)
- 空の文字列は有効ではありません
- スラッシュ('/')文字を含む任意の文字列は有効ではありません
- "."と".."は特別な意味を持つ
- nullバイトを含む任意の文字列は有効ではありません
- 他のすべての文字列が有効である
このように、EclipseのIPathをUnixファイル名で使う時、 '\'もしくは':'、ファイル名の最初か最後に空白が有るファイル名を使用できません。
解決策の提案
最初か最後の空白と、’\'文字を含む制限については、パスの新しいコンストラクタを使用する事により容易に解決ができます。':'文字の制限についてはもっと難しく、パスデリミタを管理しているオペレーティングシステムのサポートが必要になります。
解決には、IPathの興味深い2つのカテゴリを把握する必要があります。
- コードは、プラットフォーム固有の表現を必要とします。言い換えると、誰かが実際にファイルのStreamを開きたい、削除したい など。これらの本質はIPathとjava.io.Fileとの変換が必要になる事です。
- 読込みしたり、保存したり、ファイルシステムのパスを操作したりするコードは、プラットフォーム固有のコードは必要ありません。特に、パスのシリアライズの表現は、異なったプラットホームで読込み、解析する為に必須です。
(通常は、プラットフォーム特有の接頭辞のパス形式と関連しています)
IPathのtoStringメソッドではすでにこれらの2つの使用を認めています 。標準
のtoStringメソッドは、
Stringをプラットフォームを問わない文字として扱い
Pathを作成します。
toOSStringメソッドは、
java.io.Fileにて扱える、もしくは、直接ファイルシステムを扱う別のAPIが扱えるコードを作成します。
今回の提案では、既存の2つのtoStringメソッドの逆で、IPathを作成する2つのコンストラクタを提供します。
Path.fromOSString: プラットフォーム固有文字を解析するFactoryメソッドです。例えば、IPath.toOSStringの値や、java.io.File.getAbsolutePathの値を解析するでしょう。 Path.fromPortableString :このFactoryメソッドは、 IPath.toPortableStringの値のようなプラットフォーム中立な文字列をデコードします。
既存のtoStringメソッドのふるまいを変えると、あまりに多くの影響があるため、新しくtoPortableStringがプラットフォームを問わないパス表現を作成する為のメソッドとして導入されます。既存toStringメソッドは変更されません。
ほとんどのクライアントは、プラットフォーム固有のパス形式を使用します。Portableな形でパスをシリアライズする必要が有る時、パスはプラットフォーム中立な表現で to/from を変換することができます。
プラットフォーム中立なパスのコード(IPath.toPortableString)は、名前のセグメントにスラッシュ('/')以外の全ての文字を入れる事ができ、1つのコロン文字をデバイスのセパレータとして含める事ができます。パスセグメントのコロン文字は、doublingでエスケープされます。(1コロンは2コロンになります)以下は、Windowsのファイルシステムパスと、プラットフォーム中立なコードとの対応のいくつかの例です。
- "C:\folder\file.txt" は "C:/folder/file.txt" になる
- "C:folder\file.txt" は "C:folder/file.txt" になる
- "C:\folder\" は "C:/folder/" になる
コロン文字を含んでいるセグメント以外においては、プラットフォーム中立の場合と同一に見えます。以下は、Unixパスと、
IPath.toPortableStringによるコードです。
- "/etc/" は "/etc/" になる
- "/etc/passwd" は "/etc/passwd" になる
- "/etc/timeNowIs4:25:12PM" は "/etc/timeNowIs4::25::12PM" になる
- "c:/folder/file.txt" は "c::/folder/file.txt" になる
UNCパスは一般的に、デバイスを持っていませんが、始めに2つのセパレータがあるのは同じです。
- "//Server/Volume" は "//Server/Volume" になる
- "//Server/TimeIs4:25:12PM" は "//Server/TimeIs4::25::12PM" になる
もし、UNCパスがデバイスを持っていた場合、 スラッシュに先行します。
- "C://Server/Volume" は "C://Server/Volume" になる
- "C://Server/TimeIs4:25:12PM" は "C://Server/TimeIs4::25::12PM" になる
このプラットフォーム中立のコードは、全てのサポートされたプラットフォームであらゆるパスをコード化します。最も重要なのは、このtoPortableStringの実装は、Eclipse3.0で作成できるすべてのパスにて完全後方互換です。これは、以前にパスをシリアライズする為にtoStringを使っていても、ファイルフォーマットの移行無しで、新しいtoPortableString/fromPortableStringメソッドを使う事が出来るという事です。
プラットフォーム固有のPathFactoryは、全てのパスを解析するのに必要な、最低限のプラットホーム特有の必要条件を要求します。例えば、Windowsの実装では、全てを解析して、最初の":"をデバイスセパレータ、'/' と '\' パスのセパレータと見なします。他のルールはつけられません。このように、前か後ろに空白スペースがあるものは防ぐというパスの制限は、あらゆるプラットフォームでもはや必要ありません。
前述のように、プラットフォームの正しい文字や名前の細かな検証は行わないでしょう。中には、外部システムをプラットフォームにマウントするための、CygwinやSambaなどの技術を使用する人もいます。これらの状況で、ローカルファイルシステムのパス名規則は、適用されません。これらのユーザを完全にサポートするのが難しいのですが、あらゆるプラットフォーム固有の検証をしても、これらのユーザの為には更なる問題を引き起こします。unamiguouslyパスを解析するための絶対最低必要条件を課すことで、ユーザの大半はほとんどのの部分が機能します。
APIの詳細
IPathとPathの次の既存メソッドは影響を受けます。
- isValidPath / isValidSegment .これらのメソッドの実装は、
fromOSStringFacotryメソッドに変更されます。言い換えれば、パスの有効性は、プラットフォーム固有の問題になります。この仕様は、よりあいまいな表現は、特定の文字をいくつかのオペレーティングシステム上で予約されている表示に変更されます。実装では、オペレーティングシステムで要求されたデバイス区切りをチェックします。セグメント名の前後スペースの制限は、そのようなパスを許可している全てのオペレーティングシステム上で削除されます。 - Path(String, String).このコンストラクタの引数がない以前は、2番目の引数を提供されたデバイスから抜き出す振る舞いは予期せぬ動作をしていました。このふるまいは、もう2番目の引数からデバイスを解析しないように変化するでしょう。このコンストラクタは、現在のパスセグメントのすべてのプラットフォームにてコロンリテラル文字を処理します。
- IPath.append(String).この方法は、提供されるパラメーターからのパス・リテラルを暗黙に構築するか解釈します。 指定されたパスを解析する時、このメソッドはOS固有の規則を使います。
Eclipse 3.1のための新しい方法:
- Path.fromPortableString .IPathを作成するためのこのメソッドは、プラットフォーム中立な文字列使って作成されます。これは、 IPath.toPortableStringメソッドの逆です。特に、ダブルのコロンは、セグメント名中の単一のコロン特徴として解釈されます。また、最初の単一のコロン(もしあれば)はデバイス区切りとして扱われます。
- toPortableString.プラットフォーム中立なパス文字を作成するこのメソッドは、プラットフォーム中立な形でファイルを保存するのに適切です。このパスセグメントではダブルコロンリテラルを使用してエスケープされます。
- Path.fromOSString.IPath作成するためのこのメソッドは、プラットフォーム固有文字列を提供します。これは、 IPath.toOSString メソッドの逆です。Windowsでは、コロン文字はデバイスの区切りと、スラッシュセグメントのセパレータの両方として扱われます。
私たちは、Path(String)コンストラクタをどのように使えばいいですか?
この提案は、 2つの作成メソッドを、明確に、プラットフォーム中立と、プラットフォーム固有とを区別する方法紹介します。困難な問題は、1つの引数をもつPathコンストラクタの扱いです。2つのオプションがあります:
- このコンストラクタの実装は変わらず残しておくが、deprecateする。この解決策の利点は、 現在の
PathコンストラクタのAPI
仕様を破るものでなく、':'と'\'文字をどのように扱うかはっきり述べています。欠点は、全ての使用者に、2つのうち1つのPathコンストラクタを使うように要求し、パス文字列が使われている場所に依存してしまうであろうことです。
以前は無効のものとして扱われたファイルシステム・パスに対応するIPath構築しようとする時、クライアントは、IPathインスタンスメソッドエラーが発生するリスクがあるため導入にしません。たとえば、Resources Pluginを':'と'\'を含むリソースを許可するように導入。他のPluginでは、これらのリソースを古いコンストラクタでは失敗するように対応する。この解決の実験は、プラグインが新しい作成メソッドへの移行に失敗しました。以前の不正なパスを予期せず使われたため、新しい作成メソッドが壊れた為です。この結果は、API規約が破られないという事実に関わらず、後方互換について陰を落とすものでした。 - 2番目のオプションは、既存の1引数のパスコンストラクタをプラットフォーム固有にするという変更です。言い換えれば、これらメソッドのWindows実装は変更されませんが、その他のプラットフォームの実装では、デバイス区切りとして':'を扱うと停止し、パスセグメントの区切りとして'\'を扱う事ができませんでした。これは明らかに、既存の
PathコンストラクタのAPI仕様に違反します。良い面としては、このプラクティスで、ほとんどの破損をご紹介します。このnet effectは、いくつかのオペレーティングシステムでの古い制限を取り除きます。唯一の破損は、すべてのオペレーティングシステム と、現在のEclipseが動作している以外のプラットホームのファイルシステムパスで、IPathオブジェクトを作成する必要があるクライアントにて引き起こされます。
例えば、Linux上で動作しているプラグインは、Windowsシステムのリモートで扱っているファイルを、IPathの古いコンストラクタでを作成する事ができません
上記のアプローチの両方の実装を調査した結果、 2番目のオプションは、はるかに小さな破損をになることをご紹介しました。たとえば、最初のオプションは、Eclipse platform
最新バージョンで見つかるPathのコンストラクタを参照している、およそ600箇所のほとんどを修正する必要があります。2番目のオプションでは、最小のローカライズ変更を必要とするだけです。これは、プラットフォーム中立なパスのシリアライズ、デシリアライズです。これらの2つのオプションの実装調査に基づいて、2つ目のオプションをお勧めします。
例
以下に、様々なPathコンストラクタと、to*Stringメソッドの振る舞いを説明します。
デバイス "C:"と、"foo" の1セグメントの絶対パスを与えると、IPathメソッドは次のようなものを生成します。
- toString -> "C:/foo"
- toPortableString -> "C:/foo"
- toOSString (windows) -> "C:\foo"
- toOSString (Linux) -> "C:/foo"
- getDevice -> "C:"
デバイスがnullで、"C:"と"foo"の2つのセグメントの相対パスの場合
- toString -> "C:/foo"
- toPortableString -> "C::/foo"
- toOSString (windows) -> "C:\foo"
- toOSString (Linux) -> "C:/foo"
- getDevice -> (null))
" C:\\foo" ( Javaのリテラル形式で単一のバックスラッシュをエスケープ)の文字列を考えると、次のコンストラクタを生成します。
- fromOSString と Path(String)(windows) -> "C:"デバイスと、"foo"の1セグメントの絶対パス
- fromOSString と Path(String)(Linux) -> nullデバイスと、"C:\foo"の1セグメントの相対パス
- fromPortableString -> "C:"デバイスと、"\foo"の1セグメントの相対パス
"C:/foo"の場合:
- fromOSString and Path(String) (windows) -> "C:"デバイスと、"foo"の1セグメントの絶対パス
- fromOSString and Path(String) (Linux) -> nullデバイスと、"C:"と"foo"の2セグメントの相対パス
- fromPortableString -> "C:"のデバイスと、"foo"の1セグメントの絶対パス
"C::/foo"の場合:
- fromOSString と Path(String) (windows) -> "C:"デバイスと、":"と"foo"の2セグメントの相対パス (invalidパス)
- fromOSString と Path(String) (Linux) -> nullデバイスと、"C::"と"foo"の2セグメントの相対パス
- fromPortableString -> nullデバイスと、"C:"と"foo"の2セグメントの相対パス
その他の移行の問題
プラットフォーム中立な文字列のシリアライズ形式(Eclipse3.0のIPath.toStringによって作成されるような)の絶対IPathオブジェクトを保存する全ての使用者は、PathコンストラクタとtoStringメソッドよりも、新しいfromPortableString/toPortableStringメソッドを使うべきです。
Eclipse 3.0によって書かれたファイルのとの後方互換性は自動です。(ファイル形式の変更は無い もしくは ファイル形式番号を変える必要があります。) 例えばパスの文字表現を含んだファイルは、workspaceの.projectと.classpathファイルを含んで移行が必要です。
他の観測
IPath.toPortableStringとPath.fromPortableStringの提案は、お互いを可逆にします。言い換えれば、次の式
path.equals(Path.fromPortableString(path.toPortableString()))
すべてのパスにおいてtrueになり、そして
string.equals(Path.fromPortableString(string).toPortableString())
は全ての文字列についてtrueになります。これは規範的なパスの表現です。(重複スラッシュを含む文字列 もしくは "."と".."の参照は違う意味になる)さらに、
Path.fromPortableStringのEclipse3.1の実装は、
IPath.toStringのEclipse3.0の実装と完全に逆になります。
Unixでは、 toOSStringとfromOSStringは互いに逆になります。Windowsでは、セグメント名にコロンもしくはバックスラッシュが含まれて(とにかくこのようなパスはWindows上では無効です。)いないパスでは同じ事が言えます。次の例を見てみましょう。
String input = "foo::bar";
IPath pathOne = Path.fromPortableString(input);
IPath pathTwo = Path.fromOSString(pathOne.toOSString());
pathOne.equals(pathTwo) -> false!
入力の文字表現は、デバイスがnullで、"foo:bar"という名前の1セグメントです。(Windowsでは無効)
toOSStringを使った出力では、"foo:bar"になります。
fromOSStringではこのパスを、"foo:"デバイスと、"foo:"セグメントと解釈します。バックスラッシュ文字を含むセグメントを持ったパスを作成した時も、同じようにメチャクチャになります。
String input = "foo\\bar";
IPath pathOne = Path.fromPortableString(input);
IPath pathTwo = Path.fromOSString(pathOne.toOSString());
pathOne.equals(pathTwo) -> false!
この場合、"foo\bar"という名前の1セグメントのパスが入力になります。これは、
fromOSStringでは"foo"と"bar"の2セグメントのパスとして解析します。言い換えると、この提案では、"to/fromOSString"をWindowsにてバックスラッシュもしくはコロンを含むパスを扱う事を解決できてきません。これは、許容限界があるようです。
参照