blob: 76d45233ffa397499c4b7751962502da2167f545 [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.
"README.close_semantics"
Semantics of InputOutput (IO) close operations
or
Major state transitions of an IO.
TOC
---
Introduction
Motivation
Dramatis personae
Major state transitions
Typical lifecycle
Querying the state
Misc. notes on IOContainer
Introduction
------------
This writeup evolved in an attempt to analyze the existing default
implementation (output2) while implementing a second provider (terminal).
It is written as a "best effort" and undoubtedly still contains errors
regarding the default 'output2' provider.
It is primarily intended for implementors/maintainers but should be useful
to API users as well.
While the general analysis pertains to both the 'output2' and the 'terminal'
providers, some methods are only available for terminal.
Motivation
----------
The I/O APIs contain many methods and user visible actions with the
word "close" in them:
InputOutput.closeInputOutput().
The Close action in the popup menu.
The Close Tab/Close All Tabs/Close Other Tabs actions.
IOContainer.CallBacks.closed().
OutputWriter.close() (inherited from PrintWriter).
There also exist various predicates like:
InputOutput.isClosed().
IOContainer.isClosable().
There seems to be no corresponding "open" operation.
Instead we have various gets's and select's:
IOProvider.getIO()
InputOutput.getOut().
InputOutput.select().
IOContainer.select().
It turns out that methods based on the words "close" or "select" don't
all mean or do the same thing.
Dramatis personae
-----------------
IOProvider Has a list of IO's
InputOutput Abstract entity owned by an IOProvider
Has Streams.
Has a "Component".
IOContainer Abstract entity.
Has a list of Components that are visible.
The actual container implementation is hidden.
Component Concrete entity (Typically a JComponent) that appears in an
IOContainer. (IOTab in output2/Terminal in terminal).
Major state transitions
-----------------------
There are three major state "variables" that control the overall state
of an IO:
- Existence
An IO comes into existence via one of the family IOProvider.getIO().
Calling IO.closeInputOutput() disposes it. It is of course still available
until it is GC'ed but any operation on it after closeInputOutput() is
undefined.
Both output2 and terminal use the words dispose[d] internally. So
we'll adopt dispose for our terminology.
Disposal frees all resources used by an IO and additionally calls eof()
in IO.getIn().
There is no associated UI action for disposal so clients need to ensure
that they call closeInputOutput() when appropriate (heh-heh).
Disposing was called "Strong closing" in some earlier discussions.
- Visibility
An IO is visible when it is visible in it's container (with or without a tab)
_regardless_ of whether the container is visible as a window. I.e.
an IO tab may be added to the Output window even though the output
window itself is closed.
An IO can be made visible (opened?) using ...
o IO.select()
Also ensures that the container (it's TopComponent) is visible.
o IOSelect.select() which allows finer control over the visibility
of the containing TopComponent.
o IOVisibility.setVisible(true)
This one is independent of the visibility of the container itself.
A freshly created IO is visible by default but not selected! That is,
it is added to a container but the TopComponent will not be made
visible.
An IO can be made invisible (closed) using ...
o Close Action in context menu
o Clicking on the X of it's tab.
o IOVisibility.setVisible(false)
By far the most common use of the term "close" applies to this
state transition so we'll adopt close for our terminology.
This kind of closing was called "Weak closing" in some earlier discussions.
Conditional vs Unconditional closing
....................................
The following closing operations ...
o Close Action in context menu
o Clicking on the X of it's tab.
are tempered using IOVisibility.setClosable() and additionally via
the vetoing mechanism provided by IONotifier.addVetoableChangeListener()
and IOVisibility.PROP_VISIBILITY.
The following closing operations succeed unconditionally:
o IOVisibility.setVisible(false)
o InputOutput.closeInputOutput()
- Connectedness
An IO starts life in a disconnected state.
An IO is connected if one of IO.getOut(), IO.getErr() or IOTerm.connect()
are used.
A connected IO has it's title rendered in bold.
An IO is disconnected if the OutputWriters returned by getOut() and
getIn() are close()ed and once IOTerm.disconnect()'s continuation is called.
As a convenience closing getOut() will close getErr().
Not quite sure about this: With the output2 provider
an IO can go back to the connected state after a reset() and/or
the first write.
With the terminal provider IO goes back to connected state
as explained above.
The use of the word close in this context is highly misleading because even
after closing them one can write to the OutputWriters provided by getOut()
and getErr()!
It's probably best to never save the OutputWriter returned by getOut/Err()
and call getOut/Err() every time. That will ensure the correct maintenance
of the connected state.
So, if close() doesn't really close anything, what does it really mean to be
disconnected? It serves two purposes:
o A disconnected IO is eligible for reuse via
IOProvider.getIO(String name, boolean newIO = false)
provided the names match.
This is the original and primary use of this state.
The precise semantics of reuse are as follows:
- choose a set of disconnecetd IO's with matching names.
- if any are found close all but one and return it.
- else return null
o IONotifier.addVetoableChangeListener() together with
IOVisibility.PROP_VISIBLE provide a way of accidentally preventing the
closure (setVisible(false)) of an IO which cannot be made visible again.
A disconnected IO retains it's buffer so if it's is to be reused,
IO.getOut().reset() should be used to clear the buffer.
Disconnecting was called "Stream closing" in some earlier discussions.
[ The output2 implementation seems to track this state in two ways:
- OutputWriter.isClosed().
- An internal property streamClosed. ]
The visibility and connected states are independent of each other. All
four combinations make sense and are allowed.
A disposed (IO.closeInputOutput()) IO becomes invisible and is disconnected.
It is not eligible for reuse via IOProvider.getIO(..., newIO = false).
- output2 allows a disposed IO to be re-select()ed.
- terminal ignores all operations on a dispose IO.
Typical lifecycle
-----------------
IOProvider ioProvider = IOProvider.getDefault();
InputOutput io = ioProvider.getIO("name", null);
io.select();
OutputWriter ow = io.getOut();
ow.println("Hello");
ow.close(); // optional
if (IOVisibility.isSupported(IO))
IOVisibility.setVisible(false); // optional
io.closeInputOutptu();
Querying the state
------------------
Operations on an IO may be requested on any thread. However, they
are mostly performed on the EDT. As a result the state transition will
not happen serially with request to the request!
Querying of state, e.g. using IOVisibility.isClosable(), works as follows:
If the call is performed on the EDT the result is returned immediately.
Otherwise the call is blocked while the result is computed on the EDT
and passed back via a Future.
Misc. notes on IOContainer
--------------------------
Although IOContainer is part of the API it is primarily intended for use
by implementations (analog of a protected final method).
- IOContainer.remove() is used to implement closing (setVisible(false)).
It removes the Component and calls IOContainer.CallBacks.closed().
Because a disconnected IO may be reused, CallBacks.closed() should not
free any resources! [ output2 impl tries some heuristics and sometimes
frees stuff anyway ].
- What about IOContainer[.Provider].isClosable()?
It's original semantics are unclear.
The 'output2' provider always returns true.
In the 'terminal' provider I took it to mean "can the component
be made invisible"?