blob: ff50df030203079b80a851e10e2b5c177465af51 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.core.output2;
import java.util.Set;
import org.openide.windows.InputOutput;
import org.openide.windows.OutputListener;
import org.openide.windows.OutputWriter;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.io.Reader;
import org.netbeans.core.output2.options.OutputOptions;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.openide.windows.IOColorLines;
import org.openide.windows.IOColorPrint;
import org.openide.windows.IOColors;
import org.openide.windows.IOContainer;
import org.openide.windows.IOFolding;
import org.openide.windows.IOPosition;
import org.openide.windows.IOSelect;
import org.openide.windows.IOTab;
/** Implementation of InputOutput. Implements calls as a set of
* "commands" which are passed up to Dispatcher to be run on the event
* queue.
*
* @author Tim Boudreau
*/
class NbIO implements InputOutput, Lookup.Provider {
private Boolean focusTaken = null;
private volatile boolean closed = false;
private final String name;
private OutputOptions options = OutputOptions.getDefault().makeCopy();
private Action[] actions;
private NbWriter out = null;
private IOContainer ioContainer;
private Lookup lookup;
private IOTabImpl ioTab;
private IOColorsImpl ioColors;
private IOFoldingImpl ioFolding;
private IOFoldingImpl.NbIoFoldHandleDefinition currentFold = null;
/** Creates a new instance of NbIO
* @param name The name of the IO
* @param toolbarActions an optional set of toolbar actions
* @param iowin parent container accessor (null for default)
*/
NbIO(String name, Action[] toolbarActions, IOContainer ioContainer) {
this(name);
this.actions = toolbarActions;
this.ioContainer = ioContainer != null ? ioContainer : IOContainer.getDefault();
}
/** Package private constructor for unit tests */
NbIO (String name) {
this.name = name;
}
@Override
public void closeInputOutput() {
if (Controller.LOG) {
Controller.log("CLOSE INPUT OUTPUT CALLED FOR " + this); //NOI18N
}
final NbWriter currentOut;
synchronized (this) {
currentOut = out;
}
if (currentOut != null) {
if (Controller.LOG) {
Controller.log(
" - Its output is non null, calling close() on "//NOI18N
+ currentOut);
}
currentOut.close();
}
post (this, IOEvent.CMD_CLOSE, true);
}
String getName() {
return name;
}
IOContainer getIOContainer() {
return ioContainer;
}
public OutputWriter getErr() {
return ((NbWriter) getOut()).getErr();
}
synchronized NbWriter writer() {
return out;
}
void dispose() {
if (Controller.LOG) {
Controller.log(this + ": IO " + getName() + " is being disposed"); //NOI18N
}
OutWriter currentOut = null;
IOReader currentIn = null;
synchronized (this) {
if (out != null) {
if (Controller.LOG) {
Controller.log(this + ": Still has an OutWriter. Disposing it"); //NOI18N
}
currentOut = out();
out = null;
if (in != null) {
currentIn = in;
in = null;
}
focusTaken = null;
}
}
if (currentOut != null) {
currentOut.dispose();
}
if (currentIn != null) {
currentIn.eof();
}
NbIOProvider.dispose(this);
}
public OutputWriter getOut() {
synchronized (this) {
if (out == null) {
OutWriter realout = new OutWriter(this);
out = new NbWriter(realout, this);
}
return out;
}
}
/** Called by the view when polling */
synchronized OutWriter out() {
return out == null ? null : out.out();
}
/**
* Get the current OutWriter object. If it is null, throw exception.
*
* @return The current OutWriter object, never null.
* @throws IllegalStateException if no OutWriter is available.
*/
private OutWriter outOrException() {
OutWriter outWriter = out();
if (outWriter == null) {
throw new IllegalStateException("No OutWriter available"); //NOI18N
} else {
return outWriter;
}
}
void setClosed (boolean val) {
closed = val;
}
public boolean isClosed() {
return closed;
}
public boolean isErrSeparated() {
return false;
}
public boolean isFocusTaken() {
return Boolean.TRUE.equals(focusTaken);
}
synchronized boolean isStreamClosed() {
return out == null ? true : streamClosed;
}
public void select() {
if (Controller.LOG) Controller.log (this + ": select");
post (this, IOEvent.CMD_SELECT, true);
}
public void setErrSeparated(boolean value) {
//do nothing
}
public void setErrVisible(boolean value) {
//do nothing
}
public void setFocusTaken(boolean value) {
focusTaken = value ? Boolean.TRUE : Boolean.FALSE;
post (this, IOEvent.CMD_FOCUS_TAKEN, value);
}
public void setInputVisible(boolean value) {
if (Controller.LOG) Controller.log(NbIO.this + ": SetInputVisible");
post (this, IOEvent.CMD_INPUT_VISIBLE, value);
}
public void setOutputVisible(boolean value) {
//do nothing
}
private boolean streamClosed = false;
public void setStreamClosed(boolean value) {
if (streamClosed != value) {
streamClosed = value;
post (this, IOEvent.CMD_STREAM_CLOSED, value);
}
}
public void setToolbarActions(Action[] a) {
this.actions = a;
post (this, IOEvent.CMD_SET_TOOLBAR_ACTIONS, a);
}
Action[] getToolbarActions() {
return actions;
}
public void reset() {
if (Controller.LOG) Controller.log (this + ": reset");
closed = false;
streamClosed = false;
final IOReader currentIn;
synchronized (this) {
currentIn = in;
}
if (currentIn != null) {
currentIn.eof();
currentIn.reuse();
}
post (this, IOEvent.CMD_RESET, true);
}
private static void post (NbIO io, int command, boolean val) {
IOEvent evt = new IOEvent (io, command, val);
post (evt);
}
private static void post (NbIO io, int command, Object data) {
IOEvent evt = new IOEvent (io, command, data);
post (evt);
}
static void post (IOEvent evt) {
if (SwingUtilities.isEventDispatchThread()) {
if (Controller.LOG) Controller.log ("Synchronously dispatching " + evt + " from call on EQ");
evt.dispatch();
} else {
if (Controller.LOG) Controller.log ("Asynchronously posting " + evt + " to EQ");
EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue();
eq.postEvent(evt);
}
}
@Override public String toString() {
return "NbIO@" + System.identityHashCode(this) + " " + getName();
}
synchronized IOReader in() {
return in;
}
private IOReader in = null;
public synchronized Reader getIn() {
if (in == null) {
in = new IOReader();
}
return in;
}
@SuppressWarnings("deprecation")
public Reader flushReader() {
return getIn();
}
public synchronized IOFoldingImpl getIoFolding() {
if (ioFolding == null) {
ioFolding = new IOFoldingImpl();
}
return ioFolding;
}
@Override
public synchronized Lookup getLookup() {
if (lookup == null) {
ioTab = new IOTabImpl();
ioColors = new IOColorsImpl();
ioFolding = getIoFolding();
lookup = Lookups.fixed(ioTab, ioColors, new IOPositionImpl(),
new IOColorLinesImpl(), new IOColorPrintImpl(),
new IOSelectImpl(), ioFolding, options);
}
return lookup;
}
class IOReader extends Reader {
private boolean pristine = true;
IOReader() {
super (new StringBuffer());
}
void reuse() {
pristine = true;
synchronized (lock) {
inputClosed = false;
}
}
private StringBuffer buffer() {
return (StringBuffer) lock;
}
public void pushText (String txt) {
if (Controller.LOG) Controller.log (NbIO.this + ": Input text: " + txt);
synchronized (lock) {
buffer().append (txt);
lock.notifyAll();
}
}
private boolean inputClosed = false;
public void eof() {
synchronized (lock) {
try {
close();
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
}
}
}
private void checkPristine() throws IOException {
if (SwingUtilities.isEventDispatchThread()) {
throw new IOException ("Cannot call read() from the event thread, it will deadlock");
}
if (pristine) {
NbIO.this.setInputVisible(true);
pristine = false;
}
}
public int read(char cbuf[], int off, int len) throws IOException {
if (Controller.LOG) Controller.log (NbIO.this + ":Input read: " + off + " len " + len);
checkPristine();
synchronized (lock) {
while (!inputClosed && buffer().length() == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
throw (IOException) new IOException("Interrupted: " +
e.getMessage()).initCause(e);
}
}
if (inputClosed) {
reuse();
return -1;
}
int realLen = Math.min (buffer().length(), len);
buffer().getChars(0, realLen, cbuf, off);
buffer().delete(0, realLen);
return realLen;
}
}
@Override
public int read() throws IOException {
if (Controller.LOG) Controller.log (NbIO.this + ": Input read one char");
checkPristine();
synchronized (lock) {
while (!inputClosed && buffer().length() == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
throw (IOException) new IOException("Interrupted: " +
e.getMessage()).initCause(e);
}
}
if (inputClosed) {
reuse();
return -1;
}
int i = (int) buffer().charAt(0);
buffer().deleteCharAt(0);
return i;
}
}
@Override
public boolean ready() throws IOException {
synchronized (lock) {
if (inputClosed) {
reuse();
return false;
}
return buffer().length() > 0;
}
}
@Override
public long skip(long n) throws IOException {
if (Controller.LOG) Controller.log (NbIO.this + ": Input skip " + n);
checkPristine();
synchronized (lock) {
while (!inputClosed && buffer().length() == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
throw (IOException) new IOException("Interrupted: " +
e.getMessage()).initCause(e);
}
}
if (inputClosed) {
reuse();
return 0;
}
int realLen = Math.min (buffer().length(), (int) n);
buffer().delete(0, realLen);
return realLen;
}
}
public void close() throws IOException {
if (Controller.LOG) Controller.log (NbIO.this + ": Input close");
setInputVisible(false);
synchronized (lock) {
inputClosed = true;
buffer().setLength(0);
lock.notifyAll();
}
}
public boolean isClosed() {
return inputClosed;
}
}
Icon getIcon() {
return ioTab != null ? ioTab.getIcon() : null;
}
String getToolTipText() {
return ioTab != null ? ioTab.getToolTipText() : null;
}
void setTooltipText(String toolTip) {
post(NbIO.this, IOEvent.CMD_SET_TOOLTIP, toolTip);
}
Color getColor(IOColors.OutputType type) {
return ioColors != null ? ioColors.getColor(type) : AbstractLines.getDefColors()[type.ordinal()];
}
private class IOTabImpl extends IOTab {
Icon icon;
String toolTip;
@Override
protected Icon getIcon() {
return icon;
}
@Override
protected String getToolTipText() {
return toolTip;
}
@Override
protected void setIcon(Icon icon) {
this.icon = icon;
post(NbIO.this, IOEvent.CMD_SET_ICON, this.icon);
}
@Override
protected void setToolTipText(String text) {
toolTip = text;
NbIO.this.setTooltipText(toolTip);
}
}
private class IOPositionImpl extends IOPosition {
@Override
protected Position currentPosition() {
OutWriter out = out();
int size = 0;
if (out != null) {
size = out.getLines().getCharCount();
}
return new PositionImpl(size);
}
}
private class PositionImpl implements IOPosition.Position {
private int pos;
public PositionImpl(int pos) {
this.pos = pos;
}
public void scrollTo() {
post(NbIO.this, IOEvent.CMD_SCROLL, new Integer(pos));
}
}
private class IOColorLinesImpl extends IOColorLines {
@Override
protected void println(CharSequence text, OutputListener listener, boolean important, Color color) throws IOException {
OutWriter out = out();
if (out != null) {
out.print(text, listener, important, color, null, OutputKind.OUT, true);
}
}
}
private class IOColorPrintImpl extends IOColorPrint {
@Override
protected void print(CharSequence text, OutputListener listener, boolean important, Color color) throws IOException {
OutWriter out = out();
if (out != null) {
out.print(text, listener, important, color, null, OutputKind.OUT, false);
}
}
}
private class IOSelectImpl extends IOSelect {
@Override
protected void select(Set<AdditionalOperation> extraOps) {
if (Controller.LOG) Controller.log (this + ": IOSelect.select");
NbIO.post (NbIO.this, IOEvent.CMD_FINE_SELECT, extraOps);
}
}
private class IOColorsImpl extends IOColors {
Color[] clrs = new Color[OutputType.values().length];
@Override
protected Color getColor(OutputType type) {
return clrs[type.ordinal()] != null ? clrs[type.ordinal()] : options.getColorForType(type);
}
@Override
protected void setColor(OutputType type, Color color) {
clrs[type.ordinal()] = color;
post(NbIO.this, IOEvent.CMD_DEF_COLORS, type);
}
}
private class IOFoldingImpl extends IOFolding {
@Override
protected FoldHandleDefinition startFold(boolean expanded) {
final OutWriter outWriter = out();
if (outWriter == null) {
return new DummyFoldHandleDefinition();
}
synchronized (outWriter) {
if (currentFold != null) {
throw new IllegalStateException(
"The last fold hasn't been finished yet"); //NOI18N
}
return new NbIoFoldHandleDefinition(null,
getLastLineNumber(), expanded);
}
}
/**
* FoldHandleDefinition used when the output is already closed.
*/
class DummyFoldHandleDefinition extends IOFolding.FoldHandleDefinition {
@Override
public void finish() {
}
@Override
public FoldHandleDefinition startFold(boolean expanded) {
return new DummyFoldHandleDefinition();
}
@Override
public void setExpanded(boolean expanded) {
}
}
class NbIoFoldHandleDefinition extends IOFolding.FoldHandleDefinition {
private final NbIoFoldHandleDefinition parent;
private final int start;
private int end = -1;
private NbIoFoldHandleDefinition nested = null;
public NbIoFoldHandleDefinition(NbIoFoldHandleDefinition parent,
int start, boolean expanded) {
this.parent = parent;
this.start = start;
setCurrentFoldStart(start);
setExpanded(expanded, false);
}
@Override
public void finish() {
synchronized (outOrException()) {
if (nested != null) {
throw new IllegalStateException(
"Nested fold hasn't been finished."); //NOI18N
}
if (end != -1) {
throw new IllegalStateException(
"Fold has been already finished."); //NOI18N
}
if (parent == null) {
currentFold = null;
setCurrentFoldStart(-1);
} else {
parent.nested = null;
setCurrentFoldStart(parent.start);
}
end = getLastLineNumber();
}
}
@Override
public FoldHandleDefinition startFold(boolean expanded) {
synchronized (outOrException()) {
if (end != -1) {
throw new IllegalStateException(
"The fold has been alredy finished."); //NOI18N
}
if (nested != null) {
throw new IllegalStateException(
"An unfinished nested fold exists."); //NOI18N
}
NbIoFoldHandleDefinition def = new NbIoFoldHandleDefinition(
this, getLastLineNumber(), expanded);
this.nested = def;
return def;
}
}
@Override
public final void setExpanded(boolean expanded) {
setExpanded(expanded, true);
}
/**
* Expand or collapse a fold.
*
* @param expanded True to expand the fold, false to collapse it.
* @param expandParents If true, parent folds will be expanded if
* needed.
*/
private void setExpanded(boolean expanded,
boolean expandParents) {
synchronized (outOrException()) {
if (expanded) {
if (expandParents) {
getLines().showFoldAndParentFolds(start);
} else {
getLines().showFold(start);
}
} else {
getLines().hideFold(start);
}
}
}
private void setCurrentFoldStart(int foldStartIndex) {
getLines().setCurrentFoldStart(foldStartIndex);
}
private AbstractLines getLines() {
return ((AbstractLines) out().getLines());
}
}
/**
* Access to fold creation via org.netbeans.api.io API.
*
* @return The new fold handle definition.
*/
private NbIoFoldHandleDefinition createFold(
NbIoFoldHandleDefinition parent, int foldStartIndex,
boolean expanded) {
return new NbIoFoldHandleDefinition(parent, foldStartIndex,
expanded);
}
}
private int getLastLineNumber() {
return Math.max(0, out().getLines().getLineCount() - 2);
}
/**
* Set option values. The object itself is not replaced, all registered
* listeners remains untouched.
*/
void setOptions(OutputOptions options) {
this.options.assign(options);
}
/**
* Get Options object.
*/
OutputOptions getOptions() {
return this.options;
}
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
int startFold(boolean expanded) {
synchronized (outOrException()) {
int foldStartIndex = getLastLineNumber();
if (currentFold != null && currentFold.start == foldStartIndex) {
return foldStartIndex;
} else {
currentFold = getIoFolding().createFold(currentFold,
foldStartIndex, expanded);
return foldStartIndex;
}
}
}
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
void endFold(int foldStartIndex) {
synchronized (outOrException()) {
IOFoldingImpl.NbIoFoldHandleDefinition fold = currentFold;
while (fold != null && fold.start != foldStartIndex) {
fold = fold.parent;
}
if (fold != null) {
IOFoldingImpl.NbIoFoldHandleDefinition nested = currentFold;
while (nested != fold) {
nested.finish();
nested = nested.parent;
}
fold.finish();
currentFold = fold.parent;
}
}
}
}