First pass for windows support.

This commit is contained in:
Adam Murdoch
2012-08-04 12:32:15 +10:00
parent dadf93caf8
commit e5537494b0
11 changed files with 689 additions and 617 deletions

121
build.gradle Normal file → Executable file
View File

@@ -1,56 +1,65 @@
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'groovy' apply plugin: 'groovy'
apply plugin: 'cpp-lib' apply plugin: 'cpp-lib'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'application' apply plugin: 'application'
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
groovy 'org.codehaus.groovy:groovy:1.8.7' groovy 'org.codehaus.groovy:groovy:1.8.7'
testCompile 'org.spockframework:spock-core:0.6-groovy-1.8' testCompile 'org.spockframework:spock-core:0.6-groovy-1.8'
} }
mainClassName = 'net.rubygrapefruit.platform.Main' mainClassName = 'net.rubygrapefruit.platform.Main'
def nativeHeadersDir = file("$buildDir/nativeHeaders") def nativeHeadersDir = file("$buildDir/nativeHeaders")
targetCompatibility = 1.5 sourceCompatibility = 1.5
targetCompatibility = 1.5
libraries {
main { println org.gradle.internal.jvm.Jvm.current().javaHome
spec { println org.gradle.internal.jvm.Jvm.current().toolsJar
includes([nativeHeadersDir])
if (org.gradle.internal.os.OperatingSystem.current().macOsX) { libraries {
includes(['/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/']) main {
} else { spec {
includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include"]) includes([nativeHeadersDir])
includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"]) if (org.gradle.internal.os.OperatingSystem.current().macOsX) {
} includes(['/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/'])
args("-lcurses") args("-lcurses")
} } else if (org.gradle.internal.os.OperatingSystem.current().windows) {
} includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include"])
} includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32"])
args("/DWIN32")
task nativeHeaders { } else {
def outputFile = file("$nativeHeadersDir/native.h") includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include"])
inputs.files sourceSets.main.output includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"])
outputs.file outputFile args("-lcurses")
doLast { }
outputFile.parentFile.mkdirs() }
exec { }
executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah') }
args '-o', outputFile
args '-classpath', sourceSets.main.output.classesDir task nativeHeaders {
args 'net.rubygrapefruit.platform.internal.NativeLibraryFunctions' def outputFile = file("$nativeHeadersDir/native.h")
args 'net.rubygrapefruit.platform.internal.PosixFileFunctions' inputs.files sourceSets.main.output
args 'net.rubygrapefruit.platform.internal.PosixProcessFunctions' outputs.file outputFile
args 'net.rubygrapefruit.platform.internal.PosixTerminalFunctions' doLast {
args 'net.rubygrapefruit.platform.internal.TerminfoFunctions' outputFile.parentFile.mkdirs()
} exec {
} executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah')
} args '-o', outputFile
args '-classpath', sourceSets.main.output.classesDir
compileMain.dependsOn nativeHeaders args 'net.rubygrapefruit.platform.internal.NativeLibraryFunctions'
test.dependsOn compileMain args 'net.rubygrapefruit.platform.internal.PosixFileFunctions'
args 'net.rubygrapefruit.platform.internal.PosixProcessFunctions'
args 'net.rubygrapefruit.platform.internal.PosixTerminalFunctions'
args 'net.rubygrapefruit.platform.internal.TerminfoFunctions'
}
}
}
compileMain.dependsOn nativeHeaders
test.dependsOn compileMain

9
src/main/cpp/generic.c Executable file
View File

@@ -0,0 +1,9 @@
/*
* Generic functions
*/
#include "native.h"
JNIEXPORT jint JNICALL
Java_net_rubygrapefruit_platform_internal_NativeLibraryFunctions_getVersion(JNIEnv *env, jclass target) {
return 2;
}

351
src/main/cpp/posixFunctions.c → src/main/cpp/posix.c Normal file → Executable file
View File

