1. new feature: log to file

2. new feature: attach mode

Signed-off-by: pengzhile <pengzhile@gmail.com>
This commit is contained in:
pengzhile 2022-01-20 14:55:03 +08:00
parent be4d4fa72f
commit 389380dda1
16 changed files with 650 additions and 63 deletions

View File

@ -1,4 +1,4 @@
# ja-netfilter v2.1.1
# ja-netfilter v2.2.0
### A javaagent framework
@ -9,12 +9,13 @@
* add as an argument of the `java` command. eg: `java -javaagent:/absolute/path/to/ja-netfilter.jar -jar executable_jar_file.jar`
* some apps support the `JVM Options file`, you can add as a line of the `JVM Options file`.
* **WARNING: DO NOT put some unnecessary whitespace characters!**
* or execute `java -jar /path/to/ja-netfilter.jar` to use `attach mode`.
* edit your plugin config files: `${lower plugin name}.conf` file in the `config` dir where `ja-netfilter.jar` is located.
* the `config` and `plugins` directory can be specified through **the javaagent args**.
* eg: `-javaagent:/path/to/ja-netfilter.jar=appName`, your config and plugins directories will be `config-appname` and `plugins-appname`.
* if no javaagent args, they default to `config` and `plugins`.
* this mechanism will avoid extraneous and bloated `config` and `plugins`.
* the `config`, `logs` and `plugins` directories can be specified through **the javaagent args**.
* eg: `-javaagent:/path/to/ja-netfilter.jar=appName`, your config, logs and plugins directories will be `config-appname`, `logs-appname` and `plugins-appname`.
* if no javaagent args, they default to `config`, `logs` and `plugins`.
* this mechanism will avoid extraneous and bloated `config`, `logs` and `plugins`.
* run your java application and enjoy~
@ -46,8 +47,9 @@ EQUAL,somedomain
## Debug info
* the `ja-netfilter` will **NOT** output debugging information by default
* add environment variable `JANF_DEBUG=1` and start to enable it
* or add system property `-Djanf.debug=1` to enable it
* add environment variable `JANF_DEBUG=1` (log level) and start to enable it
* or add system property `-Djanf.debug=1` (log level) to enable it
* log level: `NONE=0`, `DEBUG=1`, `INFO=2`, `WARN=3`, `ERROR=4`;
## Plugin system

12
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>com.ja-netfilter</groupId>
<artifactId>ja-netfilter</artifactId>
<version>2.1.1</version>
<version>2.2.0</version>
<name>ja-netfilter</name>
<description>A javaagent framework</description>
@ -82,6 +82,7 @@
<manifestEntries>
<Built-By>neo</Built-By>
<Premain-Class>com.janetfilter.core.Launcher</Premain-Class>
<Agent-Class>com.janetfilter.core.Launcher</Agent-Class>
<Main-Class>com.janetfilter.core.Launcher</Main-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
@ -163,4 +164,13 @@
</distributionManagement>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
</project>

View File

@ -70,7 +70,7 @@ public final class Dispatcher implements ClassFileTransformer {
classFileBuffer = transformer.transform(className, classFileBuffer, order++);
}
} catch (Throwable e) {
DebugInfo.output("Transform class failed: " + className, e);
DebugInfo.error("Transform class failed: " + className, e);
}
} while (false);

View File

