commit 1d56f93e642ca38359edf718d82857363e4d0e3a Author: Adam Murdoch Date: Sun Jul 29 16:35:28 2012 +1000 initial version diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5a41352 --- /dev/null +++ b/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'java' +apply plugin: 'groovy' +apply plugin: 'cpp-lib' +apply plugin: 'idea' + +repositories { + mavenCentral() +} + +dependencies { + groovy 'org.codehaus.groovy:groovy:1.8.7' + testCompile 'org.spockframework:spock-core:0.6-groovy-1.8' +} + +def nativeHeadersDir = file("$buildDir/nativeHeaders") + +libraries { + main { + spec { + includes([nativeHeadersDir]) + includes(['/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/']) + } + } +} + +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.PosixFileFunctions' + } + } +} + +compileMain.dependsOn nativeHeaders +test.dependsOn compileMain + diff --git a/src/main/cpp/posixFileFunctions.c b/src/main/cpp/posixFileFunctions.c new file mode 100644 index 0000000..4e5d2a5 --- /dev/null +++ b/src/main/cpp/posixFileFunctions.c @@ -0,0 +1,33 @@ +#include "native.h" +#include +#include +#include + +void markFailed(JNIEnv *env, jobject result) { + jclass destClass = env->GetObjectClass(result); + jfieldID errnoField = env->GetFieldID(destClass, "errno", "I"); + env->SetIntField(result, errnoField, errno); +} + +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); + if (retval != 0) { + markFailed(env, 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); + if (retval != 0) { + markFailed(env, result); + return; + } + 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 diff --git a/src/main/java/net/rubygrapefruit/platform/NativeException.java b/src/main/java/net/rubygrapefruit/platform/NativeException.java new file mode 100644 index 0000000..71e55cb --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/NativeException.java @@ -0,0 +1,7 @@ +package net.rubygrapefruit.platform; + +public class NativeException extends RuntimeException { + public NativeException(String message) { + super(message); + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/NativeIntegration.java b/src/main/java/net/rubygrapefruit/platform/NativeIntegration.java new file mode 100644 index 0000000..1e98644 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/NativeIntegration.java @@ -0,0 +1,4 @@ +package net.rubygrapefruit.platform; + +public interface NativeIntegration { +} diff --git a/src/main/java/net/rubygrapefruit/platform/Platform.java b/src/main/java/net/rubygrapefruit/platform/Platform.java new file mode 100644 index 0000000..7e9148c --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/Platform.java @@ -0,0 +1,48 @@ +package net.rubygrapefruit.platform; + +import net.rubygrapefruit.platform.internal.FileStat; +import net.rubygrapefruit.platform.internal.FunctionResult; +import net.rubygrapefruit.platform.internal.PosixFileFunctions; + +import java.io.File; +import java.io.IOException; + +public class Platform { + private static final Object lock = new Object(); + private static boolean loaded; + + static T get(Class type) { + synchronized (lock) { + if (!loaded) { + System.setProperty("java.library.path", new File("build/binaries").getAbsolutePath()); + try { + System.load(new File("build/binaries/libnative-platform.dylib").getCanonicalPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + 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())); + } + } + + @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; + } + }); + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/UnixFileMode.java b/src/main/java/net/rubygrapefruit/platform/UnixFileMode.java new file mode 100644 index 0000000..d6eb090 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/UnixFileMode.java @@ -0,0 +1,9 @@ +package net.rubygrapefruit.platform; + +import java.io.File; + +public interface UnixFileMode 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/internal/FileStat.java b/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java new file mode 100644 index 0000000..013c661 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java @@ -0,0 +1,5 @@ +package net.rubygrapefruit.platform.internal; + +public class FileStat { + public int mode; +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/FunctionResult.java b/src/main/java/net/rubygrapefruit/platform/internal/FunctionResult.java new file mode 100644 index 0000000..71b6d00 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/FunctionResult.java @@ -0,0 +1,13 @@ +package net.rubygrapefruit.platform.internal; + +public class FunctionResult { + int errno; + + public boolean isFailed() { + return errno != 0; + } + + public int getErrno() { + return errno; + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/PosixFileFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/PosixFileFunctions.java new file mode 100644 index 0000000..360df30 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/PosixFileFunctions.java @@ -0,0 +1,7 @@ +package net.rubygrapefruit.platform.internal; + +public class PosixFileFunctions { + public static native void chmod(String file, int perms, FunctionResult result); + + public static native void stat(String file, FileStat stat, FunctionResult result); +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/UnixFileModeTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/UnixFileModeTest.groovy new file mode 100644 index 0000000..00912dd --- /dev/null +++ b/src/test/groovy/net/rubygrapefruit/platform/UnixFileModeTest.groovy @@ -0,0 +1,52 @@ +package net.rubygrapefruit.platform + +import spock.lang.Specification +import org.junit.Rule +import org.junit.rules.TemporaryFolder + +class UnixFileModeTest extends Specification { + @Rule TemporaryFolder tmpDir + final UnixFileMode file = Platform.get(UnixFileMode.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. Errno is 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. Errno is 2." + } +}