Javaのファイルの読み込みで、ファイルの参照パスの考慮が辛い...

f:id:ts0818:20210407185339j:plain

www.itmedia.co.jp

 米GoogleAndroidJava著作権を侵害しているとして米Oracle2010年8月に提訴した裁判で、米連邦最高裁判所は4月5日(現地時間)、Google著作権を侵害しなかったとの判断を下した。

GoogleがOracleとの10年越しの裁判で勝訴 最高裁はJava著作権侵害せずの判断 - ITmedia NEWS

⇧ う~ん、エンジニアからしてみたら、APIのドキュメントをちゃんとしてくれてるんであれば他に何もいらないって思っちゃいますけどね。

そして、

www.publickey1.jp

⇧ 絶妙なタイミングですね、Microsoft様。

というわけで、今回はJavaの話です。

レッツトライ~。  

 

file URI schemeって?

Wikipediaさんに聞いてみた。 

The File URI Scheme is a URI scheme defined in RFC 8089, typically used to retrieve files from within one's own computer.

https://en.wikipedia.org/wiki/File_URI_scheme

⇧ ってな感じで、「The File URI Scheme is a URI scheme」 ってところからして、What's なんだけどな...

URI  scheme」を調べてみたところ、

www.iana.org

Uniform Resource Identifier (URI) Schemes

Registration Procedure(s)

Expert Review for Permanent and Historical registrations, 
First Come First Served for Provisional registrations
Expert(s)
Graham Klyne
Reference[RFC7595][RFC Errata 4420][RFC8615]Note
Requests for permanent registration must be preceded by mailing list review, 
per Section 7.2 of [RFC7595].

Available Formats
CSV
 

https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml

⇧ ってなってるんで、「RFC 7595」を確認してみたところ、

URI scheme」自体は「RFC 3986」で定義されてるらしい。

tools.ietf.org

1. Introduction
The Uniform Resource Identifier (URI) protocol element and generic syntax is defined by [RFC3986]. Each URI begins with a scheme name, as defined by Section 3.1 of RFC 3986, that refers to a specification for identifiers within that scheme. The URI syntax provides a federated and extensible naming system, where each scheme's specification can further restrict the syntax and define the semantics of identifiers using that scheme.

https://tools.ietf.org/html/rfc7595

⇧「RFC 7595」は「URI scheme」の登録方法なんかについての情報が載ってるらしい、というか「RFC 3986」で「URI scheme」が定義されてからの議論の多さが...

で、その「scheme」ってどんだけあんの?

This article lists common URI schemes. A Uniform Resource Identifier helps identify a source without ambiguity. Many URI schemes are registered with the IANA; however, there exist many unofficial URI schemes as well. Mobile deep links are one example of a class of unofficial URI schemes that allow for linking directly to a specific location in a mobile app.

https://en.wikipedia.org/wiki/List_of_URI_schemes

⇧ はい、出ました。「非公式のスキーマも複数存在します」って、本当に止めて欲しい...これ、誰かちゃんと把握してるのかね?

っていうか、「登録方法」云々が出てきたあたりで嫌な予感はしてましたけど...

ちなみに、Javaがサポートしてる「URI scehme」ってどれぐらいあるのかな?って思ってたら、

stackoverflow.com

⇧ 同じような疑問を抱いてる方が。

と、その前に、

  • URI(Uniform Resource Identifier)」
  • 「URL(Uniform Resource Locator)」

の関係って?

danielmiessler.com

⇧ 上図がイメージしやすいかと。

見たまんまで超ザックリまとめると、「URI(Uniform Resource Identifier)」は「URL(Uniform Resource Locator)」を包括してる、ってことみたいですね。

 

Javaがデフォルトで対応してる「scheme」を確認してみる

stackoverflowの話では「java.protocol.handler.pkgs」っていう「system property」で確認できるんだそうな。

「system property」って?

docs.oracle.com

System Properties

In Properties, we examined the way an application can use Properties objects to maintain its configuration. The Java platform itself uses a Properties object to maintain its own configuration. The System class maintains a Properties object that describes the configuration of the current working environment. System properties include information about the current user, the current version of the Java runtime, and the character used to separate components of a file path name.

System Properties (The Java™ Tutorials > Essential Classes > The Platform Environment)

⇧ おそらく「System.class」の「Properties」の内の1種ってことなのかな?

そんじゃあ、「Property」って?

docs.oracle.com

Properties

