diff --git a/build.gradle b/build.gradle index dbd5136..1f9cc03 100755 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,16 @@ task nativeHeaders { } } +task nativeJar(type: Jar) { + from compileMain + archiveName = 'native-platform-jni.jar' +} + +startScripts.classpath += nativeJar.outputs.files +applicationDistribution.from(nativeJar) { + into 'lib' +} + compileMain.dependsOn nativeHeaders test.dependsOn compileMain diff --git a/src/main/cpp/posix.cpp b/src/main/cpp/posix.cpp index 4ad473a..3c212c6 100755 --- a/src/main/cpp/posix.cpp +++ b/src/main/cpp/posix.cpp @@ -67,11 +67,6 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixTerminalFunctions_isatty(JNIE switch (output) { case 0: case 1: - printf("=> checking file descriptor %d\n", output+1); - printf("=> isatty(): %d\n", isatty(output+1)); - result = fstat(output+1, &fileInfo); - printf("=> fstat(): %d\n", result); - printf("=> mode: %o\n", fileInfo.st_mode); return isatty(output+1) ? JNI_TRUE : JNI_FALSE; default: return JNI_FALSE; diff --git a/src/main/java/net/rubygrapefruit/platform/Native.java b/src/main/java/net/rubygrapefruit/platform/Native.java index feab713..882a4dc 100755 --- a/src/main/java/net/rubygrapefruit/platform/Native.java +++ b/src/main/java/net/rubygrapefruit/platform/Native.java @@ -1,59 +1,109 @@ -package net.rubygrapefruit.platform; - -import net.rubygrapefruit.platform.internal.*; -import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions; - -import java.io.File; -import java.io.IOException; - -/** - * Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration. - */ -public class Native { - private static final Object lock = new Object(); - private static boolean loaded; - - static T get(Class type) { - Platform platform = Platform.current(); - synchronized (lock) { - if (!loaded) { - if (!platform.isSupported()) { - throw new NativeException(String.format("The current platform is not supported.")); - } - try { - File libFile = new File("build/binaries/" + platform.getLibraryName()); - System.load(libFile.getCanonicalPath()); - } catch (IOException e) { - throw new RuntimeException(e); - } - int nativeVersion = NativeLibraryFunctions.getVersion(); - if (nativeVersion != NativeLibraryFunctions.VERSION) { - throw new NativeException(String.format( - "Unexpected native library version loaded. Expected %s, was %s.", nativeVersion, - NativeLibraryFunctions.VERSION)); - } - loaded = true; - } - } - if (platform.isPosix()) { - if (type.equals(PosixFile.class)) { - return type.cast(new DefaultPosixFile()); - } - if (type.equals(Process.class)) { - return type.cast(new DefaultProcess()); - } - if (type.equals(TerminalAccess.class)) { - return type.cast(new TerminfoTerminalAccess()); - } - } else if (platform.isWindows()) { - if (type.equals(Process.class)) { - return type.cast(new DefaultProcess()); - } - if (type.equals(TerminalAccess.class)) { - return type.cast(new WindowsTerminalAccess()); - } - } - throw new UnsupportedOperationException(String.format("Cannot load unsupported native integration %s.", - type.getName())); - } -} +package net.rubygrapefruit.platform; + +import net.rubygrapefruit.platform.internal.*; +import net.rubygrapefruit.platform.internal.jni.NativeLibraryFunctions; + +import java.io.*; +import java.net.URL; + +/** + * Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration. + */ +public class Native { + private static final Object lock = new Object(); + private static boolean loaded; + + /** + * Initialises the native integration, if not already initialized. + * + * @param extractDir The directory to extract native resources into. May be null. + */ + static void init(File extractDir) { + synchronized (lock) { + if (!loaded) { + Platform platform = Platform.current(); + if (!platform.isSupported()) { + throw new NativeException(String.format("The current platform is not supported.")); + } + try { + File libFile; + URL resource = Native.class.getClassLoader().getResource(platform.getLibraryName()); + if (resource != null) { + File libDir = extractDir; + if (libDir == null) { + libDir = File.createTempFile("native-platform", "dir"); + libDir.delete(); + libDir.mkdirs(); + } + libFile = new File(libDir, platform.getLibraryName()); + libFile.deleteOnExit(); + copy(resource, libFile); + } else { + libFile = new File("build/binaries/" + platform.getLibraryName()); + } + System.load(libFile.getCanonicalPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + int nativeVersion = NativeLibraryFunctions.getVersion(); + if (nativeVersion != NativeLibraryFunctions.VERSION) { + throw new NativeException(String.format( + "Unexpected native library version loaded. Expected %s, was %s.", nativeVersion, + NativeLibraryFunctions.VERSION)); + } + loaded = true; + } + } + + } + + static T get(Class type) { + init(null); + Platform platform = Platform.current(); + if (platform.isPosix()) { + if (type.equals(PosixFile.class)) { + return type.cast(new DefaultPosixFile()); + } + if (type.equals(Process.class)) { + return type.cast(new DefaultProcess()); + } + if (type.equals(TerminalAccess.class)) { + return type.cast(new TerminfoTerminalAccess()); + } + } else if (platform.isWindows()) { + if (type.equals(Process.class)) { + return type.cast(new DefaultProcess()); + } + if (type.equals(TerminalAccess.class)) { + return type.cast(new WindowsTerminalAccess()); + } + } + throw new UnsupportedOperationException(String.format("Cannot load unsupported native integration %s.", + type.getName())); + } + + private static void copy(URL source, File dest) { + try { + InputStream inputStream = source.openStream(); + try { + OutputStream outputStream = new FileOutputStream(dest); + try { + byte[] buffer = new byte[4096]; + while (true) { + int nread = inputStream.read(buffer); + if (nread < 0) { + break; + } + outputStream.write(buffer, 0, nread); + } + } finally { + outputStream.close(); + } + } finally { + inputStream.close(); + } + } catch (IOException e) { + throw new NativeException(String.format("Could not extract native JNI library."), e); + } + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/NativeException.java b/src/main/java/net/rubygrapefruit/platform/NativeException.java index 71e55cb..f3abce5 100644 --- a/src/main/java/net/rubygrapefruit/platform/NativeException.java +++ b/src/main/java/net/rubygrapefruit/platform/NativeException.java @@ -1,6 +1,10 @@ package net.rubygrapefruit.platform; public class NativeException extends RuntimeException { + public NativeException(String message, Throwable throwable) { + super(message, throwable); + } + public NativeException(String message) { super(message); } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminalAccess.java b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminalAccess.java index 897fbd3..767c078 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminalAccess.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminalAccess.java @@ -1,27 +1,29 @@ -package net.rubygrapefruit.platform.internal; - -import net.rubygrapefruit.platform.Terminal; -import net.rubygrapefruit.platform.TerminalAccess; -import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions; - -public class TerminfoTerminalAccess implements TerminalAccess { - private static Output currentlyOpen; - - @Override - public boolean isTerminal(Output output) { - return PosixTerminalFunctions.isatty(output.ordinal()); - } - - @Override - public Terminal getTerminal(Output output) { - if (currentlyOpen != null) { - throw new UnsupportedOperationException("Currently only one output can be used as a terminal."); - } - - TerminfoTerminal terminal = new TerminfoTerminal(output); - terminal.init(); - - currentlyOpen = output; - return terminal; - } -} +package net.rubygrapefruit.platform.internal; + +import net.rubygrapefruit.platform.Terminal; +import net.rubygrapefruit.platform.TerminalAccess; +import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions; + +public class TerminfoTerminalAccess implements TerminalAccess { + private static Output currentlyOpen; + private static TerminfoTerminal current; + + @Override + public boolean isTerminal(Output output) { + return PosixTerminalFunctions.isatty(output.ordinal()); + } + + @Override + public Terminal getTerminal(Output output) { + if (currentlyOpen != null && currentlyOpen != output) { + throw new UnsupportedOperationException("Currently only one output can be used as a terminal."); + } + if (current == null) { + current = new TerminfoTerminal(output); + current.init(); + } + + currentlyOpen = output; + return current; + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminalAccess.java b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminalAccess.java index 36328d6..7a7da3c 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminalAccess.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminalAccess.java @@ -1,34 +1,37 @@ -package net.rubygrapefruit.platform.internal; - -import net.rubygrapefruit.platform.NativeException; -import net.rubygrapefruit.platform.Terminal; -import net.rubygrapefruit.platform.TerminalAccess; -import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions; - -public class WindowsTerminalAccess implements TerminalAccess { - private static Output currentlyOpen; - - @Override - public boolean isTerminal(Output output) { - FunctionResult result = new FunctionResult(); - boolean console = WindowsConsoleFunctions.isConsole(output.ordinal(), result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not determine if %s is a console: %s", output, - result.getMessage())); - } - return console; - } - - @Override - public Terminal getTerminal(Output output) { - if (currentlyOpen != null) { - throw new UnsupportedOperationException("Currently only one output can be used as a terminal."); - } - - WindowsTerminal terminal = new WindowsTerminal(output); - terminal.init(); - - currentlyOpen = output; - return terminal; - } -} +package net.rubygrapefruit.platform.internal; + +import net.rubygrapefruit.platform.NativeException; +import net.rubygrapefruit.platform.Terminal; +import net.rubygrapefruit.platform.TerminalAccess; +import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions; + +public class WindowsTerminalAccess implements TerminalAccess { + private static Output currentlyOpen; + private static WindowsTerminal current; + + @Override + public boolean isTerminal(Output output) { + FunctionResult result = new FunctionResult(); + boolean console = WindowsConsoleFunctions.isConsole(output.ordinal(), result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not determine if %s is a console: %s", output, + result.getMessage())); + } + return console; + } + + @Override + public Terminal getTerminal(Output output) { + if (currentlyOpen !=null && currentlyOpen != output) { + throw new UnsupportedOperationException("Currently only one output can be used as a terminal."); + } + + if (current == null) { + current = new WindowsTerminal(output); + current.init(); + } + + currentlyOpen = output; + return current; + } +}