diff --git a/readme.md b/readme.md index f4af823..7b2a0b4 100755 --- a/readme.md +++ b/readme.md @@ -199,6 +199,7 @@ You can run `$INSTALL_DIR/bin/native-platform-test` to run the test application. libncurses cannot be loaded. * Add a method to Terminal that indicates whether the cursor wraps to the next line when a character is written to the rightmost character position. +* Check for null parameters. ### Ideas diff --git a/src/main/cpp/win.cpp b/src/main/cpp/win.cpp index 44bfdf1..1bb4f68 100755 --- a/src/main/cpp/win.cpp +++ b/src/main/cpp/win.cpp @@ -109,13 +109,55 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_setWorkingDi if (dirPath == NULL) { return; } - if (!SetCurrentDirectoryW(dirPath)) { + BOOL ok = SetCurrentDirectoryW(dirPath); + free(dirPath); + if (!ok) { mark_failed_with_errno(env, "could not set current directory", result); - free(dirPath); return; } +} - free(dirPath); +JNIEXPORT jstring JNICALL +Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_getEnvironmentVariable(JNIEnv *env, jclass target, jstring var, jobject result) { + wchar_t* varStr = java_to_wchar(env, var, result); + DWORD len = GetEnvironmentVariableW(varStr, NULL, 0); + if (len == 0) { + if (GetLastError() != ERROR_ENVVAR_NOT_FOUND) { + mark_failed_with_errno(env, "could not determine length of environment variable", result); + } + free(varStr); + return NULL; + } + wchar_t* valueStr = (wchar_t*)malloc(sizeof(wchar_t) * len); + DWORD copied = GetEnvironmentVariableW(varStr, valueStr, len); + if (copied == 0) { + if (len > 1) { + // If the value is empty, then copied will be 0 + mark_failed_with_errno(env, "could not get environment variable", result); + } + free(varStr); + free(valueStr); + return NULL; + } + free(varStr); + jstring value = wchar_to_java(env, valueStr, copied, result); + free(valueStr); + return value; +} + +JNIEXPORT void JNICALL +Java_net_rubygrapefruit_platform_internal_jni_PosixProcessFunctions_setEnvironmentVariable(JNIEnv *env, jclass target, jstring var, jstring value, jobject result) { + wchar_t* varStr = java_to_wchar(env, var, result); + wchar_t* valueStr = value == NULL ? NULL : java_to_wchar(env, value, result); + BOOL ok = SetEnvironmentVariableW(varStr, valueStr); + free(varStr); + if (valueStr != NULL) { + free(valueStr); + } + if (!ok && GetLastError() != ERROR_ENVVAR_NOT_FOUND) { + mark_failed_with_errno(env, "could not set environment var", result); + return; + } } /* diff --git a/src/main/headers/generic.h b/src/main/headers/generic.h index 1cebf03..6924568 100755 --- a/src/main/headers/generic.h +++ b/src/main/headers/generic.h @@ -23,7 +23,7 @@ extern "C" { #endif -#define NATIVE_VERSION 12 +#define NATIVE_VERSION 13 /* * Marks the given result as failed, using the given error message diff --git a/src/main/java/net/rubygrapefruit/platform/Process.java b/src/main/java/net/rubygrapefruit/platform/Process.java index 6a970fe..956fa29 100755 --- a/src/main/java/net/rubygrapefruit/platform/Process.java +++ b/src/main/java/net/rubygrapefruit/platform/Process.java @@ -1,49 +1,68 @@ -/* - * 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 java.io.File; - -/** - * Functions to query and modify a process' state. - */ -@ThreadSafe -public interface Process extends NativeIntegration { - /** - * Returns the process identifier. - * - * @throws NativeException On failure. - */ - @ThreadSafe - int getProcessId() throws NativeException; - - /** - * Returns the process' current working directory. - * - * @throws NativeException On failure. - */ - @ThreadSafe - File getWorkingDirectory() throws NativeException; - - /** - * Sets the process' working directory. - * - * @throws NativeException On failure. - */ - @ThreadSafe - void setWorkingDirectory(File directory) throws NativeException; -} +/* + * 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 java.io.File; + +/** + * Functions to query and modify a process' state. + */ +@ThreadSafe +public interface Process extends NativeIntegration { + /** + * Returns the process identifier. + * + * @throws NativeException On failure. + */ + @ThreadSafe + int getProcessId() throws NativeException; + + /** + * Returns the process' current working directory. + * + * @throws NativeException On failure. + */ + @ThreadSafe + File getWorkingDirectory() throws NativeException; + + /** + * Sets the process' working directory. + * + * @throws NativeException On failure. + */ + @ThreadSafe + void setWorkingDirectory(File directory) throws NativeException; + + /** + * Get the value of an environment variable. + * + * @return The value or null if no such environment variable. + * @throws NativeException On failure. + */ + @ThreadSafe + String getEnvironmentVariable(String name) throws NativeException; + + /** + * Sets the value of an environment variable. + * + * @param value the new value. Use null to remove the environment variable. Note that on some platforms it is not + * possible to remove the environment variable. On such platforms, the value is set to an empty string instead. + * @throws NativeException On failure. + */ + @ThreadSafe + void setEnvironmentVariable(String name, String value) throws NativeException; +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcess.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcess.java index 17b440f..2471a38 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcess.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcess.java @@ -1,46 +1,79 @@ -/* - * 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.internal; - -import net.rubygrapefruit.platform.*; -import net.rubygrapefruit.platform.internal.jni.PosixProcessFunctions; - -import java.io.File; - -public class DefaultProcess implements net.rubygrapefruit.platform.Process { - public int getProcessId() throws NativeException { - return PosixProcessFunctions.getPid(); - } - - public File getWorkingDirectory() throws NativeException { - FunctionResult result = new FunctionResult(); - String dir = PosixProcessFunctions.getWorkingDirectory(result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not get process working directory: %s", result.getMessage())); - } - return new File(dir); - } - - public void setWorkingDirectory(File directory) throws NativeException { - FunctionResult result = new FunctionResult(); - PosixProcessFunctions.setWorkingDirectory(directory.getAbsolutePath(), result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not set process working directory: %s", result.getMessage())); - } - System.setProperty("user.dir", directory.getAbsolutePath()); - } -} +/* + * 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.internal; + +import net.rubygrapefruit.platform.NativeException; +import net.rubygrapefruit.platform.Process; +import net.rubygrapefruit.platform.internal.jni.PosixProcessFunctions; + +import java.io.File; + +public class DefaultProcess implements Process { + private final Object workingDirectoryLock = new Object(); + private final Object environmentLock = new Object(); + + public int getProcessId() throws NativeException { + return PosixProcessFunctions.getPid(); + } + + public File getWorkingDirectory() throws NativeException { + FunctionResult result = new FunctionResult(); + String dir; + synchronized (workingDirectoryLock) { + dir = PosixProcessFunctions.getWorkingDirectory(result); + } + if (result.isFailed()) { + throw new NativeException(String.format("Could not get process working directory: %s", + result.getMessage())); + } + return new File(dir); + } + + public void setWorkingDirectory(File directory) throws NativeException { + FunctionResult result = new FunctionResult(); + synchronized (workingDirectoryLock) { + PosixProcessFunctions.setWorkingDirectory(directory.getAbsolutePath(), result); + System.setProperty("user.dir", directory.getAbsolutePath()); + } + if (result.isFailed()) { + throw new NativeException(String.format("Could not set process working directory: %s", + result.getMessage())); + } + } + + public String getEnvironmentVariable(String name) throws NativeException { + FunctionResult result = new FunctionResult(); + String value; + synchronized (environmentLock) { + value = PosixProcessFunctions.getEnvironmentVariable(name, result); + } + if (result.isFailed()) { + throw new NativeException(String.format("Could not get the value of environment variable '%s': %s", name, result.getMessage())); + } + return value; + } + + public void setEnvironmentVariable(String name, String value) throws NativeException { + FunctionResult result = new FunctionResult(); + synchronized (environmentLock) { + PosixProcessFunctions.setEnvironmentVariable(name, value, result); + } + if (result.isFailed()) { + throw new NativeException(String.format("Could not set the value of environment variable '%s': %s", name, result.getMessage())); + } + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java index 8a2c68a..ddc8da1 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java @@ -1,28 +1,28 @@ -/* - * 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.internal.jni; - -import net.rubygrapefruit.platform.internal.FunctionResult; -import net.rubygrapefruit.platform.internal.MutableSystemInfo; - -public class NativeLibraryFunctions { - public static final int VERSION = 12; - - public static native int getVersion(); - - public static native void getSystemInfo(MutableSystemInfo systemInfo, FunctionResult result); -} +/* + * 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.internal.jni; + +import net.rubygrapefruit.platform.internal.FunctionResult; +import net.rubygrapefruit.platform.internal.MutableSystemInfo; + +public class NativeLibraryFunctions { + public static final int VERSION = 13; + + public static native int getVersion(); + + public static native void getSystemInfo(MutableSystemInfo systemInfo, FunctionResult result); +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/PosixProcessFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/PosixProcessFunctions.java index 0110afd..fb724f0 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/PosixProcessFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/PosixProcessFunctions.java @@ -1,27 +1,31 @@ -/* - * 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.internal.jni; - -import net.rubygrapefruit.platform.internal.FunctionResult; - -public class PosixProcessFunctions { - public static native int getPid(); - - public static native String getWorkingDirectory(FunctionResult result); - - public static native void setWorkingDirectory(String dir, FunctionResult result); -} +/* + * 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.internal.jni; + +import net.rubygrapefruit.platform.internal.FunctionResult; + +public class PosixProcessFunctions { + public static native int getPid(); + + public static native String getWorkingDirectory(FunctionResult result); + + public static native void setWorkingDirectory(String dir, FunctionResult result); + + public static native String getEnvironmentVariable(String var, FunctionResult result); + + public static native void setEnvironmentVariable(String var, String value, FunctionResult result); +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy index 39a7a5a..77b2055 100755 --- a/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/ProcessTest.groovy @@ -58,4 +58,79 @@ class ProcessTest extends Specification { where: dir << ['dir', 'dir\u03b1'] } + + def "cannot set working directory to a directory that does not exist"() { + def newDir = new File(tmpDir.root, "does not exist"); + + when: + process.workingDirectory = newDir + + then: + NativeException e = thrown() + e.message.startsWith("Could not set process working directory:") + } + + def "can get and set and remove environment variable"() { + when: + def value = process.getEnvironmentVariable(varName) + + then: + value == null + System.getenv(varName) == null + System.getenv()[varName] == null + + when: + process.setEnvironmentVariable(varName, varValue) + + then: + process.getEnvironmentVariable(varName) == varValue + System.getenv(varName) == varValue + System.getenv()[varName] == varValue + + when: + process.setEnvironmentVariable(varName, null) + + then: + process.getEnvironmentVariable(varName) == null + System.getenv(varName) == null + System.getenv()[varName] == null + + where: + varName | varValue + 'TEST_ENV_VAR' | 'test value' + 'TEST_ENV_VAR\u03b1' | 'value\u03b2' + } + + def "setting environment variable to null or empty string remove the environment variable"() { + when: + def value = process.getEnvironmentVariable(varName) + + then: + value == null + System.getenv(varName) == null + System.getenv()[varName] == null + + when: + process.setEnvironmentVariable(varName, varValue) + + then: + process.getEnvironmentVariable(varName) == null + System.getenv(varName) == null + System.getenv()[varName] == null + + where: + varName | varValue + 'TEST_ENV_VAR_EMPTY' | '' + 'TEST_ENV_VAR_NULL' | null + } + + def "can remove environment variable that does not exist"() { + assert process.getEnvironmentVariable("TEST_ENV_UNKNOWN") == null + + when: + process.setEnvironmentVariable("TEST_ENV_UNKNOWN", null) + + then: + notThrown(NativeException) + } }