From 9e0493f94e8d26622cbb6859a3e48514208241e5 Mon Sep 17 00:00:00 2001 From: Adam Murdoch Date: Thu, 7 Feb 2013 16:58:11 +1100 Subject: [PATCH] Implemented Process working directory methods on windows, and fixed file system details. --- readme.md | 425 +++++----- src/main/cpp/win.cpp | 768 ++++++++++-------- src/main/headers/generic.h | 166 ++-- .../platform/PosixFileTest.groovy | 13 +- .../platform/ProcessTest.groovy | 119 +-- 5 files changed, 763 insertions(+), 728 deletions(-) diff --git a/readme.md b/readme.md index 697c5af..f4af823 100755 --- a/readme.md +++ b/readme.md @@ -1,213 +1,212 @@ - -# 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. - -These APIs support Java 5 and later. Some of these APIs overlap with APIs available in later Java versions. - -## Available bindings - -### System information - -* Get kernel name and version. -* Get machine architecture. - -### Processes - -* Get the PID of the current process. -* Get and set the process working directory. - -### Terminal and console - -These bindings work for both the UNIX terminal and the Windows console: - -* Determine if stdout/stderr are attached to a terminal. -* Query the terminal size. -* Switch between bold and normal mode on the terminal. -* Change foreground color on the terminal. -* Move terminal cursor up, down, left, right, start of line. -* Clear to end of line. - -### File systems - -* Get and set UNIX file mode. -* Create and read symbolic links. -* 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. - -## Supported platforms - -Currently ported to OS X, Linux and Windows. Support for Solaris and FreeBSD 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) -* Solaris 11 (x86) -* Windows 7 (x64), XP (x86) - -## Using - -Include `native-platform.jar` and `native-platform-${os}-${arch}.jar` in your classpath. From Gradle, you can do -this: - - repositories { - maven { url "http://repo.gradle.org/gradle/libs-releases-local" } - } - - dependencies { - compile "net.rubygrapefruit:native-platform:0.3" - } - -You can also download [here](http://repo.gradle.org/gradle/libs-releases-local/net/rubygrapefruit/) - -Some sample code to use the terminal: - - import net.rubygrapefruit.platform.Native; - import net.rubygrapefruit.platform.Terminals; - import net.rubygrapefruit.platform.Terminal; - import static net.rubygrapefruit.platform.Terminals.Output.*; - - Terminals terminals = Native.get(Terminals.class); - - // check if terminal - terminals.isTerminal(Stdout); - - // use terminal - Terminal stdout = terminals.getTerminal(Stdout); - stdout.bold(); - System.out.println("bold text"); - -## Changes - -### 0.3 - -* Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to Rene Gr�schke. - -### 0.2 - -* Fixes to make native library extraction multi-process safe. -* Fixes to windows terminal detection and reset. - -### 0.1 - -* Initial release. - -# Development - -## Building - -You will need to use the Gradle wrapper. Just run `gradlew` in the root directory. - -### Ubuntu - -The g++ compiler is required to build the native library. You will need the `g++` package for this. Usually this is already installed. - -You need to install the `libncurses5-dev` package to pick up the ncurses header files. Also worth installing the `ncurses-doc` package too. - -#### 64-bit machines with multi-arch support - -Where multi-arch support is available (e.g. recent Ubuntu releases), you can build the i386 and amd64 versions of the library on the -same machine. - -You need to install the `gcc-multilib` and `g++-multilib` packages to pick up i386 support. - -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. - -### OS X - -The g++ compiler is required to build the native library. You will need to install the XCode tools for this. - -### Solaris - -For Solaris 11, you need to install the `development/gcc-45` and `system/header` packages. - -## 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`. - -You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application. - -# Releasing - -1. Create a tag and push. -2. 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 -3. Build Java library and test app: - 1. Checkout tag. - 2. `./gradlew clean :test :uploadArchives testApp:uploadArchives -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>` -4. Checkout master -5. Increment version number in `build.gradle` and this readme. - -## Testing - -* Test on IBM JVM. -* Test on Java 5, 6, 7. -* Test on Windows 7, Windows XP - -## TODO - -### Fixes - -* Posix: allow terminal to be detected when ncurses cannot be loaded -* Windows: fix detection of shared drive under VMWare fusion and Windows XP -* Linux: detect remote filesystems. -* 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 (in particular for String conversions). -* Determine C charset once at startup -* Change readLink() implementation so that it does not need to NULL terminate the encoded content -* Don't use NewStringUTF() anywhere -* Don't use NewString() 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. -* Split out separate native library for terminal handling. -* 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 - 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. - -### Ideas - -* 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 infromation about network interfaces. -* Fire events when filesystems or network interfaces change in some way. -* Fire events when terminal size changes. -* Fire events when files change. -* Expose system keystores and authentication services. -* Expose a mechanism for generating a temporary directory. + +# 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. + +These APIs support Java 5 and later. Some of these APIs overlap with APIs available in later Java versions. + +## Available bindings + +### System information + +* Get kernel name and version. +* Get machine architecture. + +### Processes + +* Get the PID of the current process. +* Get and set the process working directory. + +### Terminal and console + +These bindings work for both the UNIX terminal and the Windows console: + +* Determine if stdout/stderr are attached to a terminal. +* Query the terminal size. +* Switch between bold and normal mode on the terminal. +* Change foreground color on the terminal. +* Move terminal cursor up, down, left, right, start of line. +* Clear to end of line. + +### File systems + +* Get and set UNIX file mode. +* Create and read symbolic links. +* 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. + +## Supported platforms + +Currently ported to OS X, Linux and Windows. Support for Solaris and FreeBSD 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) +* Solaris 11 (x86) +* Windows 7 (x64), XP (x86) + +## Using + +Include `native-platform.jar` and `native-platform-${os}-${arch}.jar` in your classpath. From Gradle, you can do +this: + + repositories { + maven { url "http://repo.gradle.org/gradle/libs-releases-local" } + } + + dependencies { + compile "net.rubygrapefruit:native-platform:0.3" + } + +You can also download [here](http://repo.gradle.org/gradle/libs-releases-local/net/rubygrapefruit/) + +Some sample code to use the terminal: + + import net.rubygrapefruit.platform.Native; + import net.rubygrapefruit.platform.Terminals; + import net.rubygrapefruit.platform.Terminal; + import static net.rubygrapefruit.platform.Terminals.Output.*; + + Terminals terminals = Native.get(Terminals.class); + + // check if terminal + terminals.isTerminal(Stdout); + + // use terminal + Terminal stdout = terminals.getTerminal(Stdout); + stdout.bold(); + System.out.println("bold text"); + +## Changes + +### 0.3 + +* Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to Rene Gr�schke. + +### 0.2 + +* Fixes to make native library extraction multi-process safe. +* Fixes to windows terminal detection and reset. + +### 0.1 + +* Initial release. + +# Development + +## Building + +You will need to use the Gradle wrapper. Just run `gradlew` in the root directory. + +### Ubuntu + +The g++ compiler is required to build the native library. You will need the `g++` package for this. Usually this is already installed. + +You need to install the `libncurses5-dev` package to pick up the ncurses header files. Also worth installing the `ncurses-doc` package too. + +#### 64-bit machines with multi-arch support + +Where multi-arch support is available (e.g. recent Ubuntu releases), you can build the i386 and amd64 versions of the library on the +same machine. + +You need to install the `gcc-multilib` and `g++-multilib` packages to pick up i386 support. + +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. + +### OS X + +The g++ compiler is required to build the native library. You will need to install the XCode tools for this. + +### Solaris + +For Solaris 11, you need to install the `development/gcc-45` and `system/header` packages. + +## 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`. + +You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application. + +# Releasing + +1. Create a tag and push. +2. 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 +3. Build Java library and test app: + 1. Checkout tag. + 2. `./gradlew clean :test :uploadArchives testApp:uploadArchives -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>` +4. Checkout master +5. Increment version number in `build.gradle` and this readme. + +## Testing + +* Test on IBM JVM. +* Test on Java 5, 6, 7. +* Test on Windows 7, Windows XP + +## TODO + +### Fixes + +* Posix: allow terminal to be detected when ncurses cannot be loaded +* Windows: fix detection of shared drive under VMWare fusion and Windows XP +* Linux: detect remote filesystems. +* 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 (in particular for String conversions). +* Determine C charset once at startup +* 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. +* Split out separate native library for terminal handling. +* 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 + 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. + +### Ideas + +* 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 infromation about network interfaces. +* Fire events when filesystems or network interfaces change in some way. +* Fire events when terminal size changes. +* Fire events when files change. +* Expose system keystores and authentication services. +* Expose a mechanism for generating a temporary directory. diff --git a/src/main/cpp/win.cpp b/src/main/cpp/win.cpp index 0fe1d5e..44bfdf1 100755 --- a/src/main/cpp/win.cpp +++ b/src/main/cpp/win.cpp @@ -1,357 +1,411 @@ -/* - * 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. - */ - -#ifdef WIN32 - -#include "native.h" -#include "generic.h" -#include -#include - -/* - * Marks the given result as failed, using the current value of GetLastError() - */ -void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { - mark_failed_with_code(env, message, GetLastError(), result); -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo(JNIEnv *env, jclass target, jobject info, jobject result) { - jclass infoClass = env->GetObjectClass(info); - - OSVERSIONINFOEX versionInfo; - versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - if (GetVersionEx((OSVERSIONINFO*)&versionInfo) == 0) { - mark_failed_with_errno(env, "could not get version info", result); - return; - } - - SYSTEM_INFO systemInfo; - GetNativeSystemInfo(&systemInfo); - jstring arch = NULL; - if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { - arch = env->NewStringUTF("amd64"); - } else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) { - arch = env->NewStringUTF("x86"); - } else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) { - arch = env->NewStringUTF("ia64"); - } else { - arch = env->NewStringUTF("unknown"); - } - - jmethodID method = env->GetMethodID(infoClass, "windows", "(IIIZLjava/lang/String;)V"); - env->CallVoidMethod(info, method, versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, - versionInfo.dwBuildNumber, versionInfo.wProductType == VER_NT_WORKSTATION, - arch); -} - -/* - * Process functions - */ - -JNIEXPORT jint JNICALL -Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) { - return GetCurrentProcessId(); -} - -/* - * File system functions - */ -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_PosixFileSystemFunctions_listFileSystems(JNIEnv *env, jclass target, jobject info, jobject result) { - wchar_t* volumeName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); - - jclass info_class = env->GetObjectClass(info); - jmethodID method = env->GetMethodID(info_class, "add", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V"); - - HANDLE handle = FindFirstVolumeW(volumeName, MAX_PATH+1); - if (handle == INVALID_HANDLE_VALUE) { - free(volumeName); - mark_failed_with_errno(env, "could not find first volume", result); - return; - } - - wchar_t* deviceName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); - wchar_t* pathNames = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); - wchar_t* fsName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); - - while(true) { - // Chop off the trailing '\' - size_t len = wcslen(volumeName); - if (len < 5) { - mark_failed_with_message(env, "volume name is too short", result); - break; - } - volumeName[len-1] = L'\0'; - - if (QueryDosDeviceW(&volumeName[4], deviceName, MAX_PATH+1) == 0) { - mark_failed_with_errno(env, "could not query dos device", result); - break; - } - volumeName[len-1] = L'\\'; - - DWORD used; - if (GetVolumePathNamesForVolumeNameW(volumeName, pathNames, MAX_PATH+1, &used) == 0) { - // TODO - try again if the buffer is too small - mark_failed_with_errno(env, "could not query volume paths", result); - break; - } - - wchar_t* cur = pathNames; - if (cur[0] != L'\0') { - // TODO - use GetDriveTypeW() to determine if removable, remote, etc - if(GetVolumeInformationW(cur, NULL, 0, NULL, NULL, NULL, fsName, MAX_PATH+1) == 0) { - if (GetLastError() != ERROR_NOT_READY) { - mark_failed_with_errno(env, "could not query volume information", result); - break; - } - wcscpy(fsName, L"unknown"); - } - for (;cur[0] != L'\0'; cur += wcslen(cur) + 1) { - env->CallVoidMethod(info, method, env->NewString((jchar*)deviceName, wcslen(deviceName)), - env->NewString((jchar*)fsName, wcslen(fsName)), env->NewString((jchar*)cur, wcslen(cur)), JNI_FALSE); - } - } - - if (FindNextVolumeW(handle, volumeName, MAX_PATH) == 0) { - if (GetLastError() != ERROR_NO_MORE_FILES) { - mark_failed_with_errno(env, "could find next volume", result); - } - break; - } - } - free(volumeName); - free(deviceName); - free(pathNames); - free(fsName); - FindVolumeClose(handle); -} - -/* - * Console functions - */ - -HANDLE getHandle(JNIEnv *env, int output, jobject result) { - HANDLE handle = output == 0 ? GetStdHandle(STD_OUTPUT_HANDLE) : GetStdHandle(STD_ERROR_HANDLE); - if (handle == INVALID_HANDLE_VALUE) { - mark_failed_with_errno(env, "could not get console handle", result); - return NULL; - } - return handle; -} - -JNIEXPORT jboolean JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_isConsole(JNIEnv *env, jclass target, jint output, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - HANDLE handle = getHandle(env, output, result); - if (handle == NULL) { - return JNI_FALSE; - } - if (!GetConsoleScreenBufferInfo(handle, &console_info)) { - if (GetLastError() == ERROR_INVALID_HANDLE) { - return JNI_FALSE; - } - mark_failed_with_errno(env, "could not get console buffer", result); - return JNI_FALSE; - } - return JNI_TRUE; -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_getConsoleSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - HANDLE handle = getHandle(env, output, result); - if (handle == NULL) { - mark_failed_with_message(env, "not a console", result); - return; - } - if (!GetConsoleScreenBufferInfo(handle, &console_info)) { - mark_failed_with_errno(env, "could not get console buffer", result); - return; - } - - jclass dimensionClass = env->GetObjectClass(dimension); - jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I"); - env->SetIntField(dimension, widthField, console_info.srWindow.Right - console_info.srWindow.Left + 1); - jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I"); - env->SetIntField(dimension, heightField, console_info.srWindow.Bottom - console_info.srWindow.Top + 1); -} - -HANDLE current_console = NULL; -WORD original_attributes = 0; -WORD current_attributes = 0; - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_initConsole(JNIEnv *env, jclass target, jint output, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - HANDLE handle = getHandle(env, output, result); - if (handle == NULL) { - mark_failed_with_message(env, "not a terminal", result); - return; - } - if (!GetConsoleScreenBufferInfo(handle, &console_info)) { - if (GetLastError() == ERROR_INVALID_HANDLE) { - mark_failed_with_message(env, "not a console", result); - } else { - mark_failed_with_errno(env, "could not get console buffer", result); - } - return; - } - current_console = handle; - original_attributes = console_info.wAttributes; - current_attributes = original_attributes; - Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(env, target, result); -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEnv *env, jclass target, jobject result) { - current_attributes |= FOREGROUND_INTENSITY; - if (!SetConsoleTextAttribute(current_console, current_attributes)) { - mark_failed_with_errno(env, "could not set text attributes", result); - } -} - -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); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_reset(JNIEnv *env, jclass target, jobject result) { - current_attributes = original_attributes; - if (!SetConsoleTextAttribute(current_console, current_attributes)) { - mark_failed_with_errno(env, "could not set text attributes", result); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) { - current_attributes &= ~ (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN); - switch (color) { - case 0: - break; - case 1: - current_attributes |= FOREGROUND_RED; - break; - case 2: - current_attributes |= FOREGROUND_GREEN; - break; - case 3: - current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN; - break; - case 4: - current_attributes |= FOREGROUND_BLUE; - break; - case 5: - current_attributes |= FOREGROUND_RED|FOREGROUND_BLUE; - break; - case 6: - current_attributes |= FOREGROUND_GREEN|FOREGROUND_BLUE; - break; - default: - current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE; - break; - } - - SetConsoleTextAttribute(current_console, current_attributes); - if (!SetConsoleTextAttribute(current_console, current_attributes)) { - mark_failed_with_errno(env, "could not set text attributes", result); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_left(JNIEnv *env, jclass target, jint count, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { - mark_failed_with_errno(env, "could not get console buffer", result); - return; - } - console_info.dwCursorPosition.X -= count; - if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { - mark_failed_with_errno(env, "could not set cursor position", result); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_right(JNIEnv *env, jclass target, jint count, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { - mark_failed_with_errno(env, "could not get console buffer", result); - return; - } - console_info.dwCursorPosition.X += count; - if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { - mark_failed_with_errno(env, "could not set cursor position", result); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_up(JNIEnv *env, jclass target, jint count, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { - mark_failed_with_errno(env, "could not get console buffer", result); - return; - } - console_info.dwCursorPosition.Y -= count; - if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { - mark_failed_with_errno(env, "could not set cursor position", result); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_down(JNIEnv *env, jclass target, jint count, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { - mark_failed_with_errno(env, "could not get console buffer", result); - return; - } - console_info.dwCursorPosition.Y += count; - if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { - mark_failed_with_errno(env, "could not set cursor position", result); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_startLine(JNIEnv *env, jclass target, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { - mark_failed_with_errno(env, "could not get console buffer", result); - return; - } - console_info.dwCursorPosition.X = 0; - if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { - mark_failed_with_errno(env, "could not set cursor position", result); - } -} - -JNIEXPORT void JNICALL -Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_clearToEndOfLine(JNIEnv *env, jclass target, jobject result) { - CONSOLE_SCREEN_BUFFER_INFO console_info; - if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { - mark_failed_with_errno(env, "could not get console buffer", result); - return; - } - DWORD count; - if (!FillConsoleOutputCharacterW(current_console, L' ', console_info.dwSize.X - console_info.dwCursorPosition.X, console_info.dwCursorPosition, &count)) { - mark_failed_with_errno(env, "could not clear console", result); - } -} - -#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. + */ + +#ifdef WIN32 + +#include "native.h" +#include "generic.h" +#include +#include + +/* + * Marks the given result as failed, using the current value of GetLastError() + */ +void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { + mark_failed_with_code(env, message, GetLastError(), result); +} + +jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result) { + if (sizeof(wchar_t) != 2) { + mark_failed_with_message(env, "unexpected size of wchar_t", result); + return NULL; + } + return env->NewString((jchar*)chars, len); +} + +wchar_t* java_to_wchar(JNIEnv *env, jstring string, jobject result) { + jsize len = env->GetStringLength(string); + wchar_t* str = (wchar_t*)malloc(sizeof(wchar_t) * (len+1)); + env->GetStringRegion(string, 0, len, (jchar*)str); + str[len] = L'\0'; + return str; +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo(JNIEnv *env, jclass target, jobject info, jobject result) { + jclass infoClass = env->GetObjectClass(info); + + OSVERSIONINFOEX versionInfo; + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (GetVersionEx((OSVERSIONINFO*)&versionInfo) == 0) { + mark_failed_with_errno(env, "could not get version info", result); + return; + } + + SYSTEM_INFO systemInfo; + GetNativeSystemInfo(&systemInfo); + jstring arch = NULL; + if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { + arch = env->NewStringUTF("amd64"); + } else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) { + arch = env->NewStringUTF("x86"); + } else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) { + arch = env->NewStringUTF("ia64"); + } else { + arch = env->NewStringUTF("unknown"); + } + + jmethodID method = env->GetMethodID(infoClass, "windows", "(IIIZLjava/lang/String;)V"); + env->CallVoidMethod(info, method, versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, + versionInfo.dwBuildNumber, versionInfo.wProductType == VER_NT_WORKSTATION, + arch); +} + +/* + * Process functions + */ + +JNIEXPORT jint JNICALL +Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) { + return GetCurrentProcessId(); +} + +JNIEXPORT jstring JNICALL +Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getWorkingDirectory(JNIEnv *env, jclass target, jobject result) { + DWORD size = GetCurrentDirectoryW(0, NULL); + if (size == 0) { + mark_failed_with_errno(env, "could not determine length of working directory path", result); + return NULL; + } + size = size+1; // Needs to include null character + wchar_t* path = (wchar_t*)malloc(sizeof(wchar_t) * size); + DWORD copied = GetCurrentDirectoryW(size, path); + if (copied == 0) { + free(path); + mark_failed_with_errno(env, "could get working directory path", result); + return NULL; + } + jstring dirName = wchar_to_java(env, path, copied, result); + free(path); + return dirName; +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_setWorkingDirectory(JNIEnv *env, jclass target, jstring dir, jobject result) { + wchar_t* dirPath = java_to_wchar(env, dir, result); + if (dirPath == NULL) { + return; + } + if (!SetCurrentDirectoryW(dirPath)) { + mark_failed_with_errno(env, "could not set current directory", result); + free(dirPath); + return; + } + + free(dirPath); +} + +/* + * File system functions + */ +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_PosixFileSystemFunctions_listFileSystems(JNIEnv *env, jclass target, jobject info, jobject result) { + wchar_t* volumeName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); + + jclass info_class = env->GetObjectClass(info); + jmethodID method = env->GetMethodID(info_class, "add", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V"); + + HANDLE handle = FindFirstVolumeW(volumeName, MAX_PATH+1); + if (handle == INVALID_HANDLE_VALUE) { + free(volumeName); + mark_failed_with_errno(env, "could not find first volume", result); + return; + } + + wchar_t* deviceName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); + wchar_t* pathNames = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); + wchar_t* fsName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); + + while(true) { + // Chop off the trailing '\' + size_t len = wcslen(volumeName); + if (len < 5) { + mark_failed_with_message(env, "volume name is too short", result); + break; + } + volumeName[len-1] = L'\0'; + + if (QueryDosDeviceW(&volumeName[4], deviceName, MAX_PATH+1) == 0) { + mark_failed_with_errno(env, "could not query dos device", result); + break; + } + volumeName[len-1] = L'\\'; + + DWORD used; + if (GetVolumePathNamesForVolumeNameW(volumeName, pathNames, MAX_PATH+1, &used) == 0) { + // TODO - try again if the buffer is too small + mark_failed_with_errno(env, "could not query volume paths", result); + break; + } + + wchar_t* cur = pathNames; + if (cur[0] != L'\0') { + // TODO - use GetDriveTypeW() to determine if removable, remote, etc + if(GetVolumeInformationW(cur, NULL, 0, NULL, NULL, NULL, fsName, MAX_PATH+1) == 0) { + if (GetLastError() != ERROR_NOT_READY) { + mark_failed_with_errno(env, "could not query volume information", result); + break; + } + wcscpy(fsName, L"unknown"); + } + for (;cur[0] != L'\0'; cur += wcslen(cur) + 1) { + env->CallVoidMethod(info, method, + wchar_to_java(env, cur, wcslen(cur), result), + wchar_to_java(env, fsName, wcslen(fsName), result), + wchar_to_java(env, deviceName, wcslen(deviceName), result), + JNI_FALSE); + } + } + + if (FindNextVolumeW(handle, volumeName, MAX_PATH) == 0) { + if (GetLastError() != ERROR_NO_MORE_FILES) { + mark_failed_with_errno(env, "could find next volume", result); + } + break; + } + } + free(volumeName); + free(deviceName); + free(pathNames); + free(fsName); + FindVolumeClose(handle); +} + +/* + * Console functions + */ + +HANDLE getHandle(JNIEnv *env, int output, jobject result) { + HANDLE handle = output == 0 ? GetStdHandle(STD_OUTPUT_HANDLE) : GetStdHandle(STD_ERROR_HANDLE); + if (handle == INVALID_HANDLE_VALUE) { + mark_failed_with_errno(env, "could not get console handle", result); + return NULL; + } + return handle; +} + +JNIEXPORT jboolean JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_isConsole(JNIEnv *env, jclass target, jint output, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + HANDLE handle = getHandle(env, output, result); + if (handle == NULL) { + return JNI_FALSE; + } + if (!GetConsoleScreenBufferInfo(handle, &console_info)) { + if (GetLastError() == ERROR_INVALID_HANDLE) { + return JNI_FALSE; + } + mark_failed_with_errno(env, "could not get console buffer", result); + return JNI_FALSE; + } + return JNI_TRUE; +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_getConsoleSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + HANDLE handle = getHandle(env, output, result); + if (handle == NULL) { + mark_failed_with_message(env, "not a console", result); + return; + } + if (!GetConsoleScreenBufferInfo(handle, &console_info)) { + mark_failed_with_errno(env, "could not get console buffer", result); + return; + } + + jclass dimensionClass = env->GetObjectClass(dimension); + jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I"); + env->SetIntField(dimension, widthField, console_info.srWindow.Right - console_info.srWindow.Left + 1); + jfieldID heightField = env->GetFieldID(dimensionClass, "rows", "I"); + env->SetIntField(dimension, heightField, console_info.srWindow.Bottom - console_info.srWindow.Top + 1); +} + +HANDLE current_console = NULL; +WORD original_attributes = 0; +WORD current_attributes = 0; + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_initConsole(JNIEnv *env, jclass target, jint output, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + HANDLE handle = getHandle(env, output, result); + if (handle == NULL) { + mark_failed_with_message(env, "not a terminal", result); + return; + } + if (!GetConsoleScreenBufferInfo(handle, &console_info)) { + if (GetLastError() == ERROR_INVALID_HANDLE) { + mark_failed_with_message(env, "not a console", result); + } else { + mark_failed_with_errno(env, "could not get console buffer", result); + } + return; + } + current_console = handle; + original_attributes = console_info.wAttributes; + current_attributes = original_attributes; + Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(env, target, result); +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEnv *env, jclass target, jobject result) { + current_attributes |= FOREGROUND_INTENSITY; + if (!SetConsoleTextAttribute(current_console, current_attributes)) { + mark_failed_with_errno(env, "could not set text attributes", result); + } +} + +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); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_reset(JNIEnv *env, jclass target, jobject result) { + current_attributes = original_attributes; + if (!SetConsoleTextAttribute(current_console, current_attributes)) { + mark_failed_with_errno(env, "could not set text attributes", result); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) { + current_attributes &= ~ (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN); + switch (color) { + case 0: + break; + case 1: + current_attributes |= FOREGROUND_RED; + break; + case 2: + current_attributes |= FOREGROUND_GREEN; + break; + case 3: + current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN; + break; + case 4: + current_attributes |= FOREGROUND_BLUE; + break; + case 5: + current_attributes |= FOREGROUND_RED|FOREGROUND_BLUE; + break; + case 6: + current_attributes |= FOREGROUND_GREEN|FOREGROUND_BLUE; + break; + default: + current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE; + break; + } + + SetConsoleTextAttribute(current_console, current_attributes); + if (!SetConsoleTextAttribute(current_console, current_attributes)) { + mark_failed_with_errno(env, "could not set text attributes", result); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_left(JNIEnv *env, jclass target, jint count, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { + mark_failed_with_errno(env, "could not get console buffer", result); + return; + } + console_info.dwCursorPosition.X -= count; + if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { + mark_failed_with_errno(env, "could not set cursor position", result); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_right(JNIEnv *env, jclass target, jint count, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { + mark_failed_with_errno(env, "could not get console buffer", result); + return; + } + console_info.dwCursorPosition.X += count; + if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { + mark_failed_with_errno(env, "could not set cursor position", result); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_up(JNIEnv *env, jclass target, jint count, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { + mark_failed_with_errno(env, "could not get console buffer", result); + return; + } + console_info.dwCursorPosition.Y -= count; + if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { + mark_failed_with_errno(env, "could not set cursor position", result); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_down(JNIEnv *env, jclass target, jint count, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { + mark_failed_with_errno(env, "could not get console buffer", result); + return; + } + console_info.dwCursorPosition.Y += count; + if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { + mark_failed_with_errno(env, "could not set cursor position", result); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_startLine(JNIEnv *env, jclass target, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { + mark_failed_with_errno(env, "could not get console buffer", result); + return; + } + console_info.dwCursorPosition.X = 0; + if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { + mark_failed_with_errno(env, "could not set cursor position", result); + } +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_clearToEndOfLine(JNIEnv *env, jclass target, jobject result) { + CONSOLE_SCREEN_BUFFER_INFO console_info; + if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { + mark_failed_with_errno(env, "could not get console buffer", result); + return; + } + DWORD count; + if (!FillConsoleOutputCharacterW(current_console, L' ', console_info.dwSize.X - console_info.dwCursorPosition.X, console_info.dwCursorPosition, &count)) { + mark_failed_with_errno(env, "could not clear console", result); + } +} + +#endif diff --git a/src/main/headers/generic.h b/src/main/headers/generic.h index b5a1dde..1cebf03 100755 --- a/src/main/headers/generic.h +++ b/src/main/headers/generic.h @@ -1,90 +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 - -#ifdef __cplusplus -extern "C" { -#endif - -#define NATIVE_VERSION 12 - -/* - * 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, jobject result); - -typedef struct wchar_struct { - // Not NULL terminated - wchar_t* chars; - // number of characters in the string - size_t len; - jstring source; - JNIEnv *env; -} wchar_str; - -/* - * Converts the given Java string to a wchar_str. Should call wchar_str_free() when finished. - * - * Returns NULL on failure. - */ -extern wchar_str* -java_to_wchar_str(JNIEnv *env, jstring string, jobject result); - -/* - * Releases resources used by the given string. - */ -extern void wchar_str_free(wchar_str* str); - -/* - * 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 char_str. 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 + +#ifdef __cplusplus +extern "C" { +#endif + +#define NATIVE_VERSION 12 + +/* + * 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, 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/PosixFileTest.groovy index bf8ab5a..acd138d 100755 --- a/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/PosixFileTest.groovy @@ -33,23 +33,16 @@ class PosixFileTest extends Specification { } def "can set mode on a file"() { - def testFile = tmpDir.newFile("test.txt") + def testFile = tmpDir.newFile(fileName) when: file.setMode(testFile, 0740) then: file.getMode(testFile) == 0740 - } - def "can set mode on a file with unicode in its name"() { - def testFile = tmpDir.newFile("test\u03b1.txt") - - when: - file.setMode(testFile, 0740) - - then: - file.getMode(testFile) == 0740 + where: + fileName << ["test.txt", "test\u03b1.txt"] } def "cannot set mode on file that does not exist"() { diff --git a/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy index 0819bdd..39a7a5a 100755 --- a/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy @@ -1,58 +1,61 @@ -/* - * 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 org.junit.Rule -import org.junit.rules.TemporaryFolder -import spock.lang.Specification - -class ProcessTest extends Specification { - @Rule TemporaryFolder tmpDir - final Process process = Native.get(Process.class) - - def "caches process instance"() { - expect: - Native.get(Process.class) == process - } - - def "can get PID"() { - expect: - process.getProcessId() != 0 - } - - def "can change working directory"() { - def newDir = tmpDir.newFolder("dir").canonicalFile - - when: - def original = process.workingDirectory - - then: - original == new File(".").canonicalFile - original == new File(System.getProperty("user.dir")) - - when: - process.workingDirectory = newDir - - then: - process.workingDirectory == newDir - new File(".").canonicalFile == newDir - new File(System.getProperty("user.dir")) == newDir - - cleanup: - process.workingDirectory = original - } -} +/* + * 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 org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +class ProcessTest extends Specification { + @Rule TemporaryFolder tmpDir + final Process process = Native.get(Process.class) + + def "caches process instance"() { + expect: + Native.get(Process.class) == process + } + + def "can get PID"() { + expect: + process.getProcessId() != 0 + } + + def "can get and change working directory"() { + def newDir = tmpDir.newFolder(dir).canonicalFile + + when: + def original = process.workingDirectory + + then: + original == new File(".").canonicalFile + original == new File(System.getProperty("user.dir")) + + when: + process.workingDirectory = newDir + + then: + process.workingDirectory == newDir + new File(".").canonicalFile == newDir + new File(System.getProperty("user.dir")) == newDir + + cleanup: + process.workingDirectory = original + + where: + dir << ['dir', 'dir\u03b1'] + } +}