Properties are configuration values managed as key/value pairs. In each pair, the key and value are both String values. The key identifies, and is used to retrieve, the value, much as a variable name is used to retrieve the variable's value. For example, an application capable of downloading files might use a property named "download.lastDirectory" to keep track of the directory used for the last download.

Properties (The Java™ Tutorials > Essential Classes > The Platform Environment)

⇧ う~ん、サッパリ分からんですな...

To manage properties, create instances of java.util.Properties. This class provides methods for the following:

  • loading key/value pairs into a Properties object from a stream,
  • retrieving a value from its key,
  • listing the keys and their values,
  • enumerating over the keys, and
  • saving the properties to a stream.

Properties (The Java™ Tutorials > Essential Classes > The Platform Environment)

⇧ 1つだけ分かったのは、「java.util.Properties」をインスタンス化すれば、「properties」を管理できるってことかね。

The System class maintains a Properties object that defines the configuration of the current working environment. For more about these properties, see System Properties. The remainder of this section explains how to use properties to manage application configuration.

Properties (The Java™ Tutorials > Essential Classes > The Platform Environment)

⇧ まぁ「defines the current working enviroment」ってのが何のことを言ってるのかが全く分からんのだけど、何かの設定値ってことみたいね。

Javaのアプリケーションでは、

Properties in the Application Life Cycle

The following figure illustrates how a typical application might manage its configuration data with a Properties object over the course of its execution.

f:id:ts0818:20210405185753p:plain

Properties (The Java™ Tutorials > Essential Classes > The Platform Environment)

  • Starting Up
    The actions given in the first three boxes occur when the application is starting up. First, the application loads the default properties from a well-known location into a Properties object. Normally, the default properties are stored in a file on disk along with the .class and other resource files for the application.
    Next, the application creates another Properties object and loads the properties that were saved from the last time the application was run. Many applications store properties on a per-user basis, so the properties loaded in this step are usually in a specific file in a particular directory maintained by this application in the user's home directory. Finally, the application uses the default and remembered properties to initialize itself.
    The key here is consistency. The application must always load and save properties to the same location so that it can find them the next time it's executed.
  • Running
    During the execution of the application, the user may change some settings, perhaps in a Preferences window, and the Properties object is updated to reflect these changes. If the users changes are to be remembered in future sessions, they must be saved.
  • Exiting
    Upon exiting, the application saves the properties to its well-known location, to be loaded again when the application is next started up.

Properties (The Java™ Tutorials > Essential Classes > The Platform Environment)

⇧上図のような感じで「Properties object」が利用されるってことみたいね。

Google翻訳した感じでは、

  • First, the application loads the default properties from a well-known location into a Properties object.
  • Next, the application creates another Properties object and loads the properties that were saved from the last time the application was run.

の少なくとも2つの「Properties object」が利用されるっぽいですかね。

で、話は戻って、「java.protocol.handler.pkgs」って「system property」を確認すれば、Javaがデフォルトで扱ってる「scheme」が確認できるし、 

blog.repy.info

⇧ 「カスタムプロトコル」ってことで「scheme」を追加できるみたいですね。

www.coppermine.jp

繰り返しになりますが、システムプロパティもそうでないプロパティも、java.util.Propertiesクラスで統一的にアクセスできます。

Java のプロパティについて再考する (ja) - notepad

⇧ ってことで、確認してみました。

package java_one_hundred_nock.seventynine;

import java.util.Properties;

public class FileSaveDone {

	public static void main(String[] args) {
		// TODO 自動生成されたメソッド・スタブ
	    Properties properties = System.getProperties();
	    properties.list(System.out);
	}
}

で、実行結果が、

-- listing properties --
sun.desktop=windows
awt.toolkit=sun.awt.windows.WToolkit
java.specification.version=11
sun.cpu.isalist=amd64
sun.jnu.encoding=MS932
java.class.path=C:\Eclipse-2020-06\pleiades-2020-06-j...
java.vm.vendor=AdoptOpenJDK
sun.arch.data.model=64
user.variant=
java.vendor.url=https://adoptopenjdk.net/
user.timezone=
os.name=Windows 10
java.vm.specification.version=11
sun.java.launcher=SUN_STANDARD
user.country=JP
sun.boot.library.path=C:\Eclipse-2020-06\pleiades-2020-06-j...
sun.java.command=java_one_hundred_nock.seventynine.Fil...
jdk.debug=release
sun.cpu.endian=little
user.home=C:\Users\Toshinobu
user.language=ja
java.specification.vendor=Oracle Corporation
java.version.date=2020-04-14
java.home=C:\Eclipse-2020-06\pleiades-2020-06-j...
file.separator=\
java.vm.compressedOopsMode=32-bit
line.separator=

