diff -r b3f63dd66843 extexecution/apichanges.xml --- a/extexecution/apichanges.xml Tue Mar 31 13:10:03 2009 +0200 +++ b/extexecution/apichanges.xml Tue Mar 31 18:51:39 2009 +0200 @@ -109,6 +109,20 @@ + Configurable charset for process streams + + + + + + Added charset() method to ExecutionDescriptor to configure + the charset used to decode process streams. + + + + + + Published as a stable API diff -r b3f63dd66843 extexecution/manifest.mf --- a/extexecution/manifest.mf Tue Mar 31 13:10:03 2009 +0200 +++ b/extexecution/manifest.mf Tue Mar 31 18:51:39 2009 +0200 @@ -2,5 +2,5 @@ AutoUpdate-Show-In-Client: false OpenIDE-Module: org.netbeans.modules.extexecution/2 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/extexecution/resources/Bundle.properties -OpenIDE-Module-Specification-Version: 1.13 +OpenIDE-Module-Specification-Version: 1.14 diff -r b3f63dd66843 extexecution/src/org/netbeans/api/extexecution/ExecutionDescriptor.java --- a/extexecution/src/org/netbeans/api/extexecution/ExecutionDescriptor.java Tue Mar 31 13:10:03 2009 +0200 +++ b/extexecution/src/org/netbeans/api/extexecution/ExecutionDescriptor.java Tue Mar 31 18:51:39 2009 +0200 @@ -39,6 +39,7 @@ package org.netbeans.api.extexecution; +import java.nio.charset.Charset; import javax.swing.event.ChangeListener; import org.netbeans.api.annotations.common.CheckReturnValue; import org.netbeans.api.annotations.common.NonNull; @@ -95,6 +96,8 @@ private final String optionsPath; + private final Charset charset; + /** * Creates the new descriptor. All properties are initalized to * null or false. @@ -120,6 +123,7 @@ this.inputOutput = data.inputOutput; this.rerunCondition = data.rerunCondition; this.optionsPath = data.optionsPath; + this.charset = data.charset; } /** @@ -518,7 +522,7 @@ } /** - * Returns a descriptor with configured options path. If not configured + * Returns a descriptor with configured options path. If configured * value is not null the {@link ExecutionService} will * display the button in the output tab displaying the proper options * when pressed. @@ -546,6 +550,35 @@ String getOptionsPath() { return optionsPath; + } + + /** + * Returns a descriptor with configured charset. If configured + * value is not null the {@link ExecutionService} will + * use the given charset to decode the process streams. When + * null the platform default will be used. + *

+ * Note that in the most common scenario of execution of OS native + * process you shouldn't need to set the charset. The platform default + * (which is the default used) is just the right choice. + *

+ * The default (not configured) value is null. + *

+ * All other properties of the returned descriptor are inherited from + * this. + * + * @param charset charset, null allowed + * @return this descriptor with configured charset + */ + @NonNull + @CheckReturnValue + public ExecutionDescriptor charset(@NullAllowed Charset charset) { + DescriptorData data = new DescriptorData(this); + return new ExecutionDescriptor(data.charset(charset)); + } + + Charset getCharset() { + return charset; } /** @@ -642,6 +675,8 @@ private String optionsPath; + private Charset charset; + public DescriptorData() { super(); } @@ -663,6 +698,7 @@ this.inputOutput = descriptor.inputOutput; this.rerunCondition = descriptor.rerunCondition; this.optionsPath = descriptor.optionsPath; + this.charset = descriptor.charset; } public DescriptorData inputOutput(InputOutput io) { @@ -745,5 +781,9 @@ return this; } + public DescriptorData charset(Charset charset) { + this.charset = charset; + return this; + } } } diff -r b3f63dd66843 extexecution/src/org/netbeans/api/extexecution/ExecutionService.java --- a/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java Tue Mar 31 13:10:03 2009 +0200 +++ b/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java Tue Mar 31 18:51:39 2009 +0200 @@ -265,11 +265,16 @@ executor = Executors.newFixedThreadPool(descriptor.isInputVisible() ? 3 : 2); + Charset charset = descriptor.getCharset(); + if (charset == null) { + charset = Charset.defaultCharset(); + } + tasks.add(InputReaderTask.newDrainingTask( - InputReaders.forStream(new BufferedInputStream(outStream), Charset.defaultCharset()), + InputReaders.forStream(new BufferedInputStream(outStream), charset), createOutProcessor(out))); tasks.add(InputReaderTask.newDrainingTask( - InputReaders.forStream(new BufferedInputStream(errStream), Charset.defaultCharset()), + InputReaders.forStream(new BufferedInputStream(errStream), charset), createErrProcessor(err))); if (descriptor.isInputVisible()) { tasks.add(InputReaderTask.newTask( diff -r b3f63dd66843 extexecution/test/unit/src/org/netbeans/api/extexecution/ExecutionServiceTest.java --- a/extexecution/test/unit/src/org/netbeans/api/extexecution/ExecutionServiceTest.java Tue Mar 31 13:10:03 2009 +0200 +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/ExecutionServiceTest.java Tue Mar 31 18:51:39 2009 +0200 @@ -39,29 +39,36 @@ package org.netbeans.api.extexecution; -import org.netbeans.api.extexecution.ExecutionDescriptor; -import org.netbeans.api.extexecution.ExecutionService; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.nio.charset.Charset; import java.util.LinkedList; +import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.netbeans.api.extexecution.input.InputProcessor; +import org.netbeans.api.extexecution.input.InputProcessors; import org.netbeans.junit.NbTestCase; import org.netbeans.modules.extexecution.InputOutputManager; import org.netbeans.api.extexecution.input.TestInputUtils; +import org.netbeans.api.extexecution.input.TestLineProcessor; /** * * @author Petr Hejl */ public class ExecutionServiceTest extends NbTestCase { + + private static final int PROCESS_TIMEOUT = 30000; public ExecutionServiceTest(String name) { super(name); @@ -333,6 +340,43 @@ assertNotNull(getInputOutput("Test #2", false, null)); } + public void testCharset() throws InterruptedException, ExecutionException, TimeoutException { + Charset charset = Charset.forName("UTF-16LE"); + final String[] lines = new String[] {"Process line \u1234", "Process line \u1235", "Process line \u1236"}; + + TestInputStream is = new TestInputStream(TestInputUtils.prepareInputStream(lines, "\n", charset, true)); + TestProcess process = new TestProcess(0, is); + is.setProcess(process); + + TestCallable callable = new TestCallable(); + callable.addProcess(process); + + final TestLineProcessor processor = new TestLineProcessor(false); + ExecutionDescriptor descriptor = new ExecutionDescriptor().charset(charset).outProcessorFactory( + new ExecutionDescriptor.InputProcessorFactory() { + + public InputProcessor newInputProcessor(InputProcessor defaultProcessor) { + return InputProcessors.bridge(processor); + } + }); + + ExecutionService service = ExecutionService.newService( + callable, descriptor, "Test"); + + Future task = service.run(); + assertNotNull(task); + + assertEquals(0, task.get(PROCESS_TIMEOUT, TimeUnit.MILLISECONDS).intValue()); + assertTrue(process.isFinished()); + assertEquals(0, process.exitValue()); + + List processed = processor.getLinesProcessed(); + assertEquals(lines.length, processed.size()); + for (int i = 0; i < lines.length; i++) { + assertEquals(lines[i], processed.get(i)); + } + } + private static InputOutputManager.InputOutputData getInputOutput(String name, boolean actions, String optionsPath) { @@ -374,12 +418,21 @@ private final int returnValue; + private final InputStream is; + private boolean finished; private boolean started; public TestProcess(int returnValue) { + this(returnValue, TestInputUtils.prepareInputStream( + new String[] {"Process line 1", "Process line 2", "Process line 3"}, "\n", + Charset.defaultCharset(), true)); + } + + public TestProcess(int returnValue, InputStream is) { this.returnValue = returnValue; + this.is = is; } public void start() { @@ -435,9 +488,7 @@ @Override public InputStream getInputStream() { - return TestInputUtils.prepareInputStream( - new String[] {"Process line 1", "Process line 2", "Process line 3"}, - "\n", Charset.forName("UTF-8"), true); + return is; } @Override @@ -468,4 +519,71 @@ } } } + + private static class TestInputStream extends FilterInputStream { + + private Process process; + + public TestInputStream(InputStream is) { + super(is); + } + + public synchronized Process getProcess() { + return process; + } + + public synchronized void setProcess(Process process) { + this.process = process; + } + + @Override + public int available() throws IOException { + int available = super.available(); + if (available <= 0) { + Process toDestroy = getProcess(); + if (toDestroy != null) { + toDestroy.destroy(); + } + } + return available; + } + + + @Override + public int read() throws IOException { + int val = super.read(); + if (val < 0) { + Process toDestroy = getProcess(); + if (toDestroy != null) { + toDestroy.destroy(); + } + } + return val; + } + + @Override + public int read(byte[] b) throws IOException { + int val = super.read(b); + if (val < 0) { + Process toDestroy = getProcess(); + if (toDestroy != null) { + toDestroy.destroy(); + } + } + return val; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int val = super.read(b, off, len); + if (val < 0) { + Process toDestroy = getProcess(); + if (toDestroy != null) { + toDestroy.destroy(); + } + } + return val; + } + } + }