[Aprog]
[aprog.git] / Aprog / src / net / sourceforge / aprog / tools / Launcher.java
blob2ea32bb0516c998a0019965b1603fc884f1b45c1
1 /*
2 * The MIT License
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
22 * THE SOFTWARE.
25 package net.sourceforge.aprog.tools;
27 import static net.sourceforge.aprog.tools.Tools.*;
29 import java.io.File;
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;
39 import java.util.Set;
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;
46 /**
47 * @author codistmonk (creation 2010-10-22)
49 public final class Launcher {
51 /**
52 * @throws IllegalInstantiationException To prevent instantiation
54 private Launcher() {
55 throw new IllegalInstantiationException();
58 /**
59 * {@value}.
60 * This is the root folder that should contain jar and native libraries.
62 public static final String LIBRARY_ROOT = "lib/";
64 /**
66 * @param mainClass
67 * <br>Not null
68 * @param commandLineArguments
69 * <br>Not null
70 * @return
71 * <br>Not null
72 * @throws RuntimeException If an error occurs
74 public static final Process launch(final Class<?> mainClass, final String... commandLineArguments) {
75 try {
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);
84 } else {
85 process = startApplicationInIDE(new File(LIBRARY_ROOT), mainClass, commandLineArguments);
88 redirectOutputsToConsole(process);
90 return process;
91 } catch (final Exception exception) {
92 return throwUnchecked(exception);
96 /**
98 * @param process
99 * <br>Not null
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}.
109 * @param input
110 * <br>Not null
111 * <br>Input-output
112 * @param output
113 * <br>Not null
114 * <br>Input-output
115 * @return
116 * <br>Not null
118 public static final Thread pipe(final InputStream input, final PrintStream output) {
119 final Thread result = new Thread() {
121 @Override
122 public final void run() {
123 final Scanner errorScanner = new Scanner(input);
125 try {
126 while (errorScanner.hasNext()) {
127 output.println(errorScanner.nextLine());
129 } catch (final Exception exception) {
130 Tools.ignore(exception);
136 result.start();
138 return result;
142 * @param mainClass
143 * <br>Not null
144 * @param applicationJar
145 * <br>Not null
146 * @param commandLineArguments
147 * <br>Not null
148 * @return
149 * <br>Not null
150 * <br>New
152 public static final Process startApplicationFromJar(final File applicationJar,
153 final Class<?> mainClass, final String... commandLineArguments) {
154 return execute(
155 applicationJar.toString(),
156 createLibraryPath(applicationJar),
157 mainClass,
158 commandLineArguments);
162 * @param mainClass
163 * <br>Not null
164 * @param libraryRoot
165 * <br>Not null
166 * @param commandLineArguments
167 * <br>Not null
168 * @return
169 * <br>Not null
170 * <br>New
172 public static final Process startApplicationInIDE(final File libraryRoot,
173 final Class<?> mainClass, final String... commandLineArguments) {
174 return execute(
175 getClassPathInIDE(getClassRoot(mainClass), libraryRoot),
176 getLibraryPath(libraryRoot),
177 mainClass,
178 commandLineArguments);
183 * @param classPath
184 * <br>Maybe null
185 * @param libraryPath
186 * <br>Maybe null
187 * @param mainClass
188 * <br>Not null
189 * @param commandLineArguments
190 * <br>Not null
191 * @return
192 * <br>Not null
193 * <br>New
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(
198 "java",
199 "-Djava.library.path=" + libraryPath,
200 "-cp", classPath,
201 mainClass.getName()),
202 commandLineArguments);
204 getLoggerForThisMethod().info(debug(DEBUG_STACK_OFFSET, (Object[]) command));
206 try {
207 return Runtime.getRuntime().exec(command);
208 } catch (final IOException exception) {
209 throw unchecked(exception);
214 * @param classRoot
215 * <br>Not null
216 * @param jarRoot
217 * <br>Not null
218 * @return
219 * <br>Not null
220 * <br>New
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);
227 result.append(jar);
230 return result.toString();
234 * @param applicationJar
235 * <br>Not null
236 * @return
237 * <br>Not null
238 * <br>New
239 * @throws RuntimeException If {@code applicationJar} cannot be read as a jar file
241 public static final String createLibraryPath(final File applicationJar) {
242 JarFile jarFile;
243 try {
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);
258 try {
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);
272 * @param base
273 * <br>Not null
274 * @return
275 * <br>Not null
276 * <br>New
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)) {
286 parents.add(parent);
288 if (!result.toString().isEmpty()) {
289 result.append(File.pathSeparator);
292 result.append(nativeLibrary.getParent());
296 return result.toString();
301 * @param base
302 * <br>Not null
303 * <br>Maybe shared
304 * @return
305 * <br>Not null
306 * <br>New
308 public static final List<File> getJars(final File base) {
309 return new JarCollector().collect(base);
314 * @param base
315 * <br>Not null
316 * <br>Maybe shared
317 * @return
318 * <br>Not null
319 * <br>New
321 public static final List<File> getNativeLibraries(final File base) {
322 return new NativeLibraryCollector().collect(base);
327 * @param file
328 * <br>Not null
329 * @return
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>
338 * <li>Linux: so;
339 * <li>Mac OS X: dylib, jnilib;
340 * <li>Windows: dll;
341 * <li>Solaris: so.
342 * </ul>
344 * @param file
345 * <br>Not null
346 * @return
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");
369 return false;
373 * @author codistmonk (creation 2010-10-22)
375 public static abstract class AbstractRecursiveCollector {
379 * @param base
380 * <br>Not null
381 * <br>Maybe shared
382 * @return
383 * <br>Not null
384 * <br>New
386 public final List<File> collect(final File base) {
387 final List<File> result = new ArrayList<File>();
389 if (this.accept(base)) {
390 result.add(base);
391 } else if (base.isDirectory()) {
392 for (final File subFile : base.listFiles()) {
393 result.addAll(this.collect(subFile));
397 return result;
402 * @param file
403 * <br>Not null
404 * @return
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 {
416 @Override
417 public final boolean accept(final File file) {
418 return isJar(file);
424 * @author codistmonk (creation 2010-10-22)
426 public static final class NativeLibraryCollector extends AbstractRecursiveCollector {
428 @Override
429 public final boolean accept(final File file) {
430 return isNativeLibrary(file);