[libnative-platform-java] 01/08: Import upstream 0.10

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Fri Jun 5 15:50:45 UTC 2015


This is an automated email from the git hooks/post-receive script.

seamlik-guest pushed a commit to branch master
in repository libnative-platform-java.

commit 83a6f7dd2ffc06dd350810b1d02976c86d7ed0d8
Author: Kai-Chung Yan <seamlikok at gmail.com>
Date:   Wed Jun 3 21:19:06 2015 +0800

    Import upstream 0.10
---
 .gitignore                                         |   2 +
 build.gradle                                       | 250 +++++++---------
 gradle/wrapper/gradle-wrapper.jar                  | Bin 0 -> 46654 bytes
 gradle/wrapper/gradle-wrapper.properties           |   6 +
 readme.md                                          | 155 ++++++----
 src/{main => curses}/cpp/curses.cpp                |   3 +-
 src/main/cpp/{osx.cpp => freebsd.cpp}              |   4 +-
 src/main/cpp/posix.cpp                             |  30 +-
 src/main/cpp/win.cpp                               | 126 +++++++-
 .../platform/MissingRegistryEntryException.java    |  10 +
 .../net/rubygrapefruit/platform/PosixFile.java     |  36 +--
 .../platform/{PosixFile.java => PosixFiles.java}   |  12 +-
 .../net/rubygrapefruit/platform/SystemInfo.java    |  10 +-
 .../rubygrapefruit/platform/WindowsRegistry.java   |  35 +++
 ...efaultPosixFile.java => DefaultPosixFiles.java} |  25 +-
 .../platform/internal/DefaultSystemInfo.java       |  11 +-
 .../platform/internal/DefaultWindowsRegistry.java  |  57 ++++
 .../rubygrapefruit/platform/internal/FileStat.java |  13 +-
 .../platform/internal/LibraryDef.java              |  28 ++
 .../platform/internal/MutableSystemInfo.java       |  18 +-
 .../platform/internal/NativeLibraryLoader.java     |   4 +-
 .../platform/internal/NativeLibraryLocator.java    |  19 +-
 .../rubygrapefruit/platform/internal/Platform.java | 114 ++++---
 .../internal/jni/NativeLibraryFunctions.java       |   2 +-
 .../internal/jni/WindowsRegistryFunctions.java     |  16 +
 src/{main => shared}/cpp/generic.cpp               |   0
 .../FileStat.java => shared/cpp/generic_posix.cpp} |  22 +-
 .../SystemInfoTest.groovy => shared/cpp/osx.cpp}   |  39 +--
 .../cpp/unix_strings.cpp}                          |  20 +-
 src/{main => shared}/headers/generic.h             | 152 +++++-----
 ...{PosixFileTest.groovy => PosixFilesTest.groovy} | 332 +++++++++++++--------
 .../rubygrapefruit/platform/SystemInfoTest.groovy  |   3 +-
 .../platform/WindowsRegistryTest.groovy            |  64 ++++
 .../net/rubygrapefruit/platform/test/Main.java     |   7 +-
 34 files changed, 1071 insertions(+), 554 deletions(-)

diff --git a/.gitignore b/.gitignore
index 8e763b8..0df0b83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,7 @@
 *.ipr
 *.iws
 .gradle
+.DS_Store
 /build
+*/build
 /out
diff --git a/build.gradle b/build.gradle
index 0f8f2e3..58037df 100755
--- a/build.gradle
+++ b/build.gradle
@@ -16,7 +16,11 @@ allprojects {
     }
 
     group = 'net.rubygrapefruit'
-    version = '0.3-rc-2'
+    version = '0.10'
+
+    if (!project.hasProperty('release')) {
+        version = "${version}-dev"
+    }
 
     sourceCompatibility = 1.5
     targetCompatibility = 1.5
@@ -60,147 +64,97 @@ task nativeHeaders {
             args 'net.rubygrapefruit.platform.internal.jni.TerminfoFunctions'
             args 'net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions'
             args 'net.rubygrapefruit.platform.internal.jni.WindowsHandleFunctions'
+            args 'net.rubygrapefruit.platform.internal.jni.WindowsRegistryFunctions'
         }
     }
 }
 