@@ -1,178 +1,173 @@
#include "native.h" #ifndef WIN32
#include <stdlib.h>
#include <sys/types.h> #include "native.h"
#include <sys/stat.h> #include <stdlib.h>
#include <errno.h> #include <sys/types.h>
#include <unistd.h> #include <sys/stat.h>
#include <sys/ioctl.h> #include <errno.h>
#include <curses.h> #include <unistd.h>
#include <term.h> #include <sys/ioctl.h>
#include <curses.h>
/* #include <term.h>
* Marks the given result as failed, using the current value of errno
*/ /*
void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { * Marks the given result as failed, using the current value of errno
jclass destClass = env->GetObjectClass(result); */
jmethodID method = env->GetMethodID(destClass, "failed", "(Ljava/lang/String;I)V"); void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) {
jstring message_str = env->NewStringUTF(message); jclass destClass = env->GetObjectClass(result);
env->CallVoidMethod(result, method, message_str, errno); jmethodID method = env->GetMethodID(destClass, "failed", "(Ljava/lang/String;I)V");
} jstring message_str = env->NewStringUTF(message);
env->CallVoidMethod(result, method, message_str, errno);
/* }
* Marks the given result as failed, using the given error message
*/ /*
void mark_failed_with_message(JNIEnv *env, const char* message, jobject result) { * Marks the given result as failed, using the given error message
jclass destClass = env->GetObjectClass(result); */
jmethodID method = env->GetMethodID(destClass, "failed", "(Ljava/lang/String;)V"); void mark_failed_with_message(JNIEnv *env, const char* message, jobject result) {
jstring message_str = env->NewStringUTF(message); jclass destClass = env->GetObjectClass(result);
env->CallVoidMethod(result, method, message_str); jmethodID method = env->GetMethodID(destClass, "failed", "(Ljava/lang/String;)V");
} jstring message_str = env->NewStringUTF(message);
env->CallVoidMethod(result, method, message_str);
/* }
* Generic functions
*/ /*
* File functions
JNIEXPORT jint JNICALL */
Java_net_rubygrapefruit_platform_internal_NativeLibraryFunctions_getVersion(JNIEnv *env, jclass target) {
return 2; JNIEXPORT void JNICALL
} Java_net_rubygrapefruit_platform_internal_PosixFileFunctions_chmod(JNIEnv *env, jclass target, jstring path, jint mode, jobject result) {
const char* pathUtf8 = env->GetStringUTFChars(path, NULL);
/* int retval = chmod(pathUtf8, mode);
* File functions env->ReleaseStringUTFChars(path, pathUtf8);
*/ if (retval != 0) {
mark_failed_with_errno(env, "could not chmod file", result);
JNIEXPORT void JNICALL }
Java_net_rubygrapefruit_platform_internal_PosixFileFunctions_chmod(JNIEnv *env, jclass target, jstring path, jint mode, jobject result) { }
const char* pathUtf8 = env->GetStringUTFChars(path, NULL);
int retval = chmod(pathUtf8, mode); JNIEXPORT void JNICALL
env->ReleaseStringUTFChars(path, pathUtf8); Java_net_rubygrapefruit_platform_internal_PosixFileFunctions_stat(JNIEnv *env, jclass target, jstring path, jobject dest, jobject result) {
if (retval != 0) { struct stat fileInfo;
mark_failed_with_errno(env, "could not chmod file", result); const char* pathUtf8 = env->GetStringUTFChars(path, NULL);
} int retval = stat(pathUtf8, &fileInfo);
} env->ReleaseStringUTFChars(path, pathUtf8);
if (retval != 0) {
JNIEXPORT void JNICALL mark_failed_with_errno(env, "could not stat file", result);
Java_net_rubygrapefruit_platform_internal_PosixFileFunctions_stat(JNIEnv *env, jclass target, jstring path, jobject dest, jobject result) { return;
struct stat fileInfo; }
const char* pathUtf8 = env->GetStringUTFChars(path, NULL); jclass destClass = env->GetObjectClass(dest);
int retval = stat(pathUtf8, &fileInfo); jfieldID modeField = env->GetFieldID(destClass, "mode", "I");
env->ReleaseStringUTFChars(path, pathUtf8); env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode);
if (retval != 0) { }
mark_failed_with_errno(env, "could not stat file", result);
return; /*
} * Process functions
jclass destClass = env->GetObjectClass(dest); */
jfieldID modeField = env->GetFieldID(destClass, "mode", "I");
env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode); JNIEXPORT jint JNICALL
} Java_net_rubygrapefruit_platform_internal_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) {
return getpid();
/* }
* Process functions
*/ /*
* Terminal functions
JNIEXPORT jint JNICALL */
Java_net_rubygrapefruit_platform_internal_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) {
return getpid(); JNIEXPORT jboolean JNICALL
} Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_isatty(JNIEnv *env, jclass target, jint output) {
switch (output) {
/* case 0:
* Terminal functions case 1:
*/ return isatty(output+1) ? JNI_TRUE : JNI_FALSE;
default:
JNIEXPORT jboolean JNICALL return JNI_FALSE;
Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_isatty(JNIEnv *env, jclass target, jint output) { }
switch (output) { }
case 0:
case 1: JNIEXPORT void JNICALL
return isatty(output+1) ? JNI_TRUE : JNI_FALSE; Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_getTerminalSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) {
default: struct winsize screen_size;
return JNI_FALSE; int retval = ioctl(output+1, TIOCGWINSZ, &screen_size);
} if (retval != 0) {
} mark_failed_with_errno(env, "could not fetch terminal size", result);
return;
JNIEXPORT void JNICALL }
Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_getTerminalSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) { jclass dimensionClass = env->GetObjectClass(dimension);
struct winsize screen_size; jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I");
int retval = ioctl(output+1, TIOCGWINSZ, &screen_size); env->SetIntField(dimension, widthField, screen_size.ws_col);
if (retval != 0) { jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I");
mark_failed_with_errno(env, "could not fetch terminal size", result); env->SetIntField(dimension, heightField, screen_size.ws_row);
return; }
}
jclass dimensionClass = env->GetObjectClass(dimension); /*
jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I"); * Terminfo functions
env->SetIntField(dimension, widthField, screen_size.ws_col); */
jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I");
env->SetIntField(dimension, heightField, screen_size.ws_row); int current_terminal = -1;
}
int write_to_terminal(int ch) {
/* write(current_terminal, &ch, 1);
* Terminfo functions }
*/
void write_capability(JNIEnv *env, const char* capability, jobject result) {
int current_terminal = -1; char* cap = tgetstr((char*)capability, NULL);
if (cap == NULL) {
int write_to_terminal(int ch) { mark_failed_with_message(env, "unknown terminal capability", result);
write(current_terminal, &ch, 1); return;
} }
if (tputs(cap, 1, write_to_terminal) == ERR) {
void write_capability(JNIEnv *env, const char* capability, jobject result) { mark_failed_with_message(env, "could not write to terminal", result);
char* cap = tgetstr((char*)capability, NULL); return;
if (cap == NULL) { }
mark_failed_with_message(env, "unknown terminal capability", result); }
return;
} JNIEXPORT void JNICALL
if (tputs(cap, 1, write_to_terminal) == ERR) { Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_initTerminal(JNIEnv *env, jclass target, jint output, jobject result) {
mark_failed_with_message(env, "could not write to terminal", result); if (!isatty(output+1)) {
return; mark_failed_with_message(env, "not a terminal", result);
} return;
} }
char* termType = getenv("TERM");
JNIEXPORT void JNICALL if (termType == NULL) {
Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_initTerminal(JNIEnv *env, jclass target, jint output, jobject result) { mark_failed_with_message(env, "$TERM not set", result);
if (!isatty(output+1)) { return;
mark_failed_with_message(env, "not a terminal", result); }
return; int retval = tgetent(NULL, termType);
} if (retval != 1) {
char* termType = getenv("TERM"); mark_failed_with_message(env, "could not get termcap entry", result);
if (termType == NULL) { return;
mark_failed_with_message(env, "$TERM not set", result); }
return; current_terminal = output + 1;
} write_capability(env, "me", result);
int retval = tgetent(NULL, termType); }
if (retval != 1) {
mark_failed_with_message(env, "could not get termcap entry", result); JNIEXPORT void JNICALL
return; Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_bold(JNIEnv *env, jclass target, jobject result) {
} write_capability(env, "md", result);
current_terminal = output + 1; }
write_capability(env, "me", result);
} JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_reset(JNIEnv *env, jclass target, jobject result) {
JNIEXPORT void JNICALL write_capability(env, "me", result);
Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_bold(JNIEnv *env, jclass target, jobject result) { }
write_capability(env, "md", result);
} JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) {
JNIEXPORT void JNICALL char* capability = tgetstr((char*)"AF", NULL);
Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_reset(JNIEnv *env, jclass target, jobject result) { if (capability == NULL) {
write_capability(env, "me", result); mark_failed_with_message(env, "unknown terminal capability", result);
} return;
}
JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) { capability = tparm(capability, color, 0, 0, 0, 0, 0, 0, 0, 0);
char* capability = tgetstr((char*)"AF", NULL); if (capability == NULL) {
if (capability == NULL) { mark_failed_with_message(env, "could not format terminal capability string", result);
mark_failed_with_message(env, "unknown terminal capability", result); return;
return; }
}
if (tputs(capability, 1, write_to_terminal) == ERR) {
capability = tparm(capability, color, 0, 0, 0, 0, 0, 0, 0, 0); mark_failed_with_message(env, "could not write to terminal", result);
if (capability == NULL) { return;
mark_failed_with_message(env, "could not format terminal capability string", result); }
return; }
}
#endif
if (tputs(capability, 1, write_to_terminal) == ERR) {
mark_failed_with_message(env, "could not write to terminal", result);
return;
}
}

