Use StackWalker on Java 9+ and add compatibility layer

This commit is contained in:
filoghost 2018-08-24 18:24:13 +02:00
parent 57f7e0e9e4
commit 0ad5cdac32
11 changed files with 136 additions and 44 deletions

15
JavaCompat/pom.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.gmail.filoghost.holographicdisplays</groupId>
<artifactId>holographicdisplays-parent</artifactId>
<version>2.3.0-SNAPSHOT</version>
</parent>
<artifactId>holographicdisplays-javacompat</artifactId>
<name>HolographicDisplays Java Compat</name>
</project>

View File

@ -0,0 +1,20 @@
package java.lang;
import java.util.function.Function;
import java.util.stream.Stream;
public class StackWalker {
public static interface StackFrame {
StackTraceElement toStackTraceElement();
}
public static StackWalker getInstance() {
return null;
}
public <T> T walk(Function<? super Stream<StackFrame>, ? extends T> function) {
return null;
}
}

View File

@ -0,0 +1,9 @@
package java.util;
public class Optional<T> {
public T orElse(T other) {
return null;
}
}

View File

@ -0,0 +1,7 @@
package java.util.function;
public interface Function<T, R> {
R apply(T t);
}

View File

@ -0,0 +1,13 @@
package java.util.stream;
import java.util.Optional;
public interface Stream<T> {
public Stream<T> skip(long n);
public Stream<T> limit(long maxSize);
public Optional<T> findFirst();
}

View File