@ -1,14 +1,17 @@
package com.janetfilter.core;
import com.janetfilter.core.utils.ProcessUtils;
import com.janetfilter.core.utils.StringUtils;
import java.io.File;
public final class Environment {
private final String pid;
private final File baseDir;
private final File agentFile;
private final File configDir;
private final File pluginsDir;
private final File logsDir;
private final String nativePrefix;
public Environment(File agentFile) {
@ -22,13 +25,20 @@ public final class Environment {
if (StringUtils.isEmpty(app)) {
configDir = new File(baseDir, "config");
pluginsDir = new File(baseDir, "plugins");
logsDir = new File(baseDir, "logs");
} else {
app = app.toLowerCase();
configDir = new File(baseDir, "config-" + app);
pluginsDir = new File(baseDir, "plugins-" + app);
logsDir = new File(baseDir, "logs-" + app);
}
nativePrefix = StringUtils.randomMethodName(15) + "_";
pid = ProcessUtils.currentId();
}
public String getPid() {
return pid;
}
public File getBaseDir() {
@ -47,6 +57,10 @@ public final class Environment {
return pluginsDir;
}
public File getLogsDir() {
return logsDir;
}
public String getNativePrefix() {
return nativePrefix;
}
@ -54,11 +68,13 @@ public final class Environment {
@Override
public String toString() {
return "Environment: {" +
"\n\tbaseDir=" + baseDir +
", \n\tagentFile=" + agentFile +
", \n\tconfigDir=" + configDir +
", \n\tpluginsDir=" + pluginsDir +
", \n\tnativePrefix=" + nativePrefix +
"\n\tpid = " + pid +
", \n\tbaseDir = " + baseDir +
", \n\tagentFile = " + agentFile +
", \n\tconfigDir = " + configDir +
", \n\tpluginsDir = " + pluginsDir +
", \n\tlogsDir = " + logsDir +
", \n\tnativePrefix = " + nativePrefix +
"\n}";
}
}

View File

@ -8,7 +8,8 @@ import java.util.Set;
public class Initializer {
public static void init(Instrumentation inst, Environment environment) {
DebugInfo.output(environment.toString());
DebugInfo.useFile(environment.getLogsDir());
DebugInfo.info(environment.toString());
Dispatcher dispatcher = new Dispatcher();
new PluginManager(inst, dispatcher, environment).loadPlugins();
@ -26,7 +27,7 @@ public class Initializer {
try {
inst.retransformClasses(c);
} catch (Throwable e) {
DebugInfo.output("Retransform class failed: " + name, e);
DebugInfo.error("Retransform class failed: " + name, e);
}
}
}

View File

@ -1,25 +1,49 @@
package com.janetfilter.core;
import com.janetfilter.core.attach.VMLauncher;
import com.janetfilter.core.attach.VMSelector;
import com.janetfilter.core.commons.DebugInfo;
import com.janetfilter.core.utils.WhereIsUtils;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.net.URI;
import java.net.URL;
import java.util.jar.JarFile;
public class Launcher {
private static final String VERSION = "v2.1.1";
public static final String ATTACH_ARG = "--attach";
public static final String VERSION = "v2.2.0";
private static boolean loaded = false;
public static void main(String[] args) {
URI jarURI;
try {
jarURI = WhereIsUtils.getJarURI();
} catch (Throwable e) {
DebugInfo.error("Can not locate `ja-netfilter` jar file.", e);
return;
}
String jarPath = jarURI.getPath();
if (args.length > 1 && args[0].equals(ATTACH_ARG)) {
VMLauncher.attachVM(jarPath, args[1], args.length > 2 ? args[2] : null);
return;
}
printUsage();
try {
new VMSelector(new File(jarPath)).select();
} catch (Throwable e) {
System.err.println(" ERROR: Select virtual machine failed.");
e.printStackTrace(System.err);
}
}
public static void premain(String args, Instrumentation inst) {
if (loaded) {
DebugInfo.output("WARN: You have multiple `ja-netfilter` as -javaagent.");
DebugInfo.warn("You have multiple `ja-netfilter` as javaagent.");
return;
}
@ -28,9 +52,9 @@ public class Launcher {
URI jarURI;
try {
loaded = true;
jarURI = getJarURI();
jarURI = WhereIsUtils.getJarURI();
} catch (Throwable e) {
DebugInfo.output("ERROR: Can not locate ja-netfilter jar file.", e);
DebugInfo.error("Can not locate `ja-netfilter` jar file.", e);
return;
}
@ -38,13 +62,17 @@ public class Launcher {
try {
inst.appendToBootstrapClassLoaderSearch(new JarFile(agentFile));
} catch (Throwable e) {
DebugInfo.output("ERROR: Can not access ja-netfilter jar file.", e);
DebugInfo.error("Can not access `ja-netfilter` jar file.", e);
return;
}
Initializer.init(inst, new Environment(agentFile, args)); // for some custom UrlLoaders
}
public static void agentmain(String args, Instrumentation inst) {
premain(args, inst);
}
private static void printUsage() {
String content = "\n ============================================================================ \n" +
"\n" +
@ -57,28 +85,5 @@ public class Launcher {
" ============================================================================ \n\n";
System.out.print(content);
System.out.flush();
}
private static URI getJarURI() throws Exception {
URL url = Launcher.class.getProtectionDomain().getCodeSource().getLocation();
if (null != url) {
return url.toURI();
}
String resourcePath = "/4cc9c353c626d6510ca855ab6907ed7f64400257.txt";
url = Launcher.class.getResource(resourcePath);
if (null == url) {
throw new Exception("Can not locate resource file.");
}
String path = url.getPath();
if (!path.endsWith("!" + resourcePath)) {
throw new Exception("Invalid resource path.");
}
path = path.substring(0, path.length() - resourcePath.length() - 1);
return new URI(path);
}
}

View File

@ -0,0 +1,51 @@
package com.janetfilter.core.attach;
public class VMDescriptor {
private String id;
private String className;
private String args;
private Boolean old = true;
public VMDescriptor(String id, String className, String args) {
this.id = id;
this.className = className;
this.args = args;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getArgs() {
return args;
}
public void setArgs(String args) {
this.args = args;
}
public Boolean getOld() {
return old;
}
public void setOld(Boolean old) {
this.old = old;
}
@Override
public String toString() {
return id + " " + className;
}
}

View File

@ -0,0 +1,81 @@
package com.janetfilter.core.attach;
import com.janetfilter.core.Launcher;
import com.janetfilter.core.utils.ProcessUtils;
import com.janetfilter.core.utils.WhereIsUtils;
import com.sun.tools.attach.VirtualMachine;
import java.io.File;
import java.io.IOException;
public class VMLauncher {
public static void attachVM(String agentFile, String pid, String args) {
try {
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentFile, args);
vm.detach();
} catch (IOException e) {
if (e.getMessage().startsWith("Non-numeric value found")) {
System.out.println("WARN: The jdk used by `ja-netfilter` does not match the attached jdk version");
}
} catch (Throwable e) {
System.err.println("Attach failed: " + pid);
e.printStackTrace(System.err);
return;
}
System.out.println("ATTACHED SUCCESSFULLY: " + pid);
}
public static void launch(File thisJar, VMDescriptor descriptor, String args) throws Exception {
File javaCommand = WhereIsUtils.findJava();
if (null == javaCommand) {
throw new Exception("Can not locate java command, unable to start attach mode.");
}
ProcessBuilder pb;
double version = Double.parseDouble(System.getProperty("java.specification.version"));
if (version > 1.8D) {
pb = buildProcess(javaCommand, thisJar, descriptor.getId(), args);
} else {
File toolsJar = WhereIsUtils.findToolsJar();
if (null == toolsJar) {
throw new Exception("Can not locate tools.jar file, unable to start attach mode.");
}
pb = buildProcess(javaCommand, thisJar, descriptor.getId(), args, toolsJar);
}
int exitValue = ProcessUtils.start(pb);
if (0 != exitValue) {
throw new Exception("Attach mode failed: " + exitValue);
}
}
private static ProcessBuilder buildProcess(File java, File thisJar, String id, String args) {
String[] cmdArray = new String[]{
java.getAbsolutePath(),
"-Djanf.debug=" + System.getProperty("janf.debug", "0"),
"-jar",
thisJar.getAbsolutePath(),
Launcher.ATTACH_ARG,
id, args
};
return new ProcessBuilder(cmdArray);
}
private static ProcessBuilder buildProcess(File java, File thisJar, String id, String args, File toolsJar) {
String[] cmdArray = new String[]{
java.getAbsolutePath(),
"-Djanf.debug=" + System.getProperty("janf.debug", "0"),
"-Xbootclasspath/a:" + toolsJar.getAbsolutePath(),
"-jar",
thisJar.getAbsolutePath(),
Launcher.ATTACH_ARG,
id, args
};
return new ProcessBuilder(cmdArray);
}
}

View File

@ -0,0 +1,124 @@
package com.janetfilter.core.attach;
import com.janetfilter.core.utils.DateUtils;
import com.janetfilter.core.utils.ProcessUtils;
import com.janetfilter.core.utils.WhereIsUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class VMSelector {
private final File thisJar;
private List<VMDescriptor> descriptors;
public VMSelector(File thisJar) {
this.thisJar = thisJar;
}
private List<VMDescriptor> getVMList() throws Exception {
File jpsCommand = WhereIsUtils.findJPS();
if (null == jpsCommand) {
throw new Exception("jps command not found");
}
List<String> list = new ArrayList<>();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ProcessUtils.start(new ProcessBuilder(jpsCommand.getAbsolutePath(), "-lv"), bos);
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bos.toByteArray())));
while ((line = reader.readLine()) != null) {
list.add(line);
}
String processId = ProcessUtils.currentId();
return list.stream()
.map(s -> {
String[] section = (s + " ").split(" ", 3);
return new VMDescriptor(section[0].trim(), section[1].trim(), section[2].trim());
})
.filter(d -> !d.getId().equals(processId) && !"sun.tools.jps.Jps".equals(d.getClassName()) && !"jdk.jcmd/sun.tools.jps.Jps".equals(d.getClassName()))
.sorted(Comparator.comparingInt(d -> Integer.parseInt(d.getId())))
.collect(Collectors.toList());
}
private String getInput() throws IOException {
return new BufferedReader(new InputStreamReader(System.in)).readLine().trim();
}
private void processSelect() throws Exception {
System.out.print(" Select: ");
String input = getInput();
switch (input) {
case "Q":
case "q":
System.exit(0);
case "R":
case "r":
System.out.println(" =========================== " + DateUtils.formatDateTime() + " ============================");
select();
return;
case "":
processSelect();
return;
default:
int index;
try {
index = Integer.parseInt(input);
} catch (NumberFormatException e) {
invalidInput(input);
return;
}
if (index < 1) {
invalidInput(input);
return;
}
if (index > descriptors.size()) {
invalidInput(input);
return;
}
System.out.print(" Agent args: ");
input = getInput();
try {
VMLauncher.launch(thisJar, descriptors.get(index - 1), input);
} catch (Exception e) {
System.err.println("> Attach to: " + index + " failed.");
e.printStackTrace(System.err);
return;
}
break;
}
}
private void invalidInput(String input) throws Exception {
System.err.println("> Invalid input: " + input);
processSelect();
}
public void select() throws Exception {
boolean first = null == descriptors;
List<VMDescriptor> temp = getVMList();
if (null != descriptors && !descriptors.isEmpty()) {
temp.forEach(d -> d.setOld(descriptors.stream().anyMatch(d1 -> d.getId().equals(d1.getId()))));
}
descriptors = temp;
System.out.println(" Java Virtual Machine List: (Select and attach" + (first ? "" : ", + means the new one") + ")");
int index = 1;
for (VMDescriptor d : descriptors) {
System.out.printf(" %3d]:%s%s %s%n", index++, d.getOld() ? " " : "+", d.getId(), d.getClassName());
}
System.out.println(" r]: <Refresh virtual machine list>");
System.out.println(" q]: <Quit the ja-netfilter>");
processSelect();
}
}

View File

@ -80,13 +80,13 @@ public class ConfigParser {
}
map.get(lastSection).add(rule);
DebugInfo.output("Add section: " + lastSection + ", rule: " + rule);
DebugInfo.debug("Add section: " + lastSection + ", rule: " + rule);
break;
}
}
}
DebugInfo.output("Config file loaded: " + file);
DebugInfo.debug("Config file loaded: " + file);
return map;
}
}

