From e5537494b05a1cf6b6abd757c869ac17d7cb863a Mon Sep 17 00:00:00 2001 From: Adam Murdoch Date: Sat, 4 Aug 2012 12:32:15 +1000 Subject: [PATCH] First pass for windows support. --- build.gradle | 121 +++--- src/main/cpp/generic.c | 9 + src/main/cpp/{posixFunctions.c => posix.c} | 351 ++++++++-------- src/main/cpp/win.c | 15 + .../net/rubygrapefruit/platform/Main.java | 82 ++-- .../net/rubygrapefruit/platform/Native.java | 383 +++++++++--------- .../net/rubygrapefruit/platform/Process.java | 16 +- .../platform/internal/Platform.java | 142 ++++--- .../platform/PosixFileTest.groovy | 107 ++--- .../platform/ProcessTest.groovy | 30 +- .../platform/TerminalTest.groovy | 50 +-- 11 files changed, 689 insertions(+), 617 deletions(-) mode change 100644 => 100755 build.gradle create mode 100755 src/main/cpp/generic.c rename src/main/cpp/{posixFunctions.c => posix.c} (96%) mode change 100644 => 100755 create mode 100755 src/main/cpp/win.c mode change 100644 => 100755 src/main/java/net/rubygrapefruit/platform/Main.java mode change 100644 => 100755 src/main/java/net/rubygrapefruit/platform/Native.java mode change 100644 => 100755 src/main/java/net/rubygrapefruit/platform/Process.java mode change 100644 => 100755 src/main/java/net/rubygrapefruit/platform/internal/Platform.java mode change 100644 => 100755 src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy mode change 100644 => 100755 src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy mode change 100644 => 100755 src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy diff --git a/build.gradle b/build.gradle old mode 100644 new mode 100755 index 20e2051..8eb5066 --- a/build.gradle +++ b/build.gradle @@ -1,56 +1,65 @@ -apply plugin: 'java' -apply plugin: 'groovy' -apply plugin: 'cpp-lib' -apply plugin: 'idea' -apply plugin: 'application' - -repositories { - mavenCentral() -} - -dependencies { - groovy 'org.codehaus.groovy:groovy:1.8.7' - testCompile 'org.spockframework:spock-core:0.6-groovy-1.8' -} - -mainClassName = 'net.rubygrapefruit.platform.Main' -def nativeHeadersDir = file("$buildDir/nativeHeaders") -targetCompatibility = 1.5 - -libraries { - main { - spec { - includes([nativeHeadersDir]) - if (org.gradle.internal.os.OperatingSystem.current().macOsX) { - includes(['/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/']) - } else { - includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include"]) - includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"]) - } - args("-lcurses") - } - } -} - -task nativeHeaders { - def outputFile = file("$nativeHeadersDir/native.h") - inputs.files sourceSets.main.output - outputs.file outputFile - doLast { - outputFile.parentFile.mkdirs() - exec { - executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah') - args '-o', outputFile - args '-classpath', sourceSets.main.output.classesDir - args 'net.rubygrapefruit.platform.internal.NativeLibraryFunctions' - 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 - +apply plugin: 'java' +apply plugin: 'groovy' +apply plugin: 'cpp-lib' +apply plugin: 'idea' +apply plugin: 'application' + +repositories { + mavenCentral() +} + +dependencies { + groovy 'org.codehaus.groovy:groovy:1.8.7' + testCompile 'org.spockframework:spock-core:0.6-groovy-1.8' +} + +mainClassName = 'net.rubygrapefruit.platform.Main' +def nativeHeadersDir = file("$buildDir/nativeHeaders") +sourceCompatibility = 1.5 +targetCompatibility = 1.5 + +println org.gradle.internal.jvm.Jvm.current().javaHome +println org.gradle.internal.jvm.Jvm.current().toolsJar + +libraries { + main { + spec { + includes([nativeHeadersDir]) + if (org.gradle.internal.os.OperatingSystem.current().macOsX) { + includes(['/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/']) + 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") + } else { + includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include"]) + includes(["${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"]) + args("-lcurses") + } + } + } +} + +task nativeHeaders { + def outputFile = file("$nativeHeadersDir/native.h") + inputs.files sourceSets.main.output + outputs.file outputFile + doLast { + outputFile.parentFile.mkdirs() + exec { + executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah') + args '-o', outputFile + args '-classpath', sourceSets.main.output.classesDir + args 'net.rubygrapefruit.platform.internal.NativeLibraryFunctions' + 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 + diff --git a/src/main/cpp/generic.c b/src/main/cpp/generic.c new file mode 100755 index 0000000..0d437ae --- /dev/null +++ b/src/main/cpp/generic.c @@ -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; +} diff --git a/src/main/cpp/posixFunctions.c b/src/main/cpp/posix.c old mode 100644 new mode 100755 similarity index 96% rename from src/main/cpp/posixFunctions.c rename to src/main/cpp/posix.c index 8b9eb61..7a8ffa4 --- a/src/main/cpp/posixFunctions.c +++ b/src/main/cpp/posix.c @@ -1,178 +1,173 @@ -#include "native.h" -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Marks the given result as failed, using the current value of errno - */ -void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { - jclass destClass = env->GetObjectClass(result); - 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) { - jclass destClass = env->GetObjectClass(result); - jmethodID method = env->GetMethodID(destClass, "failed", "(Ljava/lang/String;)V"); - jstring message_str = env->NewStringUTF(message); - env->CallVoidMethod(result, method, message_str); -} - -/* - * Generic functions - */ - -JNIEXPORT jint JNICALL -Java_net_rubygrapefruit_platform_internal_NativeLibraryFunctions_getVersion(JNIEnv *env, jclass target) { - return 2; -} - -/* - * File functions - */ - -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); - 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_stat(JNIEnv *env, jclass target, jstring path, jobject dest, jobject result) { - struct stat fileInfo; - const char* pathUtf8 = env->GetStringUTFChars(path, NULL); - int retval = stat(pathUtf8, &fileInfo); - env->ReleaseStringUTFChars(path, pathUtf8); - if (retval != 0) { - mark_failed_with_errno(env, "could not stat file", result); - return; - } - jclass destClass = env->GetObjectClass(dest); - jfieldID modeField = env->GetFieldID(destClass, "mode", "I"); - env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode); -} - -/* - * Process functions - */ - -JNIEXPORT jint JNICALL -Java_net_rubygrapefruit_platform_internal_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) { - return getpid(); -} - -/* - * Terminal functions - */ - -JNIEXPORT jboolean JNICALL -Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_isatty(JNIEnv *env, jclass target, jint output) { - switch (output) { - case 0: - case 1: - return isatty(output+1) ? JNI_TRUE : JNI_FALSE; - default: - return JNI_FALSE; - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_getTerminalSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) { - struct winsize screen_size; - int retval = ioctl(output+1, TIOCGWINSZ, &screen_size); - if (retval != 0) { - mark_failed_with_errno(env, "could not fetch terminal size", result); - return; - } - jclass dimensionClass = env->GetObjectClass(dimension); - jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I"); - env->SetIntField(dimension, widthField, screen_size.ws_col); - jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I"); - env->SetIntField(dimension, heightField, screen_size.ws_row); -} - -/* - * Terminfo functions - */ - -int current_terminal = -1; - -int write_to_terminal(int ch) { - write(current_terminal, &ch, 1); -} - -void write_capability(JNIEnv *env, const char* capability, jobject result) { - char* cap = tgetstr((char*)capability, NULL); - if (cap == NULL) { - mark_failed_with_message(env, "unknown terminal capability", result); - return; - } - if (tputs(cap, 1, write_to_terminal) == ERR) { - mark_failed_with_message(env, "could not write to terminal", result); - return; - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_initTerminal(JNIEnv *env, jclass target, jint output, jobject result) { - if (!isatty(output+1)) { - mark_failed_with_message(env, "not a terminal", result); - return; - } - char* termType = getenv("TERM"); - if (termType == NULL) { - mark_failed_with_message(env, "$TERM not set", result); - return; - } - int retval = tgetent(NULL, termType); - if (retval != 1) { - mark_failed_with_message(env, "could not get termcap entry", result); - return; - } - current_terminal = output + 1; - write_capability(env, "me", result); -} - -JNIEXPORT void JNICALL -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_reset(JNIEnv *env, jclass target, jobject result) { - write_capability(env, "me", result); -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) { - char* capability = tgetstr((char*)"AF", NULL); - if (capability == NULL) { - mark_failed_with_message(env, "unknown terminal capability", result); - return; - } - - capability = tparm(capability, color, 0, 0, 0, 0, 0, 0, 0, 0); - if (capability == NULL) { - mark_failed_with_message(env, "could not format terminal capability string", result); - return; - } - - if (tputs(capability, 1, write_to_terminal) == ERR) { - mark_failed_with_message(env, "could not write to terminal", result); - return; - } -} +#ifndef WIN32 + +#include "native.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Marks the given result as failed, using the current value of errno + */ +void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { + jclass destClass = env->GetObjectClass(result); + 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) { + jclass destClass = env->GetObjectClass(result); + jmethodID method = env->GetMethodID(destClass, "failed", "(Ljava/lang/String;)V"); + jstring message_str = env->NewStringUTF(message); + env->CallVoidMethod(result, method, message_str); +} + +/* + * File functions + */ + +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); + 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_stat(JNIEnv *env, jclass target, jstring path, jobject dest, jobject result) { + struct stat fileInfo; + const char* pathUtf8 = env->GetStringUTFChars(path, NULL); + int retval = stat(pathUtf8, &fileInfo); + env->ReleaseStringUTFChars(path, pathUtf8); + if (retval != 0) { + mark_failed_with_errno(env, "could not stat file", result); + return; + } + jclass destClass = env->GetObjectClass(dest); + jfieldID modeField = env->GetFieldID(destClass, "mode", "I"); + env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode); +} + +/* + * Process functions + */ + +JNIEXPORT jint JNICALL +Java_net_rubygrapefruit_platform_internal_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) { + return getpid(); +} + +/* + * Terminal functions + */ + +JNIEXPORT jboolean JNICALL +Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_isatty(JNIEnv *env, jclass target, jint output) { + switch (output) { + case 0: + case 1: + return isatty(output+1) ? JNI_TRUE : JNI_FALSE; + default: + return JNI_FALSE; + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_PosixTerminalFunctions_getTerminalSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) { + struct winsize screen_size; + int retval = ioctl(output+1, TIOCGWINSZ, &screen_size); + if (retval != 0) { + mark_failed_with_errno(env, "could not fetch terminal size", result); + return; + } + jclass dimensionClass = env->GetObjectClass(dimension); + jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I"); + env->SetIntField(dimension, widthField, screen_size.ws_col); + jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I"); + env->SetIntField(dimension, heightField, screen_size.ws_row); +} + +/* + * Terminfo functions + */ + +int current_terminal = -1; + +int write_to_terminal(int ch) { + write(current_terminal, &ch, 1); +} + +void write_capability(JNIEnv *env, const char* capability, jobject result) { + char* cap = tgetstr((char*)capability, NULL); + if (cap == NULL) { + mark_failed_with_message(env, "unknown terminal capability", result); + return; + } + if (tputs(cap, 1, write_to_terminal) == ERR) { + mark_failed_with_message(env, "could not write to terminal", result); + return; + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_initTerminal(JNIEnv *env, jclass target, jint output, jobject result) { + if (!isatty(output+1)) { + mark_failed_with_message(env, "not a terminal", result); + return; + } + char* termType = getenv("TERM"); + if (termType == NULL) { + mark_failed_with_message(env, "$TERM not set", result); + return; + } + int retval = tgetent(NULL, termType); + if (retval != 1) { + mark_failed_with_message(env, "could not get termcap entry", result); + return; + } + current_terminal = output + 1; + write_capability(env, "me", result); +} + +JNIEXPORT void JNICALL +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_reset(JNIEnv *env, jclass target, jobject result) { + write_capability(env, "me", result); +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_TerminfoFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) { + char* capability = tgetstr((char*)"AF", NULL); + if (capability == NULL) { + mark_failed_with_message(env, "unknown terminal capability", result); + return; + } + + capability = tparm(capability, color, 0, 0, 0, 0, 0, 0, 0, 0); + if (capability == NULL) { + mark_failed_with_message(env, "could not format terminal capability string", result); + return; + } + + if (tputs(capability, 1, write_to_terminal) == ERR) { + mark_failed_with_message(env, "could not write to terminal", result); + return; + } +} + +#endif diff --git a/src/main/cpp/win.c b/src/main/cpp/win.c new file mode 100755 index 0000000..b80bd9d --- /dev/null +++ b/src/main/cpp/win.c @@ -0,0 +1,15 @@ +#ifdef WIN32 + +#include "native.h" +#include + +/* + * Process functions + */ + +JNIEXPORT jint JNICALL +Java_net_rubygrapefruit_platform_internal_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) { + return GetCurrentProcessId(); +} + +#endif diff --git a/src/main/java/net/rubygrapefruit/platform/Main.java b/src/main/java/net/rubygrapefruit/platform/Main.java old mode 100644 new mode 100755 index 714f080..6b9ddd5 --- a/src/main/java/net/rubygrapefruit/platform/Main.java +++ b/src/main/java/net/rubygrapefruit/platform/Main.java @@ -1,41 +1,41 @@ -package net.rubygrapefruit.platform; - -public class Main { - public static void main(String[] args) { - System.out.println(); - System.out.println("* OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch")); - - Process process = Native.get(Process.class); - System.out.println("* PID: " + process.getPid()); - - TerminalAccess terminalAccess = Native.get(TerminalAccess.class); - boolean stdoutIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stdout); - boolean stderrIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stderr); - System.out.println("* stdout: " + (stdoutIsTerminal ? "terminal" : "not a terminal")); - System.out.println("* stderr: " + (stderrIsTerminal ? "terminal" : "not a terminal")); - if (stdoutIsTerminal) { - Terminal terminal = terminalAccess.getTerminal(TerminalAccess.Output.Stdout); - TerminalSize terminalSize = terminal.getTerminalSize(); - System.out.println("* terminal size: " + terminalSize.getCols() + " cols x " + terminalSize.getRows() + " rows"); - System.out.println(); - System.out.println("TERMINAL OUTPUT"); - System.out.print("[normal] "); - terminal.bold(); - System.out.print("[bold]"); - terminal.normal(); - System.out.println(" [normal]"); - - System.out.println("here are the colors:"); - for (Terminal.Color color : Terminal.Color.values()) { - terminal.foreground(color); - System.out.print(String.format("[%s] ", color.toString().toLowerCase())); - terminal.bold(); - System.out.print(String.format("[%s]", color.toString().toLowerCase())); - terminal.normal(); - System.out.println(); - } - } - - System.out.println(); - } -} +package net.rubygrapefruit.platform; + +public class Main { + public static void main(String[] args) { + System.out.println(); + System.out.println("* OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch")); + + Process process = Native.get(Process.class); + System.out.println("* PID: " + process.getProcessId()); + + TerminalAccess terminalAccess = Native.get(TerminalAccess.class); + boolean stdoutIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stdout); + boolean stderrIsTerminal = terminalAccess.isTerminal(TerminalAccess.Output.Stderr); + System.out.println("* stdout: " + (stdoutIsTerminal ? "terminal" : "not a terminal")); + System.out.println("* stderr: " + (stderrIsTerminal ? "terminal" : "not a terminal")); + if (stdoutIsTerminal) { + Terminal terminal = terminalAccess.getTerminal(TerminalAccess.Output.Stdout); + TerminalSize terminalSize = terminal.getTerminalSize(); + System.out.println("* terminal size: " + terminalSize.getCols() + " cols x " + terminalSize.getRows() + " rows"); + System.out.println(); + System.out.println("TERMINAL OUTPUT"); + System.out.print("[normal] "); + terminal.bold(); + System.out.print("[bold]"); + terminal.normal(); + System.out.println(" [normal]"); + + System.out.println("here are the colors:"); + for (Terminal.Color color : Terminal.Color.values()) { + terminal.foreground(color); + System.out.print(String.format("[%s] ", color.toString().toLowerCase())); + terminal.bold(); + System.out.print(String.format("[%s]", color.toString().toLowerCase())); + terminal.normal(); + System.out.println(); + } + } + + System.out.println(); + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/Native.java b/src/main/java/net/rubygrapefruit/platform/Native.java old mode 100644 new mode 100755 index c5d343c..b17f8e2 --- a/src/main/java/net/rubygrapefruit/platform/Native.java +++ b/src/main/java/net/rubygrapefruit/platform/Native.java @@ -1,181 +1,202 @@ -package net.rubygrapefruit.platform; - -import net.rubygrapefruit.platform.internal.*; - -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; - -/** - * 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) { - 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 = 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 (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 DefaultTerminalAccess()); - } - throw new UnsupportedOperationException(String.format("Cannot load unknown native integration %s.", - type.getName())); - } - - private static class DefaultPosixFile implements PosixFile { - @Override - public void setMode(File file, int perms) { - FunctionResult result = new FunctionResult(); - PosixFileFunctions.chmod(file.getPath(), perms, result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not set UNIX mode on %s: %s", file, result.getMessage())); - } - } - - @Override - public int getMode(File file) { - FunctionResult result = new FunctionResult(); - FileStat stat = new FileStat(); - PosixFileFunctions.stat(file.getPath(), stat, result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not get UNIX mode on %s: %s", file, result.getMessage())); - } - return stat.mode; - } - } - - private static class DefaultProcess implements Process { - @Override - public int getPid() throws NativeException { - return PosixProcessFunctions.getPid(); - } - } - - private static class DefaultTerminalAccess 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."); - } - - DefaultTerminal terminal = new DefaultTerminal(output); - terminal.init(); - - currentlyOpen = output; - return terminal; - } - } - - private static class DefaultTerminal implements Terminal { - private final TerminalAccess.Output output; - private final PrintStream stream; - private Color foreground; - - public DefaultTerminal(TerminalAccess.Output output) { - this.output = output; - stream = output == TerminalAccess.Output.Stdout ? System.out : System.err; - } - - public void init() { - 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())); - } - Runtime.getRuntime().addShutdownHook(new Thread(){ - @Override - public void run() { - reset(); - } - }); - } - - @Override - public TerminalSize getTerminalSize() { - MutableTerminalSize terminalSize = new MutableTerminalSize(); - FunctionResult result = new FunctionResult(); - PosixTerminalFunctions.getTerminalSize(output.ordinal(), terminalSize, result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not get terminal size: %s", result.getMessage())); - } - return terminalSize; - } - - @Override - public Terminal foreground(Color color) { - stream.flush(); - FunctionResult result = new FunctionResult(); - TerminfoFunctions.foreground(color.ordinal(), result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not switch foreground color: %s", result.getMessage())); - } - foreground = color; - return this; - } - - @Override - public Terminal bold() { - stream.flush(); - FunctionResult result = new FunctionResult(); - TerminfoFunctions.bold(result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not switch to bold mode: %s", result.getMessage())); - } - return this; - } - - @Override - public Terminal normal() { - reset(); - if (foreground != null) { - foreground(foreground); - } - 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; - } - } -} +package net.rubygrapefruit.platform; + +import net.rubygrapefruit.platform.internal.*; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; + +/** + * 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())); + } + + private static class DefaultPosixFile implements PosixFile { + @Override + public void setMode(File file, int perms) { + FunctionResult result = new FunctionResult(); + PosixFileFunctions.chmod(file.getPath(), perms, result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not set UNIX mode on %s: %s", file, result.getMessage())); + } + } + + @Override + public int getMode(File file) { + FunctionResult result = new FunctionResult(); + FileStat stat = new FileStat(); + PosixFileFunctions.stat(file.getPath(), stat, result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not get UNIX mode on %s: %s", file, result.getMessage())); + } + return stat.mode; + } + } + + private static class DefaultProcess implements Process { + @Override + public int getProcessId() throws NativeException { + return PosixProcessFunctions.getPid(); + } + } + + private static 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."); + } + + DefaultTerminal terminal = new DefaultTerminal(output); + terminal.init(); + + currentlyOpen = output; + return terminal; + } + } + + private static class DefaultTerminal implements Terminal { + private final TerminalAccess.Output output; + private final PrintStream stream; + private Color foreground; + + public DefaultTerminal(TerminalAccess.Output output) { + this.output = output; + stream = output == TerminalAccess.Output.Stdout ? System.out : System.err; + } + + public void init() { + 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())); + } + Runtime.getRuntime().addShutdownHook(new Thread(){ + @Override + public void run() { + reset(); + } + }); + } + + @Override + public TerminalSize getTerminalSize() { + MutableTerminalSize terminalSize = new MutableTerminalSize(); + FunctionResult result = new FunctionResult(); + PosixTerminalFunctions.getTerminalSize(output.ordinal(), terminalSize, result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not get terminal size: %s", result.getMessage())); + } + return terminalSize; + } + + @Override + public Terminal foreground(Color color) { + stream.flush(); + FunctionResult result = new FunctionResult(); + TerminfoFunctions.foreground(color.ordinal(), result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not switch foreground color: %s", result.getMessage())); + } + foreground = color; + return this; + } + + @Override + public Terminal bold() { + stream.flush(); + FunctionResult result = new FunctionResult(); + TerminfoFunctions.bold(result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not switch to bold mode: %s", result.getMessage())); + } + return this; + } + + @Override + public Terminal normal() { + reset(); + if (foreground != null) { + foreground(foreground); + } + 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(); + } + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/Process.java b/src/main/java/net/rubygrapefruit/platform/Process.java old mode 100644 new mode 100755 index 3e5df0e..aa8dfc7 --- a/src/main/java/net/rubygrapefruit/platform/Process.java +++ b/src/main/java/net/rubygrapefruit/platform/Process.java @@ -1,8 +1,8 @@ -package net.rubygrapefruit.platform; - -/** - * Functions to query and modify a process' meta-data - */ -public interface Process extends NativeIntegration { - int getPid() throws NativeException; -} +package net.rubygrapefruit.platform; + +/** + * Functions to query and modify a process' meta-data + */ +public interface Process extends NativeIntegration { + int getProcessId() throws NativeException; +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java old mode 100644 new mode 100755 index 82b1f04..429cfa6 --- a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java @@ -1,61 +1,81 @@ -package net.rubygrapefruit.platform.internal; - -public abstract class Platform { - private static Platform platform; - - public static Platform current() { - synchronized (Platform.class) { - if (platform == null) { - String osName = System.getProperty("os.name").toLowerCase(); - if (osName.contains("windows")) { - platform = new Windows(); - } else if (osName.contains("linux")) { - platform = new Linux(); - } else if (osName.contains("os x")) { - platform = new OsX(); - } else { - platform = new Unsupported(); - } - } - return platform; - } - } - - public boolean isSupported() { - return true; - } - - public abstract String getLibraryName(); - - private static class Windows extends Platform { - @Override - public String getLibraryName() { - return "native-platform.dll"; - } - } - - private static class Linux extends Platform { - @Override - public String getLibraryName() { - return "libnative-platform.so"; - } - } - - private static class OsX extends Platform { - @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(); - } - } -} +package net.rubygrapefruit.platform.internal; + +public abstract class Platform { + private static Platform platform; + + public static Platform current() { + synchronized (Platform.class) { + if (platform == null) { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("windows")) { + platform = new Windows(); + } else if (osName.contains("linux")) { + platform = new Linux(); + } else if (osName.contains("os x")) { + platform = new OsX(); + } else { + platform = new Unsupported(); + } + } + return platform; + } + } + + public boolean isSupported() { + return true; + } + + public boolean isPosix() { + return false; + } + + public boolean isWindows() { + return false; + } + + public abstract String getLibraryName(); + + private static class Windows extends Platform { + @Override + public boolean isWindows() { + return true; + } + + @Override + public String getLibraryName() { + return "native-platform.dll"; + } + } + + private static abstract class Posix extends Platform { + @Override + public boolean isPosix() { + return true; + } + } + + private static class Linux extends Posix { + @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(); + } + } +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy old mode 100644 new mode 100755 index 17c71eb..2336fa0 --- a/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy @@ -1,52 +1,55 @@ -package net.rubygrapefruit.platform - -import spock.lang.Specification -import org.junit.Rule -import org.junit.rules.TemporaryFolder - -class PosixFileTest extends Specification { - @Rule TemporaryFolder tmpDir - final PosixFile file = Native.get(PosixFile.class) - - def "can set mode on a file"() { - def testFile = tmpDir.newFile("test.txt") - - when: - file.setMode(testFile, 0740) - - then: - file.getMode(testFile) == 0740 - } - - def "can set mode on a file with unicode in its name"() { - def testFile = tmpDir.newFile("test\u03b1.txt") - - when: - file.setMode(testFile, 0740) - - then: - file.getMode(testFile) == 0740 - } - - def "throws exception on failure to set mode"() { - def file = new File(tmpDir.root, "unknown") - - when: - this.file.setMode(file, 0660) - - then: - NativeException e = thrown() - e.message == "Could not set UNIX mode on $file: could not chmod file (errno 2)" - } - - def "throws exception on failure to get mode"() { - def file = new File(tmpDir.root, "unknown") - - when: - this.file.getMode(file) - - then: - NativeException e = thrown() - e.message == "Could not get UNIX mode on $file: could not stat file (errno 2)" - } -} +package net.rubygrapefruit.platform + +import spock.lang.Specification +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.IgnoreIf +import net.rubygrapefruit.platform.internal.Platform + +@IgnoreIf({Platform.current().windows}) +class PosixFileTest extends Specification { + @Rule TemporaryFolder tmpDir + final PosixFile file = Native.get(PosixFile.class) + + def "can set mode on a file"() { + def testFile = tmpDir.newFile("test.txt") + + when: + file.setMode(testFile, 0740) + + then: + file.getMode(testFile) == 0740 + } + + def "can set mode on a file with unicode in its name"() { + def testFile = tmpDir.newFile("test\u03b1.txt") + + when: + file.setMode(testFile, 0740) + + then: + file.getMode(testFile) == 0740 + } + + def "throws exception on failure to set mode"() { + def file = new File(tmpDir.root, "unknown") + + when: + this.file.setMode(file, 0660) + + then: + NativeException e = thrown() + e.message == "Could not set UNIX mode on $file: could not chmod file (errno 2)" + } + + def "throws exception on failure to get mode"() { + def file = new File(tmpDir.root, "unknown") + + when: + this.file.getMode(file) + + then: + NativeException e = thrown() + e.message == "Could not get UNIX mode on $file: could not stat file (errno 2)" + } +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy old mode 100644 new mode 100755 index c3d51c8..8f65b97 --- a/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy @@ -1,15 +1,15 @@ -package net.rubygrapefruit.platform - -import org.junit.Rule -import org.junit.rules.TemporaryFolder -import spock.lang.Specification - -class ProcessTest extends Specification { - @Rule TemporaryFolder tmpDir - final Process process = Native.get(Process.class) - - def "can get PID"() { - expect: - process.getPid() != 0 - } -} +package net.rubygrapefruit.platform + +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +class ProcessTest extends Specification { + @Rule TemporaryFolder tmpDir + final Process process = Native.get(Process.class) + + def "can get PID"() { + expect: + process.getProcessId() != 0 + } +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy old mode 100644 new mode 100755 index d0b5c6e..b6ac707 --- a/src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy @@ -1,25 +1,25 @@ -package net.rubygrapefruit.platform - -import org.junit.Rule -import org.junit.rules.TemporaryFolder -import spock.lang.Specification - -class TerminalTest extends Specification { - @Rule TemporaryFolder tmpDir - final TerminalAccess terminal = Native.get(TerminalAccess.class) - - def "can check if attached to terminal"() { - expect: - !terminal.isTerminal(TerminalAccess.Output.Stdout); - !terminal.isTerminal(TerminalAccess.Output.Stderr); - } - - def "cannot determine terminal size from a test"() { - when: - terminal.getTerminal(TerminalAccess.Output.Stdout) - - then: - NativeException e = thrown() - e.message == 'Could not open terminal: not a terminal' - } -} +package net.rubygrapefruit.platform + +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +class TerminalTest extends Specification { + @Rule TemporaryFolder tmpDir + final TerminalAccess terminal = Native.get(TerminalAccess.class) + + def "can check if attached to terminal"() { + expect: + !terminal.isTerminal(TerminalAccess.Output.Stdout); + !terminal.isTerminal(TerminalAccess.Output.Stderr); + } + + def "cannot access terminal from a test"() { + when: + terminal.getTerminal(TerminalAccess.Output.Stdout) + + then: + NativeException e = thrown() + e.message == 'Could not open terminal: not a terminal' + } +}