- Bundle native library in jar and extract at runtime.

- Don't reinitialise terminal in TerminalAccess.getTerminal() if already initialised.
This commit is contained in:
Adam Murdoch
2012-08-10 08:50:40 +10:00
parent 3c9720f9ab
commit 8621ef80e9
6 changed files with 189 additions and 125 deletions

View File

@@ -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;

View File

@@ -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 extends NativeIntegration> T get(Class<T> 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 extends NativeIntegration> T get(Class<T> 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);
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}