Обзор Java App Bundlers
Итак, в прошлый раз я писал об инструменте для сборки приложений JavaFXPackager. Там было 2 каких-то способа собрать приложение, но ни один из них не мог быть удобно вызван просто из кода. Но мы же труЪ Java-программисты. И вот для таких труЪ-программистов с версии 8u20 и был создан в JDK специальный API в JavaFXPackager, который позволяет просто вот так взять и собрать бандл из ваших бинарников. Одна проблема — этот API незадокументирован. Но не беда, разберёмся.Источники информацииПервичным источником будет, естественно, JavaDoc, который, впрочем, тоже не доступен публично. Поэтому мы соберём его сами из исходников. Не заставляя это делать читателей, я просто выложу его: ginz.bitbucket.org/fxpackager-javadoc. Конечно, нелишним будет иметь у себя исходный код, так как документирован API плохо.Введение
Какой класс заставляет в первую очередь обратить на себя внимание? Видимо, интерфейс Bundler с вот такой спецификацией:
// action methods
File execute (Map
// information methods
Collection
execute принимает Map параметров и выходную директорию в качестве параметров и создаёт в данной директории бандл в каком-то ожидаемом формате. Но ожидать правильного исполнения мы можем только если validate проходит без исключений, а их он может кинуть 2 вида: UnsupportedPlatformException и ConfigException, говорящие названия которых подсказывают, что в случае выкидывания первого вы просто используете бандлер, который не поддерживается вашей платформой. Второй же выкидывается, если у вас что-то не так с переданными параметрами.
А что вообще можно передавать в качестве параметров в эти методы? Большая часть ключей в этих методах находится во всяких статических экземплярах класса BundlerParamInfo, большая часть из которых (общие кроссплатформенные параметры) находятся в классе StandardBundlerParam, а более специфичные (платформенно-зависимые), как правило, находятся в самих классах-реализациях Bundler’а: LinuxAppBundler, WinExeBundler и так далее. Хотя не совсем очевидно, каким образом этот класс вообще связан с тем, что мы кладём в params, который передаём execute. На самом деле оказывается, что id, получаемый getID (), является ключом в этом Map’е, а значение должно быть типа, которым параметризован BundlerParamInfo.
А как получить экземпляры бандлеров?
Самый простой метод получения всех возможных «предустановленных» Bundler’ов через статический метод createBundlersInstance интерфейса Bundlers (Warning, HOT: Java 8):
public static List
И что, никаких примеров?! А, точно, можно попробовать перестать теоретизировать и нарисовать какой-нибудь пример.Поскольку мне кажется, что статическая типизация всегда лучше, то мы можем обернуть создание ассоциативного массива параметров во что-то типа BunderParamsBuilder:
public class BundlerParamsBuilder {
private Map
public
public BundlerParamsBuilder unsafeSetParam (String key, Object value) { params.put (key, value); return this; }
public Map
RelativeFileSet mainJar = new RelativeFileSet (jar.getParent ().toFile (), new HashSet
bundlers.forEach (bundler → bundler.execute (params, directoryWithBundles.toFile ()));
System.out.println («Bundles are created in » + directoryWithBundles);
System.out.println («Parameters after bundling:» + params);
Выхлоп после запуска будет примерно следующий:
Bundles are created in /tmp/bundles5791581710818077755
Parameters after bundling: {appVersion=1.0, copyright=Copyright © 2015, stopOnUninstall=true, .mac-jdk.runtime.rules=[Lcom.oracle.tools.packager.JreUtils$Rule;@4c3e4790, mac.app.bundler=Mac Application Image, linux.deb.imageRoot=/tmp/fxbundler6592356981290936843/images/linux-deb.image/helloworld-1.0/opt, buildRoot=/tmp/fxbundler6592356981290936843, mac.bundle-id-signing-prefix=HelloWorld., linux.deb.licenseText=Unknown, linux.deb.maintainer=Unknown
//… if (getDefaultValueFunction () != null) { T result = getDefaultValueFunction ().apply (params); if (result!= null) { params.put (getID (), result); } return result; } //… Ага, если параметр не был найден в params и он может быть получен через другие параметры (за это отвечает функция defaultValueFunction), то значение, полученное таким образом, запихивается в params. Например, мы не указали параметр MAIN_JAR, но MAIN_JAR обязателен для того, чтобы создать запускаемый файл. Посмотрим на то, как определяется MAIN_JAR: в качестве defaultValueFunction видим: params → { extractMainClassInfoFromAppResources (params); return (RelativeFileSet) params.get («mainJar»); } Вот и ответ, откуда там появляется значение mainJar.Полный список параметров Для того, чтобы не искать каждый раз параметры, я сделал табличку, в которую вывел информацию по всем найденным статическим экземплярам BundlerParamInfo.Заключение Описан самый минимум и самые очевидные возможности, так что не стесняйтесь читать JavaDoc и даже код.Статья будет улучшаться и исправляться по запросу комментаторов.