org.scijava.util.AppUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scijava-common Show documentation
Show all versions of scijava-common Show documentation
SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.
/*
* #%L
* SciJava Common shared library for SciJava software.
* %%
* Copyright (C) 2009 - 2017 Board of Regents of the University of
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
* Institute of Molecular Cell Biology and Genetics, University of
* Konstanz, and KNIME GmbH.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package org.scijava.util;
import java.io.File;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.scijava.util.Types;
/**
* Useful methods for obtaining details of the SciJava application environment.
*
* @author Johannes Schindelin
* @author Curtis Rueden
*/
public final class AppUtils {
private static final Class> mainClass;
static {
// Get the class whose main method launched the application. The heuristic
// will fail if the main thread has terminated before this class loads.
final String className = DebugUtils.getMainClassName();
mainClass = className == null ? null : Types.load(className);
}
private AppUtils() {
// prevent instantiation of utility class
}
/**
* Gets the class whose main method launched the application.
*
* @return The launching class, or null if the main method terminated before
* the {@code AppUtils} class was loaded.
*/
public static Class> getMainClass() {
return mainClass;
}
/**
* Gets the application root directory. If the given system property is set,
* it is used. Otherwise, we scan up the tree from the given class for a
* suitable directory.
*
* @param sysProp System property which may point at the root directory. If
* this is set to a valid directory, it is used.
* @param c The class from which the base directory should be derived.
* @param baseSubdirectory A hint for what to expect for a directory structure
* beneath the application base directory. If this value is null
* (i.e., no hint is given), the heuristic scans up the directory
* tree looking for the topmost pom.xml file.
* @see AppUtils#getBaseDirectory(File, String)
*/
public static File getBaseDirectory(final String sysProp, final Class> c,
final String baseSubdirectory)
{
final String property = System.getProperty(sysProp);
if (property != null) {
final File dir = new File(property);
if (dir.isDirectory()) return dir;
}
// look for valid base directory relative to this class
final File basePath = AppUtils.getBaseDirectory(c, baseSubdirectory);
if (basePath != null) return basePath;
// NB: Look for valid base directory relative to the main class which
// launched this environment. We will reach this logic, e.g., if the
// application is running via "mvn exec:exec". In this case, most classes
// (including this one) will be located in JARs in the local Maven
// repository cache (~/.m2/repository), so the corePath will be null.
// However, the classes of the launching project will be located in
// target/classes, so we search up the tree from one of those.
final Class> mc = AppUtils.getMainClass();
final File appPath = mc == null ? null : AppUtils.getBaseDirectory(mc);
if (appPath != null) return appPath;
// last resort: use current working directory
return new File(".").getAbsoluteFile();
}
/**
* Gets the base file system directory containing the given class file.
*
* @param c The class from which the base directory should be derived.
* @see #getBaseDirectory(File, String)
*/
public static File getBaseDirectory(final Class> c) {
return getBaseDirectory(c, null);
}
/**
* Gets the base file system directory containing the given class file.
*
* @param c The class from which the base directory should be derived.
* @param baseSubdirectory A hint for what to expect for a directory structure
* beneath the application base directory.
* @see #getBaseDirectory(File, String)
*/
public static File getBaseDirectory(final Class> c,
final String baseSubdirectory)
{
// see: http://stackoverflow.com/a/12733172/1207769
// step 1: convert Class to URL
final URL location = Types.location(c);
// step 2: convert URL to File
File baseFile;
try {
baseFile = FileUtils.urlToFile(location);
}
catch (final IllegalArgumentException exc) {
// URL can't be converted to a file
baseFile = null;
}
// step 3: get the file's base directory
return getBaseDirectory(baseFile, baseSubdirectory);
}
/**
* Gets the base file system directory from the given known class location.
*
* This method uses heuristics to find the base directory in a variety of
* situations. Depending on your execution environment, the class may be
* located in one of several different places:
*
*
* - In the Maven build directory. Typically this is the
* {@code target/classes} folder of a given component. In this case, the class
* files reside directly on the file system (not in a JAR file). The base
* directory is defined as the toplevel Maven directory for the multi-module
* project. For example, if you have checked out {@code scijava-common.git} to
* {@code ~/sjc}, the {@code org.scijava.Context} class will be located at
* {@code ~/sjc/scijava-common/target/classes/org/scijava/Context.class}.
* Asking for the base directory for that class will yield
* {@code ~/sjc/scijava-common}, as long as you correctly specify
* {@code scijava-common} for the {@code baseSubdirectory}.
* - Within a JAR file in the Maven local repository cache. Typically
* this cache is located in {@code ~/.m2/repository}. The location will be
* {@code groupId/artifactId/version/artifactId-version.jar} where
* {@code groupId}, {@code artifactId} and {@code version} are the Maven GAV
* coordinates. Note that in this case, no base directory with respect to
* the given class can be found, and this method will return null.
* - Within a JAR file beneath the base directory. Common cases
* include running the application from a standalone application bundle (e.g.,
* the JARs may all be located within a {@code jars} folder of the application
* distribution archive) or using a JAR packaged by Maven and residing in the
* Maven build folder (typically {@code target/artifactId-version.jar}).
* However, this could potentially be anywhere beneath the base directory.
* This method assumes the JAR will be nested exactly one level deep; i.e., it
* computes the base directory as the parent directory of the one containing
* the JAR file.
*
*
* As you can see, it is quite complicated to find the base directory properly
* in all cases. The specific execution environments we have tested include:
*
*
* - Running from Eclipse. How Eclipse structures the classpath
* depends on which dependencies are open (i.e., project dependencies) versus
* closed (i.e., JAR dependencies). Each project dependency has a classpath
* entry in its Maven build directory. Each JAR dependency has a
* classpath entry within a JAR file in the Maven local repository
* cache.
* - Running via Maven. If you execute the application using Maven
* (e.g., {@code mvn exec:exec}, or with a fully Maven-compatible IDE such as
* NetBeans or IntelliJ IDEA) then all dependencies will reside within JAR
* files in the local Maven repository cache. But the executed project
* itself will reside in its Maven build directory. So as long as you
* ask for the base directory relative to a class
* of the executed project it will be found.
* - Running as an application bundle (e.g., ImageJ). Typically this
* means downloading ImageJ from the web site, unpacking it and running the
* ImageJ launcher (double-clicking ImageJ-win32.exe on Windows,
* double-clicking the {@code ImageJ.app} on OS X, etc.). In this case, all
* components reside in the {@code jars} folder of the application bundle, and
* the base directory will be found one level above that.
*
*
* @param classLocation The location from which the base directory should be
* derived.
* @param baseSubdirectory A hint for what to expect for a directory structure
* beneath the application base directory. If this value is null
* (i.e., no hint is given), the heuristic scans up the directory
* tree looking for the topmost pom.xml file.
*/
public static File getBaseDirectory(final File classLocation,
final String baseSubdirectory)
{
if (classLocation == null) return null;
String path = FileUtils.getPath(classLocation).replace('\\', '/');
if (path.contains("/.m2/repository/")) {
// NB: The class is in a JAR in the Maven repository cache.
// We cannot find the application base directory relative to this path.
return null;
}
// check whether the class is in a Maven build directory
String basePrefix = "/";
if (baseSubdirectory != null) basePrefix += baseSubdirectory + "/";
final String targetClassesSuffix = basePrefix + "target/classes";
final String targetTestClassesSuffix = basePrefix + "target/test-classes";
final String[] suffixes = {targetClassesSuffix, targetTestClassesSuffix};
for (final String suffix : suffixes) {
if (!path.endsWith(suffix)) continue;
// NB: The class is a file beneath a Maven build directory.
path = path.substring(0, path.length() - suffix.length());
File dir = new File(path);
if (baseSubdirectory == null) {
// NB: There is no hint as to the directory structure.
// So we scan up the tree to find the topmost pom.xml file.
while (dir.getParentFile() != null &&
new File(dir.getParentFile(), "pom.xml").exists())
{
dir = dir.getParentFile();
}
}
return dir;
}
final Pattern pattern =
Pattern.compile(".*(" + Pattern.quote(basePrefix + "target/") +
"[^/]*\\.jar)");
final Matcher matcher = pattern.matcher(path);
if (matcher.matches()) {
// NB: The class is in the Maven build directory inside a JAR file
// ("target/[something].jar").
final int index = matcher.start(1);
path = path.substring(0, index);
return new File(path);
}
if (path.endsWith(".jar")) {
final File jarDirectory = classLocation.getParentFile();
// NB: The class is in a JAR file, which we assume is nested one level
// deep (i.e., directly beneath the application base directory).
return jarDirectory.getParentFile();
}
// NB: As a last resort, we use the class location directly.
return classLocation;
}
}