Достижение эффекта изменения рабочего каталога Java - PullRequest
0 голосов
/ 26 апреля 2018

Контекст: мое программное обеспечение зависит от вызова библиотеки, которая может принимать только относительные пути в качестве входных данных из-за старого ограничения. Мне нужно, чтобы пути были относительно известного каталога. Библиотека может сделать внутренний вызов, например,

java.io.File fooBar = new java.io.File("foo/bar");

Мне нужно это, чтобы дать мне /nwd/foo/bar, а не, скажем, /cwd/foo/bar, где /cwd - это рабочий каталог, из которого был запущен java.

Для всех намерений и целей я не могу изменить внутреннее поведение этой библиотеки. Ручное переопределение методов, создающих экземпляры этих объектов, потребует переписывания всей библиотеки.

Заманчивым решением было бы просто System.setProperty("user.dir", "/nwd") перед вызовом библиотеки, но на самом деле это не дает мне желаемого эффекта. В самом деле, если бы я позвонил fooBar.getAbsolutePath(), я бы получил желаемый /nwd/foo/bar, но если бы я проверил fooBar.exists() или попытался открыть файл для чтения или записи, оказалось бы, что файл не существует, потому что на самом деле он пытаюсь открыть /cwd/foo/bar. Фактически, если fooBar вместо этого было инициализировано

java.io.File fooBar = new java.io.File(new java.io.File("foo/bar").getAbsolutePath());

это действительно сработает, потому что тогда объект File фактически содержит абсолютные ссылки.

В этот момент я так расстроен, что мне все равно, если для этого понадобится хакерское решение. Мне просто нужен эффект смены рабочего каталога.

Ответы [ 4 ]

0 голосов
/ 04 мая 2018

почему бы не скопировать файл из / nwd / foo / bar в / cwd / foo / bar, тогда вы можете использовать существующую библиотеку такой, какая она есть:)

0 голосов
/ 04 мая 2018

Большой большой большой отказ от ответственности здесь! Не делай этого !!!!

Вы не должны устанавливать корневой каталог после его установки. Я не совсем уверен, в чем здесь философия дизайна, но я предполагаю, что это связано с безопасностью и не позволяет вредоносному коду заставить ваше приложение делать что-то неожиданное. Тем не менее, есть способы, которыми мы можем обойти это, используя некоторые неприятные размышления.

Я добился этого взлома путем отладки, когда класс Paths находит каталог по умолчанию и загружает файлы. Я использую отражение для редактирования значений класса FileSystem, который, как я видел, он использует. Этот хак будет зависеть от операционной системы, так как он редактирует экземпляр FileSystem. Это также может быть изменено, поскольку нет гарантии, что экземпляр не будет обновлен. Вы должны будете проверить его на всех целевых системах, так как они будут иметь различные FileSystem реализации (НО ПОЖАЛУЙСТА, НЕ ДЕЛАЙТЕ НАСТОЯЩЕГО ЭТОГО) .

Кроме того, с новыми изменениями в Java 9 вы не сможете легко заставить этот хак работать (они не разрешают использовать отражение для редактирования классов, не представленных «модулем»). Лучше всего раскрутить новый экземпляр JVM и сделать вызов с переданным свойством -Droot. Это не очень хорошее решение, но и кодовая база, с которой вы работаете. Вы должны активно пытаться исправить это более разумным способом. (Может быть, лучше всего переписать библиотеку, если бизнес-функция действительно важна)

Рабочая демонстрация с файлом в моем рабочем каталоге и файлом в моем C:\\:

public static void main(final String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    Files.readAllLines(new File("foo.txt").toPath()).forEach(System.out::println);

    final Class windowsFileSystemClass = Thread.currentThread().getContextClassLoader().loadClass("sun.nio.fs.WindowsFileSystem");
    final Field windowsFileSystemClassField = windowsFileSystemClass.getDeclaredField("defaultDirectory");
    windowsFileSystemClassField.setAccessible(true);

    final Class windowsPathClass = Thread.currentThread().getContextClassLoader().loadClass("sun.nio.fs.WindowsPath");
    final Field windowsPathFsField = windowsPathClass.getDeclaredField("fs");
    windowsPathFsField.setAccessible(true);

    // Hack that uses a path instance to grab reference to the shared FileSystem instance.
    final Object fileSystem = windowsPathFsField.get(Paths.get(""));
    windowsFileSystemClassField.set(fileSystem, "C:\\");

    Files.readAllLines(new File("foo.txt").toPath()).forEach(System.out::println);
}

Это выводит:

I'm in the working directory
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.Main (file:/C:/Users/calve/IdeaProjects/untitled1/out/production/untitled1/) to field sun.nio.fs.WindowsFileSystem.defaultDirectory
WARNING: Please consider reporting this to the maintainers of com.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
I'm in C: now

Обратите внимание и прислушайтесь к предупреждениям, только что выданным JVM.

0 голосов
/ 04 мая 2018

Очень альтернативный способ изменить CWD библиотеки - запустить ее в другом Java-процессе, для которого вы можете указать CWD во время запуска (см., Например, ProcessBuilder документация). Если я правильно понимаю проблему, ваш текущий поток чем-то похож на

 launch program with CWD 'a'
 use library X that expects CWD to be 'b' // <-- problem here

Новый поток будет

 launch program with CWD 'a'
 determine desired CWD for launching library X
 launch wrapper for library X with CWD 'b'
    internally, library X is happy because its CWD is as expected

Конечно, это заставит вас написать полную оболочку и сообщать об этом с помощью сокетов и сериализации или любой другой коммуникационной стратегии по вашему выбору. С другой стороны, это позволит вам запускать несколько экземпляров вашей библиотеки бок о бок, не мешая их CWD друг другу - за счет JVM и накладных расходов на связь.

0 голосов
/ 03 мая 2018

Я не думаю, что это большая проблема, потому что относительный путь также может начинаться с root.

Итак, предположим, что ваш текущий каталог /user/home и вы хотите сослаться на /user/tarun/foo/bar, тогда вы дадите относительный путь к вашей библиотеке, равный ../tarun/foo/bar.

И для этого вы можете использовать код, как описано ниже, так что поток

Как построить относительный путь в Java из двух абсолютных путей (или URL-адресов)?

String path = "/var/data/stuff/xyz.dat";
String base = "/var/data";
String relative = new File(base).toURI().relativize(new File(path).toURI()).getPath();
...