diff --git a/src/main/java/net/rubygrapefruit/platform/ProcessLauncher.java b/src/main/java/net/rubygrapefruit/platform/ProcessLauncher.java new file mode 100644 index 0000000..8070878 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/ProcessLauncher.java @@ -0,0 +1,36 @@ +/* + * 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.lang.Process; + +/** + * Used to start processes, taking care of some platform-specific issues when launching processes concurrently or + * launching processes that will run in the background. + */ +@ThreadSafe +public interface ProcessLauncher extends NativeIntegration { + /** + * Starts a process from the given settings. + * + * @param processBuilder The process settings. + * @return the process + * @throws NativeException On failure to start the process. + */ + @ThreadSafe + Process start(ProcessBuilder processBuilder) throws NativeException; +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java new file mode 100644 index 0000000..261f7bf --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultProcessLauncher.java @@ -0,0 +1,38 @@ +/* + * 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.ProcessLauncher; + +import java.io.IOException; + +public class DefaultProcessLauncher implements ProcessLauncher { + private final Object startLock = new Object(); + + public Process start(ProcessBuilder processBuilder) throws NativeException { + try { + synchronized (startLock) { + // Start a single process at a time, to avoid streams to child process being inherited by other + // children before the parent + return processBuilder.start(); + } + } catch (IOException e) { + throw new NativeException(String.format("Could not start '%s'", processBuilder.command().get(0)), e); + } + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java index 3eb04c6..6787103 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java @@ -131,6 +131,9 @@ public abstract class Platform { if (type.equals(Process.class)) { return type.cast(new WrapperProcess(new DefaultProcess())); } + if (type.equals(ProcessLauncher.class)) { + return type.cast(new DefaultProcessLauncher()); + } if (type.equals(Terminals.class)) { nativeLibraryLoader.load(getCursesLibraryName()); int nativeVersion = TerminfoFunctions.getVersion(); diff --git a/src/test/groovy/net/rubygrapefruit/platform/ProcessLauncherTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/ProcessLauncherTest.groovy new file mode 100644 index 0000000..6f37dc0 --- /dev/null +++ b/src/test/groovy/net/rubygrapefruit/platform/ProcessLauncherTest.groovy @@ -0,0 +1,41 @@ +/* + * Copyright 2012 Adam Murdoch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.rubygrapefruit.platform + +import spock.lang.Specification + +class ProcessLauncherTest extends Specification { + final ProcessLauncher launcher = Native.get(ProcessLauncher) + + def "can start a child process"() { + def javaHome = System.getProperty("java.home") + def exe = "${javaHome}/bin/java" + ProcessBuilder builder = new ProcessBuilder(exe, "-version") + builder.redirectErrorStream(true) + + when: + def process = launcher.start(builder) + def stdout = new ByteArrayOutputStream() + def stdoutThread = process.consumeProcessOutputStream(stdout) + def result = process.waitFor() + stdoutThread.join() + + then: + result == 0 + stdout.toString().contains(System.getProperty('java.vm.version')) + } +}