View File

@ -1,18 +1,95 @@
package com.janetfilter.core.commons;
import com.janetfilter.core.utils.DateUtils;
import com.janetfilter.core.utils.ProcessUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class DebugInfo {
private static final boolean DEBUG = "1".equals(System.getenv("JANF_DEBUG")) || "1".equals(System.getProperty("janf.debug"));
private static final String CLASS_NAME = DebugInfo.class.getName();
private static final String LOG_TEMPLATE = "[%s] %s DEBUG : %s%n";
private static final String LOG_TEMPLATE = "[%s] %s %s : %s%n";
private static final Level LOG_LEVEL;
private static File logFile;
static {
Level level = Level.of(System.getProperty("janf.debug"));
LOG_LEVEL = Level.NONE == level ? Level.of(System.getenv("JANF_DEBUG")) : level;
}
public static void useFile(File dir) {
if (LOG_LEVEL == Level.NONE || null == dir) {
return;
}
if (!dir.exists() && !dir.mkdirs()) {
error("Can't make directory: " + dir);
return;
}
if (!dir.isDirectory()) {
error("It's not a directory: " + dir);
return;
}
if (!dir.canWrite()) {
error("Read-only directory: " + dir);
return;
}
File file = new File(dir, String.format("%s-%s.log", DateUtils.formatDate(), ProcessUtils.currentId()));
if (file.exists()) {
error("Log file exists: " + file);
return;
}
logFile = file;
}
public static void debug(String content, Throwable e) {
output(Level.DEBUG, content, e);
}
public static void debug(String content) {
debug(content, null);
}
public static void info(String content, Throwable e) {
output(Level.INFO, content, e);
}
public static void info(String content) {
info(content, null);
}
public static void warn(String content, Throwable e) {
output(Level.WARN, content, e);
}
public static void warn(String content) {
warn(content, null);
}
public static void error(String content, Throwable e) {
output(Level.ERROR, content, e);
}
public static void error(String content) {
error(content, null);
}
public static void output(String content) {
output(content, null);
debug(content);
}
public static void output(String content, Throwable e) { // No logger lib required
if (!DEBUG) {
debug(content, e);
}
public static void output(Level level, String content, Throwable e) { // No logger lib required
if (Level.NONE == LOG_LEVEL || level.ordinal() < LOG_LEVEL.ordinal()) {
return;
}
@ -26,15 +103,74 @@ public class DebugInfo {
}
}
String outContent = String.format(LOG_TEMPLATE, DateUtils.formatNow(), caller, content);
String outContent = String.format(LOG_TEMPLATE, DateUtils.formatDateTime(), caller, level, content);
if (null == e) {
System.out.print(outContent);
writeContent(outContent);
return;
}
synchronized (DebugInfo.class) {
System.out.print(outContent);
e.printStackTrace(System.err);
writeContent(outContent, System.err);
writeException(e);
}
}
private static void writeContent(String content) {
writeContent(content, System.out);
}
private static void writeContent(String content, PrintStream fallback) {
if (null == logFile) {
fallback.print(content);
return;
}
try (PrintStream ps = new PrintStream(new FileOutputStream(logFile, true))) {
ps.print(content);
} catch (IOException e) {
fallback.println(content);
}
}
private static void writeException(Throwable e) {
writeException(e, System.err);
}
private static void writeException(Throwable e, PrintStream fallback) {
if (null == logFile) {
e.printStackTrace(fallback);
return;
}
try (PrintStream ps = new PrintStream(new FileOutputStream(logFile, true))) {
e.printStackTrace(ps);
} catch (IOException ex) {
e.printStackTrace(fallback);
}
}
private enum Level {
NONE, DEBUG, INFO, WARN, ERROR;
public static Level of(String valueStr) {
if (null == valueStr) {
return NONE;
}
int value;
try {
value = Integer.parseInt(valueStr);
} catch (NumberFormatException e) {
return NONE;
}
for (Level level : values()) {
if (level.ordinal() == value) {
return level;
}
}
return NONE;
}
}
}