15
src/main/cpp/win.c Executable file
View File

@@ -0,0 +1,15 @@
#ifdef WIN32
#include "native.h"
#include <windows.h>
/*
* Process functions
*/
JNIEXPORT jint JNICALL
Java_net_rubygrapefruit_platform_internal_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) {
return GetCurrentProcessId();
}
#endif

82
src/main/java/net/rubygrapefruit/platform/Main.java Normal file → Executable file
View File

@@ -1,41 +1,41 @@
package net.rubygrapefruit.platform; package net.rubygrapefruit.platform;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
System.out.println(); System.out.println();
System.out.println("* OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch")); System.out.println("* OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch"));
Process process = Native.get(Process.class); Process process = Native.get(Process.class);
System.out.println("* PID: " + process.getPid()); System.out.println("* PID: " + process.getProcessId());
TerminalAccess terminalAccess = Native.get(TerminalAccess.class); TerminalAccess terminalAccess = Native.get(TerminalAccess.class);
boolean stdoutIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stdout); boolean stdoutIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stdout);
boolean stderrIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stderr); boolean stderrIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stderr);
System.out.println("* stdout: " + (stdoutIsTerminal ? "terminal" : "not a terminal")); System.out.println("* stdout: " + (stdoutIsTerminal ? "terminal" : "not a terminal"));
System.out.println("* stderr: " + (stderrIsTerminal ? "terminal" : "not a terminal")); System.out.println("* stderr: " + (stderrIsTerminal ? "terminal" : "not a terminal"));
if (stdoutIsTerminal) { if (stdoutIsTerminal) {
Terminal terminal = terminalAccess.getTerminal(TerminalAccess.Output.Stdout); Terminal terminal = terminalAccess.getTerminal(TerminalAccess.Output.Stdout);
TerminalSize terminalSize = terminal.getTerminalSize(); TerminalSize terminalSize = terminal.getTerminalSize();
System.out.println("* terminal size: " + terminalSize.getCols() + " cols x " + terminalSize.getRows() + " rows"); System.out.println("* terminal size: " + terminalSize.getCols() + " cols x " + terminalSize.getRows() + " rows");
System.out.println(); System.out.println();
System.out.println("TERMINAL OUTPUT"); System.out.println("TERMINAL OUTPUT");
System.out.print("[normal] "); System.out.print("[normal] ");
terminal.bold(); terminal.bold();
System.out.print("[bold]"); System.out.print("[bold]");
terminal.normal(); terminal.normal();
System.out.println(" [normal]"); System.out.println(" [normal]");
System.out.println("here are the colors:"); System.out.println("here are the colors:");
for (Terminal.Color color : Terminal.Color.values()) { for (Terminal.Color color : Terminal.Color.values()) {
terminal.foreground(color); terminal.foreground(color);
System.out.print(String.format("[%s] ", color.toString().toLowerCase())); System.out.print(String.format("[%s] ", color.toString().toLowerCase()));
terminal.bold(); terminal.bold();
System.out.print(String.format("[%s]", color.toString().toLowerCase())); System.out.print(String.format("[%s]", color.toString().toLowerCase()));
terminal.normal(); terminal.normal();
System.out.println(); System.out.println();
} }
} }
System.out.println(); System.out.println();
} }
} }

