/******************************************************************************* * Copyright (c) 2012 Pivotal Software, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.core.launch; import java.io.IOException; import java.util.Map; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.model.IStreamMonitor; import org.eclipse.debug.core.model.IStreamsProxy; import org.eclipse.debug.core.model.IStreamsProxy2; import org.eclipse.debug.core.model.RuntimeProcess; import org.eclipse.debug.internal.core.OutputStreamMonitor; /** * Replaces RuntimeProcess, so that we can manipulate/transform the output streams from the process. * * @author Kris De Volder * * @since 2.8 */ public class GrailsRuntimeProcess extends RuntimeProcess { public static class StreamsProxy implements IStreamsProxy, IStreamsProxy2 { public StreamsProxy(IStreamMonitor out, IStreamMonitor err, IStreamsProxy in) { super(); this.out = out; this.err = err; this.in = in; } private IStreamMonitor out; private IStreamMonitor err; private IStreamsProxy in; // Only cares about the 'write' method. public IStreamMonitor getErrorStreamMonitor() { return err; } public IStreamMonitor getOutputStreamMonitor() { return out; } public void write(String input) throws IOException { in.write(input); } public void closeInputStream() throws IOException { if (in instanceof IStreamsProxy2) { ((IStreamsProxy2) in).closeInputStream(); } } } public static final String NEWLINE = System.getProperty("line.separator"); private IStreamsProxy mustCloseStreamProxy; public GrailsRuntimeProcess(ILaunch launch, Process process, String label, Map attributes) { super(launch, process, label, attributes); } @Override protected IStreamsProxy createStreamsProxy() { mustCloseStreamProxy = super.createStreamsProxy(); //We must make sure that the streams proxy created from super is closed when process terminates. //This won't happen automatically because our StreamsProxy, which wraps it doesn't extend the //org.eclipse.debug.internal.core.StreamsProxy class. return removeDuplicates(mustCloseStreamProxy); } @Override public void terminate() throws DebugException { if (!isTerminated()) { //If we don't forcibly destroy the StreamsProxy then, on Windows the // 'terminated()' method may hang when it tries to close the streams // nicely. This will stop it from properly closing resources // and firing a termination event. Visibly to the user: 'Stop' button // won't work. //See https://issuetracker.springsource.com/browse/STS-3800 if (mustCloseStreamProxy instanceof org.eclipse.debug.internal.core.StreamsProxy) { ((org.eclipse.debug.internal.core.StreamsProxy) mustCloseStreamProxy).kill(); } } super.terminate(); } @Override protected void terminated() { try { if (mustCloseStreamProxy instanceof org.eclipse.debug.internal.core.StreamsProxy) { ((org.eclipse.debug.internal.core.StreamsProxy) mustCloseStreamProxy).close(); } } finally { mustCloseStreamProxy = null; super.terminated(); } } private IStreamsProxy removeDuplicates(IStreamsProxy streamsProxy) { return new StreamsProxy(removeDuplicates(streamsProxy.getOutputStreamMonitor()), streamsProxy.getErrorStreamMonitor(), streamsProxy); } /** * Removes 'duplicates' as follows: * <p> * When the next line is just a copy of the previous line with some extra text added to the end... * Then the next line is not printed, instead, the extra characters are appended to the previous line. */ private IStreamMonitor removeDuplicates(IStreamMonitor mon) { return new TransformedStreamMonitor((OutputStreamMonitor) mon, new Grails20OutputCleaner()); } }