java.specification.name=Java Platform API Specification
java.vm.specification.vendor=Oracle Corporation
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
user.script=
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
java.runtime.version=11.0.7+10
user.name=Toshinobu
path.separator=;
os.version=10.0
java.runtime.name=OpenJDK Runtime Environment
file.encoding=UTF-8
java.vm.name=OpenJDK 64-Bit Server VM
java.vendor.version=AdoptOpenJDK
java.vendor.url.bug=https://github.com/AdoptOpenJDK/openj...
java.io.tmpdir=C:\Users\TOSHIN~1\AppData\Local\Temp\
java.version=11.0.7
user.dir=C:\Eclipse-2020-06\pleiades-2020-06-j...
os.arch=amd64
java.vm.specification.name=Java Virtual Machine Specification
java.awt.printerjob=sun.awt.windows.WPrinterJob
sun.os.patch.level=
java.library.path=C:\Eclipse-2020-06\pleiades-2020-06-j...
java.vendor=AdoptOpenJDK
java.vm.info=mixed mode
java.vm.version=11.0.7+10
sun.io.unicode.encoding=UnicodeLittle
java.class.version=55.0

⇧ というわけで、「java.protocol.handler.pkgs」ていう「key」の「property」なんて存在しないんですけど...

どういうこと?

「ドキュメント」を見る限りでは、

docs.oracle.com

指定されたプロトコルで最初にURLオブジェクトを生成するときには、そのプロトコルのためのストリーム・プロトコル・ハンドラ・オブジェクトが生成されます。このストリーム・プロトコル・ハンドラ・オブジェクトは、URLStreamHandlerクラスのインスタンスです。

  1. 以前にアプリケーションがURLStreamHandlerFactoryインスタンスをストリーム・ハンドラ・ファクトリとして設定している場合は、そのインスタンスcreateURLStreamHandlerメソッドがプロトコル文字列を引数として呼び出されて、ストリーム・プロトコル・ハンドラを作成する。
  2. URLStreamHandlerFactoryがまだ設定されていない場合、またはcreateURLStreamHandlerメソッドがnullを返した場合は、ServiceLoaderカニズムを使用して、システム・クラス・ローダーを使用してURLStreamHandlerProvider実装を特定します。 プロバイダが配置される順序は実装固有であり、実装は配置されたプロバイダを自由にキャッシュできます。 createURLStreamHandlerからスローされたServiceConfigurationErrorErrorまたはRuntimeExceptionが発生した場合は、呼び出しスレッドに伝播されます。 各プロバイダの createURLStreamHandlerメソッド(インスタンス化されている場合)は、プロバイダがnull以外を返すか、すべてのプロバイダが使い果たされるまで、プロトコル文字列とともに呼び出されます。
  3. 前のステップでプロトコル・ハンドラが見つからない場合、コンストラクタはシステム・プロパティの値を読み取ります:
    java.protocol.handler.pkgs
    このシステム・プロパティの値がnullでなければ、値は、垂直スラッシュ文字「|」で区切られた、パッケージのリストとして解釈される。 コンストラクタは次の名前を持つクラスをロードしようとする。
    <package>.<protocol>.Handler
    ここで、<package>はパッケージの名前に置き換えられ、<protocol>プロトコルの名前に置き換えられます。 このクラスが存在しない場合、あるいはクラスは存在してもそれがURLStreamHandlerのサブクラスではない場合には、リストにある次のパッケージを試すことになる。
  4. 前のステップでプロトコル・ハンドラが見つからない場合、コンストラクタは組み込みのプロトコル・ハンドラをロードしようとします。 このクラスが存在しない場合、またはクラスは存在するがURLStreamHandlerのサブクラスではない場合は、MalformedURLExceptionがスローされる。

 

次のプロトコルプロトコル・ハンドラは、検索パス上に存在することが保証されています。

     http, https, file, and jar
 

その他のプロトコルプロトコル・ハンドラも使用可能になっている可能性があります。 クラスパス上のプラットフォーム・クラスまたはクラスをロードするために使用されるプロトコル・ハンドラなどの一部のプロトコル・ハンドラは、オーバーライドできません。 そのような制限の詳細、およびそれらの制限が(実行時の初期化中など)に適用される場合、実装固有であり、したがって指定されていない

