package com.oracle.svm.thirdparty.jline;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.jdk.Resources;
import com.oracle.svm.core.jni.JNIRuntimeAccess;
import com.oracle.svm.util.ReflectionUtil;
@AutomaticFeature
final class JLineFeature implements Feature {
private static final List<String> JNI_CLASS_NAMES = Arrays.asList(
"org.fusesource.jansi.internal.CLibrary",
"org.fusesource.jansi.internal.CLibrary$WinSize",
"org.fusesource.jansi.internal.CLibrary$Termios",
"org.fusesource.jansi.internal.Kernel32",
"org.fusesource.jansi.internal.Kernel32$SMALL_RECT",
"org.fusesource.jansi.internal.Kernel32$COORD",
"org.fusesource.jansi.internal.Kernel32$CONSOLE_SCREEN_BUFFER_INFO",
"org.fusesource.jansi.internal.Kernel32$CHAR_INFO",
"org.fusesource.jansi.internal.Kernel32$KEY_EVENT_RECORD",
"org.fusesource.jansi.internal.Kernel32$MOUSE_EVENT_RECORD",
"org.fusesource.jansi.internal.Kernel32$WINDOW_BUFFER_SIZE_RECORD",
"org.fusesource.jansi.internal.Kernel32$FOCUS_EVENT_RECORD",
"org.fusesource.jansi.internal.Kernel32$MENU_EVENT_RECORD",
"org.fusesource.jansi.internal.Kernel32$INPUT_RECORD");
private static final List<String> RUNTIME_INIT_CLASS_NAMES = Arrays.asList(
"org.fusesource.jansi.AnsiConsole",
"org.fusesource.jansi.WindowsAnsiOutputStream",
"org.fusesource.jansi.WindowsAnsiProcessor");
Class<?> terminalFactoryClass;
@Override
public boolean isInConfiguration(IsInConfigurationAccess access) {
terminalFactoryClass = access.findClassByName("jline.TerminalFactory");
return terminalFactoryClass != null;
}
@Override
public void duringSetup(DuringSetupAccess access) {
if (Platform.includedIn(Platform.WINDOWS.class)) {
Stream.concat(JNI_CLASS_NAMES.stream(), RUNTIME_INIT_CLASS_NAMES.stream())
.map(access::findClassByName)
.filter(Objects::nonNull)
.forEach(RuntimeClassInitialization::initializeAtRunTime);
}
}
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
Object[] createMethods = Arrays.stream(terminalFactoryClass.getDeclaredMethods())
.filter(m -> Modifier.isStatic(m.getModifiers()) && m.getName().equals("create"))
.toArray();
access.registerReachabilityHandler(JLineFeature::registerTerminalConstructor, createMethods);
if (Platform.includedIn(Platform.WINDOWS.class)) {
JNI_CLASS_NAMES.stream()
.map(access::findClassByName)
.filter(Objects::nonNull)
.map(jniClass -> ReflectionUtil.lookupMethod(jniClass, "init"))
.forEach(initMethod -> access.registerReachabilityHandler(a -> registerJNIFields(initMethod), initMethod));
}
}
private static void registerTerminalConstructor(DuringAnalysisAccess access) {
Class<?> terminalClass = access.findClassByName(Platform.includedIn(Platform.WINDOWS.class) ? "jline.AnsiWindowsTerminal" : "jline.UnixTerminal");
if (terminalClass != null) {
RuntimeReflection.register(terminalClass);
RuntimeReflection.register(terminalClass.getDeclaredConstructors());
}
}
private AtomicBoolean resourceRegistered = new AtomicBoolean();
private void registerJNIFields(Method initMethod) {
Class<?> jniClass = initMethod.getDeclaringClass();
JNIRuntimeAccess.register(jniClass.getDeclaredFields());
if (!resourceRegistered.getAndSet(true)) {
String resource = "META-INF/native/windows64/jansi.dll";
Resources.registerResource(resource, jniClass.getClassLoader().getResourceAsStream(resource));
}
}
}