Implemented Process working directory methods on windows, and fixed file system details.

This commit is contained in:
Adam Murdoch
2013-02-07 16:58:11 +11:00
parent 27bc7d8a76
commit 9e0493f94e
5 changed files with 763 additions and 728 deletions

425
readme.md
View File

@@ -1,213 +1,212 @@
# Native-platform: Java bindings for various native APIs # 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. 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. These APIs support Java 5 and later. Some of these APIs overlap with APIs available in later Java versions.
## Available bindings ## Available bindings
### System information ### System information
* Get kernel name and version. * Get kernel name and version.
* Get machine architecture. * Get machine architecture.
### Processes ### Processes
* Get the PID of the current process. * Get the PID of the current process.
* Get and set the process working directory. * Get and set the process working directory.
### Terminal and console ### Terminal and console
These bindings work for both the UNIX terminal and the Windows console: These bindings work for both the UNIX terminal and the Windows console:
* Determine if stdout/stderr are attached to a terminal. * Determine if stdout/stderr are attached to a terminal.
* Query the terminal size. * Query the terminal size.
* Switch between bold and normal mode on the terminal. * Switch between bold and normal mode on the terminal.
* Change foreground color on the terminal. * Change foreground color on the terminal.
* Move terminal cursor up, down, left, right, start of line. * Move terminal cursor up, down, left, right, start of line.
* Clear to end of line. * Clear to end of line.
### File systems ### File systems
* Get and set UNIX file mode. * Get and set UNIX file mode.
* Create and read symbolic links. * Create and read symbolic links.
* List the available file systems on the machine * List the available file systems on the machine
* Query file system mount point. * Query file system mount point.
* Query file system type. * Query file system type.
* Query file system device name. * Query file system device name.
* Query whether a file system is local or remote. * Query whether a file system is local or remote.
## Supported platforms ## 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 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) * OS X 10.7.4, 10.8 (x86_64), 10.6.7 (i386)
* Ubunutu 12.04 (amd64), 8.04.4 (i386, amd64) * Ubunutu 12.04 (amd64), 8.04.4 (i386, amd64)
* Solaris 11 (x86) * Solaris 11 (x86)
* Windows 7 (x64), XP (x86) * Windows 7 (x64), XP (x86)
## Using ## Using
Include `native-platform.jar` and `native-platform-${os}-${arch}.jar` in your classpath. From Gradle, you can do Include `native-platform.jar` and `native-platform-${os}-${arch}.jar` in your classpath. From Gradle, you can do
this: this:
repositories { repositories {
maven { url "http://repo.gradle.org/gradle/libs-releases-local" } maven { url "http://repo.gradle.org/gradle/libs-releases-local" }
} }
dependencies { dependencies {
compile "net.rubygrapefruit:native-platform:0.3" compile "net.rubygrapefruit:native-platform:0.3"
} }
You can also download [here](http://repo.gradle.org/gradle/libs-releases-local/net/rubygrapefruit/) You can also download [here](http://repo.gradle.org/gradle/libs-releases-local/net/rubygrapefruit/)
Some sample code to use the terminal: Some sample code to use the terminal:
import net.rubygrapefruit.platform.Native; import net.rubygrapefruit.platform.Native;
import net.rubygrapefruit.platform.Terminals; import net.rubygrapefruit.platform.Terminals;
import net.rubygrapefruit.platform.Terminal; import net.rubygrapefruit.platform.Terminal;
import static net.rubygrapefruit.platform.Terminals.Output.*; import static net.rubygrapefruit.platform.Terminals.Output.*;
Terminals terminals = Native.get(Terminals.class); Terminals terminals = Native.get(Terminals.class);
// check if terminal // check if terminal
terminals.isTerminal(Stdout); terminals.isTerminal(Stdout);
// use terminal // use terminal
Terminal stdout = terminals.getTerminal(Stdout); Terminal stdout = terminals.getTerminal(Stdout);
stdout.bold(); stdout.bold();
System.out.println("bold text"); System.out.println("bold text");
## Changes ## Changes
### 0.3 ### 0.3
* Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to Rene Gr<47>schke. * Fixes to work with 64-bit OpenJDK 7 on Mac OS X. Thanks to Rene Gr<47>schke.
### 0.2 ### 0.2
* Fixes to make native library extraction multi-process safe. * Fixes to make native library extraction multi-process safe.
* Fixes to windows terminal detection and reset. * Fixes to windows terminal detection and reset.
### 0.1 ### 0.1
* Initial release. * Initial release.
# Development # Development
## Building ## 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 directory.
### Ubuntu ### 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 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. 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 #### 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 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. same machine.
You need to install the `gcc-multilib` and `g++-multilib` packages to pick up i386 support. 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. You need to install the `lib32ncurses5-dev` package to pick up the ncurses i386 version.
To build, include `-Pmultiarch` on the command-line. To build, include `-Pmultiarch` on the command-line.
### Windows ### Windows
You need to install Visual studio, and build from a Visual studio command prompt. You need to install Visual studio, and build from a Visual studio command prompt.
### OS X ### 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 tools for this.
### Solaris ### Solaris
For Solaris 11, you need to install the `development/gcc-45` and `system/header` packages. For Solaris 11, you need to install the `development/gcc-45` and `system/header` packages.
## Running ## Running
Run `gradle installApp` to install the test application into `test-app/build/install/native-platform-test`. Or 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 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. You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application.
# Releasing # Releasing
1. Create a tag and push. 1. Create a tag and push.
2. Build each variant: 2. Build each variant:
1. Checkout tag. 1. Checkout tag.
2. `./gradlew clean :test :uploadJni -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>` 2. `./gradlew clean :test :uploadJni -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
* OS X universal * OS X universal
* Linux i386, using Ubunutu 8.04 * Linux i386, using Ubunutu 8.04
* Linux amd64, using Ubunutu 8.04 * Linux amd64, using Ubunutu 8.04
* Windows x86, using VC++ 2010 * Windows x86, using VC++ 2010
* Windows x64 * Windows x64
3. Build Java library and test app: 3. Build Java library and test app:
1. Checkout tag. 1. Checkout tag.
2. `./gradlew clean :test :uploadArchives testApp:uploadArchives -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>` 2. `./gradlew clean :test :uploadArchives testApp:uploadArchives -Prelease -PartifactoryUserName=<> -PartifactoryPassword=<>`
4. Checkout master 4. Checkout master
5. Increment version number in `build.gradle` and this readme. 5. Increment version number in `build.gradle` and this readme.
## Testing ## Testing
* Test on IBM JVM. * Test on IBM JVM.
* Test on Java 5, 6, 7. * Test on Java 5, 6, 7.
* Test on Windows 7, Windows XP * Test on Windows 7, Windows XP
## TODO ## TODO
### Fixes ### Fixes
* Posix: allow terminal to be detected when ncurses cannot be loaded * Posix: allow terminal to be detected when ncurses cannot be loaded
* Windows: fix detection of shared drive under VMWare fusion and Windows XP * Windows: fix detection of shared drive under VMWare fusion and Windows XP
* Linux: detect remote filesystems. * Linux: detect remote filesystems.
* Solaris: fix unicode file name handling. * Solaris: fix unicode file name handling.
* Solaris: fail for unsupported architecture. * Solaris: fail for unsupported architecture.
* Solaris: build 32 bit and 64 bit libraries. * Solaris: build 32 bit and 64 bit libraries.
* Freebsd: finish port. * Freebsd: finish port.
* Freebsd: fail for unsupported architecture. * Freebsd: fail for unsupported architecture.
* Freebsd: build 32 bit and 64 bit libraries. * Freebsd: build 32 bit and 64 bit libraries.
### Improvements ### Improvements
* Use wchar_to_java() for windows system and file system info. * Use wchar_to_java() for windows system and file system info.
* Test network file systems on Mac, Linux, Windows * Test network file systems on Mac, Linux, Windows
* Test mount points on Windows * Test mount points on Windows
* Cache class, method and field lookups (in particular for String conversions). * Cache class, method and field lookups (in particular for String conversions).
* Determine C charset once at startup * Determine C charset once at startup
* Change readLink() implementation so that it does not need to NULL terminate the encoded content * Change readLink() implementation so that it does not need to NULL terminate the encoded content
* Don't use NewStringUTF() anywhere * 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.
* Use iconv() to convert from C char string to UTF-16 when converting from C char string to Java String. * Support for cygwin terminal
* Support for cygwin terminal * Use TERM=xtermc instead of TERM=xterm on Solaris.
* Use TERM=xtermc instead of TERM=xterm on Solaris. * Add diagnostics for terminal.
* Add diagnostics for terminal. * Split out separate native library for terminal handling.
* Split out separate native library for terminal handling. * Version each native interface separately.
* Version each native interface separately. * String names for errno values.
* String names for errno values. * Split into multiple projects.
* Split into multiple projects. * Convert to c.
* Convert to c. * Use fully decomposed form for unicode file names on hfs+ filesystems.
* Use fully decomposed form for unicode file names on hfs+ filesystems. * Extend FileSystem to deal with removable media.
* 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
* 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.
System.out/System.err point to. * Add a Terminal implementation that uses ANSI control codes. Use this on UNIX platforms when TERM != 'dumb' and
* Add a Terminal implementation that uses ANSI control codes. Use this on UNIX platforms when TERM != 'dumb' and libncurses cannot be loaded.
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
* 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.
rightmost character position.
### Ideas
### Ideas
* Expose platform-specific HTTP proxy configuration. Query registry on windows to determine IE settings.
* 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 native named semaphores, mutexes and condition variables (CreateMutex, CreateSemaphore, CreateEvent, semget, sem_open, etc). * Expose infromation about network interfaces.
* Expose infromation about network interfaces. * Fire events when filesystems or network interfaces change in some way.
* Fire events when filesystems or network interfaces change in some way. * Fire events when terminal size changes.
* Fire events when terminal size changes. * Fire events when files change.
* Fire events when files change. * Expose system keystores and authentication services.
* Expose system keystores and authentication services. * Expose a mechanism for generating a temporary directory.
* Expose a mechanism for generating a temporary directory.

View File

@@ -1,357 +1,411 @@
/* /*
* Copyright 2012 Adam Murdoch * Copyright 2012 Adam Murdoch
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#ifdef WIN32 #ifdef WIN32
#include "native.h" #include "native.h"
#include "generic.h" #include "generic.h"
#include <windows.h> #include <windows.h>
#include <wchar.h> #include <wchar.h>
/* /*
* Marks the given result as failed, using the current value of GetLastError() * Marks the given result as failed, using the current value of GetLastError()
*/ */
void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) { void mark_failed_with_errno(JNIEnv *env, const char* message, jobject result) {
mark_failed_with_code(env, message, GetLastError(), result); mark_failed_with_code(env, message, GetLastError(), result);
} }
JNIEXPORT void JNICALL jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result) {
Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo(JNIEnv *env, jclass target, jobject info, jobject result) { if (sizeof(wchar_t) != 2) {
jclass infoClass = env->GetObjectClass(info); mark_failed_with_message(env, "unexpected size of wchar_t", result);
return NULL;
OSVERSIONINFOEX versionInfo; }
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); return env->NewString((jchar*)chars, len);
if (GetVersionEx((OSVERSIONINFO*)&versionInfo) == 0) { }
mark_failed_with_errno(env, "could not get version info", result);
return; 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));
SYSTEM_INFO systemInfo; env->GetStringRegion(string, 0, len, (jchar*)str);
GetNativeSystemInfo(&systemInfo); str[len] = L'\0';
jstring arch = NULL; return str;
if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { }
arch = env->NewStringUTF("amd64");
} else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) { JNIEXPORT void JNICALL
arch = env->NewStringUTF("x86"); Java_net_rubygrapefruit_platform_internal_jni_NativeLibraryFunctions_getSystemInfo(JNIEnv *env, jclass target, jobject info, jobject result) {
} else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) { jclass infoClass = env->GetObjectClass(info);
arch = env->NewStringUTF("ia64");
} else { OSVERSIONINFOEX versionInfo;
arch = env->NewStringUTF("unknown"); versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
} if (GetVersionEx((OSVERSIONINFO*)&versionInfo) == 0) {
mark_failed_with_errno(env, "could not get version info", result);
jmethodID method = env->GetMethodID(infoClass, "windows", "(IIIZLjava/lang/String;)V"); return;
env->CallVoidMethod(info, method, versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, }
versionInfo.dwBuildNumber, versionInfo.wProductType == VER_NT_WORKSTATION,
arch); SYSTEM_INFO systemInfo;
} GetNativeSystemInfo(&systemInfo);
jstring arch = NULL;
/* if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
* Process functions arch = env->NewStringUTF("amd64");
*/ } else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) {
arch = env->NewStringUTF("x86");
JNIEXPORT jint JNICALL } else if (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) {
Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) { arch = env->NewStringUTF("ia64");
return GetCurrentProcessId(); } else {
} arch = env->NewStringUTF("unknown");
}
/*
* File system functions jmethodID method = env->GetMethodID(infoClass, "windows", "(IIIZLjava/lang/String;)V");
*/ env->CallVoidMethod(info, method, versionInfo.dwMajorVersion, versionInfo.dwMinorVersion,
JNIEXPORT void JNICALL versionInfo.dwBuildNumber, versionInfo.wProductType == VER_NT_WORKSTATION,
Java_net_rubygrapefruit_platform_internal_jni_PosixFileSystemFunctions_listFileSystems(JNIEnv *env, jclass target, jobject info, jobject result) { arch);
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"); * Process functions
*/
HANDLE handle = FindFirstVolumeW(volumeName, MAX_PATH+1);
if (handle == INVALID_HANDLE_VALUE) { JNIEXPORT jint JNICALL
free(volumeName); Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getPid(JNIEnv *env, jclass target) {
mark_failed_with_errno(env, "could not find first volume", result); return GetCurrentProcessId();
return; }
}
JNIEXPORT jstring JNICALL
wchar_t* deviceName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getWorkingDirectory(JNIEnv *env, jclass target, jobject result) {
wchar_t* pathNames = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); DWORD size = GetCurrentDirectoryW(0, NULL);
wchar_t* fsName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1)); if (size == 0) {
mark_failed_with_errno(env, "could not determine length of working directory path", result);
while(true) { return NULL;
// Chop off the trailing '\' }
size_t len = wcslen(volumeName); size = size+1; // Needs to include null character
if (len < 5) { wchar_t* path = (wchar_t*)malloc(sizeof(wchar_t) * size);
mark_failed_with_message(env, "volume name is too short", result); DWORD copied = GetCurrentDirectoryW(size, path);
break; if (copied == 0) {
} free(path);
volumeName[len-1] = L'\0'; mark_failed_with_errno(env, "could get working directory path", result);
return NULL;
if (QueryDosDeviceW(&volumeName[4], deviceName, MAX_PATH+1) == 0) { }
mark_failed_with_errno(env, "could not query dos device", result); jstring dirName = wchar_to_java(env, path, copied, result);
break; free(path);
} return dirName;
volumeName[len-1] = L'\\'; }
DWORD used; JNIEXPORT void JNICALL
if (GetVolumePathNamesForVolumeNameW(volumeName, pathNames, MAX_PATH+1, &used) == 0) { Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_setWorkingDirectory(JNIEnv *env, jclass target, jstring dir, jobject result) {
// TODO - try again if the buffer is too small wchar_t* dirPath = java_to_wchar(env, dir, result);
mark_failed_with_errno(env, "could not query volume paths", result); if (dirPath == NULL) {
break; return;
} }
if (!SetCurrentDirectoryW(dirPath)) {
wchar_t* cur = pathNames; mark_failed_with_errno(env, "could not set current directory", result);
if (cur[0] != L'\0') { free(dirPath);
// TODO - use GetDriveTypeW() to determine if removable, remote, etc return;
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); free(dirPath);
break; }
}
wcscpy(fsName, L"unknown"); /*
} * File system functions
for (;cur[0] != L'\0'; cur += wcslen(cur) + 1) { */
env->CallVoidMethod(info, method, env->NewString((jchar*)deviceName, wcslen(deviceName)), JNIEXPORT void JNICALL
env->NewString((jchar*)fsName, wcslen(fsName)), env->NewString((jchar*)cur, wcslen(cur)), JNI_FALSE); 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);
if (FindNextVolumeW(handle, volumeName, MAX_PATH) == 0) { jmethodID method = env->GetMethodID(info_class, "add", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V");
if (GetLastError() != ERROR_NO_MORE_FILES) {
mark_failed_with_errno(env, "could find next volume", result); HANDLE handle = FindFirstVolumeW(volumeName, MAX_PATH+1);
} if (handle == INVALID_HANDLE_VALUE) {
break; free(volumeName);
} mark_failed_with_errno(env, "could not find first volume", result);
} return;
free(volumeName); }
free(deviceName);
free(pathNames); wchar_t* deviceName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
free(fsName); wchar_t* pathNames = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
FindVolumeClose(handle); wchar_t* fsName = (wchar_t*)malloc(sizeof(wchar_t) * (MAX_PATH+1));
}
while(true) {
/* // Chop off the trailing '\'
* Console functions size_t len = wcslen(volumeName);
*/ if (len < 5) {
mark_failed_with_message(env, "volume name is too short", result);
HANDLE getHandle(JNIEnv *env, int output, jobject result) { break;
HANDLE handle = output == 0 ? GetStdHandle(STD_OUTPUT_HANDLE) : GetStdHandle(STD_ERROR_HANDLE); }
if (handle == INVALID_HANDLE_VALUE) { volumeName[len-1] = L'\0';
mark_failed_with_errno(env, "could not get console handle", result);
return NULL; if (QueryDosDeviceW(&volumeName[4], deviceName, MAX_PATH+1) == 0) {
} mark_failed_with_errno(env, "could not query dos device", result);
return handle; break;
} }
volumeName[len-1] = L'\\';
JNIEXPORT jboolean JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_isConsole(JNIEnv *env, jclass target, jint output, jobject result) { DWORD used;
CONSOLE_SCREEN_BUFFER_INFO console_info; if (GetVolumePathNamesForVolumeNameW(volumeName, pathNames, MAX_PATH+1, &used) == 0) {
HANDLE handle = getHandle(env, output, result); // TODO - try again if the buffer is too small
if (handle == NULL) { mark_failed_with_errno(env, "could not query volume paths", result);
return JNI_FALSE; break;
} }
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
if (GetLastError() == ERROR_INVALID_HANDLE) { wchar_t* cur = pathNames;
return JNI_FALSE; if (cur[0] != L'\0') {
} // TODO - use GetDriveTypeW() to determine if removable, remote, etc
mark_failed_with_errno(env, "could not get console buffer", result); if(GetVolumeInformationW(cur, NULL, 0, NULL, NULL, NULL, fsName, MAX_PATH+1) == 0) {
return JNI_FALSE; if (GetLastError() != ERROR_NOT_READY) {
} mark_failed_with_errno(env, "could not query volume information", result);
return JNI_TRUE; break;
} }
wcscpy(fsName, L"unknown");
JNIEXPORT void JNICALL }
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_getConsoleSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) { for (;cur[0] != L'\0'; cur += wcslen(cur) + 1) {
CONSOLE_SCREEN_BUFFER_INFO console_info; env->CallVoidMethod(info, method,
HANDLE handle = getHandle(env, output, result); wchar_to_java(env, cur, wcslen(cur), result),
if (handle == NULL) { wchar_to_java(env, fsName, wcslen(fsName), result),
mark_failed_with_message(env, "not a console", result); wchar_to_java(env, deviceName, wcslen(deviceName), result),
return; JNI_FALSE);
} }
if (!GetConsoleScreenBufferInfo(handle, &console_info)) { }
mark_failed_with_errno(env, "could not get console buffer", result);
return; if (FindNextVolumeW(handle, volumeName, MAX_PATH) == 0) {
} if (GetLastError() != ERROR_NO_MORE_FILES) {
mark_failed_with_errno(env, "could find next volume", result);
jclass dimensionClass = env->GetObjectClass(dimension); }
jfieldID widthField = env->GetFieldID(dimensionClass, "cols", "I"); break;
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); free(volumeName);
} free(deviceName);
free(pathNames);
HANDLE current_console = NULL; free(fsName);
WORD original_attributes = 0; FindVolumeClose(handle);
WORD current_attributes = 0; }
JNIEXPORT void JNICALL /*
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_initConsole(JNIEnv *env, jclass target, jint output, jobject result) { * Console functions
CONSOLE_SCREEN_BUFFER_INFO console_info; */
HANDLE handle = getHandle(env, output, result);
if (handle == NULL) { HANDLE getHandle(JNIEnv *env, int output, jobject result) {
mark_failed_with_message(env, "not a terminal", result); HANDLE handle = output == 0 ? GetStdHandle(STD_OUTPUT_HANDLE) : GetStdHandle(STD_ERROR_HANDLE);
return; if (handle == INVALID_HANDLE_VALUE) {
} mark_failed_with_errno(env, "could not get console handle", result);
if (!GetConsoleScreenBufferInfo(handle, &console_info)) { return NULL;
if (GetLastError() == ERROR_INVALID_HANDLE) { }
mark_failed_with_message(env, "not a console", result); return handle;
} else { }
mark_failed_with_errno(env, "could not get console buffer", result);
} JNIEXPORT jboolean JNICALL
return; Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_isConsole(JNIEnv *env, jclass target, jint output, jobject result) {
} CONSOLE_SCREEN_BUFFER_INFO console_info;
current_console = handle; HANDLE handle = getHandle(env, output, result);
original_attributes = console_info.wAttributes; if (handle == NULL) {
current_attributes = original_attributes; return JNI_FALSE;
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(env, target, result); }
} if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
if (GetLastError() == ERROR_INVALID_HANDLE) {
JNIEXPORT void JNICALL return JNI_FALSE;
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEnv *env, jclass target, jobject result) { }
current_attributes |= FOREGROUND_INTENSITY; mark_failed_with_errno(env, "could not get console buffer", result);
if (!SetConsoleTextAttribute(current_console, current_attributes)) { return JNI_FALSE;
mark_failed_with_errno(env, "could not set text attributes", result); }
} return JNI_TRUE;
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(JNIEnv *env, jclass target, jobject result) { Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_getConsoleSize(JNIEnv *env, jclass target, jint output, jobject dimension, jobject result) {
current_attributes &= ~FOREGROUND_INTENSITY; CONSOLE_SCREEN_BUFFER_INFO console_info;
SetConsoleTextAttribute(current_console, current_attributes); HANDLE handle = getHandle(env, output, result);
if (!SetConsoleTextAttribute(current_console, current_attributes)) { if (handle == NULL) {
mark_failed_with_errno(env, "could not set text attributes", result); mark_failed_with_message(env, "not a console", result);
} return;
} }
if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
JNIEXPORT void JNICALL mark_failed_with_errno(env, "could not get console buffer", result);
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_reset(JNIEnv *env, jclass target, jobject result) { return;
current_attributes = original_attributes; }
if (!SetConsoleTextAttribute(current_console, current_attributes)) {
mark_failed_with_errno(env, "could not set text attributes", result); 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");
JNIEXPORT void JNICALL env->SetIntField(dimension, heightField, console_info.srWindow.Bottom - console_info.srWindow.Top + 1);
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) { HANDLE current_console = NULL;
case 0: WORD original_attributes = 0;
break; WORD current_attributes = 0;
case 1:
current_attributes |= FOREGROUND_RED; JNIEXPORT void JNICALL
break; Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_initConsole(JNIEnv *env, jclass target, jint output, jobject result) {
case 2: CONSOLE_SCREEN_BUFFER_INFO console_info;
current_attributes |= FOREGROUND_GREEN; HANDLE handle = getHandle(env, output, result);
break; if (handle == NULL) {
case 3: mark_failed_with_message(env, "not a terminal", result);
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN; return;
break; }
case 4: if (!GetConsoleScreenBufferInfo(handle, &console_info)) {
current_attributes |= FOREGROUND_BLUE; if (GetLastError() == ERROR_INVALID_HANDLE) {
break; mark_failed_with_message(env, "not a console", result);
case 5: } else {
current_attributes |= FOREGROUND_RED|FOREGROUND_BLUE; mark_failed_with_errno(env, "could not get console buffer", result);
break; }
case 6: return;
current_attributes |= FOREGROUND_GREEN|FOREGROUND_BLUE; }
break; current_console = handle;
default: original_attributes = console_info.wAttributes;
current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE; current_attributes = original_attributes;
break; Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(env, target, result);
} }
SetConsoleTextAttribute(current_console, current_attributes); JNIEXPORT void JNICALL
if (!SetConsoleTextAttribute(current_console, current_attributes)) { Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_bold(JNIEnv *env, jclass target, jobject result) {
mark_failed_with_errno(env, "could not set text attributes", 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_left(JNIEnv *env, jclass target, jint count, jobject result) {
CONSOLE_SCREEN_BUFFER_INFO console_info; JNIEXPORT void JNICALL
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_normal(JNIEnv *env, jclass target, jobject result) {
mark_failed_with_errno(env, "could not get console buffer", result); current_attributes &= ~FOREGROUND_INTENSITY;
return; SetConsoleTextAttribute(current_console, current_attributes);
} if (!SetConsoleTextAttribute(current_console, current_attributes)) {
console_info.dwCursorPosition.X -= count; mark_failed_with_errno(env, "could not set text attributes", result);
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_reset(JNIEnv *env, jclass target, jobject result) {
JNIEXPORT void JNICALL current_attributes = original_attributes;
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_right(JNIEnv *env, jclass target, jint count, jobject result) { if (!SetConsoleTextAttribute(current_console, current_attributes)) {
CONSOLE_SCREEN_BUFFER_INFO console_info; mark_failed_with_errno(env, "could not set text attributes", result);
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { }
mark_failed_with_errno(env, "could not get console buffer", result); }
return;
} JNIEXPORT void JNICALL
console_info.dwCursorPosition.X += count; Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_foreground(JNIEnv *env, jclass target, jint color, jobject result) {
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { current_attributes &= ~ (FOREGROUND_BLUE|FOREGROUND_RED|FOREGROUND_GREEN);
mark_failed_with_errno(env, "could not set cursor position", result); switch (color) {
} case 0:
} break;
case 1:
JNIEXPORT void JNICALL current_attributes |= FOREGROUND_RED;
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_up(JNIEnv *env, jclass target, jint count, jobject result) { break;
CONSOLE_SCREEN_BUFFER_INFO console_info; case 2:
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { current_attributes |= FOREGROUND_GREEN;
mark_failed_with_errno(env, "could not get console buffer", result); break;
return; case 3:
} current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN;
console_info.dwCursorPosition.Y -= count; break;
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { case 4:
mark_failed_with_errno(env, "could not set cursor position", result); current_attributes |= FOREGROUND_BLUE;
} break;
} case 5:
current_attributes |= FOREGROUND_RED|FOREGROUND_BLUE;
JNIEXPORT void JNICALL break;
Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_down(JNIEnv *env, jclass target, jint count, jobject result) { case 6:
CONSOLE_SCREEN_BUFFER_INFO console_info; current_attributes |= FOREGROUND_GREEN|FOREGROUND_BLUE;
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { break;
mark_failed_with_errno(env, "could not get console buffer", result); default:
return; current_attributes |= FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
} break;
console_info.dwCursorPosition.Y += count; }
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
mark_failed_with_errno(env, "could not set cursor position", result); 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_startLine(JNIEnv *env, jclass target, jobject result) {
CONSOLE_SCREEN_BUFFER_INFO console_info; JNIEXPORT void JNICALL
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_left(JNIEnv *env, jclass target, jint count, jobject result) {
mark_failed_with_errno(env, "could not get console buffer", result); CONSOLE_SCREEN_BUFFER_INFO console_info;
return; if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
} mark_failed_with_errno(env, "could not get console buffer", result);
console_info.dwCursorPosition.X = 0; return;
if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) { }
mark_failed_with_errno(env, "could not set cursor position", result); 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_clearToEndOfLine(JNIEnv *env, jclass target, jobject result) {
CONSOLE_SCREEN_BUFFER_INFO console_info; JNIEXPORT void JNICALL
if (!GetConsoleScreenBufferInfo(current_console, &console_info)) { Java_net_rubygrapefruit_platform_internal_jni_WindowsConsoleFunctions_right(JNIEnv *env, jclass target, jint count, jobject result) {
mark_failed_with_errno(env, "could not get console buffer", result); CONSOLE_SCREEN_BUFFER_INFO console_info;
return; if (!GetConsoleScreenBufferInfo(current_console, &console_info)) {
} mark_failed_with_errno(env, "could not get console buffer", result);
DWORD count; return;
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); console_info.dwCursorPosition.X += count;
} if (!SetConsoleCursorPosition(current_console, console_info.dwCursorPosition)) {
} mark_failed_with_errno(env, "could not set cursor position", result);
}
#endif }
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

View File

@@ -1,90 +1,76 @@
/* /*
* Copyright 2012 Adam Murdoch * Copyright 2012 Adam Murdoch
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#ifndef __INCLUDE_GENERIC_H__ #ifndef __INCLUDE_GENERIC_H__
#define __INCLUDE_GENERIC_H__ #define __INCLUDE_GENERIC_H__
#include <jni.h> #include <jni.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#define NATIVE_VERSION 12 #define NATIVE_VERSION 12
/* /*
* Marks the given result as failed, using the given error message * Marks the given result as failed, using the given error message
*/ */
extern void mark_failed_with_message(JNIEnv *env, const char* message, jobject result); 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() * 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); 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 * 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); extern void mark_failed_with_code(JNIEnv *env, const char* message, int error_code, jobject result);
typedef struct wchar_struct { /*
// Not NULL terminated * Converts the given Java string to a NULL terminated wchar_str. Should call free() when finished.
wchar_t* chars; *
// number of characters in the string * Returns NULL on failure.
size_t len; */
jstring source; extern wchar_t*
JNIEnv *env; java_to_wchar(JNIEnv *env, jstring string, jobject result);
} wchar_str;
/*
/* * Converts the given wchar_t string to a Java string.
* Converts the given Java string to a wchar_str. Should call wchar_str_free() when finished. *
* * Returns NULL on failure.
* Returns NULL on failure. */
*/ extern jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result);
extern wchar_str*
java_to_wchar_str(JNIEnv *env, jstring string, jobject result); /*
* Converts the given Java string to a NULL terminated char string. Should call free() when finished.
/* *
* Releases resources used by the given string. * Returns NULL on failure.
*/ */
extern void wchar_str_free(wchar_str* str); extern char* java_to_char(JNIEnv *env, jstring string, jobject result);
/* /*
* Converts the given wchar_t string to a Java string. * Converts the given NULL terminated char string to a Java string.
* *
* Returns NULL on failure. * Returns NULL on failure.
*/ */
extern jstring wchar_to_java(JNIEnv* env, const wchar_t* chars, size_t len, jobject result); extern jstring char_to_java(JNIEnv* env, const char* chars, jobject result);
/* #ifdef __cplusplus
* Converts the given Java string to a char_str. Should call free() when finished. }
* #endif
* Returns NULL on failure.
*/ #endif
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

View File

@@ -33,23 +33,16 @@ class PosixFileTest extends Specification {
} }
def "can set mode on a file"() { def "can set mode on a file"() {
def testFile = tmpDir.newFile("test.txt") def testFile = tmpDir.newFile(fileName)
when: when:
file.setMode(testFile, 0740) file.setMode(testFile, 0740)
then: then:
file.getMode(testFile) == 0740 file.getMode(testFile) == 0740
}
def "can set mode on a file with unicode in its name"() { where:
def testFile = tmpDir.newFile("test\u03b1.txt") fileName << ["test.txt", "test\u03b1.txt"]
when:
file.setMode(testFile, 0740)
then:
file.getMode(testFile) == 0740
} }
def "cannot set mode on file that does not exist"() { def "cannot set mode on file that does not exist"() {

View File

@@ -1,58 +1,61 @@
/* /*
* Copyright 2012 Adam Murdoch * Copyright 2012 Adam Murdoch
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.rubygrapefruit.platform package net.rubygrapefruit.platform
import org.junit.Rule import org.junit.Rule
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import spock.lang.Specification import spock.lang.Specification
class ProcessTest extends Specification { class ProcessTest extends Specification {
@Rule TemporaryFolder tmpDir @Rule TemporaryFolder tmpDir
final Process process = Native.get(Process.class) final Process process = Native.get(Process.class)
def "caches process instance"() { def "caches process instance"() {
expect: expect:
Native.get(Process.class) == process Native.get(Process.class) == process
} }
def "can get PID"() { def "can get PID"() {
expect: expect:
process.getProcessId() != 0 process.getProcessId() != 0
} }
def "can change working directory"() { def "can get and change working directory"() {
def newDir = tmpDir.newFolder("dir").canonicalFile def newDir = tmpDir.newFolder(dir).canonicalFile
when: when:
def original = process.workingDirectory def original = process.workingDirectory
then: then:
original == new File(".").canonicalFile original == new File(".").canonicalFile
original == new File(System.getProperty("user.dir")) original == new File(System.getProperty("user.dir"))
when: when:
process.workingDirectory = newDir process.workingDirectory = newDir
then: then:
process.workingDirectory == newDir process.workingDirectory == newDir
new File(".").canonicalFile == newDir new File(".").canonicalFile == newDir
new File(System.getProperty("user.dir")) == newDir new File(System.getProperty("user.dir")) == newDir
cleanup: cleanup:
process.workingDirectory = original process.workingDirectory = original
}
} where:
dir << ['dir', 'dir\u03b1']
}
}