URL (Java SE 11 & JDK 11 )

⇧「java.protocol.handler.pkgs」っていう「システム・プロパティ」が存在するって言ってますな。

で、

docs.oracle.com

⇧「System.getProperties()」の一覧に「java.protocol.handler.pkgs」が存在しないんだけど...

何すか、もう、これ「ドキュメント」からデフォルトの「プロトコル・ハンドラ(「scheme」なことだと思うけど)」を転記してコーディングしろってこと?

痛恨過ぎるでしょ...

まさか「AdoptOpenJDK」だからってことないよね?

ちなみに「AdoptOpenJDK」は「Eclipse Foundation」に寄贈されて「Eclipse Adoptium」になるらしい。

codezine.jp

 Eclipse Foundationは、AdoptOpenJDK Technical Steering Committeeとの協力による、Adoptiumワーキンググループの正式な設立を3月23日(現地時間)に発表した。

Eclipse Foundation、企業向けJavaランタイムのマルチベンダー配信のためのAdoptiumワーキンググループを設立:CodeZine(コードジン)

⇧ 2021年3月23日(現地時間)ってことは、結構、最近の話よね。

すみません、脱線しましたが、まぁ、Javaがデフォルト対応してる「scheme」についてコーディングで取得できないみたいなんですが、Java 11のドキュメント(「Oracle JDK」のドキュメントだと思うけど)を見る限りでは、

の4つがデフォルトで対応してる「scheme」ってことみたいね。

Javaっていうと、

  • JAR(Java ARchive)
  • WAR (Web application ARchive)
  • EAR(Enterprise ARchive)

の圧縮ファイルがあるっぽいんだけど、「jar」以外の圧縮ファイルを対応させたい場合は、「scheme」を追加してあげる必要があるってことですかね。

まぁ、このあたりは、フレームワークなんかが良しなにやってくれるんですかね?

それにしても、Javaのバージョンをどんどん上げていくんなら、どの「システム・プロパティ」が取得できるのかできないかの記載をちゃんとして欲しいよね...

取得できないんならできないんで良いんだけど、明示して欲しい...

そして、まさかの

stackoverflow.com

⇧ 1つのファイルパスで複数の「scheme」が混在してることがあるケースがあるみたいね...いや~地獄ですな...

 

java.nioだとエラー出るやん...

何か、ネットの情報を見ると、『「java.io」でコーディングなんてマジでイケてない、これからは「java.nio」でしょ』とか宣う方々がおられたんですが、普通に「java.nio」だと対応しきれない場合があるみたいね...

で、ファイルのパスとかも「文字列」で受け取ることもあるあるじゃないですか。

stackoverflow.com

Fo checking if it's a local file you can simply do this:

public static boolean isLocalFile(String path) {
        return new File(path).exists();
}

To check if it's a url Cameron Ketcham's answer is the best one.

https://stackoverflow.com/questions/6575114/how-do-i-determine-whether-a-path-is-a-local-file-or-not

⇧ 上記サイト様を見た感じ、

  • local file ←ローカルファイル
  • or not ←ローカルファイル以外

ってことを判定しようとすると、「java.nio」だと何か無理っぽい気がするんですよね。

でもって、「java.io」パッケージを使って「new File(path).exists()」とかすれば判定できるよ、って仰ってる方がおられたのですが、確かに「ローカルファイル」が存在する場合は良いのですが、「ローカルファイル」が存在しない場合のパターンが判断できないっぽい気がするんよね...。

どういうことかと言うと、例えば、

というようなファイルパスの文字列があったとして、 

  • 「ローカルファイル以外のファイルが存在するでFalse」
  • 「ローカルファイル以外のファイルが存在しなくてFalse」
  • 「ローカルファイルが存在しなくてFalse」

という感じで、「False」の内容の区別ができませんと。(「False」としか返してくれないからね...)

 

じゃあ、どうするの?

これについては、私自身がJava有識者ってわけではないので、何とも言えないですが、「文字列」から判断するってことをするしかないんじゃないかと。

つまり、『この文字や文字列が先頭に含まれてたら~』、って感じで判断していくしかなさそう。

まぁ、「ファイルパス」の文字列が、ちゃんとしたフォーマットになって渡されるって担保されてるような環境であれば、悩まなくても済む話だとは思うんだけど、実際問題、開発現場とかだとどうなのかね?

幸い、Javaがデフォルトで対応してる「scheme」が『http』『https』『file』『jar』の4つしか存在しないらしいので、チェックリストっぽいものは作れそうですと。