View File

@ -52,9 +52,9 @@ public final class PluginManager {
throw new RuntimeException("Load plugin timeout");
}
DebugInfo.output(String.format("============ All plugins loaded, %.2fs elapsed ============", (System.currentTimeMillis() - startTime) / 1000D));
DebugInfo.debug(String.format("============ All plugins loaded, %.2fs elapsed ============", (System.currentTimeMillis() - startTime) / 1000D));
} catch (Throwable e) {
DebugInfo.output("Load plugin failed", e);
DebugInfo.error("Load plugin failed", e);
}
}
@ -93,9 +93,9 @@ public final class PluginManager {
dispatcher.addTransformers(pluginEntry.getTransformers());
DebugInfo.output("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}");
DebugInfo.debug("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}");
} catch (Throwable e) {
DebugInfo.output("Parse plugin info failed", e);
DebugInfo.error("Parse plugin info failed", e);
}
}
}

View File

@ -14,10 +14,18 @@ public class DateUtils {
return FULL_DF.format(date);
}
public static String formatDateTime() {
return FULL_DF.format(new Date());
}
public static String formatDate(Date date) {
return DATE_DF.format(date);
}
public static String formatDate() {
return formatDate(new Date());
}
public static String formatTime(Date date) {
return TIME_DF.format(date);
}
@ -33,8 +41,4 @@ public class DateUtils {
public static Date parseDateTime(String dateTimeStr) throws ParseException {
return FULL_DF.parse(dateTimeStr);
}
public static String formatNow() {
return formatDateTime(new Date());
}
}

