| /* |
| * 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; |
| } |
| } |
| } |
| } |