方針としては、Java SEのAPIを使う場合は、

  • 異なるマシン間
    java.net.URL パッケージ、java.ioパッケージ、java.nioパッケージ
  • 上記以外
    java.ioパッケージ、java.nioパッケージ

のパッケージのAPIを組み合わせていけば、「ローカルファイル」「ローカルファイル以外」とか混在してるファイルパスに対応できるんじゃないかと。 

とりあえずは、Windowsの場合でコーディングしてみる。(他のOSのファイルパスのパターンが分からないので...というかWindowsもファイルパスのパターンの全量も分からんけども...) 

■/java_one_hundred_nock/src/java_one_hundred_nock/seventynine/FileSave.java

package java_one_hundred_nock.seventynine;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;

public class FileSave {

  private static String DEFAULT_ERROR_TXT = "error.txt";

  public static void saveUrlFiles(List<String> getFileList, String saveFolder) {
    List<String> errorFilePathList = new ArrayList<>();
    getFileList.forEach(filePath ->{
      try {
        if ("file".equals(filePath.split(":")[0])) {
          saveOtherFile(filePath, saveFolder, errorFilePathList);
        } else {
          URL url = new URL(filePath);
          if (url != null) {
            Files.copy(url.openStream(), Paths.get(saveFolder + Paths.get(url.getPath().substring(url.getPath().lastIndexOf("/")+1)).getFileName()), StandardCopyOption.REPLACE_EXISTING);
          }
        }

      } catch (MalformedURLException e) {
        // TODO 自動生成された catch ブロック
        if (Files.notExists(Paths.get(saveFolder + DEFAULT_ERROR_TXT))) {
          try {
            Files.createFile(Paths.get(saveFolder  + DEFAULT_ERROR_TXT));
          } catch (IOException e1) {
            // TODO 自動生成された catch ブロック
            e1.printStackTrace();
          }
        }
        //e.printStackTrace();
        errorFilePathList.add(filePath);
      } catch (IOException e) {
        // TODO 自動生成された catch ブロック
        e.printStackTrace();
      }
      writeErrorFilePath(errorFilePathList, saveFolder);
    });
  }

  public static void saveOtherFiles(List<String> getFileList, String saveFolder) {
    List<String> errorFilePathList = new ArrayList<>();
    getFileList.forEach(filePath ->{

      saveOtherFile(filePath, saveFolder, errorFilePathList);
      writeErrorFilePath(errorFilePathList, saveFolder);
    });
  }

  private static void saveOtherFile(String filePath, String saveFolder, List<String> errorFilePathList) {
    if (new File(filePath).exists()) {
      try {
        Files.copy(Paths.get(filePath), Paths.get(saveFolder + Paths.get(filePath).getFileName()), StandardCopyOption.REPLACE_EXISTING);

      } catch (IOException e) {
        // TODO 自動生成された catch ブロック
        if (Files.notExists(Paths.get(saveFolder + DEFAULT_ERROR_TXT))) {
          try {
            Files.createFile(Paths.get(saveFolder  + DEFAULT_ERROR_TXT));
          } catch (IOException e1) {
            // TODO 自動生成された catch ブロック
            e1.printStackTrace();
          }
        }
        //e.printStackTrace();
        errorFilePathList.add(filePath);
      }
    }
  }

  /**
   * エラーになるファイルパスを記録
   * @param errorFilePathList
   * @param saveFolder
   */
  private static void writeErrorFilePath(List<String> errorFilePathList, String saveFolder) {
    if (errorFilePathList.size() > 0) {
            try {
        Files.write(Paths.get(saveFolder  + DEFAULT_ERROR_TXT), errorFilePathList, Charset.forName("UTF-8"),
            StandardOpenOption.TRUNCATE_EXISTING);
      } catch (IOException e) {
        // TODO 自動生成された catch ブロック
        e.printStackTrace();
      }
    }
  }
}

■/java_one_hundred_nock/src/java_one_hundred_nock/seventynine/FileSaveDone.java

