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 <[email protected]> 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; + +@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 - -@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 + +@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 + +@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 _______________________________________________ pkg-java-commits mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-java-commits