View File

@ -0,0 +1,75 @@
package com.janetfilter.core.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
public class ProcessUtils {
private static String processId;
public synchronized static String currentId() {
if (null == processId) {
String name = ManagementFactory.getRuntimeMXBean().getName() + "@";
processId = name.split("@", 2)[0];
}
return processId;
}
public static int start(ProcessBuilder pb) throws Exception {
return start(pb, System.out, System.err);
}
public static int start(ProcessBuilder pb, OutputStream out) throws Exception {
return start(pb, out, null);
}
public static int start(ProcessBuilder pb, OutputStream out, OutputStream err) throws Exception {
Process p = pb.start();
List<Thread> threads = new ArrayList<>();
if (null != out) {
threads.add(new Thread(new RedirectOutput(p.getInputStream(), out)));
}
if (null != err) {
threads.add(new Thread(new RedirectOutput(p.getErrorStream(), err)));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
return p.waitFor();
}
static class RedirectOutput implements Runnable {
private static final int BUFF_SIZE = 1024;
private final InputStream origin;
private final OutputStream dest;
RedirectOutput(InputStream origin, OutputStream dest) {
this.origin = origin;
this.dest = dest;
}
public void run() {
int length;
byte[] buffer = new byte[BUFF_SIZE];
try {
while ((length = origin.read(buffer)) != -1) {
dest.write(buffer, 0, length);
}
} catch (IOException e) {
throw new RuntimeException("ERROR: Redirect output failed.", e);
}
}
}
}

View File

@ -0,0 +1,82 @@
package com.janetfilter.core.utils;
import com.janetfilter.core.Launcher;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
public class WhereIsUtils {
private static final String JAVA_HOME = System.getProperty("java.home");
public static File findJPS() {
String[] paths = new String[]{"bin/jps", "bin/jps.exe", "../bin/jps", "../bin/jps.exe"};
for (String path : paths) {
File file = new File(JAVA_HOME, path);
if (file.exists() && file.isFile() && file.canExecute()) {
return getCanonicalFile(file);
}
}
return null;
}
public static File findJava() {
String[] paths = new String[]{"bin/java", "bin/java.exe", "../bin/java", "../bin/java.exe"};
for (String path : paths) {
File file = new File(JAVA_HOME, path);
if (file.exists() && file.isFile() && file.canExecute()) {
return getCanonicalFile(file);
}
}
return null;
}
public static File findToolsJar() {
String[] paths = new String[]{"lib/tools.jar", "../lib/tools.jar", "../../lib/tools.jar"};
for (String path : paths) {
File file = new File(JAVA_HOME, path);
if (file.exists() && file.isFile()) {
return getCanonicalFile(file);
}
}
return null;
}
public static URI getJarURI() throws Exception {
URL url = Launcher.class.getProtectionDomain().getCodeSource().getLocation();
if (null != url) {
return url.toURI();
}
String resourcePath = "/288daf08f4ba46dfde71b7f0624b0ad7f234a67a.txt";
url = Launcher.class.getResource(resourcePath);
if (null == url) {
throw new Exception("Can not locate resource file.");
}
String path = url.getPath();
if (!path.endsWith("!" + resourcePath)) {
throw new Exception("Invalid resource path.");
}
path = path.substring(0, path.length() - resourcePath.length() - 1);
return new URI(path);
}
private static File getCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
return null;
}
}
}