383
src/main/java/net/rubygrapefruit/platform/Native.java Normal file → Executable file
View File

@@ -1,181 +1,202 @@
package net.rubygrapefruit.platform; package net.rubygrapefruit.platform;
import net.rubygrapefruit.platform.internal.*; import net.rubygrapefruit.platform.internal.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
/** /**
* Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration. * Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration.
*/ */
public class Native { public class Native {
private static final Object lock = new Object(); private static final Object lock = new Object();
private static boolean loaded; private static boolean loaded;
static <T extends NativeIntegration> T get(Class<T> type) { static <T extends NativeIntegration> T get(Class<T> type) {
synchronized (lock) { Platform platform = Platform.current();
if (!loaded) { synchronized (lock) {
Platform platform = Platform.current(); if (!loaded) {
if (!platform.isSupported()) { if (!platform.isSupported()) {
throw new NativeException(String.format("The current platform is not supported.")); throw new NativeException(String.format("The current platform is not supported."));
} }
try { try {
File libFile = new File("build/binaries/" + platform.getLibraryName()); File libFile = new File("build/binaries/" + platform.getLibraryName());
System.load(libFile.getCanonicalPath()); System.load(libFile.getCanonicalPath());
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
int nativeVersion = NativeLibraryFunctions.getVersion(); int nativeVersion = NativeLibraryFunctions.getVersion();
if (nativeVersion != NativeLibraryFunctions.VERSION) { if (nativeVersion != NativeLibraryFunctions.VERSION) {
throw new NativeException(String.format( throw new NativeException(String.format(
"Unexpected native library version loaded. Expected %s, was %s.", nativeVersion, "Unexpected native library version loaded. Expected %s, was %s.", nativeVersion,
NativeLibraryFunctions.VERSION)); NativeLibraryFunctions.VERSION));
} }
loaded = true; loaded = true;
} }
} }
if (type.equals(PosixFile.class)) { if (platform.isPosix()) {
return type.cast(new DefaultPosixFile()); if (type.equals(PosixFile.class)) {
} return type.cast(new DefaultPosixFile());
if (type.equals(Process.class)) { }
return type.cast(new DefaultProcess()); if (type.equals(Process.class)) {
} return type.cast(new DefaultProcess());
if (type.equals(TerminalAccess.class)) { }
return type.cast(new DefaultTerminalAccess()); if (type.equals(TerminalAccess.class)) {
} return type.cast(new TerminfoTerminalAccess());
throw new UnsupportedOperationException(String.format("Cannot load unknown native integration %s.", }
type.getName())); } else if (platform.isWindows()) {
} if (type.equals(Process.class)) {
return type.cast(new DefaultProcess());
private static class DefaultPosixFile implements PosixFile { }
@Override if (type.equals(TerminalAccess.class)) {
public void setMode(File file, int perms) { return type.cast(new WindowsTerminalAccess());
FunctionResult result = new FunctionResult(); }
PosixFileFunctions.chmod(file.getPath(), perms, result); }
if (result.isFailed()) { throw new UnsupportedOperationException(String.format("Cannot load unsupported native integration %s.",
throw new NativeException(String.format("Could not set UNIX mode on %s: %s", file, result.getMessage())); type.getName()));
} }
}
private static class DefaultPosixFile implements PosixFile {
@Override @Override
public int getMode(File file) { public void setMode(File file, int perms) {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
FileStat stat = new FileStat(); PosixFileFunctions.chmod(file.getPath(), perms, result);
PosixFileFunctions.stat(file.getPath(), stat, result); if (result.isFailed()) {
if (result.isFailed()) { throw new NativeException(String.format("Could not set UNIX mode on %s: %s", file, result.getMessage()));
throw new NativeException(String.format("Could not get UNIX mode on %s: %s", file, result.getMessage())); }
} }
return stat.mode;
} @Override
} public int getMode(File file) {
FunctionResult result = new FunctionResult();
private static class DefaultProcess implements Process { FileStat stat = new FileStat();
@Override PosixFileFunctions.stat(file.getPath(), stat, result);
public int getPid() throws NativeException { if (result.isFailed()) {
return PosixProcessFunctions.getPid(); throw new NativeException(String.format("Could not get UNIX mode on %s: %s", file, result.getMessage()));
} }
} return stat.mode;
}
private static class DefaultTerminalAccess implements TerminalAccess { }
private static Output currentlyOpen;
private static class DefaultProcess implements Process {
@Override @Override
public boolean isTerminal(Output output) { public int getProcessId() throws NativeException {
return PosixTerminalFunctions.isatty(output.ordinal()); return PosixProcessFunctions.getPid();
} }
}
@Override
public Terminal getTerminal(Output output) { private static class TerminfoTerminalAccess implements TerminalAccess {
if (currentlyOpen != null) { private static Output currentlyOpen;
throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
} @Override
public boolean isTerminal(Output output) {
DefaultTerminal terminal = new DefaultTerminal(output); return PosixTerminalFunctions.isatty(output.ordinal());
terminal.init(); }
currentlyOpen = output; @Override
return terminal; public Terminal getTerminal(Output output) {
} if (currentlyOpen != null) {
} throw new UnsupportedOperationException("Currently only one output can be used as a terminal.");
}
private static class DefaultTerminal implements Terminal {
private final TerminalAccess.Output output; DefaultTerminal terminal = new DefaultTerminal(output);
private final PrintStream stream; terminal.init();
private Color foreground;
currentlyOpen = output;
public DefaultTerminal(TerminalAccess.Output output) { return terminal;
this.output = output; }
stream = output == TerminalAccess.Output.Stdout ? System.out : System.err; }
}
private static class DefaultTerminal implements Terminal {
public void init() { private final TerminalAccess.Output output;
stream.flush(); private final PrintStream stream;
FunctionResult result = new FunctionResult(); private Color foreground;
TerminfoFunctions.initTerminal(output.ordinal(), result);
if (result.isFailed()) { public DefaultTerminal(TerminalAccess.Output output) {
throw new NativeException(String.format("Could not open terminal: %s", result.getMessage())); this.output = output;
} stream = output == TerminalAccess.Output.Stdout ? System.out : System.err;
Runtime.getRuntime().addShutdownHook(new Thread(){ }
@Override
public void run() { public void init() {
reset(); stream.flush();
} FunctionResult result = new FunctionResult();
}); TerminfoFunctions.initTerminal(output.ordinal(), result);
} if (result.isFailed()) {
throw new NativeException(String.format("Could not open terminal: %s", result.getMessage()));
@Override }
public TerminalSize getTerminalSize() { Runtime.getRuntime().addShutdownHook(new Thread(){
MutableTerminalSize terminalSize = new MutableTerminalSize(); @Override
FunctionResult result = new FunctionResult(); public void run() {
PosixTerminalFunctions.getTerminalSize(output.ordinal(), terminalSize, result); reset();
if (result.isFailed()) { }
throw new NativeException(String.format("Could not get terminal size: %s", result.getMessage())); });
} }
return terminalSize;
} @Override
public TerminalSize getTerminalSize() {
@Override MutableTerminalSize terminalSize = new MutableTerminalSize();
public Terminal foreground(Color color) { FunctionResult result = new FunctionResult();
stream.flush(); PosixTerminalFunctions.getTerminalSize(output.ordinal(), terminalSize, result);
FunctionResult result = new FunctionResult(); if (result.isFailed()) {
TerminfoFunctions.foreground(color.ordinal(), result); throw new NativeException(String.format("Could not get terminal size: %s", result.getMessage()));
if (result.isFailed()) { }
throw new NativeException(String.format("Could not switch foreground color: %s", result.getMessage())); return terminalSize;
} }
foreground = color;
return this; @Override
} public Terminal foreground(Color color) {
stream.flush();
@Override FunctionResult result = new FunctionResult();
public Terminal bold() { TerminfoFunctions.foreground(color.ordinal(), result);
stream.flush(); if (result.isFailed()) {
FunctionResult result = new FunctionResult(); throw new NativeException(String.format("Could not switch foreground color: %s", result.getMessage()));
TerminfoFunctions.bold(result); }
if (result.isFailed()) { foreground = color;
throw new NativeException(String.format("Could not switch to bold mode: %s", result.getMessage())); return this;
} }
return this;
} @Override
public Terminal bold() {
@Override stream.flush();
public Terminal normal() { FunctionResult result = new FunctionResult();
reset(); TerminfoFunctions.bold(result);
if (foreground != null) { if (result.isFailed()) {
foreground(foreground); throw new NativeException(String.format("Could not switch to bold mode: %s", result.getMessage()));
} }
return this; return this;
} }
@Override @Override
public Terminal reset() { public Terminal normal() {
stream.flush(); reset();
FunctionResult result = new FunctionResult(); if (foreground != null) {
TerminfoFunctions.reset(result); foreground(foreground);
if (result.isFailed()) { }
throw new NativeException(String.format("Could not reset terminal: %s", result.getMessage())); return this;
} }
return this;
} @Override
} public Terminal reset() {
} stream.flush();
FunctionResult result = new FunctionResult();
TerminfoFunctions.reset(result);
if (result.isFailed()) {
throw new NativeException(String.format("Could not reset terminal: %s", result.getMessage()));
}
return this;
}
}
private static class WindowsTerminalAccess implements TerminalAccess {
@Override
public boolean isTerminal(Output output) {
return false;
}
@Override
public Terminal getTerminal(Output output) {
throw new UnsupportedOperationException();
}
}
}

16
src/main/java/net/rubygrapefruit/platform/Process.java Normal file → Executable file
View File

@@ -1,8 +1,8 @@
package net.rubygrapefruit.platform; package net.rubygrapefruit.platform;
/** /**
* Functions to query and modify a process' meta-data * Functions to query and modify a process' meta-data
*/ */
public interface Process extends NativeIntegration { public interface Process extends NativeIntegration {
int getPid() throws NativeException; int getProcessId() throws NativeException;
} }

View File

@@ -1,61 +1,81 @@
package net.rubygrapefruit.platform.internal; package net.rubygrapefruit.platform.internal;
public abstract class Platform { public abstract class Platform {
private static Platform platform; private static Platform platform;
public static Platform current() { public static Platform current() {
synchronized (Platform.class) { synchronized (Platform.class) {
if (platform == null) { if (platform == null) {
String osName = System.getProperty("os.name").toLowerCase(); String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("windows")) { if (osName.contains("windows")) {
platform = new Windows(); platform = new Windows();
} else if (osName.contains("linux")) { } else if (osName.contains("linux")) {
platform = new Linux(); platform = new Linux();
} else if (osName.contains("os x")) { } else if (osName.contains("os x")) {
platform = new OsX(); platform = new OsX();
} else { } else {
platform = new Unsupported(); platform = new Unsupported();
} }
} }
return platform; return platform;
} }
} }
public boolean isSupported() { public boolean isSupported() {
return true; return true;
} }
public abstract String getLibraryName(); public boolean isPosix() {
return false;
private static class Windows extends Platform { }
@Override
public String getLibraryName() { public boolean isWindows() {
return "native-platform.dll"; return false;
} }
}
public abstract String getLibraryName();
private static class Linux extends Platform {
@Override private static class Windows extends Platform {
public String getLibraryName() { @Override
return "libnative-platform.so"; public boolean isWindows() {
} return true;
} }
private static class OsX extends Platform { @Override
@Override public String getLibraryName() {
public String getLibraryName() { return "native-platform.dll";
return "libnative-platform.dylib"; }
} }
}
private static abstract class Posix extends Platform {
private static class Unsupported extends Platform { @Override
@Override public boolean isPosix() {
public boolean isSupported() { return true;
return false; }
} }
public String getLibraryName() { private static class Linux extends Posix {
throw new UnsupportedOperationException(); @Override
} public String getLibraryName() {
} return "libnative-platform.so";
} }
}
private static class OsX extends Posix {
@Override
public String getLibraryName() {
return "libnative-platform.dylib";
}
}
private static class Unsupported extends Platform {
@Override
public boolean isSupported() {
return false;
}
public String getLibraryName() {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -1,52 +1,55 @@
package net.rubygrapefruit.platform package net.rubygrapefruit.platform
import spock.lang.Specification import spock.lang.Specification
import org.junit.Rule import org.junit.Rule
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import spock.lang.IgnoreIf
class PosixFileTest extends Specification { import net.rubygrapefruit.platform.internal.Platform
@Rule TemporaryFolder tmpDir
final PosixFile file = Native.get(PosixFile.class) @IgnoreIf({Platform.current().windows})
class PosixFileTest extends Specification {
def "can set mode on a file"() { @Rule TemporaryFolder tmpDir
def testFile = tmpDir.newFile("test.txt") final PosixFile file = Native.get(PosixFile.class)
when: def "can set mode on a file"() {
file.setMode(testFile, 0740) def testFile = tmpDir.newFile("test.txt")
then: when:
file.getMode(testFile) == 0740 file.setMode(testFile, 0740)
}
then:
def "can set mode on a file with unicode in its name"() { file.getMode(testFile) == 0740
def testFile = tmpDir.newFile("test\u03b1.txt") }
when: def "can set mode on a file with unicode in its name"() {
file.setMode(testFile, 0740) def testFile = tmpDir.newFile("test\u03b1.txt")
then: when:
file.getMode(testFile) == 0740 file.setMode(testFile, 0740)
}
then:
def "throws exception on failure to set mode"() { file.getMode(testFile) == 0740
def file = new File(tmpDir.root, "unknown") }
when: def "throws exception on failure to set mode"() {
this.file.setMode(file, 0660) def file = new File(tmpDir.root, "unknown")
then: when:
NativeException e = thrown() this.file.setMode(file, 0660)
e.message == "Could not set UNIX mode on $file: could not chmod file (errno 2)"
} then:
NativeException e = thrown()
def "throws exception on failure to get mode"() { e.message == "Could not set UNIX mode on $file: could not chmod file (errno 2)"
def file = new File(tmpDir.root, "unknown") }
when: def "throws exception on failure to get mode"() {
this.file.getMode(file) def file = new File(tmpDir.root, "unknown")
then: when:
NativeException e = thrown() this.file.getMode(file)
e.message == "Could not get UNIX mode on $file: could not stat file (errno 2)"
} then:
} NativeException e = thrown()
e.message == "Could not get UNIX mode on $file: could not stat file (errno 2)"
}
}

View File

@@ -1,15 +1,15 @@
package net.rubygrapefruit.platform package net.rubygrapefruit.platform
import org.junit.Rule import org.junit.Rule
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import spock.lang.Specification import spock.lang.Specification
class ProcessTest extends Specification { class ProcessTest extends Specification {
@Rule TemporaryFolder tmpDir @Rule TemporaryFolder tmpDir
final Process process = Native.get(Process.class) final Process process = Native.get(Process.class)
def "can get PID"() { def "can get PID"() {
expect: expect:
process.getPid() != 0 process.getProcessId() != 0
} }
} }

View File

@@ -1,25 +1,25 @@
package net.rubygrapefruit.platform package net.rubygrapefruit.platform
import org.junit.Rule import org.junit.Rule
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import spock.lang.Specification import spock.lang.Specification
class TerminalTest extends Specification { class TerminalTest extends Specification {
@Rule TemporaryFolder tmpDir @Rule TemporaryFolder tmpDir
final TerminalAccess terminal = Native.get(TerminalAccess.class) final TerminalAccess terminal = Native.get(TerminalAccess.class)
def "can check if attached to terminal"() { def "can check if attached to terminal"() {
expect: expect:
!terminal.isTerminal(TerminalAccess.Output.Stdout); !terminal.isTerminal(TerminalAccess.Output.Stdout);
!terminal.isTerminal(TerminalAccess.Output.Stderr); !terminal.isTerminal(TerminalAccess.Output.Stderr);
} }
def "cannot determine terminal size from a test"() { def "cannot access terminal from a test"() {
when: when:
terminal.getTerminal(TerminalAccess.Output.Stdout) terminal.getTerminal(TerminalAccess.Output.Stdout)
then: then:
NativeException e = thrown() NativeException e = thrown()
e.message == 'Could not open terminal: not a terminal' e.message == 'Could not open terminal: not a terminal'
} }
} }