@ -35,6 +35,7 @@ import com.gmail.filoghost.holographicdisplays.task.StartupLoadHologramsTask;
import com.gmail.filoghost.holographicdisplays.task.WorldPlayerCounterTask;
import com.gmail.filoghost.holographicdisplays.util.ConsoleLogger;
import com.gmail.filoghost.holographicdisplays.util.NMSVersion;
import com.gmail.filoghost.holographicdisplays.util.Utils;
import com.gmail.filoghost.holographicdisplays.util.VersionUtils;
public class HolographicDisplays extends JavaPlugin {
@ -161,7 +162,7 @@ public class HolographicDisplays extends JavaPlugin {
if (requiredVersionError == null) {
ProtocolLibHook protocolLibHook;
if (VersionUtils.classExists("com.comphenix.protocol.wrappers.WrappedDataWatcher$WrappedDataWatcherObject")) {
if (Utils.classExists("com.comphenix.protocol.wrappers.WrappedDataWatcher$WrappedDataWatcherObject")) {
// Only the new version contains this class
ConsoleLogger.log(Level.INFO, "Found ProtocolLib, using new version.");
protocolLibHook = new com.gmail.filoghost.holographicdisplays.bridge.protocollib.current.ProtocolLibHookImpl();

View File

@ -36,6 +36,13 @@
<version>1.8-R0.1-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>holographicdisplays-javacompat</artifactId>
<version>2.3.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -53,15 +53,10 @@ public class Utils extends Object {
return new ArrayList<T>();
}
public static <T> Set<T> newSet() {
return new HashSet<T>();
}
@SuppressWarnings("unchecked")
public static <T> T[] listToArray(List<T> list) {
return (T[]) list.toArray();
}
public static int floor(double num) {
int floor = (int) num;
@ -107,10 +102,12 @@ public class Utils extends Object {
return join(elements, separator, 0, elements.size());
}
public static String sanitize(String s) {
return s != null ? s : "null";
}
public static boolean isThereNonNull(Object... objects) {
if (objects == null) {
return false;
@ -124,4 +121,14 @@ public class Utils extends Object {
return false;
}
public static boolean classExists(String className) {
try {
Class.forName(className);
return true;
} catch (Throwable t) {
return false;
}
}
}

View File

@ -79,14 +79,4 @@ public class VersionUtils {
return isVersionGreaterEqual(reference, lowest) && isVersionLessEqual(reference, highest);
}
public static boolean classExists(String className) {
try {
Class.forName(className);
return true;
} catch (Throwable t) {
return false;
}
}
}

View File

@ -1,47 +1,69 @@
package com.gmail.filoghost.holographicdisplays.util.reflection;
import java.lang.reflect.Method;
import java.lang.StackWalker.StackFrame;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Stream;
import com.gmail.filoghost.holographicdisplays.util.ConsoleLogger;
import com.gmail.filoghost.holographicdisplays.util.Utils;
public class ReflectionUtils {
private static Method getStackTraceElementMethod;
private static Method getStackTraceDepthMethod;
private static final boolean HAS_JAVA9_STACKWALKER = Utils.classExists("java.lang.StackWalker");
private static boolean stackTraceErrorPrinted;
private static final ReflectMethod<Integer> GET_STACKTRACE_DEPTH_METHOD = new ReflectMethod<Integer>(Throwable.class, "getStackTraceDepth");
private static final ReflectMethod<StackTraceElement> GET_STACKTRACE_ELEMENT_METHOD = new ReflectMethod<StackTraceElement>(Throwable.class, "getStackTraceElement", int.class);
private static boolean stackTraceError;
/**
* If you only need one stack trace element this is faster than Throwable.getStackTrace()[element],
* it doesn't generate the full stack trace.
* it doesn't generate the full stack trace (except as fallback).
*/
public static StackTraceElement getStackTraceElement(int index) {
try {
if (getStackTraceElementMethod == null) {
getStackTraceElementMethod = Throwable.class.getDeclaredMethod("getStackTraceElement", int.class);
getStackTraceElementMethod.setAccessible(true);
}
if (getStackTraceDepthMethod == null) {
getStackTraceDepthMethod = Throwable.class.getDeclaredMethod("getStackTraceDepth");
getStackTraceDepthMethod.setAccessible(true);
}
Throwable dummyThrowable = new Throwable();
int depth = (Integer) getStackTraceDepthMethod.invoke(dummyThrowable);
if (index < depth) {
return (StackTraceElement) getStackTraceElementMethod.invoke(new Throwable(), index);
} else {
return null;
}
} catch (Throwable t) {
if (!stackTraceErrorPrinted) {
ConsoleLogger.log(Level.WARNING, "Unable to get a stacktrace element, please inform the developer. You will only see this error once to avoid spam.", t);
stackTraceErrorPrinted = true;
public static StackTraceElement getStackTraceElement(final int index) {
// Use the faster methods only if there hasn't been any error while executing them previously
if (!stackTraceError) {
try {
if (HAS_JAVA9_STACKWALKER) {
// Use the Java 9 StackWalker API
StackFrame result = StackWalker.getInstance().walk(new Function<Stream<StackFrame>, StackFrame>() {
@Override
public StackFrame apply(Stream<StackFrame> stream) {
return stream.skip(index).limit(1).findFirst().orElse(null);
}
});
return result != null ? result.toStackTraceElement() : null;
} else {
// Use reflection to avoid generating the full stacktrace
Throwable dummyThrowable = new Throwable();
int depth = GET_STACKTRACE_DEPTH_METHOD.invoke(dummyThrowable);
if (index < depth) {
return GET_STACKTRACE_ELEMENT_METHOD.invoke(dummyThrowable, index);
} else {
return null;
}
}
} catch (Throwable t) {
if (!stackTraceError) {
ConsoleLogger.log(Level.WARNING, "Unable to get a stacktrace element, please inform the developer. You will only see this error once and a fallback method will be used.", t);
stackTraceError = true;
}
}
}
// Fallback slower method, generates the full stack trace (it should never be called if everything works as expected)
StackTraceElement[] fullStackTrace = new Throwable().getStackTrace();
if (index < fullStackTrace.length) {
return fullStackTrace[index];
} else {
return null;
}
}
}

View File

@ -18,6 +18,7 @@
<modules>
<module>API</module>
<module>JavaCompat</module>
<module>Utils</module>
<module>NMS/Interfaces</module>
<module>NMS/v1_8_R1</module>