4 * Copyright 2010 Codist Monk.
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 package net
.sourceforge
.aprog
.tools
;
27 import static net
.sourceforge
.aprog
.tools
.Tools
.*;
30 import java
.io
.FileOutputStream
;
31 import java
.io
.IOException
;
32 import java
.io
.InputStream
;
33 import java
.io
.PrintStream
;
34 import java
.util
.ArrayList
;
35 import java
.util
.HashSet
;
36 import java
.util
.List
;
37 import java
.util
.Locale
;
38 import java
.util
.Scanner
;
40 import java
.util
.jar
.JarEntry
;
41 import java
.util
.jar
.JarFile
;
42 import java
.util
.logging
.Level
;
44 import net
.sourceforge
.aprog
.tools
.IllegalInstantiationException
;
47 * @author codistmonk (creation 2010-10-22)
49 public final class Launcher
{
52 * @throws IllegalInstantiationException To prevent instantiation
55 throw new IllegalInstantiationException();
60 * This is the root folder that should contain jar and native libraries.
62 public static final String LIBRARY_ROOT
= "lib/";
68 * @param commandLineArguments
72 * @throws RuntimeException If an error occurs
74 public static final Process
launch(final Class
<?
> mainClass
, final String
... commandLineArguments
) {
76 final File applicationFile
= getClassRoot(mainClass
);
78 getLoggerForThisMethod().log(Level
.INFO
, "Launching application in {0}", applicationFile
);
80 final Process process
;
82 if (isJar(applicationFile
)) {
83 process
= startApplicationFromJar(applicationFile
, mainClass
, commandLineArguments
);
85 process
= startApplicationInIDE(new File(LIBRARY_ROOT
), mainClass
, commandLineArguments
);
88 redirectOutputsToConsole(process
);
91 } catch (final Exception exception
) {
92 return throwUnchecked(exception
);
101 public static final void redirectOutputsToConsole(final Process process
) {
102 pipe(process
.getErrorStream(), System
.err
);
103 pipe(process
.getInputStream(), System
.out
);
107 * Creates and starts a new thread that scans {@code input} (with a {@link Scanner}) and writes each line to {@code output}.
118 public static final Thread
pipe(final InputStream input
, final PrintStream output
) {
119 final Thread result
= new Thread() {
122 public final void run() {
123 final Scanner errorScanner
= new Scanner(input
);
126 while (errorScanner
.hasNext()) {
127 output
.println(errorScanner
.nextLine());
129 } catch (final Exception exception
) {
130 Tools
.ignore(exception
);
144 * @param applicationJar
146 * @param commandLineArguments
152 public static final Process
startApplicationFromJar(final File applicationJar
,
153 final Class
<?
> mainClass
, final String
... commandLineArguments
) {
155 applicationJar
.toString(),
156 createLibraryPath(applicationJar
),
158 commandLineArguments
);
166 * @param commandLineArguments
172 public static final Process
startApplicationInIDE(final File libraryRoot
,
173 final Class
<?
> mainClass
, final String
... commandLineArguments
) {
175 getClassPathInIDE(getClassRoot(mainClass
), libraryRoot
),
176 getLibraryPath(libraryRoot
),
178 commandLineArguments
);
189 * @param commandLineArguments
195 public static final Process
execute(final String classPath
, final String libraryPath
,
196 final Class
<?
> mainClass
, final String
... commandLineArguments
) {
197 final String
[] command
= append(array(
199 "-Djava.library.path=" + libraryPath
,
201 mainClass
.getName()),
202 commandLineArguments
);
204 getLoggerForThisMethod().info(debug(DEBUG_STACK_OFFSET
, (Object
[]) command
));
207 return Runtime
.getRuntime().exec(command
);
208 } catch (final IOException exception
) {
209 throw unchecked(exception
);
222 public static final String
getClassPathInIDE(final File classRoot
, final File jarRoot
) {
223 final StringBuilder result
= new StringBuilder(classRoot
.toString());
225 for (final File jar
: getJars(jarRoot
)) {
226 result
.append(File
.pathSeparator
);
230 return result
.toString();
234 * @param applicationJar
239 * @throws RuntimeException If {@code applicationJar} cannot be read as a jar file
241 public static final String
createLibraryPath(final File applicationJar
) {
244 jarFile
= new JarFile(applicationJar
);
245 } catch (final IOException exception
) {
246 throw unchecked(exception
);
249 final File nativeLibraryBase
= createTemporaryFile("lib", "", null).getParentFile();
251 for (final JarEntry entry
: iterable(jarFile
.entries())) {
252 final File entryFile
= new File(entry
.getName());
253 final String entryName
= entryFile
.getName();
255 if (isNativeLibrary(entryFile
)) {
256 getLoggerForThisMethod().log(Level
.INFO
, "Unpacking native library: {0}", entryName
);
259 write(jarFile
.getInputStream(entry
), new FileOutputStream(new File(nativeLibraryBase
, entryName
)));
260 } catch (final IOException exception
) {
261 getLoggerForThisMethod().log(Level
.SEVERE
, null, exception
);
266 getLoggerForThisMethod().log(Level
.INFO
, "Native libraries unpacked in: {0}", nativeLibraryBase
);
268 return getLibraryPath(nativeLibraryBase
);
278 public static final String
getLibraryPath(final File base
) {
279 final StringBuilder result
= new StringBuilder();
280 final Set
<String
> parents
= new HashSet
<String
>();
282 for (final File nativeLibrary
: getNativeLibraries(base
)) {
283 final String parent
= nativeLibrary
.getParentFile().getAbsolutePath();
285 if (!parents
.contains(parent
)) {
288 if (!result
.toString().isEmpty()) {
289 result
.append(File
.pathSeparator
);
292 result
.append(nativeLibrary
.getParent());
296 return result
.toString();
308 public static final List
<File
> getJars(final File base
) {
309 return new JarCollector().collect(base
);
321 public static final List
<File
> getNativeLibraries(final File base
) {
322 return new NativeLibraryCollector().collect(base
);
330 * <br>Range: any boolean
332 public static final boolean isJar(final File file
) {
333 return file
.getName().toLowerCase(Locale
.ENGLISH
).endsWith(".jar");
337 * Determines if {@code file} is a native library by looking at its extension (case insensitive):<ul>
339 * <li>Mac OS X: dylib, jnilib;
347 * <br>Range: any boolean
349 public static final boolean isNativeLibrary(final File file
) {
350 final String osName
= System
.getProperty("os.name").toLowerCase(Locale
.ENGLISH
);
351 final String fileName
= file
.getName().toLowerCase();
353 if (osName
.startsWith("linux")) {
354 return fileName
.endsWith(".so");
357 if (osName
.startsWith("mac os x")) {
358 return fileName
.endsWith(".dylib") || fileName
.endsWith(".jnilib");
361 if (osName
.startsWith("windows")) {
362 return fileName
.endsWith(".dll");
365 if (osName
.startsWith("solaris")) {
366 return fileName
.endsWith(".so");
373 * @author codistmonk (creation 2010-10-22)
375 public static abstract class AbstractRecursiveCollector
{
386 public final List
<File
> collect(final File base
) {
387 final List
<File
> result
= new ArrayList
<File
>();
389 if (this.accept(base
)) {
391 } else if (base
.isDirectory()) {
392 for (final File subFile
: base
.listFiles()) {
393 result
.addAll(this.collect(subFile
));
405 * <br>Range: any boolean
407 public abstract boolean accept(final File file
);
412 * @author codistmonk (creation 2010-10-22)
414 public static final class JarCollector
extends AbstractRecursiveCollector
{
417 public final boolean accept(final File file
) {
424 * @author codistmonk (creation 2010-10-22)
426 public static final class NativeLibraryCollector
extends AbstractRecursiveCollector
{
429 public final boolean accept(final File file
) {
430 return isNativeLibrary(file
);