1 /*
   2  * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.jpackage.test;
  24 
  25 import java.awt.Desktop;
  26 import java.awt.GraphicsEnvironment;
  27 import java.io.File;
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.util.ArrayList;
  31 import java.util.Collection;
  32 import java.util.Collections;
  33 import java.util.HashMap;
  34 import java.util.HashSet;
  35 import java.util.List;
  36 import java.util.ListIterator;
  37 import java.util.Map;
  38 import java.util.Objects;
  39 import java.util.Set;
  40 import java.util.function.BiConsumer;
  41 import java.util.function.BiFunction;
  42 import java.util.function.Consumer;
  43 import java.util.function.Predicate;
  44 import java.util.stream.Collectors;
  45 import java.util.stream.Stream;
  46 import jdk.incubator.jpackage.internal.AppImageFile;
  47 import jdk.incubator.jpackage.internal.ApplicationLayout;
  48 import jdk.jpackage.test.Functional.ThrowingBiConsumer;
  49 import jdk.jpackage.test.Functional.ThrowingConsumer;
  50 import jdk.jpackage.test.Functional.ThrowingRunnable;
  51 import jdk.jpackage.test.Functional.ThrowingSupplier;
  52 
  53 
  54 
  55 /**
  56  * Instance of PackageTest is for configuring and running a single jpackage
  57  * command to produce platform specific package bundle.
  58  *
  59  * Provides methods to hook up custom configuration of jpackage command and
  60  * verification of the output bundle.
  61  */
  62 public final class PackageTest extends RunnablePackageTest {
  63 
  64     public PackageTest() {
  65         excludeTypes = new HashSet<>();
  66         forTypes();
  67         setExpectedExitCode(0);
  68         namedInitializers = new HashSet<>();
  69         handlers = currentTypes.stream()
  70                 .collect(Collectors.toMap(v -> v, v -> new Handler()));
  71         packageHandlers = createDefaultPackageHandlers();
  72     }
  73 
  74     public PackageTest excludeTypes(PackageType... types) {
  75         excludeTypes.addAll(Stream.of(types).collect(Collectors.toSet()));
  76         return forTypes(currentTypes);
  77     }
  78 
  79     public PackageTest excludeTypes(Collection<PackageType> types) {
  80         return excludeTypes(types.toArray(PackageType[]::new));
  81     }
  82 
  83     public PackageTest forTypes(PackageType... types) {
  84         Collection<PackageType> newTypes;
  85         if (types == null || types.length == 0) {
  86             newTypes = PackageType.NATIVE;
  87         } else {
  88             newTypes = Stream.of(types).collect(Collectors.toSet());
  89         }
  90         currentTypes = newTypes.stream()
  91                 .filter(PackageType::isSupported)
  92                 .filter(Predicate.not(excludeTypes::contains))
  93                 .collect(Collectors.toUnmodifiableSet());
  94         return this;
  95     }
  96 
  97     public PackageTest forTypes(Collection<PackageType> types) {
  98         return forTypes(types.toArray(PackageType[]::new));
  99     }
 100 
 101     public PackageTest notForTypes(PackageType... types) {
 102         return notForTypes(List.of(types));
 103     }
 104 
 105     public PackageTest notForTypes(Collection<PackageType> types) {
 106         Set<PackageType> workset = new HashSet<>(currentTypes);
 107         workset.removeAll(types);
 108         return forTypes(workset);
 109     }
 110 
 111     public PackageTest setExpectedExitCode(int v) {
 112         expectedJPackageExitCode = v;
 113         return this;
 114     }
 115 
 116     private PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v,
 117             String id) {
 118         if (id != null) {
 119             if (namedInitializers.contains(id)) {
 120                 return this;
 121             }
 122 
 123             namedInitializers.add(id);
 124         }
 125         currentTypes.forEach(type -> handlers.get(type).addInitializer(
 126                 ThrowingConsumer.toConsumer(v)));
 127         return this;
 128     }
 129 
 130     private PackageTest addRunOnceInitializer(ThrowingRunnable v, String id) {
 131         return addInitializer(new ThrowingConsumer<JPackageCommand>() {
 132             @Override
 133             public void accept(JPackageCommand unused) throws Throwable {
 134                 if (!executed) {
 135                     executed = true;
 136                     v.run();
 137                 }
 138             }
 139 
 140             private boolean executed;
 141         }, id);
 142     }
 143 
 144     public PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v) {
 145         return addInitializer(v, null);
 146     }
 147 
 148     public PackageTest addRunOnceInitializer(ThrowingRunnable v) {
 149         return addRunOnceInitializer(v, null);
 150     }
 151 
 152     public PackageTest addBundleVerifier(
 153             ThrowingBiConsumer<JPackageCommand, Executor.Result> v) {
 154         currentTypes.forEach(type -> handlers.get(type).addBundleVerifier(
 155                 ThrowingBiConsumer.toBiConsumer(v)));
 156         return this;
 157     }
 158 
 159     public PackageTest addBundleVerifier(ThrowingConsumer<JPackageCommand> v) {
 160         return addBundleVerifier(
 161                 (cmd, unused) -> ThrowingConsumer.toConsumer(v).accept(cmd));
 162     }
 163 
 164     public PackageTest addBundlePropertyVerifier(String propertyName,
 165             Predicate<String> pred, String predLabel) {
 166         return addBundleVerifier(cmd -> {
 167             final String value;
 168             if (TKit.isLinux()) {
 169                 value = LinuxHelper.getBundleProperty(cmd, propertyName);
 170             } else if (TKit.isWindows()) {
 171                 value = WindowsHelper.getMsiProperty(cmd, propertyName);
 172             } else {
 173                 throw new IllegalStateException();
 174             }
 175             TKit.assertTrue(pred.test(value), String.format(
 176                     "Check value of %s property %s [%s]", propertyName,
 177                     predLabel, value));
 178         });
 179     }
 180 
 181     public PackageTest addBundlePropertyVerifier(String propertyName,
 182             String expectedPropertyValue) {
 183         return addBundlePropertyVerifier(propertyName,
 184                 expectedPropertyValue::equals, "is");
 185     }
 186 
 187     public PackageTest addBundleDesktopIntegrationVerifier(boolean integrated) {
 188         forTypes(PackageType.LINUX, () -> {
 189             LinuxHelper.addBundleDesktopIntegrationVerifier(this, integrated);
 190         });
 191         return this;
 192     }
 193 
 194     public PackageTest addInstallVerifier(ThrowingConsumer<JPackageCommand> v) {
 195         currentTypes.forEach(type -> handlers.get(type).addInstallVerifier(
 196                 ThrowingConsumer.toConsumer(v)));
 197         return this;
 198     }
 199 
 200     public PackageTest addUninstallVerifier(ThrowingConsumer<JPackageCommand> v) {
 201         currentTypes.forEach(type -> handlers.get(type).addUninstallVerifier(
 202                 ThrowingConsumer.toConsumer(v)));
 203         return this;
 204     }
 205 
 206     public PackageTest setPackageInstaller(Consumer<JPackageCommand> v) {
 207         currentTypes.forEach(
 208                 type -> packageHandlers.get(type).installHandler = v);
 209         return this;
 210     }
 211 
 212     public PackageTest setPackageUnpacker(
 213             BiFunction<JPackageCommand, Path, Path> v) {
 214         currentTypes.forEach(type -> packageHandlers.get(type).unpackHandler = v);
 215         return this;
 216     }
 217 
 218     public PackageTest setPackageUninstaller(Consumer<JPackageCommand> v) {
 219         currentTypes.forEach(
 220                 type -> packageHandlers.get(type).uninstallHandler = v);
 221         return this;
 222     }
 223 
 224     static void withTestFileAssociationsFile(FileAssociations fa,
 225             ThrowingConsumer<Path> consumer) {
 226         final Path testFileDefaultName = Path.of("test" + fa.getSuffix());
 227         TKit.withTempFile(testFileDefaultName, testFile -> {
 228             if (TKit.isLinux()) {
 229                 LinuxHelper.initFileAssociationsTestFile(testFile);
 230             }
 231             consumer.accept(testFile);
 232         });
 233     }
 234 
 235     PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa,
 236             String... faLauncherDefaultArgs) {
 237 
 238         // Setup test app to have valid jpackage command line before
 239         // running check of type of environment.
 240         addHelloAppInitializer(null);
 241 
 242         String noActionMsg = "Not running file associations test";
 243         if (GraphicsEnvironment.isHeadless()) {
 244             TKit.trace(String.format(
 245                     "%s because running in headless environment", noActionMsg));
 246             return this;
 247         }
 248 
 249         addInstallVerifier(cmd -> {
 250             if (cmd.isFakeRuntime(noActionMsg) || cmd.isPackageUnpacked(noActionMsg)) {
 251                 return;
 252             }
 253 
 254             withTestFileAssociationsFile(fa, testFile -> {
 255                 testFile = testFile.toAbsolutePath().normalize();
 256 
 257                 final Path appOutput = testFile.getParent()
 258                         .resolve(HelloApp.OUTPUT_FILENAME);
 259                 Files.deleteIfExists(appOutput);
 260 
 261                 TKit.trace(String.format("Use desktop to open [%s] file",
 262                         testFile));
 263                 Desktop.getDesktop().open(testFile.toFile());
 264                 TKit.waitForFileCreated(appOutput, 7);
 265 
 266                 List<String> expectedArgs = new ArrayList<>(List.of(
 267                         faLauncherDefaultArgs));
 268                 expectedArgs.add(testFile.toString());
 269 
 270                 // Wait a little bit after file has been created to
 271                 // make sure there are no pending writes into it.
 272                 Thread.sleep(3000);
 273                 HelloApp.verifyOutputFile(appOutput, expectedArgs,
 274                         Collections.emptyMap());
 275             });
 276         });
 277 
 278         forTypes(PackageType.LINUX, () -> {
 279             LinuxHelper.addFileAssociationsVerifier(this, fa);
 280         });
 281 
 282         return this;
 283     }
 284 
 285     public PackageTest forTypes(Collection<PackageType> types, Runnable action) {
 286         Set<PackageType> oldTypes = Set.of(currentTypes.toArray(
 287                 PackageType[]::new));
 288         try {
 289             forTypes(types);
 290             action.run();
 291         } finally {
 292             forTypes(oldTypes);
 293         }
 294         return this;
 295     }
 296 
 297     public PackageTest forTypes(PackageType type, Runnable action) {
 298         return forTypes(List.of(type), action);
 299     }
 300 
 301     public PackageTest notForTypes(Collection<PackageType> types, Runnable action) {
 302         Set<PackageType> workset = new HashSet<>(currentTypes);
 303         workset.removeAll(types);
 304         return forTypes(workset, action);
 305     }
 306 
 307     public PackageTest notForTypes(PackageType type, Runnable action) {
 308         return notForTypes(List.of(type), action);
 309     }
 310 
 311     public PackageTest configureHelloApp() {
 312         return configureHelloApp(null);
 313     }
 314 
 315     public PackageTest configureHelloApp(String javaAppDesc) {
 316         addHelloAppInitializer(javaAppDesc);
 317         addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput);
 318         return this;
 319     }
 320 
 321     public final static class Group extends RunnablePackageTest {
 322         public Group(PackageTest... tests) {
 323             handlers = Stream.of(tests)
 324                     .map(PackageTest::createPackageTypeHandlers)
 325                     .flatMap(List<Consumer<Action>>::stream)
 326                     .collect(Collectors.toUnmodifiableList());
 327         }
 328 
 329         @Override
 330         protected void runAction(Action... action) {
 331             if (Set.of(action).contains(Action.UNINSTALL)) {
 332                 ListIterator<Consumer<Action>> listIterator = handlers.listIterator(
 333                         handlers.size());
 334                 while (listIterator.hasPrevious()) {
 335                     var handler = listIterator.previous();
 336                     List.of(action).forEach(handler::accept);
 337                 }
 338             } else {
 339                 handlers.forEach(handler -> List.of(action).forEach(handler::accept));
 340             }
 341         }
 342 
 343         private final List<Consumer<Action>> handlers;
 344     }
 345 
 346     final static class PackageHandlers {
 347         Consumer<JPackageCommand> installHandler;
 348         Consumer<JPackageCommand> uninstallHandler;
 349         BiFunction<JPackageCommand, Path, Path> unpackHandler;
 350     }
 351 
 352     @Override
 353     protected void runActions(List<Action[]> actions) {
 354         createPackageTypeHandlers().forEach(
 355                 handler -> actions.forEach(
 356                         action -> List.of(action).forEach(handler::accept)));
 357     }
 358 
 359     @Override
 360     protected void runAction(Action... action) {
 361         throw new UnsupportedOperationException();
 362     }
 363 
 364     private void addHelloAppInitializer(String javaAppDesc) {
 365         addInitializer(
 366                 cmd -> new HelloApp(JavaAppDesc.parse(javaAppDesc)).addTo(cmd),
 367                 "HelloApp");
 368     }
 369 
 370     private List<Consumer<Action>> createPackageTypeHandlers() {
 371         return PackageType.NATIVE.stream()
 372                 .map(type -> {
 373                     Handler handler = handlers.entrySet().stream()
 374                         .filter(entry -> !entry.getValue().isVoid())
 375                         .filter(entry -> entry.getKey() == type)
 376                         .map(entry -> entry.getValue())
 377                         .findAny().orElse(null);
 378                     Map.Entry<PackageType, Handler> result = null;
 379                     if (handler != null) {
 380                         result = Map.entry(type, handler);
 381                     }
 382                     return result;
 383                 })
 384                 .filter(Objects::nonNull)
 385                 .map(entry -> createPackageTypeHandler(
 386                         entry.getKey(), entry.getValue()))
 387                 .collect(Collectors.toList());
 388     }
 389 
 390     private Consumer<Action> createPackageTypeHandler(
 391             PackageType type, Handler handler) {
 392         return ThrowingConsumer.toConsumer(new ThrowingConsumer<Action>() {
 393             @Override
 394             public void accept(Action action) throws Throwable {
 395                 if (action == Action.FINALIZE) {
 396                     if (unpackDir != null && Files.isDirectory(unpackDir)
 397                             && !unpackDir.startsWith(TKit.workDir())) {
 398                         TKit.deleteDirectoryRecursive(unpackDir);
 399                     }
 400                 }
 401 
 402                 if (aborted) {
 403                     return;
 404                 }
 405 
 406                 final JPackageCommand curCmd;
 407                 if (Set.of(Action.INITIALIZE, Action.CREATE).contains(action)) {
 408                     curCmd = cmd;
 409                 } else {
 410                     curCmd = cmd.createImmutableCopy();
 411                 }
 412 
 413                 switch (action) {
 414                     case UNPACK: {
 415                         var handler = packageHandlers.get(type).unpackHandler;
 416                         if (!(aborted = (handler == null))) {
 417                             unpackDir = TKit.createTempDirectory(
 418                                             String.format("unpacked-%s",
 419                                                     type.getName()));
 420                             unpackDir = handler.apply(cmd, unpackDir);
 421                             cmd.setUnpackedPackageLocation(unpackDir);
 422                         }
 423                         break;
 424                     }
 425 
 426                     case INSTALL: {
 427                         var handler = packageHandlers.get(type).installHandler;
 428                         if (!(aborted = (handler == null))) {
 429                             handler.accept(curCmd);
 430                         }
 431                         break;
 432                     }
 433 
 434                     case UNINSTALL: {
 435                         var handler = packageHandlers.get(type).uninstallHandler;
 436                         if (!(aborted = (handler == null))) {
 437                             handler.accept(curCmd);
 438                         }
 439                         break;
 440                     }
 441 
 442                     case CREATE:
 443                         handler.accept(action, curCmd);
 444                         aborted = (expectedJPackageExitCode != 0);
 445                         return;
 446 
 447                     default:
 448                         handler.accept(action, curCmd);
 449                         break;
 450                 }
 451 
 452                 if (aborted) {
 453                     TKit.trace(
 454                             String.format("Aborted [%s] action of %s command",
 455                                     action, cmd.getPrintableCommandLine()));
 456                 }
 457             }
 458 
 459             private Path unpackDir;
 460             private boolean aborted;
 461             private final JPackageCommand cmd = Functional.identity(() -> {
 462                 JPackageCommand result = new JPackageCommand();
 463                 result.setDefaultInputOutput().setDefaultAppName();
 464                 if (BUNDLE_OUTPUT_DIR != null) {
 465                     result.setArgumentValue("--dest", BUNDLE_OUTPUT_DIR.toString());
 466                 }
 467                 type.applyTo(result);
 468                 return result;
 469             }).get();
 470         });
 471     }
 472 
 473     private class Handler implements BiConsumer<Action, JPackageCommand> {
 474 
 475         Handler() {
 476             initializers = new ArrayList<>();
 477             bundleVerifiers = new ArrayList<>();
 478             installVerifiers = new ArrayList<>();
 479             uninstallVerifiers = new ArrayList<>();
 480         }
 481 
 482         boolean isVoid() {
 483             return initializers.isEmpty();
 484         }
 485 
 486         void addInitializer(Consumer<JPackageCommand> v) {
 487             initializers.add(v);
 488         }
 489 
 490         void addBundleVerifier(BiConsumer<JPackageCommand, Executor.Result> v) {
 491             bundleVerifiers.add(v);
 492         }
 493 
 494         void addInstallVerifier(Consumer<JPackageCommand> v) {
 495             installVerifiers.add(v);
 496         }
 497 
 498         void addUninstallVerifier(Consumer<JPackageCommand> v) {
 499             uninstallVerifiers.add(v);
 500         }
 501 
 502         @Override
 503         public void accept(Action action, JPackageCommand cmd) {
 504             switch (action) {
 505                 case INITIALIZE:
 506                     initializers.forEach(v -> v.accept(cmd));
 507                     if (cmd.isImagePackageType()) {
 508                         throw new UnsupportedOperationException();
 509                     }
 510                     cmd.executePrerequisiteActions();
 511                     break;
 512 
 513                 case CREATE:
 514                     Executor.Result result = cmd.execute(expectedJPackageExitCode);
 515                     if (expectedJPackageExitCode == 0) {
 516                         TKit.assertFileExists(cmd.outputBundle());
 517                     } else {
 518                         TKit.assertPathExists(cmd.outputBundle(), false);
 519                     }
 520                     verifyPackageBundle(cmd, result);
 521                     break;
 522 
 523                 case VERIFY_INSTALL:
 524                     if (expectedJPackageExitCode == 0) {
 525                         verifyPackageInstalled(cmd);
 526                     }
 527                     break;
 528 
 529                 case VERIFY_UNINSTALL:
 530                     if (expectedJPackageExitCode == 0) {
 531                         verifyPackageUninstalled(cmd);
 532                     }
 533                     break;
 534             }
 535         }
 536 
 537         private void verifyPackageBundle(JPackageCommand cmd,
 538                 Executor.Result result) {
 539             if (expectedJPackageExitCode == 0) {
 540                 if (PackageType.LINUX.contains(cmd.packageType())) {
 541                     LinuxHelper.verifyPackageBundleEssential(cmd);
 542                 }
 543             }
 544             bundleVerifiers.forEach(v -> v.accept(cmd, result));
 545         }
 546 
 547         private void verifyPackageInstalled(JPackageCommand cmd) {
 548             final String formatString;
 549             if (cmd.isPackageUnpacked()) {
 550                 formatString = "Verify unpacked: %s";
 551             } else {
 552                 formatString = "Verify installed: %s";
 553             }
 554             TKit.trace(String.format(formatString, cmd.getPrintableCommandLine()));
 555 
 556             TKit.assertDirectoryExists(cmd.appRuntimeDirectory());
 557             if (!cmd.isRuntime()) {
 558                 TKit.assertExecutableFileExists(cmd.appLauncherPath());
 559 
 560                 if (PackageType.WINDOWS.contains(cmd.packageType())
 561                         && !cmd.isPackageUnpacked(
 562                                 "Not verifying desktop integration")) {
 563                     new WindowsHelper.DesktopIntegrationVerifier(cmd);
 564                 }
 565             }
 566 
 567             if (cmd.isPackageUnpacked()) {
 568                 final Path appImageFile = AppImageFile.getPathInAppImage(
 569                         Path.of(""));
 570                 try (Stream<Path> walk = ThrowingSupplier.toSupplier(
 571                         () -> Files.walk(cmd.unpackedPackageDirectory())).get()) {
 572                     walk.filter(path -> path.getFileName().equals(appImageFile))
 573                         .findFirst()
 574                         .ifPresent(path -> TKit.assertPathExists(path, false));
 575                 }
 576             } else {
 577                 TKit.assertPathExists(AppImageFile.getPathInAppImage(
 578                         cmd.appInstallationDirectory()), false);
 579             }
 580 
 581             installVerifiers.forEach(v -> v.accept(cmd));
 582         }
 583 
 584         private void verifyPackageUninstalled(JPackageCommand cmd) {
 585             TKit.trace(String.format("Verify uninstalled: %s",
 586                     cmd.getPrintableCommandLine()));
 587             if (!cmd.isRuntime()) {
 588                 TKit.assertPathExists(cmd.appLauncherPath(), false);
 589 
 590                 if (PackageType.WINDOWS.contains(cmd.packageType())) {
 591                     new WindowsHelper.DesktopIntegrationVerifier(cmd);
 592                 }
 593             }
 594 
 595             Path appInstallDir = cmd.appInstallationDirectory();
 596             if (TKit.isLinux() && Path.of("/").equals(appInstallDir)) {
 597                 ApplicationLayout appLayout = cmd.appLayout();
 598                 TKit.assertPathExists(appLayout.runtimeDirectory(), false);
 599             } else {
 600                 TKit.assertPathExists(appInstallDir, false);
 601             }
 602 
 603             uninstallVerifiers.forEach(v -> v.accept(cmd));
 604         }
 605 
 606         private final List<Consumer<JPackageCommand>> initializers;
 607         private final List<BiConsumer<JPackageCommand, Executor.Result>> bundleVerifiers;
 608         private final List<Consumer<JPackageCommand>> installVerifiers;
 609         private final List<Consumer<JPackageCommand>> uninstallVerifiers;
 610     }
 611 
 612     private static Map<PackageType, PackageHandlers> createDefaultPackageHandlers() {
 613         HashMap<PackageType, PackageHandlers> handlers = new HashMap<>();
 614         if (TKit.isLinux()) {
 615             handlers.put(PackageType.LINUX_DEB, LinuxHelper.createDebPackageHandlers());
 616             handlers.put(PackageType.LINUX_RPM, LinuxHelper.createRpmPackageHandlers());
 617         }
 618 
 619         if (TKit.isWindows()) {
 620             handlers.put(PackageType.WIN_MSI, WindowsHelper.createMsiPackageHandlers());
 621             handlers.put(PackageType.WIN_EXE, WindowsHelper.createExePackageHandlers());
 622         }
 623 
 624         if (TKit.isOSX()) {
 625             handlers.put(PackageType.MAC_DMG,  MacHelper.createDmgPackageHandlers());
 626             handlers.put(PackageType.MAC_PKG,  MacHelper.createPkgPackageHandlers());
 627         }
 628 
 629         return handlers;
 630     }
 631 
 632     private Collection<PackageType> currentTypes;
 633     private Set<PackageType> excludeTypes;
 634     private int expectedJPackageExitCode;
 635     private Map<PackageType, Handler> handlers;
 636     private Set<String> namedInitializers;
 637     private Map<PackageType, PackageHandlers> packageHandlers;
 638 
 639     private final static File BUNDLE_OUTPUT_DIR;
 640 
 641     static {
 642         final String propertyName = "output";
 643         String val = TKit.getConfigProperty(propertyName);
 644         if (val == null) {
 645             BUNDLE_OUTPUT_DIR = null;
 646         } else {
 647             BUNDLE_OUTPUT_DIR = new File(val).getAbsoluteFile();
 648 
 649             if (!BUNDLE_OUTPUT_DIR.isDirectory()) {
 650                 throw new IllegalArgumentException(String.format("Invalid value of %s sytem property: [%s]. Should be existing directory",
 651                         TKit.getConfigPropertyName(propertyName),
 652                         BUNDLE_OUTPUT_DIR));
 653             }
 654         }
 655     }
 656 }