diff --git a/build.gradle b/build.gradle index 5a41352..da7beb0 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'java' apply plugin: 'groovy' apply plugin: 'cpp-lib' apply plugin: 'idea' +apply plugin: 'application' repositories { mavenCentral() @@ -12,6 +13,7 @@ dependencies { testCompile 'org.spockframework:spock-core:0.6-groovy-1.8' } +mainClassName = 'net.rubygrapefruit.platform.Main' def nativeHeadersDir = file("$buildDir/nativeHeaders") libraries { @@ -34,6 +36,8 @@ task nativeHeaders { args '-o', outputFile args '-classpath', sourceSets.main.output.classesDir args 'net.rubygrapefruit.platform.internal.PosixFileFunctions' + args 'net.rubygrapefruit.platform.internal.PosixProcessFunctions' + args 'net.rubygrapefruit.platform.internal.PosixTerminalFunctions' } } } diff --git a/src/main/cpp/posixFileFunctions.c b/src/main/cpp/posixFunctions.c similarity index 71% rename from src/main/cpp/posixFileFunctions.c rename to src/main/cpp/posixFunctions.c index 4e5d2a5..7ac27d0 100644 --- a/src/main/cpp/posixFileFunctions.c +++ b/src/main/cpp/posixFunctions.c @@ -2,6 +2,7 @@ #include #include #include +#include void markFailed(JNIEnv *env, jobject result) { jclass destClass = env->GetObjectClass(result); @@ -30,4 +31,20 @@ Java_net_rubygrapefruit_platform_internal_PosixFileFunctions_stat(JNIEnv *env, j jclass destClass = env->GetObjectClass(dest); jfieldID modeField = env->GetFieldID(destClass, "mode", "I"); env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode); -} \ No newline at end of file +} + +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: + case 1: + return isatty(output+1) ? JNI_TRUE : JNI_FALSE; + default: + return JNI_FALSE; + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/Main.java b/src/main/java/net/rubygrapefruit/platform/Main.java new file mode 100644 index 0000000..61c117d --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/Main.java @@ -0,0 +1,11 @@ +package net.rubygrapefruit.platform; + +public class Main { + public static void main(String[] args) { + Process process = Platform.get(Process.class); + System.out.println("* PID: " + process.getPid()); + Terminal terminal = Platform.get(Terminal.class); + System.out.println("* stdout: " + (terminal.isTerminal(Terminal.Output.Stdout) ? "terminal" : "not a terminal")); + System.out.println("* stderr: " + (terminal.isTerminal(Terminal.Output.Stderr) ? "terminal" : "not a terminal")); + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/Platform.java b/src/main/java/net/rubygrapefruit/platform/Platform.java index 7e9148c..92c681a 100644 --- a/src/main/java/net/rubygrapefruit/platform/Platform.java +++ b/src/main/java/net/rubygrapefruit/platform/Platform.java @@ -1,12 +1,13 @@ package net.rubygrapefruit.platform; -import net.rubygrapefruit.platform.internal.FileStat; -import net.rubygrapefruit.platform.internal.FunctionResult; -import net.rubygrapefruit.platform.internal.PosixFileFunctions; +import net.rubygrapefruit.platform.internal.*; 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 Platform { private static final Object lock = new Object(); private static boolean loaded; @@ -23,26 +24,50 @@ public class Platform { loaded = true; } } - return type.cast(new UnixFileMode() { - @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. Errno is %d.", file, result.getErrno())); + if (type.equals(PosixFile.class)) { + return type.cast(new DefaultPosixFile()); + } + if (type.equals(Process.class)) { + return type.cast(new DefaultProcess()); + } + if (type.equals(Terminal.class)) { + return type.cast(new Terminal(){ + @Override + public boolean isTerminal(Output output) { + return PosixTerminalFunctions.isatty(output.ordinal()); } - } + }); + } + throw new UnsupportedOperationException(String.format("Cannot load unknown native integration %s.", + type.getName())); + } - @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. Errno is %d.", file, result.getErrno())); - } - return stat.mode; + 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. Errno is %d.", file, result.getErrno())); } - }); + } + + @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. Errno is %d.", file, result.getErrno())); + } + return stat.mode; + } + } + + private static class DefaultProcess implements Process { + @Override + public int getPid() throws NativeException { + return PosixProcessFunctions.getPid(); + } } } diff --git a/src/main/java/net/rubygrapefruit/platform/UnixFileMode.java b/src/main/java/net/rubygrapefruit/platform/PosixFile.java similarity index 59% rename from src/main/java/net/rubygrapefruit/platform/UnixFileMode.java rename to src/main/java/net/rubygrapefruit/platform/PosixFile.java index d6eb090..0fa97ef 100644 --- a/src/main/java/net/rubygrapefruit/platform/UnixFileMode.java +++ b/src/main/java/net/rubygrapefruit/platform/PosixFile.java @@ -2,7 +2,10 @@ package net.rubygrapefruit.platform; import java.io.File; -public interface UnixFileMode extends NativeIntegration { +/** + * Functions to query and modify a file's POSIX meta-data. + */ +public interface PosixFile extends NativeIntegration { void setMode(File path, int perms) throws NativeException; int getMode(File path) throws NativeException; diff --git a/src/main/java/net/rubygrapefruit/platform/Process.java b/src/main/java/net/rubygrapefruit/platform/Process.java new file mode 100644 index 0000000..3e5df0e --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/Process.java @@ -0,0 +1,8 @@ +package net.rubygrapefruit.platform; + +/** + * Functions to query and modify a process' meta-data + */ +public interface Process extends NativeIntegration { + int getPid() throws NativeException; +} diff --git a/src/main/java/net/rubygrapefruit/platform/Terminal.java b/src/main/java/net/rubygrapefruit/platform/Terminal.java new file mode 100644 index 0000000..bbdb17b --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/Terminal.java @@ -0,0 +1,7 @@ +package net.rubygrapefruit.platform; + +public interface Terminal extends NativeIntegration { + enum Output {Stdout, Stderr} + + boolean isTerminal(Output output); +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/PosixProcessFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/PosixProcessFunctions.java new file mode 100644 index 0000000..d429f2f --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/PosixProcessFunctions.java @@ -0,0 +1,5 @@ +package net.rubygrapefruit.platform.internal; + +public class PosixProcessFunctions { + public static native int getPid(); +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/PosixTerminalFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/PosixTerminalFunctions.java new file mode 100644 index 0000000..15d89e2 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/PosixTerminalFunctions.java @@ -0,0 +1,5 @@ +package net.rubygrapefruit.platform.internal; + +public class PosixTerminalFunctions { + public static native boolean isatty(int fildes); +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/UnixFileModeTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy similarity index 91% rename from src/test/groovy/net/rubygrapefruit/platform/UnixFileModeTest.groovy rename to src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy index 00912dd..3d3acaf 100644 --- a/src/test/groovy/net/rubygrapefruit/platform/UnixFileModeTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy @@ -4,9 +4,9 @@ import spock.lang.Specification import org.junit.Rule import org.junit.rules.TemporaryFolder -class UnixFileModeTest extends Specification { +class PosixFileTest extends Specification { @Rule TemporaryFolder tmpDir - final UnixFileMode file = Platform.get(UnixFileMode.class) + final PosixFile file = Platform.get(PosixFile.class) def "can set mode on a file"() { def testFile = tmpDir.newFile("test.txt") diff --git a/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy new file mode 100644 index 0000000..089e0a2 --- /dev/null +++ b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy @@ -0,0 +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 = Platform.get(Process.class) + + def "can get PID"() { + expect: + process.getPid() != 0 + } +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy new file mode 100644 index 0000000..f3231ad --- /dev/null +++ b/src/test/groovy/net/rubygrapefruit/platform/TerminalTest.groovy @@ -0,0 +1,16 @@ +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 Terminal terminal = Platform.get(Terminal.class) + + def "can check if attached to terminal"() { + expect: + !terminal.isTerminal(Terminal.Output.Stdout); + !terminal.isTerminal(Terminal.Output.Stderr); + } +}