package java_one_hundred_nock.seventynine;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FileSaveDone {

  public static void main(String[] args) {
    // Java SEのデフォルトで対応してる「scheme」
    String[] defaultSchemeArr = {"http", "https", "file", "jar"};
    List<String> defaultSchemeList = new ArrayList<>(Arrays.asList(defaultSchemeArr));

    String saveFolder = "C:\\temp\\"; // 保存先フォルダ
    // ファイルパスの文字列
    String[] fileListArr = {
        "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/200px-Wikipedia-logo-v2.svg.png",
        "file://C:\\Users\\Toshinobu\\Desktop\\soft_work\\01_尤度_m05-s20.png",
        "file://\\\\wsl$\\docker-desktop\\docker-desktop-deploy-version",
        "file://C:/Users/Toshinobu/Desktop/soft_work/01_尤度_m05-s20.png",
        "file:////wsl$/docker-desktop/docker-desktop-deploy-version",
        "ppp://C:/Users/Toshinobu/Desktop/soft_work/01_尤度_m05-s20.png",
        ":////wsl$/docker-desktop/docker-desktop-deploy-version",
        "C:/Users/Toshinobu/Desktop/soft_work/01_尤度_m05-s20.png",
        "//wsl$/docker-desktop/docker-desktop-deploy-version",
        "C:\\Users\\Toshinobu\\Desktop\\soft_work\\01_尤度_m05-s20.png",
        "\\\\wsl$\\docker-desktop\\docker-desktop-deploy-version",
        "/01_尤度_m05-s20.png",
        "jar://wsl$/docker-desktop/docker-desktop-deploy-version",
        "jar:////wsl$/docker-desktop/docker-desktop-deploy-version",
        "jar://C:/Users/Toshinobu/Desktop/soft_work/01_尤度_m05-s20.png",
        "01_尤度_m05-s20.png"
    };

    // ファイルパスの文字列を「scheme」のあるなしで分けたものを格納する用
    Map<Integer, String> urlFilePathMap = new HashMap<>();
    List<Map<Integer, String>> urlFilePathList = new ArrayList<>();
    Map<Integer, String> otherFilePathMap = new HashMap<>();
    List<Map<Integer, String>> otherFilePathList = new ArrayList<>();

    // 実際に分ける
    for (int i = 0; i < fileListArr.length; i++) {
      // java.net.URLパッケージのAPIで対応するファイルパス
      if (defaultSchemeList.contains(fileListArr[i].split(":")[0].toLowerCase())) {
        urlFilePathMap.put(i, fileListArr[i]);
        urlFilePathList.add(urlFilePathMap);
      } else { // java.net.URLパッケージのAPI以外で対応するファイルパス
        otherFilePathMap.put(i, fileListArr[i]);
        otherFilePathList.add(otherFilePathMap);
      }
    }

    // List<Map<Integer, String>>からList<String>にする
    List<String> urlFileList = new ArrayList<>();
    for (int i =0; i < urlFilePathList.size();) {
      for (Integer key: urlFilePathList.get(i).keySet()) {
        urlFileList.add(urlFilePathList.get(i).get(key));
      }
      break;
    }

    // List<Map<Integer, String>>からList<String>にする
    List<String> otherFileList = new ArrayList<>();
    for (int i =0; i < otherFilePathList.size();) {
      for (Integer key: otherFilePathList.get(i).keySet()) {
        otherFileList.add(otherFilePathList.get(i).get(key));
      }
      break;
    }

    FileSave.saveOtherFiles(otherFileList, saveFolder);
    FileSave.saveUrlFiles(urlFileList, saveFolder);
  }
}

⇧ う~ん...何か収拾のつかない感じになってしまった...

というか、仮に「ファイルパス」が1000万個とかあったら、1000万回new File(filePath)されるかもしれないこともあるって、駄目駄目なコーディングですな...

実行してみると、

f:id:ts0818:20210406183506p:plain

⇧ 存在するファイルパスについては、保存先に指定したフォルダに保存され、

f:id:ts0818:20210406183614p:plain

⇧「scheme」付きで存在しないファイルパスを「error.txt」に保存、なんだけど、「scheme」が「file://」で存在しないファイルパスと「ローカルファイル」で存在しないファイルパスとか考慮できてないっす...

もちろん、1つのファイルパスに複数「scheme」が混在してる場合も対応できておらず...

って言うか、「ローカルファイル」「ローカルファイル以外」が混在してるファイルパスが存在するかどうかの判定って、どうするのが良いんですかね?

ベストプラクティス的なものを知りたい...

いや~、何か疲れた... 

まぁ、一つだけ言えることは、「java.io」「java.nio」云々の前に、「ファイルパス」の文字列をどう処理するかってことのほうが大事な気がするんだけどな...

それとも、通常の業務であれば、絶対確実な「ファイルパス」の形の文字列が来る想定になってるとかで、心配無用なんですかね?

今回はこのへんで。