/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation * Patrik Suzzi <psuzzi@gmail.com> - Bug 507661 *******************************************************************************/ package org.eclipse.ui.console; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import org.eclipse.swt.graphics.Color; import org.eclipse.ui.internal.console.IOConsolePartitioner; import org.eclipse.ui.internal.console.StreamDecoder; /** * OutputStream used to write to an IOConsole. * <p> * Clients are not intended to instantiate this class directly, instead * use <code>IOConsole.newOutputStream()</code>. * </p> * <p> * Clients should avoid writing large amounts of output to this stream in the UI * thread. The console needs to process the output in the UI thread and if the client * hogs the UI thread writing output to the console, the console will not be able * to process the output. * </p> * @since 3.1 * @noinstantiate This class is not intended to be instantiated by clients. * @noextend This class is not intended to be subclassed by clients. */ public class IOConsoleOutputStream extends OutputStream { /** * Flag indicating whether this stream has been closed. */ private boolean closed = false; /** * The console's document partitioner. */ private IOConsolePartitioner partitioner; /** * The console this stream is attached to. */ private IOConsole console; /** * Flag indicating that the console should be activated when data * is written to this stream. */ private boolean activateOnWrite = false; /** * The color used to decorate data written to this stream. */ private Color color; /** * The font style used to decorate data written to this stream. */ private int fontStyle; private StreamDecoder decoder; private boolean prependCR; /** * Constructs a new output stream on the given console. * * @param console I/O console */ IOConsoleOutputStream(IOConsole console, Charset charset) { this.decoder = new StreamDecoder(charset); this.console = console; this.partitioner = (IOConsolePartitioner) console.getPartitioner(); } /** * Returns the font style used to decorate data written to this stream. * * @return the font style used to decorate data written to this stream */ public int getFontStyle() { return fontStyle; } /** * Sets the font style to be used to decorate data written to this stream. * * @param newFontStyle the font style to be used to decorate data written to this stream */ public void setFontStyle(int newFontStyle) { if (newFontStyle != fontStyle) { int old = fontStyle; fontStyle = newFontStyle; console.firePropertyChange(this, IConsoleConstants.P_FONT_STYLE, Integer.valueOf(old), Integer.valueOf(fontStyle)); } } /** * Returns whether the console this stream is writing to will be activated when this stream * is written to. * * @return whether the console this stream is writing to will be activated when this stream * is written to. */ public boolean isActivateOnWrite() { return activateOnWrite; } /** * Sets whether to activate the console this stream is writing to when this stream * is written to. * * @param activateOnWrite whether the console this stream is writing to will be activated when this stream * is written to. */ public void setActivateOnWrite(boolean activateOnWrite) { this.activateOnWrite = activateOnWrite; } /** * Sets the color of this stream. Use <code>null</code> to indicate * the default color. * * @param newColor color of this stream, or <code>null</code> */ public void setColor(Color newColor) { Color old = color; if (old == null || !old.equals(newColor)) { color = newColor; console.firePropertyChange(this, IConsoleConstants.P_STREAM_COLOR, old, newColor); } } /** * Returns the color of this stream, or <code>null</code> * if default. * * @return the color of this stream, or <code>null</code> */ public Color getColor() { return color; } /** * Returns true if the stream has been closed * @return true is the stream has been closed, false otherwise. */ public synchronized boolean isClosed() { return closed; } /* * (non-Javadoc) * @see java.io.OutputStream#close() */ @Override public synchronized void close() throws IOException { if(closed) { // Closeable#close() has no effect if already closed return; } StringBuilder builder = new StringBuilder(); if (prependCR) { // force writing of last /r prependCR = false; builder.append('\r'); } this.decoder.finish(builder); if (builder.length() > 0) { notifyParitioner(builder.toString()); } console.streamClosed(this); closed = true; partitioner = null; } /* * (non-Javadoc) * @see java.io.OutputStream#flush() */ @Override public void flush() throws IOException { if(closed) { throw new IOException("Output Stream is closed"); //$NON-NLS-1$ } } /* * (non-Javadoc) * @see java.io.OutputStream#write(byte[], int, int) */ @Override public synchronized void write(byte[] b, int off, int len) throws IOException { if (closed) { throw new IOException("Output Stream is closed"); //$NON-NLS-1$ } StringBuilder builder = new StringBuilder(); this.decoder.decode(builder, b, off, len); encodedWrite(builder.toString()); } /* * (non-Javadoc) * @see java.io.OutputStream#write(byte[]) */ @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } /* * (non-Javadoc) * @see java.io.OutputStream#write(int) */ @Override public void write(int b) throws IOException { write(new byte[] {(byte)b}, 0, 1); } /** * Writes a character array to the attached console. * * @param buffer the char array to write to the attached console * @throws IOException if the stream is closed * @since 3.7 */ public void write(char[] buffer) throws IOException { String str = new String(buffer); this.encodedWrite(str); } /** * Writes a character array using specified offset and length to the * attached console. * * @param buffer the char array to write to the attached console. * @param off the initial offset * @param len the length * @throws IOException if the stream is closed * @since 3.7 */ public void write(char[] buffer, int off, int len) throws IOException { String str = new String(buffer, off, len); this.encodedWrite(str); } /** * Writes a character sequence to the attached console. * * @param chars the string/characters to write to the attached console. * @throws IOException if the stream is closed. * @since 3.7 */ public void write(CharSequence chars) throws IOException { String str = chars.toString(); encodedWrite(str); } /** * Writes a string to the attached console. * * @param str the string to write to the attached console * @throws IOException if the stream is closed */ public void write(String str) throws IOException { encodedWrite(str); } private synchronized void encodedWrite(String encodedString) throws IOException { if(closed) { throw new IOException("Output Stream is closed"); //$NON-NLS-1$ } String newencoding = encodedString; if (prependCR){ newencoding = "\r" + newencoding; //$NON-NLS-1$ prependCR=false; } if (newencoding.endsWith("\r")) { //$NON-NLS-1$ prependCR = true; newencoding = new String(newencoding.substring(0, newencoding.length() - 1)); } notifyParitioner(newencoding); } private void notifyParitioner(String encodedString) throws IOException { try { partitioner.streamAppended(this, encodedString); if (activateOnWrite) { console.activate(); } else { ConsolePlugin.getDefault().getConsoleManager().warnOfContentChange(console); } } catch (IOException e) { if (!closed) { close(); } throw e; } } /** * Sets the character encoding used to interpret characters written to this steam. * * @param encoding encoding identifier */ public void setEncoding(String encoding) { Charset charset = Charset.forName(encoding); try { this.setCharset(charset); } catch (IOException ioe) { // ignore exception while writing final characters // to avoid API break } } /** * @param charset set the Charset for the attached console * @throws IOException if the stream is closed * @since 3.7 */ public synchronized void setCharset(Charset charset) throws IOException { if (closed) { throw new IOException("Output Stream is closed"); //$NON-NLS-1$ } StringBuilder builder = new StringBuilder(); this.decoder.finish(builder); if (builder.length() > 0) { this.encodedWrite(builder.toString()); } this.decoder = new StreamDecoder(charset); } }