-cpp {
-    sourceSets {
-        main {
-            source.exclude 'curses.cpp'
+model {
+    platforms {
+        osx_i386 {
+            architecture "i386"
+            operatingSystem "osx"
         }
-        curses {
-            source.srcDirs = ['src/main/cpp']
-            source.include 'curses.cpp'
-            source.include 'generic.cpp'
-            source.include 'generic_posix.cpp'
+        osx_amd64 {
+            architecture "amd64"
+            operatingSystem "osx"
         }
-    }
-}
-
-def variants = [:]
-
-libraries {
-    if (org.gradle.internal.os.OperatingSystem.current().macOsX) {
-        all {
-            spec {
-                includes(files('/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/'))
-                args("-arch", "x86_64", "-arch", "i386")
-            }
+        linux_i386 {
+            architecture "i386"
+            operatingSystem "linux"
         }
-        universal {
-            sourceSets << cpp.sourceSets.main
-            spec {
-                baseName = 'native-platform-osx-universal'
-                args("-o", outputFile)
-            }
+        linux_amd64 {
+            architecture "amd64"
+            operatingSystem "linux"
         }
-        cursesUniversal {
-            sourceSets << cpp.sourceSets.curses
-            spec {
-                baseName = 'native-platform-curses-osx-universal'
-                args("-lcurses")
-                args("-o", outputFile)
-            }
+        windows_i386 {
+            architecture "i386"
+            operatingSystem "windows"
         }
-        variants['osx-universal'] = [universal, cursesUniversal]
-    } else if (org.gradle.internal.os.OperatingSystem.current().windows) {
-        all {
-            spec {
-                includes(files("${org.gradle.internal.jvm.Jvm.current().javaHome}/include"))
-                includes(files("${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32"))
-                args("/DWIN32")
-            }
+        windows_amd64 {
+            architecture "amd64"
+            operatingSystem "windows"
         }
-
-        def out = new ByteArrayOutputStream()
-        exec {
-            commandLine "cl.exe", "/?"
-            errorOutput = out
-            standardOutput = new ByteArrayOutputStream()
+        freebsd_i386 {
+            architecture "i386"
+            operatingSystem "freebsd"
         }
-        def header = out.toString().readLines().head()
-        if (header.endsWith("for 80x86") || header.endsWith("for x86")) {
-            i386 {
-                sourceSets << cpp.sourceSets.main
-                spec {
-                    baseName = 'native-platform-windows-i386'
-                }
-            }
-            variants['windows-i386'] = [i386]
-        } else if (header.endsWith("for x64")) {
-            amd64 {
-                sourceSets << cpp.sourceSets.main
-                spec {
-                    baseName = 'native-platform-windows-amd64'
-                }
-            }
-            variants['windows-amd64'] = [amd64]
-        } else {
-            throw new RuntimeException("Cannot determine compiler's target architecture")
+        freebsd_amd64 {
+            architecture "amd64"
+            operatingSystem "freebsd"
         }
+    }
+}
 
-    } else if (org.gradle.internal.os.OperatingSystem.current().linux) {
-        all {
-            spec {
-                includes(files("${org.gradle.internal.jvm.Jvm.current().javaHome}/include"))
-                includes(files("${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"))
-            }
-        }
-        if (System.getProperty('os.arch') == 'i386' || project.hasProperty('multiarch')) {
-            i386 {
-                sourceSets << cpp.sourceSets.main
-                spec {
-                    baseName = 'native-platform-linux-i386'
-                    args("-m32")
-                }
-            }
-            cursesI386 {
-                sourceSets << cpp.sourceSets.curses
-                spec {
-                    baseName = 'native-platform-curses-linux-i386'
-                    args("-m32", "-lcurses")
-                }
-            }
-            variants['linux-i386'] = [i386, cursesI386]
+libraries {
+    nativePlatform {
+        baseName 'native-platform'
+    }
+    nativePlatformCurses {
+        baseName 'native-platform-curses'
+        targetPlatforms "osx_i386", "osx_amd64", "linux_i386", "linux_amd64", "freebsd_i386", "freebsd_amd64"
+        binaries.all {
+            linker.args "-lcurses"
         }
-        if (System.getProperty('os.arch') == 'amd64' || project.hasProperty('multiarch')) {
-            amd64 {
-                sourceSets << cpp.sourceSets.main
-                spec {
-                    baseName = 'native-platform-linux-amd64'
-                    args("-m64")
-                }
+    }
+
+    all {
+        binaries.all {
+            if (targetPlatform.operatingSystem.macOsX) {
+                cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include"
+                cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include/darwin"
+                linker.args '-mmacosx-version-min=10.4'
+            } else if (targetPlatform.operatingSystem.linux) {
+                cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include"
+                cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"
+            } else if (targetPlatform.operatingSystem.windows) {
+                cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include"
+                cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32"
+                linker.args "Shlwapi.lib", "Advapi32.lib"
+            } else if (targetPlatform.operatingSystem.freeBSD) {
+                cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include"
+                cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include/freebsd"
             }
-            cursesAmd64 {
-                sourceSets << cpp.sourceSets.curses
-                spec {
-                    baseName = 'native-platform-curses-linux-amd64'
-                    args("-m64", "-lcurses")
-                }
+            cppCompiler.args "-I${nativeHeadersDir}"
+            tasks.withType(CppCompile) { task ->
+                task.dependsOn nativeHeaders
             }
-            variants['linux-amd64'] = [amd64, cursesAmd64]
         }
-    } else {
-        baseName = "native-platform-solaris"
-        main {
-            sourceSets << cpp.sourceSets.main
-            sourceSets << cpp.sourceSets.curses
-            spec {
-                includes(files("${org.gradle.internal.jvm.Jvm.current().javaHome}/include"))
-                includes(files("${org.gradle.internal.jvm.Jvm.current().javaHome}/include/solaris"))
-                args("-DSOLARIS", "-lcurses")
-            }
+    }
+}
+
+sources {
+    nativePlatform {
+        cpp {
+            source.srcDirs = ['src/shared/cpp', 'src/main/cpp']
+            exportedHeaders.srcDirs = ['src/shared/headers']
         }
-        variants['solaris'] = [main]
     }
-    all {
-        spec {
-            includes(files(nativeHeadersDir, 'src/main/headers'))
+    nativePlatformCurses {
+        cpp {
+            source.srcDirs = ['src/shared/cpp', 'src/curses/cpp']
+            exportedHeaders.srcDirs = ['src/shared/headers']
         }
-        def task = tasks["compile${spec.binary.name.capitalize()}"]
-        task.dependsOn nativeHeaders
-        test.dependsOn spec
     }
 }
 
@@ -210,23 +164,40 @@ configurations {
 
 def deployer = uploadJni.repositories.mavenDeployer
 
-variants.each { variant, libs ->
-    def variantName = GUtil.toCamelCase(variant)
-    def nativeJar = task("nativeJar${variantName}", type: Jar) {
-        from libs.collect { tasks["compile${it.name.capitalize()}"] }
-        baseName = "native-platform-$variant"
+binaries.withType(SharedLibraryBinary) { binary ->
+    if (!buildable) {
+        return
     }
-    artifacts {
-        jni nativeJar
-        runtime nativeJar
+    def arch = System.properties['os.arch']
+    if (targetPlatform.operatingSystem.name in ['linux', 'freebsd'] && targetPlatform.architecture.name != arch) {
+        // Native plugins don't detect whether multilib support is available or not. Assume not for now
+        return
     }
-    def jniPom = deployer.addFilter(variant) { artifact, file ->
-        return file == nativeJar.archivePath
+
+    def variantName = "${targetPlatform.operatingSystem.name}-${targetPlatform.architecture.name}"
+    def taskName = "jar-${variantName}"
+    def nativeJar = project.tasks.findByName(taskName)
+    if (nativeJar == null) {
+        nativeJar = project.task(taskName, type: Jar) {
+            baseName = "native-platform-$variantName"
+        }
+        artifacts {
+            jni nativeJar
+            runtime nativeJar
+        }
+        def jniPom = deployer.addFilter(variantName) { artifact, file ->
+            return file == nativeJar.archivePath
+        }
+        jniPom.groupId = project.group
+        jniPom.artifactId = nativeJar.baseName
+        jniPom.version = project.version
+        jniPom.scopeMappings.mappings.clear()
     }
-    jniPom.groupId = project.group
-    jniPom.artifactId = nativeJar.baseName
-    jniPom.version = project.version
-    jniPom.scopeMappings.mappings.clear()
+
+    def builderTask = binary.tasks.builder
+    nativeJar.into("net/rubygrapefruit/platform/$variantName") { from builderTask.outputFile }
+    nativeJar.dependsOn builderTask
+    test.dependsOn nativeJar
 }
 
 javadoc {
@@ -258,14 +229,11 @@ mainPom.scopeMappings.mappings.clear()
 mainPom.withXml { provider ->
     def node = provider.asNode()
     def deps = node.appendNode('dependencies')
-    ['osx-universal', 'linux-amd64', 'linux-i386', 'windows-amd64', 'windows-i386'].each { platform ->
+    ['osx-i386', 'osx-amd64', 'linux-amd64', 'linux-i386',
+            'windows-amd64', 'windows-i386', 'freebsd-i386', 'freebsd-amd64'].each { platform ->
         def dep = deps.appendNode('dependency')
         dep.appendNode('groupId', project.group)
         dep.appendNode('artifactId', "native-platform-${platform}")
         dep.appendNode('version', project.version)
     }
 }
-
-task wrapper(type: Wrapper) {
-    gradleVersion = "1.3-20120907220018+0000"
-}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..55c2bb6
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..24afe54
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Sep 08 10:15:39 EST 2012
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-2.0-20140401014412+0000-all.zip
diff --git a/readme.md b/readme.md
index 16a536f..d058683 100755
--- a/readme.md
+++ b/readme.md
@@ -1,7 +1,8 @@
 
 # Native-platform: Java bindings for various native APIs
 
-A collection of cross-platform Java APIs for various native APIs. Supports OS X, Linux, Solaris and Windows.
+A collection of cross-platform Java APIs for various native APIs. Currently supports OS X, Linux, Windows and FreeBSD
+on Intel architectures.
 
 These APIs support Java 5 and later. Some of these APIs overlap with APIs available in later Java versions.
 
@@ -33,20 +34,26 @@ These bindings work for both the UNIX terminal and the Windows console:
 
 * Get and set UNIX file mode.
 * Create and read symbolic links.
-* List the available file systems on the machine
+* Determine file type.
+* List the available file systems on the machine.
 * Query file system mount point.
 * Query file system type.
 * Query file system device name.
 * Query whether a file system is local or remote.
 
+### Windows
+
+* Query registry value.
+* Query the subkeys and values of a registry key.
+
 ## Supported platforms
 
-Currently ported to OS X, Linux and Windows. Support for Solaris and FreeBSD is a work in progress. Tested on:
+Currently ported to OS X, Linux, FreeBSD and Windows. Support for Solaris is a work in progress. Tested on:
 
-* OS X 10.7.4, 10.8 (x86_64), 10.6.7 (i386)
-* Ubunutu 12.04 (amd64), 8.04.4 (i386, amd64)
-* Windows 7 (x64), XP (x86)
-* Solaris 11 (x86)
+* OS X 10.9.1 (x86_64), 10.6.7 (i386)
+* Ubunutu 13.10 (amd64), 12.10 (amd64), 8.04.4 (i386, amd64)
+* FreeBSD 8.3 (amd64, i386), 10.0 (amd64, i386)
+* Windows 8.1 (x64), 7 (x64), XP (x86, x64)
 
 ## Using
 
@@ -58,7 +65,7 @@ this:
     }
 
     dependencies {
-        compile "net.rubygrapefruit:native-platform:0.3"
+        compile "net.rubygrapefruit:native-platform:0.9"
     }
 
 You can also download [here](http://repo.gradle.org/gradle/libs-releases-local/net/rubygrapefruit/)
@@ -82,13 +89,45 @@ Some sample code to use the terminal:
 
 ## Changes
 
+### 0.10
+
+* Fixes for broken 0.9 release.
+
+### 0.9
+
+* Fixes for non-ascii file names on OS X when running under the Apple JVM.
+
+### 0.8
+
+* Ported to FreeBSD. Thanks to [Zsolt Kúti](https://github.com/tinca).
+
+### 0.7
+
+* Some fixes for a broken 0.6 release.
+
+### 0.6
+
+* Some fixes for Windows 7 and OS X 10.6.
+
+You should avoid using this release, and use 0.7 or later instead.
+
+### 0.5
+
+* Query the available values of a Windows registry key. Thanks to [Michael Putters](https://github.com/mputters).
+
+### 0.4
+
+* Get file type.
+* Query Windows registry value and subkeys.
+* Fixes to work on 64-bit Windows XP.
+
 ### 0.3
 
 * Get and set process working directory.
 * Get and set process environment variables.
 * Launch processes.
 * Fixed character set issue on Linux and Mac OS X.
-* Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to Rene Gr�schke.
+* Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to [Rene Gr�schke](https://github.com/breskeby).
 
 ### 0.2
 
@@ -103,11 +142,11 @@ Some sample code to use the terminal:
 
 ## Building
 
-You will need to use the Gradle wrapper. Just run `gradlew` in the root directory.
+You will need to use the Gradle wrapper. Just run `gradlew` in the root of the source repo.
 
 ### Ubuntu
 
-The g++ compiler is required to build the native library. You will need the `g++` package for this. Usually this is already installed.
+The g++ compiler is required to build the native library. You will need to install the `g++` package for this.
 
 You need to install the `libncurses5-dev` package to pick up the ncurses header files. Also worth installing the `ncurses-doc` package too.
 
@@ -120,15 +159,13 @@ You need to install the `gcc-multilib` and `g++-multilib` packages to pick up i3
 
 You need to install the `lib32ncurses5-dev` package to pick up the ncurses i386 version.
 
-To build, include `-Pmultiarch` on the command-line.
-
 ### Windows
 
-You need to install Visual studio, and build from a Visual studio command prompt.
+You need to install Visual studio 2010 or later, plus the Windows SDK to allow you to build both x86 and x64 binaries.
 
 ### OS X
 
-The g++ compiler is required to build the native library. You will need to install the XCode tools for this.
+The g++ compiler is required to build the native library. You will need to install the XCode command-line tools for this.
 
 ### Solaris
 
@@ -137,27 +174,24 @@ For Solaris 11, you need to install the `development/gcc-45` and `system/header`
 ## Running
 
 Run `gradle installApp` to install the test application into `test-app/build/install/native-platform-test`. Or
-`gradle distZip` to create an application distribtion in `test-app/build/distributions/native-platform-test-$version.zip`.
+`gradle distZip` to create an application distribution in `test-app/build/distributions/native-platform-test-$version.zip`.
 
 You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application.
 
 # Releasing
 
 1. Check the version number in `build.gradle`.
-2. Create a tag and push.
-3. Build each variant:
+2. Check that the native interface version has been incremented since last release, when changes have been made to native code.
+3. Create a tag.
+4. Build each variant:
     1. Checkout tag.
-    2. `./gradlew clean :test :uploadJni -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
-    * OS X universal
-    * Linux i386, using Ubunutu 8.04
-    * Linux amd64, using Ubunutu 8.04
-    * Windows x86, using VC++ 2010
-    * Windows x64
-4. Build Java library and test app:
+    2. `./gradlew clean :test :uploadJni -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`.
+5. Build Java library and test app:
     1. Checkout tag.
     2. `./gradlew clean :test :uploadArchives testApp:uploadArchives -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
-5. Checkout master
-6. Increment version number in `build.gradle` and this readme.
+6. Checkout master
+7. Increment version number in `build.gradle` and this readme.
+8. Push tag and changes.
 
 ## Testing
 
@@ -169,6 +203,10 @@ You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application.
 
 ### Fixes
 
+* Linux: Fix detection of multiarch support
+* FreeBSD: Fix detection of multiarch support
+* All: `Process.getPid()` should return a long
+* All: fail subsequent calls to `Native.get()` when `Native.initialize()` fails.
 * Posix: allow terminal to be detected when ncurses cannot be loaded
 * Windows: fix detection of shared drive under VMWare fusion and Windows XP
 * Windows: restore std handles after launching child process
@@ -176,38 +214,51 @@ You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application.
 * Solaris: fix unicode file name handling.
 * Solaris: fail for unsupported architecture.
 * Solaris: build 32 bit and 64 bit libraries.
-* Freebsd: finish port.
-* Freebsd: fail for unsupported architecture.
-* Freebsd: build 32 bit and 64 bit libraries.
 
 ### Improvements
 
-* Use wchar_to_java() for windows system and file system info.
-* Test network file systems on Mac, Linux, Windows
-* Test mount points on Windows
-* Cache class, method and field lookups
-* Change readLink() implementation so that it does not need to NULL terminate the encoded content
-* Don't use NewStringUTF() anywhere
-* Use iconv() to convert from C char string to UTF-16 when converting from C char string to Java String.
-* Support for cygwin terminal
-* Use TERM=xtermc instead of TERM=xterm on Solaris.
-* Add diagnostics for terminal.
-* Version each native interface separately.
-* String names for errno values.
-* Split into multiple projects.
-* Convert to c.
-* Use fully decomposed form for unicode file names on hfs+ filesystems.
-* Extend FileSystem to deal with removable media.
-* Add a method to Terminal that returns a PrintStream that can be used to write to the terminal, regardless of what
-  System.out/System.err point to.
-* Add a Terminal implementation that uses ANSI control codes. Use this on UNIX platforms when TERM != 'dumb' and
+* All: fall back to WrapperProcessLauncher + DefaultProcessLauncher for all platforms, regardless of whether a
+  native integration is available or not.
+* All: change the terminal API to handle the fact that stdout/stderr/stdin can all be attached to the same or to
+  different terminals.
+* All: have `Terminal` extend `Appendable` and `Flushable`
+* All: add a method to `Terminal` that returns a `PrintStream` that can be used to write to the terminal, regardless of what
+  `System.out` or `System.err` point to.
+* Windows: use `wchar_to_java()` for system and file system info.
+* All: test network file systems
+* Windows: test mount points
+* All: cache class, method and field lookups
+* Unix: change `readLink()` implementation so that it does not need to NULL terminate the encoded content
+* All: don't use `NewStringUTF()` anywhere
+* Mac: change `java_to_char()` to convert java string directly to utf-8 char string.
+* Mac: change `char_to_java()` to assume utf-8 encoding and convert directly to java string.
+* Linux: change `char_to_java()` to use `iconv()` to convert from C char string to UTF-16 then to java string.
+* Windows: support for cygwin terminal
+* Solaris: use `TERM=xtermc` instead of `TERM=xterm`.
+* All: add diagnostics for terminal.
+* All: version each native interface separately.
+* All: string names for errno values.
+* All: split into multiple projects.
+* Mac: use fully decomposed form for unicode file names on hfs+ filesystems.
+* All: extend FileSystem to deal with removable media.
+* Unix: add a Terminal implementation that uses ANSI control codes. Use this when TERM != 'dumb' and
   libncurses cannot be loaded.
-* Add a method to Terminal that indicates whether the cursor wraps to the next line when a character is written to the
-  rightmost character position.
-* Check for null parameters.
+* All: add a method to Terminal that indicates whether the cursor wraps to the next line when a character is written
+  to the rightmost character position.
+* All: check for null parameters.
 
 ### Ideas
 
+* Publish to bintray.
+* Expose meta-data about an NTFS volume:
+    * Does the volume support 8.3 file names: Query [FILE_FS_PERSISTENT_VOLUME_INFORMATION](http://msdn.microsoft.com/en-us/library/windows/hardware/ff540280.aspx)
+      using [DeviceIoControl()](http://msdn.microsoft.com/en-us/library/aa363216.aspx)
+* Expose native desktop notification services:
+    * OS X message center
+    * Growl
+    * Snarl
+    * dnotify
+* Locate various system directories (eg program files on windows).
 * Expose platform-specific HTTP proxy configuration. Query registry on windows to determine IE settings.
 * Expose native named semaphores, mutexes and condition variables (CreateMutex, CreateSemaphore, CreateEvent, semget, sem_open, etc).
 * Expose information about network interfaces.
diff --git a/src/main/cpp/curses.cpp b/src/curses/cpp/curses.cpp
similarity index 99%
rename from src/main/cpp/curses.cpp
rename to src/curses/cpp/curses.cpp
index 10633eb..f5e8352 100644
--- a/src/main/cpp/curses.cpp
+++ b/src/curses/cpp/curses.cpp
@@ -17,7 +17,7 @@
 /*
  * Curses functions
  */
-#ifndef WIN32
+#ifndef _WIN32
 
 #include "native.h"
 #include "generic.h"
@@ -47,6 +47,7 @@ const char* terminal_capabilities[9];
 
 int write_to_terminal(TERMINAL_CHAR_TYPE ch) {
     write(current_terminal, &ch, 1);
+    return ch;
 }
 
 const char* getcap(const char* capability) {
diff --git a/src/main/cpp/osx.cpp b/src/main/cpp/freebsd.cpp
similarity index 95%
rename from src/main/cpp/osx.cpp
rename to src/main/cpp/freebsd.cpp
index 507f8db..63a8fb7 100644
--- a/src/main/cpp/osx.cpp
+++ b/src/main/cpp/freebsd.cpp
@@ -15,9 +15,9 @@
  */
 
 /*
- * OS X specific functions.
+ * FreeBSD (including OS X) specific functions.
  */
-#ifdef __APPLE__
+#if defined(__APPLE__) || defined(__FreeBSD__)
 
 #include "native.h"
 #include "generic.h"
diff --git a/src/main/cpp/posix.cpp b/src/main/cpp/posix.cpp
index 94e31c3..de106ae 100755
--- a/src/main/cpp/posix.cpp
+++ b/src/main/cpp/posix.cpp
@@ -17,7 +17,7 @@
 /*
  * POSIX platform functions.
  */
-#ifndef WIN32
+#ifndef _WIN32
 
 #include "native.h"
 #include "generic.h"
@@ -71,15 +71,37 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixFileFunctions_stat(JNIEnv *en
     if (pathStr == NULL) {
         return;
     }
-    int retval = stat(pathStr, &fileInfo);
+    int retval = lstat(pathStr, &fileInfo);
     free(pathStr);
-    if (retval != 0) {
+    if (retval != 0 && errno != ENOENT) {
         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);
+    jfieldID typeField = env->GetFieldID(destClass, "type", "I");
+
+    if (retval != 0) {
+        env->SetIntField(dest, typeField, 4);
+    } else {
+        env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode);
+        int type;
+        switch (fileInfo.st_mode & S_IFMT) {
+            case S_IFREG:
+                type = 0;
+                break;
+            case S_IFDIR:
+                type = 1;
+                break;
+            case S_IFLNK:
+                type = 2;
+                break;
+            default:
+                type= 3;
+        }
+        env->SetIntField(dest, typeField, type);
+    }
 }
 
 JNIEXPORT void JNICALL
diff --git a/src/main/cpp/win.cpp b/src/main/cpp/win.cpp
index 586c62f..94cb671 100755
--- a/src/main/cpp/win.cpp
+++ b/src/main/cpp/win.cpp
@@ -14,11 +14,12 @@
  *    limitations under the License.
  */
 
-#ifdef WIN32
+#ifdef _WIN32
 
 #include "native.h"
 #include "generic.h"
 #include <windows.h>
+#include <Shlwapi.h>
 #include <wchar.h>
 
 /*
@@ -323,7 +324,6 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEn
 JNIEXPORT void JNICALL
 Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(JNIEnv *env, jclass target, jobject result) {
     current_attributes &= ~FOREGROUND_INTENSITY;
-    SetConsoleTextAttribute(current_console, current_attributes);
     if (!SetConsoleTextAttribute(current_console, current_attributes)) {
         mark_failed_with_errno(env, "could not set text attributes", result);
     }
@@ -366,7 +366,6 @@ Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_foreground
             break;
     }
 
-    SetConsoleTextAttribute(current_console, current_attributes);
     if (!SetConsoleTextAttribute(current_console, current_attributes)) {
         mark_failed_with_errno(env, "could not set text attributes", result);
     }
@@ -462,7 +461,9 @@ void uninheritStream(JNIEnv *env, DWORD stdInputHandle, jobject result) {
     }
     boolean ok = SetHandleInformation(streamHandle, HANDLE_FLAG_INHERIT, 0);
     if (!ok) {
-        mark_failed_with_errno(env, "could not change std handle", result);
+        if (GetLastError() != ERROR_INVALID_PARAMETER && GetLastError() != ERROR_INVALID_HANDLE) {
+            mark_failed_with_errno(env, "could not change std handle", result);
+        }
     }
 }
 
@@ -477,4 +478,121 @@ JNIEXPORT void JNICALL
 Java_net_rubygrapefruit_platform_internal_jni_WindowsHandleFunctions_restoreStandardHandles(JNIEnv *env, jclass target, jobject result) {
 }
 
+HKEY get_key_from_ordinal(jint keyNum) {
+    return keyNum == 0 ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+}
+
+JNIEXPORT jstring JNICALL
+Java_net_rubygrapefruit_platform_internal_jni_WindowsRegistryFunctions_getStringValue(JNIEnv *env, jclass target, jint keyNum, jstring subkey, jstring valueName, jobject result) {
+    HKEY key = get_key_from_ordinal(keyNum);
+    wchar_t* subkeyStr = java_to_wchar(env, subkey, result);
+    wchar_t* valueNameStr = java_to_wchar(env, valueName, result);
+    DWORD size = 0;
+
+    LONG retval = SHRegGetValueW(key, subkeyStr, valueNameStr, SRRF_RT_REG_SZ, NULL, NULL, &size);
+    if (retval != ERROR_SUCCESS) {
+        free(subkeyStr);
+        free(valueNameStr);
+        if (retval != ERROR_FILE_NOT_FOUND) {
+            mark_failed_with_code(env, "could not determine size of registry value", retval, NULL, result);
+        }
+        return NULL;
+    }
+
+    wchar_t* value = (wchar_t*)malloc(sizeof(wchar_t) * (size+1));
+    retval = SHRegGetValueW(key, subkeyStr, valueNameStr, SRRF_RT_REG_SZ, NULL, value, &size);
+    free(subkeyStr);
+    free(valueNameStr);
+    if (retval != ERROR_SUCCESS) {
+        free(value);
+        mark_failed_with_code(env, "could not get registry value", retval, NULL, result);
+        return NULL;
+    }
+
+    jstring jvalue = wchar_to_java(env, value, wcslen(value), result);
+    free(value);
+
+    return jvalue;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_net_rubygrapefruit_platform_internal_jni_WindowsRegistryFunctions_getSubkeys(JNIEnv *env, jclass target, jint keyNum, jstring subkey, jobject subkeys, jobject result) {
+    wchar_t* subkeyStr = java_to_wchar(env, subkey, result);
+    jclass subkeys_class = env->GetObjectClass(subkeys);
+    jmethodID method = env->GetMethodID(subkeys_class, "add", "(Ljava/lang/Object;)Z");
+
+    HKEY key;
+    LONG retval = RegOpenKeyExW(get_key_from_ordinal(keyNum), subkeyStr, 0, KEY_READ, &key);
+    if (retval != ERROR_SUCCESS) {
+        free(subkeyStr);
+        if (retval != ERROR_FILE_NOT_FOUND) {
+            mark_failed_with_code(env, "could open registry key", retval, NULL, result);
+        }
+        return false;
+    }
+
+    DWORD subkeyCount;
+    DWORD maxSubkeyLen;
+    retval = RegQueryInfoKeyW(key, NULL, NULL, NULL, &subkeyCount, &maxSubkeyLen, NULL, NULL, NULL, NULL, NULL, NULL);
+    if (retval != ERROR_SUCCESS) {
+        mark_failed_with_code(env, "could query registry key", retval, NULL, result);
+    } else {
+        wchar_t* keyNameStr = (wchar_t*)malloc(sizeof(wchar_t) * (maxSubkeyLen+1));
+        for (int i = 0; i < subkeyCount; i++) {
+            DWORD keyNameLen = maxSubkeyLen + 1;
+            retval = RegEnumKeyExW(key, i, keyNameStr, &keyNameLen, NULL, NULL, NULL, NULL);
+            if (retval != ERROR_SUCCESS) {
+                mark_failed_with_code(env, "could enumerate registry subkey", retval, NULL, result);
+                break;
+            }
+            env->CallVoidMethod(subkeys, method, wchar_to_java(env, keyNameStr, wcslen(keyNameStr), result));
+        }
+        free(keyNameStr);
+    }
+
+    RegCloseKey(key);
+    free(subkeyStr);
+    return true;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_net_rubygrapefruit_platform_internal_jni_WindowsRegistryFunctions_getValueNames(JNIEnv *env, jclass target, jint keyNum, jstring subkey, jobject names, jobject result) {
+    wchar_t* subkeyStr = java_to_wchar(env, subkey, result);
+    jclass names_class = env->GetObjectClass(names);
+    jmethodID method = env->GetMethodID(names_class, "add", "(Ljava/lang/Object;)Z");
+
+    HKEY key;
+    LONG retval = RegOpenKeyExW(get_key_from_ordinal(keyNum), subkeyStr, 0, KEY_READ, &key);
+    if (retval != ERROR_SUCCESS) {
+        free(subkeyStr);
+        if (retval != ERROR_FILE_NOT_FOUND) {
+            mark_failed_with_code(env, "could open registry key", retval, NULL, result);
+        }
+        return false;
+    }
+
+    DWORD valueCount;
+    DWORD maxValueNameLen;
+    retval = RegQueryInfoKeyW(key, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount, &maxValueNameLen, NULL, NULL, NULL);
+    if (retval != ERROR_SUCCESS) {
+        mark_failed_with_code(env, "could query registry key", retval, NULL, result);
+    } else {
+        wchar_t* valueNameStr = (wchar_t*)malloc(sizeof(wchar_t) * (maxValueNameLen+1));
+        for (int i = 0; i < valueCount; i++) {
+            DWORD valueNameLen = maxValueNameLen + 1;
+            retval = RegEnumValueW(key, i, valueNameStr, &valueNameLen, NULL, NULL, NULL, NULL);
+            if (retval != ERROR_SUCCESS) {
+                mark_failed_with_code(env, "could enumerate registry value name", retval, NULL, result);
+                break;
+            }
+            env->CallVoidMethod(names, method, wchar_to_java(env, valueNameStr, wcslen(valueNameStr), result));
+        }
+        free(valueNameStr);
+    }
+
+    RegCloseKey(key);
+    free(subkeyStr);
+    return true;
+}
+
 #endif
diff --git a/src/main/java/net/rubygrapefruit/platform/MissingRegistryEntryException.java b/src/main/java/net/rubygrapefruit/platform/MissingRegistryEntryException.java
new file mode 100644
index 0000000..4d79688
--- /dev/null
+++ b/src/main/java/net/rubygrapefruit/platform/MissingRegistryEntryException.java
@@ -0,0 +1,10 @@
+package net.rubygrapefruit.platform;
+
+/**
+ * Thrown when attempting to query an unknown registry key or value.
+ */
+public class MissingRegistryEntryException extends NativeException {
+    public MissingRegistryEntryException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/net/rubygrapefruit/platform/PosixFile.java b/src/main/java/net/rubygrapefruit/platform/PosixFile.java
index 4141bcd..0884949 100644
--- a/src/main/java/net/rubygrapefruit/platform/PosixFile.java
+++ b/src/main/java/net/rubygrapefruit/platform/PosixFile.java
@@ -16,42 +16,20 @@
 
 package net.rubygrapefruit.platform;
 
-import java.io.File;
-
 /**
- * Functions to query and modify a file's POSIX meta-data.
+ * Provides some information about a file. This is a snapshot and does not change.
  */
 @ThreadSafe
-public interface PosixFile extends NativeIntegration {
-    /**
-     * Sets the mode for the given file.
-     *
-     * @throws NativeException On failure.
-     */
-    @ThreadSafe
-    void setMode(File path, int perms) throws NativeException;
-
-    /**
-     * Gets the mode for the given file.
-     *
-     * @throws NativeException On failure.
-     */
-    @ThreadSafe
-    int getMode(File path) throws NativeException;
+public interface PosixFile {
+    enum Type {File, Directory, Symlink, Other, Missing}
 
     /**
-     * Creates a symbolic link.
-     *
-     * @throws NativeException On failure.
+     * Returns the type of this file.
      */
-    @ThreadSafe
-    void symlink(File link, String contents) throws NativeException;
+    Type getType();
 
     /**
-     * Reads the contents of a symbolic link.
-     *
-     * @throws NativeException On failure.
+     * Returns the mode of this file.
      */
-    @ThreadSafe
-    String readLink(File link) throws NativeException;
+    int getMode();
 }
diff --git a/src/main/java/net/rubygrapefruit/platform/PosixFile.java b/src/main/java/net/rubygrapefruit/platform/PosixFiles.java
similarity index 82%
copy from src/main/java/net/rubygrapefruit/platform/PosixFile.java
copy to src/main/java/net/rubygrapefruit/platform/PosixFiles.java
index 4141bcd..39b1db1 100644
--- a/src/main/java/net/rubygrapefruit/platform/PosixFile.java
+++ b/src/main/java/net/rubygrapefruit/platform/PosixFiles.java
@@ -22,7 +22,7 @@ import java.io.File;
  * Functions to query and modify a file's POSIX meta-data.
  */
 @ThreadSafe
-public interface PosixFile extends NativeIntegration {
+public interface PosixFiles extends NativeIntegration {
     /**
      * Sets the mode for the given file.
      *
@@ -40,7 +40,7 @@ public interface PosixFile extends NativeIntegration {
     int getMode(File path) throws NativeException;
 
     /**
-     * Creates a symbolic link.
+     * Creates a symbolic link with given contents.
      *
      * @throws NativeException On failure.
      */
@@ -54,4 +54,12 @@ public interface PosixFile extends NativeIntegration {
      */
     @ThreadSafe
     String readLink(File link) throws NativeException;
+
+    /**
+     * Returns basic information about the given file.
+     *
+     * @throws NativeException On failure.
+     */
+    @ThreadSafe
+    PosixFile stat(File path) throws NativeException;
 }
diff --git a/src/main/java/net/rubygrapefruit/platform/SystemInfo.java b/src/main/java/net/rubygrapefruit/platform/SystemInfo.java
index 59d1a70..393ecc5 100644
--- a/src/main/java/net/rubygrapefruit/platform/SystemInfo.java
+++ b/src/main/java/net/rubygrapefruit/platform/SystemInfo.java
@@ -21,6 +21,8 @@ package net.rubygrapefruit.platform;
  */
 @ThreadSafe
 public interface SystemInfo extends NativeIntegration {
+    enum Architecture { i386, amd64 }
+
     /**
      * Returns the name of the kernel for the current operating system.
      */
@@ -34,8 +36,14 @@ public interface SystemInfo extends NativeIntegration {
     String getKernelVersion();
 
     /**
+     * Returns the machine architecture name, as reported by the operating system.
+     */
+    @ThreadSafe
+    String getArchitectureName();
+
+    /**
      * Returns the machine architecture, as reported by the operating system.
      */
     @ThreadSafe
-    String getMachineArchitecture();
+    Architecture getArchitecture();
 }
diff --git a/src/main/java/net/rubygrapefruit/platform/WindowsRegistry.java b/src/main/java/net/rubygrapefruit/platform/WindowsRegistry.java
new file mode 100644
index 0000000..13be0fd
--- /dev/null
+++ b/src/main/java/net/rubygrapefruit/platform/WindowsRegistry.java
@@ -0,0 +1,35 @@
+package net.rubygrapefruit.platform;
+
+import java.util.List;
+
+ at ThreadSafe
+public interface WindowsRegistry extends NativeIntegration {
+    public enum Key {
+        HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER
+    }
+
+    /**
+     * Returns a registry key value as a String.
+     *
+     * @throws NativeException On failure.
+     * @throws MissingRegistryEntryException When the requested key or value does not exist.
+     */
+    String getStringValue(Key key, String subkey, String value) throws NativeException;
+
+    /**
+     * Lists the subkeys of a registry key.
+     *
+     * @throws NativeException On failure.
+     * @throws MissingRegistryEntryException When the requested key does not exist.
+     */
+    List<String> getSubkeys(Key key, String subkey) throws NativeException;
+
+    /**
+     * Lists the value names of a registry key.
+     *
+     * @throws NativeException On failure.
+     * @throws MissingRegistryEntryException When the requested key does not exist.
+     */
+    List<String> getValueNames(Key key, String subkey) throws NativeException;
+
+}
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFile.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFiles.java
similarity index 81%
rename from src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFile.java
rename to src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFiles.java
index e920c67..2adfa25 100755
--- a/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFile.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFiles.java
@@ -18,11 +18,22 @@ package net.rubygrapefruit.platform.internal;
 
 import net.rubygrapefruit.platform.NativeException;
 import net.rubygrapefruit.platform.PosixFile;
+import net.rubygrapefruit.platform.PosixFiles;
 import net.rubygrapefruit.platform.internal.jni.PosixFileFunctions;
 
 import java.io.File;
 
-public class DefaultPosixFile implements PosixFile {
+public class DefaultPosixFiles implements PosixFiles {
+    public PosixFile stat(File file) throws NativeException {
+        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 posix file details of %s: %s", file, result.getMessage()));
+        }
+        return stat;
+    }
+
     public void setMode(File file, int perms) {
         FunctionResult result = new FunctionResult();
         PosixFileFunctions.chmod(file.getPath(), perms, result);
@@ -32,16 +43,13 @@ public class DefaultPosixFile implements PosixFile {
     }
 
     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()));
+        PosixFile stat = stat(file);
+        if (stat.getType() == PosixFile.Type.Missing) {
+            throw new NativeException(String.format("Could not get UNIX mode on %s: file does not exist.", file));
         }
-        return stat.mode;
+        return stat.getMode();
     }
 
-    @Override
     public String readLink(File link) throws NativeException {
         FunctionResult result = new FunctionResult();
         String contents = PosixFileFunctions.readlink(link.getPath(), result);
@@ -51,7 +59,6 @@ public class DefaultPosixFile implements PosixFile {
         return contents;
     }
 
-    @Override
     public void symlink(File link, String contents) throws NativeException {
         FunctionResult result = new FunctionResult();
         PosixFileFunctions.symlink(link.getPath(), contents, result);
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultSystemInfo.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultSystemInfo.java
index dcd0af0..91da096 100644
--- a/src/main/java/net/rubygrapefruit/platform/internal/DefaultSystemInfo.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultSystemInfo.java
@@ -32,18 +32,19 @@ public class DefaultSystemInfo implements SystemInfo {
         }
     }
 
-    @Override
     public String getKernelName() {
         return systemInfo.getKernelName();
     }
 
-    @Override
     public String getKernelVersion() {
         return systemInfo.getKernelVersion();
     }
 
-    @Override
-    public String getMachineArchitecture() {
-        return systemInfo.getMachineArchitecture();
+    public String getArchitectureName() {
+        return systemInfo.getArchitectureName();
+    }
+
+    public Architecture getArchitecture() {
+        return systemInfo.getArchitecture();
     }
 }
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsRegistry.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsRegistry.java
new file mode 100644
index 0000000..2e2785c
--- /dev/null
+++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsRegistry.java
@@ -0,0 +1,57 @@
+package net.rubygrapefruit.platform.internal;
+
+import net.rubygrapefruit.platform.MissingRegistryEntryException;
+import net.rubygrapefruit.platform.NativeException;
+import net.rubygrapefruit.platform.WindowsRegistry;
+import net.rubygrapefruit.platform.internal.jni.WindowsRegistryFunctions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultWindowsRegistry implements WindowsRegistry {
+    public String getStringValue(Key key, String subkey, String valueName) throws NativeException {
+        FunctionResult result = new FunctionResult();
+        String value = WindowsRegistryFunctions.getStringValue(key.ordinal(), subkey, valueName, result);
+        if (result.isFailed()) {
+            throw new NativeException(String.format("Could not get value '%s' of registry key '%s\\%s': %s", valueName,
+                    key,
+                    subkey, result.getMessage()));
+        }
+        if (value == null) {
+            throw new MissingRegistryEntryException(String.format(
+                    "Could not get value '%s' of registry key '%s\\%s' as it does not exist.", valueName, key, subkey));
+        }
+        return value;
+    }
+
+    public List<String> getSubkeys(Key key, String subkey) throws NativeException {
+        FunctionResult result = new FunctionResult();
+        ArrayList<String> subkeys = new ArrayList<String>();
+        boolean found = WindowsRegistryFunctions.getSubkeys(key.ordinal(), subkey, subkeys, result);
+        if (result.isFailed()) {
+            throw new NativeException(String.format("Could not list the subkeys of registry key '%s\\%s': %s", key,
+                    subkey, result.getMessage()));
+        }
+        if (!found) {
+            throw new MissingRegistryEntryException(String.format(
+                    "Could not list the subkeys of registry key '%s\\%s' as it does not exist.", key, subkey));
+        }
+        return subkeys;
+    }
+
+    public List<String> getValueNames(Key key, String subkey) throws NativeException {
+        FunctionResult result = new FunctionResult();
+        ArrayList<String> names = new ArrayList<String>();
+        boolean found = WindowsRegistryFunctions.getValueNames(key.ordinal(), subkey, names, result);
+        if (result.isFailed()) {
+            throw new NativeException(String.format("Could not list the values of registry key '%s\\%s': %s", key,
+                    subkey, result.getMessage()));
+        }
+        if (!found) {
+            throw new MissingRegistryEntryException(String.format(
+                    "Could not list the values of registry key '%s\\%s' as it does not exist.", key, subkey));
+        }
+        return names;
+    }
+
+}
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java b/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java
index d51628e..5349dab 100644
--- a/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java
@@ -16,6 +16,17 @@
 
 package net.rubygrapefruit.platform.internal;
 
-public class FileStat {
+import net.rubygrapefruit.platform.PosixFile;
+
+public class FileStat implements PosixFile {
     public int mode;
+    public int type;
+
+    public int getMode() {
+        return mode;
+    }
+
+    public Type getType() {
+        return Type.values()[type];
+    }
 }
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/LibraryDef.java b/src/main/java/net/rubygrapefruit/platform/internal/LibraryDef.java
new file mode 100644
index 0000000..914143d
--- /dev/null
+++ b/src/main/java/net/rubygrapefruit/platform/internal/LibraryDef.java
@@ -0,0 +1,28 @@
+package net.rubygrapefruit.platform.internal;
+
+public class LibraryDef {
+    final String name;
+    final String platform;
+
+    public LibraryDef(String name, String platform) {
+        this.name = name;
+        this.platform = platform;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        LibraryDef other = (LibraryDef) obj;
+        return name.equals(other.name) && platform.equals(other.platform);
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode() ^ platform.hashCode();
+    }
+}
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/MutableSystemInfo.java b/src/main/java/net/rubygrapefruit/platform/internal/MutableSystemInfo.java
index 4e5ec5a..2c1e51e 100755
--- a/src/main/java/net/rubygrapefruit/platform/internal/MutableSystemInfo.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/MutableSystemInfo.java
@@ -16,6 +16,7 @@
 
 package net.rubygrapefruit.platform.internal;
 
+import net.rubygrapefruit.platform.NativeException;
 import net.rubygrapefruit.platform.SystemInfo;
 
 public class MutableSystemInfo implements SystemInfo {
@@ -32,10 +33,21 @@ public class MutableSystemInfo implements SystemInfo {
         return osVersion;
     }
 
-    public String getMachineArchitecture() {
+    public String getArchitectureName() {
         return machineArchitecture;
     }
 
+    public Architecture getArchitecture() {
+        if (machineArchitecture.equals("amd64") || machineArchitecture.equals("x86_64")) {
+            return Architecture.amd64;
+        }
+        if (machineArchitecture.equals("i386") || machineArchitecture.equals("x86") || machineArchitecture.equals("i686")) {
+            return Architecture.i386;
+        }
+        throw new NativeException(String.format("Cannot determine architecture from kernel architecture name '%s'.",
+                machineArchitecture));
+    }
+
     // Called from native code
     void windows(int major, int minor, int build, boolean workstation, String arch) {
         osName = toWindowsVersionName(major, minor, workstation);
@@ -52,7 +64,7 @@ public class MutableSystemInfo implements SystemInfo {
                     case 1:
                         return "Windows XP";
                     case 2:
-                        return "Windows Server 2003";
+                        return workstation ? "Windows XP Professional" : "Windows Server 2003";
                 }
                 break;
             case 6:
@@ -63,6 +75,8 @@ public class MutableSystemInfo implements SystemInfo {
                         return workstation ? "Windows 7" : "Windows Server 2008 R2";
                     case 2:
                         return workstation ? "Windows 8" : "Windows Server 2012";
+                    case 3:
+                        return workstation ? "Windows 8.1" : "Windows Server 2012 R2";
                 }
                 break;
         }
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java
index e1ac576..e0de565 100755
--- a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLoader.java
@@ -38,9 +38,9 @@ public class NativeLibraryLoader {
             return;
         }
         try {
-            File libFile = nativeLibraryLocator.find(libraryFileName);
+            File libFile = nativeLibraryLocator.find(new LibraryDef(libraryFileName, platform.getId()));
             if (libFile == null) {
-                throw new NativeIntegrationUnavailableException(String.format("Native library is not available for %s.", platform));
+                throw new NativeIntegrationUnavailableException(String.format("Native library '%s' is not available for %s.", libraryFileName, platform));
             }
             System.load(libFile.getCanonicalPath());
         } catch (NativeException e) {
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java
index 585fee1..9d9a175 100755
--- a/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/NativeLibraryLocator.java
@@ -30,9 +30,10 @@ public class NativeLibraryLocator {
         this.extractDir = extractDir;
     }
 
-    public File find(String libraryFileName) throws IOException {
+    public File find(LibraryDef libraryDef) throws IOException {
+        String resourceName = String.format("net/rubygrapefruit/platform/%s/%s", libraryDef.platform, libraryDef.name);
         if (extractDir != null) {
-            File libFile = new File(extractDir, String.format("%s/%s", NativeLibraryFunctions.VERSION, libraryFileName));
+            File libFile = new File(extractDir, String.format("%s/%s/%s", NativeLibraryFunctions.VERSION, libraryDef.platform, libraryDef.name));
             File lockFile = new File(libFile.getParentFile(), libFile.getName() + ".lock");
             lockFile.getParentFile().mkdirs();
             lockFile.createNewFile();
@@ -44,7 +45,7 @@ public class NativeLibraryLocator {
                     // Library has been extracted
                     return libFile;
                 }
-                URL resource = getClass().getClassLoader().getResource(libraryFileName);
+                URL resource = getClass().getClassLoader().getResource(resourceName);
                 if (resource != null) {
                     // Extract library and write marker to lock file
                     libFile.getParentFile().mkdirs();
@@ -58,20 +59,26 @@ public class NativeLibraryLocator {
                 lockFileAccess.close();
             }
         } else {
-            URL resource = getClass().getClassLoader().getResource(libraryFileName);
+            URL resource = getClass().getClassLoader().getResource(resourceName);
             if (resource != null) {
                 File libFile;
                 File libDir = File.createTempFile("native-platform", "dir");
                 libDir.delete();
                 libDir.mkdirs();
-                libFile = new File(libDir, libraryFileName);
+                libFile = new File(libDir, libraryDef.name);
                 libFile.deleteOnExit();
                 copy(resource, libFile);
                 return libFile;
             }
         }
 
-        File libFile = new File("build/binaries/" + libraryFileName);
+        String componentName = libraryDef.name.replaceFirst("^lib", "").replaceFirst("\\.\\w+$", "");
+        int pos = componentName.indexOf("-");
+        while (pos >= 0) {
+            componentName = componentName.substring(0, pos) + Character.toUpperCase(componentName.charAt(pos + 1)) + componentName.substring(pos + 2);
+            pos = componentName.indexOf("-", pos);
+        }
+        File libFile = new File(String.format("build/binaries/%sSharedLibrary/%s/%s", componentName, libraryDef.platform.replace("-", "_"), libraryDef.name));
         if (libFile.isFile()) {
             return libFile;
         }
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java
index 0956961..75be46c 100755
--- a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java
@@ -43,13 +43,23 @@ public abstract class Platform {
                     else if (arch.equals("i386") || arch.equals("x86")) {
                         platform = new Linux32Bit();
                     }
-                } else if (osName.contains("os x")) {
-                    if (arch.equals("i386") || arch.equals("x86_64") || arch.equals("amd64")) {
-                        platform = new OsX();
+                } else if (osName.contains("os x") || osName.contains("darwin")) {
+                    if (arch.equals("i386")) {
+                        platform = new OsX32Bit();
                     }
-                } else if (osName.contains("sunos")) {
-                    platform = new Solaris();
-                } else {
+                    else if (arch.equals("x86_64") || arch.equals("amd64") || arch.equals("universal")) {
+                        platform = new OsX64Bit();
+                    }
+                }
+                else if (osName.contains("freebsd")) {
+                    if (arch.equals("amd64")) {
+                        platform = new FreeBSD64Bit();
+                    }
+                    else if (arch.equals("i386") || arch.equals("x86")) {
+                        platform = new FreeBSD32Bit();
+                    }
+                }
+                if (platform == null) {
                     platform = new Unsupported();
                 }
             }
@@ -74,6 +84,8 @@ public abstract class Platform {
         throw new NativeIntegrationUnavailableException(String.format("Native integration is not available for %s.", toString()));
     }
 
+    public abstract String getId();
+
     private static String getOperatingSystem() {
         return System.getProperty("os.name");
     }
@@ -89,6 +101,11 @@ public abstract class Platform {
         }
 
         @Override
+        public String getLibraryName() {
+            return "native-platform.dll";
+        }
+
+        @Override
         public <T extends NativeIntegration> T get(Class<T> type, NativeLibraryLoader nativeLibraryLoader) {
             if (type.equals(Process.class)) {
                 return type.cast(new WrapperProcess(new DefaultProcess(), true));
@@ -105,21 +122,24 @@ public abstract class Platform {
             if (type.equals(FileSystems.class)) {
                 return type.cast(new PosixFileSystems());
             }
+            if (type.equals(WindowsRegistry.class)) {
+                return type.cast(new DefaultWindowsRegistry());
+            }
             return super.get(type, nativeLibraryLoader);
         }
     }
 
     private static class Window32Bit extends Windows {
         @Override
-        public String getLibraryName() {
-            return "native-platform-windows-i386.dll";
+        public String getId() {
+            return "windows-i386";
         }
     }
 
     private static class Window64Bit extends Windows {
         @Override
-        public String getLibraryName() {
-            return "native-platform-windows-amd64.dll";
+        public String getId() {
+            return "windows-amd64";
         }
     }
 
@@ -128,8 +148,8 @@ public abstract class Platform {
 
         @Override
         public <T extends NativeIntegration> T get(Class<T> type, NativeLibraryLoader nativeLibraryLoader) {
-            if (type.equals(PosixFile.class)) {
-                return type.cast(new DefaultPosixFile());
+            if (type.equals(PosixFiles.class)) {
+                return type.cast(new DefaultPosixFiles());
             }
             if (type.equals(Process.class)) {
                 return type.cast(new WrapperProcess(new DefaultProcess(), false));
@@ -148,16 +168,6 @@ public abstract class Platform {
             if (type.equals(SystemInfo.class)) {
                 return type.cast(new DefaultSystemInfo());
             }
-            return super.get(type, nativeLibraryLoader);
-        }
-    }
-
-    private abstract static class Unix extends Posix {
-    }
-
-    private abstract static class Linux extends Unix {
-        @Override
-        public <T extends NativeIntegration> T get(Class<T> type, NativeLibraryLoader nativeLibraryLoader) {
             if (type.equals(FileSystems.class)) {
                 return type.cast(new PosixFileSystems());
             }
@@ -165,63 +175,77 @@ public abstract class Platform {
         }
     }
 
-    private static class Linux32Bit extends Linux {
+    private abstract static class Unix extends Posix {
         @Override
         public String getLibraryName() {
-            return "libnative-platform-linux-i386.so";
+            return "libnative-platform.so";
         }
 
         @Override
         String getCursesLibraryName() {
-            return "libnative-platform-curses-linux-i386.so";
+            return "libnative-platform-curses.so";
         }
     }
 
-    private static class Linux64Bit extends Linux {
+    private static class Linux32Bit extends Unix {
         @Override
-        public String getLibraryName() {
-            return "libnative-platform-linux-amd64.so";
+        public String getId() {
+            return "linux-i386";
         }
+    }
 
+    private static class Linux64Bit extends Unix {
         @Override
-        String getCursesLibraryName() {
-            return "libnative-platform-curses-linux-amd64.so";
+        public String getId() {
+            return "linux-amd64";
         }
     }
 
-    private static class Solaris extends Unix {
+    private static class FreeBSD32Bit extends Unix {
         @Override
-        public String getLibraryName() {
-            return "libnative-platform-solaris.so";
+        public String getId() {
+            return "freebsd-i386";
         }
+    }
 
+    private static class FreeBSD64Bit extends Unix {
         @Override
-        String getCursesLibraryName() {
-            return "libnative-platform-curses-solaris.so";
+        public String getId() {
+            return "freebsd-amd64";
         }
     }
 
-    private static class OsX extends Posix {
+    private static abstract class OsX extends Posix {
         @Override
-        public <T extends NativeIntegration> T get(Class<T> type, NativeLibraryLoader nativeLibraryLoader) {
-            if (type.equals(FileSystems.class)) {
-                return type.cast(new PosixFileSystems());
-            }
-            return super.get(type, nativeLibraryLoader);
+        public String getLibraryName() {
+            return "libnative-platform.dylib";
         }
 
         @Override
-        public String getLibraryName() {
-            return "libnative-platform-osx-universal.dylib";
+        String getCursesLibraryName() {
+            return "libnative-platform-curses.dylib";
         }
+    }
 
+    private static class OsX32Bit extends OsX {
         @Override
-        String getCursesLibraryName() {
-            return "libnative-platform-curses-osx-universal.dylib";
+        public String getId() {
+            return "osx-i386";
+        }
+    }
+
+    private static class OsX64Bit extends OsX {
+        @Override
+        public String getId() {
+            return "osx-amd64";
         }
     }
 
     private static class Unsupported extends Platform {
+        @Override
+        public String getId() {
+            throw new UnsupportedOperationException();
+        }
     }
 
 }
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java
index 1a3576d..2991b8b 100755
--- a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java
+++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java
@@ -20,7 +20,7 @@ import net.rubygrapefruit.platform.internal.FunctionResult;
 import net.rubygrapefruit.platform.internal.MutableSystemInfo;
 
 public class NativeLibraryFunctions {
-    public static final int VERSION = 15;
+    public static final int VERSION = 19;
 
     public static native int getVersion();
 
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsRegistryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsRegistryFunctions.java
new file mode 100755
index 0000000..e3aaca0
--- /dev/null
+++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsRegistryFunctions.java
@@ -0,0 +1,16 @@
+package net.rubygrapefruit.platform.internal.jni;
+
+import net.rubygrapefruit.platform.internal.FunctionResult;
+
+import java.util.List;
+
+public class WindowsRegistryFunctions {
+    // Returns null for unknown key or value
+    public static native String getStringValue(int key, String subkey, String value, FunctionResult result);
+
+    // Returns false for unknown key
+    public static native boolean getSubkeys(int key, String subkey, List<String> subkeys, FunctionResult result);
+
+    // Returns false for unknown key
+    public static native boolean getValueNames(int key, String subkey, List<String> names, FunctionResult result);
+}
diff --git a/src/main/cpp/generic.cpp b/src/shared/cpp/generic.cpp
similarity index 100%
rename from src/main/cpp/generic.cpp
rename to src/shared/cpp/generic.cpp
diff --git a/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java b/src/shared/cpp/generic_posix.cpp
similarity index 60%
copy from src/main/java/net/rubygrapefruit/platform/internal/FileStat.java
copy to src/shared/cpp/generic_posix.cpp
index d51628e..a44a19a 100644
--- a/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java
+++ b/src/shared/cpp/generic_posix.cpp
@@ -14,8 +14,24 @@
  *    limitations under the License.
  */
 
-package net.rubygrapefruit.platform.internal;
+/*
+ * POSIX platform functions.
+ */
+#ifndef _WIN32
+
+#include "native.h"
+#include "generic.h"
+#include <errno.h>
 
-public class FileStat {
-    public int mode;
+void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) {
+    const char * errno_message = NULL;
+    switch(errno) {
+        case ENOENT:
+            errno_message = "ENOENT";
+            break;
+    }
+
+    mark_failed_with_code(env, message, errno, errno_message, result);
 }
+
+#endif
diff --git a/src/test/groovy/net/rubygrapefruit/platform/SystemInfoTest.groovy b/src/shared/cpp/osx.cpp
old mode 100755
new mode 100644
similarity index 52%
copy from src/test/groovy/net/rubygrapefruit/platform/SystemInfoTest.groovy
copy to src/shared/cpp/osx.cpp
index 18d20f2..1064043
--- a/src/test/groovy/net/rubygrapefruit/platform/SystemInfoTest.groovy
+++ b/src/shared/cpp/osx.cpp
@@ -14,25 +14,28 @@
  *    limitations under the License.
  */
 
-package net.rubygrapefruit.platform
-
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
-import spock.lang.Specification
+/*
+ * POSIX platform functions.
+ */
+#ifdef __APPLE__
 
-class SystemInfoTest extends Specification {
-    @Rule TemporaryFolder tmpDir
-    final SystemInfo systemInfo = Native.get(SystemInfo.class)
+#include "native.h"
+#include "generic.h"
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
 
-    def "caches system info instance"() {
-        expect:
-        Native.get(SystemInfo.class) == systemInfo
-    }
+char* java_to_char(JNIEnv *env, jstring string, jobject result) {
+    size_t len = env->GetStringLength(string);
+    size_t bytes = env->GetStringUTFLength(string);
+    char* chars = (char*)malloc(bytes + 1);
+    env->GetStringUTFRegion(string, 0, len, chars);
+    chars[bytes] = 0;
+    return chars;
+}
 
-    def "can query OS details"() {
-        expect:
-        systemInfo.kernelName
-        systemInfo.kernelVersion
-        systemInfo.machineArchitecture
-    }
+jstring char_to_java(JNIEnv* env, const char* chars, jobject result) {
+    return env->NewStringUTF(chars);
 }
+
+#endif
diff --git a/src/main/cpp/generic_posix.cpp b/src/shared/cpp/unix_strings.cpp
similarity index 83%
rename from src/main/cpp/generic_posix.cpp
rename to src/shared/cpp/unix_strings.cpp
index a70a967..c6aa3fa 100644
--- a/src/main/cpp/generic_posix.cpp
+++ b/src/shared/cpp/unix_strings.cpp
@@ -15,28 +15,16 @@
  */
 
 /*
- * POSIX platform functions.
+ * UNIX string conversion functions.
  */
-#ifndef WIN32
+#if defined(__linux__) || defined(__FreeBSD__)
 
 #include "native.h"
 #include "generic.h"
 #include <stdlib.h>
-#include <errno.h>
 #include <string.h>
 #include <wchar.h>
 
-void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) {
-    const char * errno_message = NULL;
-    switch(errno) {
-        case ENOENT:
-            errno_message = "ENOENT";
-            break;
-    }
-
-    mark_failed_with_code(env, message, errno, errno_message, result);
-}
-
 char* java_to_char(JNIEnv *env, jstring string, jobject result) {
     size_t stringLen = env->GetStringLength(string);
     wchar_t* wideString = (wchar_t*)malloc(sizeof(wchar_t) * (stringLen+1));
@@ -48,7 +36,7 @@ char* java_to_char(JNIEnv *env, jstring string, jobject result) {
     env->ReleaseStringChars(string, javaString);
 
     size_t bytes = wcstombs(NULL, wideString, 0);
-    if (bytes < 0) {
+    if (bytes == (size_t)-1) {
         mark_failed_with_message(env, "could not convert string to current locale", result);
         free(wideString);
         return NULL;
@@ -64,7 +52,7 @@ char* java_to_char(JNIEnv *env, jstring string, jobject result) {
 jstring char_to_java(JNIEnv* env, const char* chars, jobject result) {
     size_t bytes = strlen(chars);
     wchar_t* wideString = (wchar_t*)malloc(sizeof(wchar_t) * (bytes+1));
-    if (mbstowcs(wideString, chars, bytes+1) < 0) {
+    if (mbstowcs(wideString, chars, bytes+1) == (size_t)-1) {
         mark_failed_with_message(env, "could not convert string from current locale", result);
         free(wideString);
         return NULL;
diff --git a/src/main/headers/generic.h b/src/shared/headers/generic.h
similarity index 95%
rename from src/main/headers/generic.h
rename to src/shared/headers/generic.h
index 35b50af..a44e5d6 100755
--- a/src/main/headers/generic.h
+++ b/src/shared/headers/generic.h
@@ -1,76 +1,76 @@
-/*
- * Copyright 2012 Adam Murdoch
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-
-#ifndef __INCLUDE_GENERIC_H__
-#define __INCLUDE_GENERIC_H__
-
-#include <jni.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define NATIVE_VERSION 15
-
-/*
- * Marks the given result as failed, using the given error message
- */
-extern void mark_failed_with_message(JNIEnv *env, const char* message, jobject result);
-
-/*
- * Marks the given result as failed, using the given error message and the current value of errno/GetLastError()
- */
-extern void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result);
-
-/*
- * Marks the given result as failed, using the given error message and error code
- */
-extern void mark_failed_with_code(JNIEnv *env, const char* message, int error_code, const char* error_code_message, jobject result);
-
-/*
- * Converts the given Java string to a NULL terminated wchar_str. Should call free() when finished.
- *
- * Returns NULL on failure.
- */
-extern wchar_t*
-java_to_wchar(JNIEnv *env, jstring string, jobject result);
-
-/*
- * Converts the given wchar_t string to a Java string.
- *
- * Returns NULL on failure.
- */
-extern jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result);
-
-/*
- * Converts the given Java string to a NULL terminated char string. Should call free() when finished.
- *
- * Returns NULL on failure.
- */
-extern char* java_to_char(JNIEnv *env, jstring string, jobject result);
-
-/*
- * Converts the given NULL terminated char string to a Java string.
- *
- * Returns NULL on failure.
- */
-extern jstring char_to_java(JNIEnv* env, const char* chars, jobject result);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
+/*
+ * Copyright 2012 Adam Murdoch
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef __INCLUDE_GENERIC_H__
+#define __INCLUDE_GENERIC_H__
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NATIVE_VERSION 19
+
+/*
+ * Marks the given result as failed, using the given error message
+ */
+extern void mark_failed_with_message(JNIEnv *env, const char* message, jobject result);
+
+/*
+ * Marks the given result as failed, using the given error message and the current value of errno/GetLastError()
+ */
+extern void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result);
+
+/*
+ * Marks the given result as failed, using the given error message and error code
+ */
+extern void mark_failed_with_code(JNIEnv *env, const char* message, int error_code, const char* error_code_message, jobject result);
+
+/*
+ * Converts the given Java string to a NULL terminated wchar_str. Should call free() when finished.
+ *
+ * Returns NULL on failure.
+ */
+extern wchar_t*
+java_to_wchar(JNIEnv *env, jstring string, jobject result);
+
+/*
+ * Converts the given wchar_t string to a Java string.
+ *
+ * Returns NULL on failure.
+ */
+extern jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result);
+
+/*
+ * Converts the given Java string to a NULL terminated char string. Should call free() when finished.
+ *
+ * Returns NULL on failure.
+ */
+extern char* java_to_char(JNIEnv *env, jstring string, jobject result);
+
+/*
+ * Converts the given NULL terminated char string to a Java string.
+ *
+ * Returns NULL on failure.
+ */
+extern jstring char_to_java(JNIEnv* env, const char* chars, jobject result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/PosixFilesTest.groovy
similarity index 64%
rename from src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy
rename to src/test/groovy/net/rubygrapefruit/platform/PosixFilesTest.groovy
index 630d058..9d0fe94 100755
--- a/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy
+++ b/src/test/groovy/net/rubygrapefruit/platform/PosixFilesTest.groovy
@@ -1,129 +1,203 @@
-/*
- * Copyright 2012 Adam Murdoch
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-
-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
-
- at IgnoreIf({Platform.current().windows})
-class PosixFileTest extends Specification {
-    @Rule TemporaryFolder tmpDir
-    final PosixFile file = Native.get(PosixFile.class)
-
-    def "caches file instance"() {
-        expect:
-        Native.get(PosixFile.class) == file
-    }
-
-    def "can set mode on a file"() {
-        def testFile = tmpDir.newFile(fileName)
-
-        when:
-        file.setMode(testFile, 0740)
-
-        then:
-        file.getMode(testFile) == 0740
-
-        where:
-        fileName << ["test.txt", "test\u03b1\u2295.txt"]
-    }
-
-    def "cannot set mode on file that does not exist"() {
-        def testFile = new File(tmpDir.root, "unknown")
-
-        when:
-        file.setMode(testFile, 0660)
-
-        then:
-        NativeException e = thrown()
-        e.message == "Could not set UNIX mode on $testFile: could not chmod file (ENOENT errno 2)"
-    }
-
-    def "cannot get mode on file that does not exist"() {
-        def testFile = new File(tmpDir.root, "unknown")
-
-        when:
-        file.getMode(testFile)
-
-        then:
-        NativeException e = thrown()
-        e.message == "Could not get UNIX mode on $testFile: could not stat file (ENOENT errno 2)"
-    }
-
-    def "can create symbolic link"() {
-        def testFile = new File(tmpDir.root, "test.txt")
-        testFile.text = "hi"
-        def symlinkFile = new File(tmpDir.root, "symlink")
-
-        when:
-        file.symlink(symlinkFile, testFile.name)
-
-        then:
-        symlinkFile.file
-        symlinkFile.text == "hi"
-        symlinkFile.canonicalFile == testFile.canonicalFile
-    }
-
-    def "can read symbolic link"() {
-        def symlinkFile = new File(tmpDir.root, "symlink")
-
-        when:
-        file.symlink(symlinkFile, "target")
-
-        then:
-        file.readLink(symlinkFile) == "target"
-    }
-
-    def "cannot read a symlink that does not exist"() {
-        def symlinkFile = new File(tmpDir.root, "symlink")
-
-        when:
-        file.readLink(symlinkFile)
-
-        then:
-        NativeException e = thrown()
-        e.message == "Could not read symlink $symlinkFile: could not lstat file (ENOENT errno 2)"
-    }
-
-    def "cannot read a symlink that is not a symlink"() {
-        def symlinkFile = tmpDir.newFile("not-a-symlink.txt")
-
-        when:
-        file.readLink(symlinkFile)
-
-        then:
-        NativeException e = thrown()
-        e.message == "Could not read symlink $symlinkFile: could not readlink (errno 22)"
-    }
-
-    def "can create and read symlink with unicode in its name"() {
-        def testFile = new File(tmpDir.root, "target\u03b2\u2295")
-        testFile.text = 'hi'
-        def symlinkFile = new File(tmpDir.root, "symlink\u03b2\u2296")
-
-        when:
-        file.symlink(symlinkFile, testFile.name)
-
-        then:
-        file.readLink(symlinkFile) == testFile.name
-        symlinkFile.file
-        symlinkFile.canonicalFile == testFile.canonicalFile
-    }
-}
+/*
+ * Copyright 2012 Adam Murdoch
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+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
+
+ at IgnoreIf({Platform.current().windows})
+class PosixFilesTest extends Specification {
+    @Rule TemporaryFolder tmpDir
+    final PosixFiles file = Native.get(PosixFiles.class)
+
+    def "caches file instance"() {
+        expect:
+        Native.get(PosixFiles.class) == file
+    }
+
+    def "can get details of a file"() {
+        def testFile = tmpDir.newFile(fileName)
+
+        when:
+        def stat = file.stat(testFile)
+
+        then:
+        stat.type == PosixFile.Type.File
+        stat.mode != 0
+
+        where:
+        fileName << ["test.txt", "test\u03b1\u2295.txt"]
+    }
+
+    def "can get details of a directory"() {
+        def testFile = tmpDir.newFolder(fileName)
+
+        when:
+        def stat = file.stat(testFile)
+
+        then:
+        stat.type == PosixFile.Type.Directory
+        stat.mode != 0
+
+        where:
+        fileName << ["test-dir", "test\u03b1\u2295-dir"]
+    }
+
+    def "can get details of a missing file"() {
+        def testFile = new File(tmpDir.root, fileName)
+
+        when:
+        def stat = file.stat(testFile)
+
+        then:
+        stat.type == PosixFile.Type.Missing
+        stat.mode == 0
+
+        where:
+        fileName << ["test-dir", "test\u03b1\u2295-dir"]
+    }
+
+    def "can set mode on a file"() {
+        def testFile = tmpDir.newFile(fileName)
+
+        when:
+        file.setMode(testFile, 0740)
+
+        then:
+        file.getMode(testFile) == 0740
+        file.stat(testFile).mode == 0740
+
+        where:
+        fileName << ["test.txt", "test\u03b1\u2295.txt"]
+    }
+
+    def "can set mode on a directory"() {
+        def testFile = tmpDir.newFolder(fileName)
+
+        when:
+        file.setMode(testFile, 0740)
+
+        then:
+        file.getMode(testFile) == 0740
+        file.stat(testFile).mode == 0740
+
+        where:
+        fileName << ["test-dir", "test\u03b1\u2295-dir"]
+    }
+
+    def "cannot set mode on file that does not exist"() {
+        def testFile = new File(tmpDir.root, "unknown")
+
+        when:
+        file.setMode(testFile, 0660)
+
+        then:
+        NativeException e = thrown()
+        e.message == "Could not set UNIX mode on $testFile: could not chmod file (ENOENT errno 2)"
+    }
+
+    def "cannot get mode on file that does not exist"() {
+        def testFile = new File(tmpDir.root, "unknown")
+
+        when:
+        file.getMode(testFile)
+
+        then:
+        NativeException e = thrown()
+        e.message == "Could not get UNIX mode on $testFile: file does not exist."
+    }
+
+    def "can create symbolic link"() {
+        def testFile = new File(tmpDir.root, "test.txt")
+        testFile.text = "hi"
+        def symlinkFile = new File(tmpDir.root, "symlink")
+
+        when:
+        file.symlink(symlinkFile, testFile.name)
+
+        then:
+        symlinkFile.file
+        symlinkFile.text == "hi"
+        symlinkFile.canonicalFile == testFile.canonicalFile
+    }
+
+    def "can read symbolic link"() {
+        def symlinkFile = new File(tmpDir.root, "symlink")
+
+        when:
+        file.symlink(symlinkFile, "target")
+
+        then:
+        file.readLink(symlinkFile) == "target"
+    }
+
+    def "cannot read a symlink that does not exist"() {
+        def symlinkFile = new File(tmpDir.root, "symlink")
+
+        when:
+        file.readLink(symlinkFile)
+
+        then:
+        NativeException e = thrown()
+        e.message == "Could not read symlink $symlinkFile: could not lstat file (ENOENT errno 2)"
+    }
+
+    def "cannot read a symlink that is not a symlink"() {
+        def symlinkFile = tmpDir.newFile("not-a-symlink.txt")
+
+        when:
+        file.readLink(symlinkFile)
+
+        then:
+        NativeException e = thrown()
+        e.message == "Could not read symlink $symlinkFile: could not readlink (errno 22)"
+    }
+
+    def "can create and read symlink with unicode in its name"() {
+        def testFile = new File(tmpDir.root, "target\u03b2\u2295")
+        testFile.text = 'hi'
+        def symlinkFile = new File(tmpDir.root, "symlink\u03b2\u2296")
+
+        when:
+        file.symlink(symlinkFile, testFile.name)
+
+        then:
+        file.readLink(symlinkFile) == testFile.name
+        symlinkFile.file
+        symlinkFile.canonicalFile == testFile.canonicalFile
+    }
+
+    def "can get details of a symlink"() {
+        def testFile = new File(tmpDir.newFolder("parent"), fileName)
+
+        given:
+        file.symlink(testFile, "target")
+
+        when:
+        def stat = file.stat(testFile)
+
+        then:
+        stat.type == PosixFile.Type.Symlink
+        stat.mode != 0
+
+        where:
+        fileName << ["test.txt", "test\u03b1\u2295.txt"]
+    }
+}
diff --git a/src/test/groovy/net/rubygrapefruit/platform/SystemInfoTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/SystemInfoTest.groovy
index 18d20f2..a7316bc 100755
--- a/src/test/groovy/net/rubygrapefruit/platform/SystemInfoTest.groovy
+++ b/src/test/groovy/net/rubygrapefruit/platform/SystemInfoTest.groovy
@@ -33,6 +33,7 @@ class SystemInfoTest extends Specification {
         expect:
         systemInfo.kernelName
         systemInfo.kernelVersion
-        systemInfo.machineArchitecture
+        systemInfo.architectureName
+        systemInfo.architecture
     }
 }
diff --git a/src/test/groovy/net/rubygrapefruit/platform/WindowsRegistryTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/WindowsRegistryTest.groovy
new file mode 100644
index 0000000..bc1320d
--- /dev/null
+++ b/src/test/groovy/net/rubygrapefruit/platform/WindowsRegistryTest.groovy
@@ -0,0 +1,64 @@
+package net.rubygrapefruit.platform
+
+import net.rubygrapefruit.platform.internal.Platform
+import spock.lang.IgnoreIf
+import spock.lang.Specification
+
+ at IgnoreIf({!Platform.current().windows})
+class WindowsRegistryTest extends Specification {
+    def windowsRegistry = Native.get(WindowsRegistry)
+
+    def "can read string value"() {
+        expect:
+        def currentVersion = windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows NT\CurrentVersion/, "CurrentVersion")
+        currentVersion.matches("\\d+\\.\\d+")
+        def path = new File(windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_CURRENT_USER, "Volatile Environment", "APPDATA"))
+        path.directory
+    }
+
+    def "cannot read value that does not exist"() {
+        when:
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows NT\CurrentVersion/, "Unknown")
+
+        then:
+        def e = thrown(MissingRegistryEntryException)
+        e.message == /Could not get value 'Unknown' of registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' as it does not exist./
+    }
+
+    def "cannot read value of key that does not exist"() {
+        when:
+        windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Unknown/, "Value")
+
+        then:
+        def e = thrown(MissingRegistryEntryException)
+        e.message == /Could not get value 'Value' of registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Unknown' as it does not exist./
+    }
+
+    def "can list subkeys of a key"() {
+        expect:
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft/).flatten().contains("Windows NT")
+    }
+
+    def "cannot list subkeys of key that does not exist"() {
+        when:
+        windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Unknown/)
+
+        then:
+        def e = thrown(MissingRegistryEntryException)
+        e.message == /Could not list the subkeys of registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Unknown' as it does not exist./
+    }
+
+    def "cannot list values of a key"() {
+        expect:
+        windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows NT\CurrentVersion/).flatten().contains("CurrentVersion")
+    }
+
+    def "cannot list values of key that does not exist"() {
+        when:
+        windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Unknown/)
+
+        then:
+        def e = thrown(MissingRegistryEntryException)
+        e.message == /Could not list the values of registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Unknown' as it does not exist./
+    }
+}
diff --git a/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java b/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java
index 6eb4dea..c1028a0 100755
--- a/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java
+++ b/test-app/src/main/java/net/rubygrapefruit/platform/test/Main.java
@@ -45,12 +45,11 @@ public class Main {
         }
 
         System.out.println();
-        System.out.println("* OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch"));
         System.out.println("* JVM: " + System.getProperty("java.vm.vendor") + ' ' + System.getProperty("java.version"));
-        System.out.println("* Encoding: " + System.getProperty("file.encoding"));
+        System.out.println("* OS (JVM): " + System.getProperty("os.name") + ' ' + System.getProperty("os.version") + ' ' + System.getProperty("os.arch"));
 
         SystemInfo systemInfo = Native.get(SystemInfo.class);
-        System.out.println("* Kernel: " + systemInfo.getKernelName() + ' ' + systemInfo.getKernelVersion() + ' ' + systemInfo.getMachineArchitecture());
+        System.out.println("* OS (Kernel): " + systemInfo.getKernelName() + ' ' + systemInfo.getKernelVersion() + ' ' + systemInfo.getArchitectureName() + " (" + systemInfo.getArchitecture() + ")");
 
         Process process = Native.get(Process.class);
         System.out.println("* PID: " + process.getProcessId());
@@ -58,7 +57,7 @@ public class Main {
         FileSystems fileSystems = Native.get(FileSystems.class);
         System.out.println("* File systems: ");
         for (FileSystem fileSystem : fileSystems.getFileSystems()) {
-            System.out.println("    * " + fileSystem.getMountPoint() + ' ' + fileSystem.getFileSystemType() + ' ' + fileSystem.getDeviceName() + (fileSystem.isRemote() ? " remote" : " local"));
+            System.out.println("    * " + fileSystem.getMountPoint() + " -> " + fileSystem.getDeviceName() + " (" + fileSystem.getFileSystemType() + (fileSystem.isRemote() ? " remote" : " local") + ")");
         }
 
         Terminals terminals = Native.get(Terminals.class);

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/libnative-platform-java.git



More information about the pkg-java-commits mailing list