| java.lang.Object threaddemo.util.TwoWaySupport
TwoWaySupport | abstract public class TwoWaySupport (Code) | | Support for bidirectional construction of a derived model from an underlying model.
Based on a lock which is assumed to control both models.
Handles all locking and scheduling associated with such a system.
It is possible to "nest" supports so that the derived model of one is the
underlying model of another - but they must still share a common lock.
"Derive" means to take the underlying model (not represented explicitly here,
but assumed to be "owned" by the subclass) and produce the derived model;
typically this will involve parsing or the like. This operates in a read lock.
"Recreate" means to take a new derived model (which may in fact be the same
as the old derived model but with different structure) and somehow change the
underlying model on that basis.
"Initiate" means to start derivation asynchronously, not waiting for the
result to be complete; this operation is idempotent, i.e. you can call it
whenever you think you might like the value later, but it will not cause
gratuitous extra derivations.
"Invalidate" means to signal that the underlying model has somehow changed
and that if there is any derived model it should be considered stale.
Invalidating when there is not yet any derived model is a no-op.
There are four different kinds of "values" which are employed by this class
and which you should be careful to differentiate:
The state of the underlying model. This is not explicitly modeled
by this class. Subclasses are expected to use that state as needed in
TwoWaySupport.doDerive and
TwoWaySupport.doRecreate .
The state ("value") of the derived model. This is never null and is the
return value of
TwoWaySupport.doRecreate ,
TwoWaySupport.getValueBlocking ,
TwoWaySupport.getValueNonBlocking , and
TwoWaySupport.getStaleValueNonBlocking (except
where those methods are documented to return null), as well as the first
parameter to
TwoWaySupport.doRecreate ,
TwoWaySupport.doDerive , and
TwoWaySupport.DerivationResult.TwoWaySupport and the parameter to
TwoWaySupport.createReference .
Deltas in the underlying model. These may in fact be entire new copies
of an underlying model, or some diff-like structure, or an
java.util.EventObject ,
etc. - whatever seems most convenient. These are never null and are the argument
type of
TwoWaySupport.invalidate and the second argument type of
TwoWaySupport.doDerive as well as both argument types and the return value of
TwoWaySupport.composeUnderlyingDeltas .
Deltas in the derived model. Again these may be of the same form as the
derived model itself - just replacing the model wholesale - or they may be some
kind of diff or event structure. These are again never null and are the argument
for
TwoWaySupport.mutate and the second argument for
TwoWaySupport.doRecreate and
TwoWaySupport.DerivationResult.TwoWaySupport .
Setting a new derived value explicitly always sets it immediately.
When getting the derived value, you have several choices. You can ask for the
exact value, if necessary waiting for it to be derived for the first time, or
rederived if it is stale. Or you can ask for the value if it is fresh or accept
null if it is missing or stale. Or you can ask for the value if it is fresh or
stale and accept null if it is missing. The latter two operations do not block
(except to get the read lock) and so are valuable in views.
Derivation is started immediately after an initiate operation if there is
no derived model yet. If there is a model but it is stale and you ask to
initiate derivation, by default this also starts immediately, but you may
instead give a delay before the new derivation starts (assuming no one asks
for the exact derived value before then); this is useful for cases where
derivation is time-consuming (e.g. a complex parse) and for performance
reasons you wish to avoid triggering it too frivolously. For example, you may
be invalidating the derived model after every keystroke which changes a text
document, but would prefer to wait a few seconds before showing new results.
In case a recreate operation is attempted during a delay in which the model
is stale, or simply while a derivation is in progress with or without a preceding
delay, there is a conflict: the recreated model is probably a modification of
the old stale underlying model, and it is likely that setting it as the new derived
model and recreating the underlying model would clobber intermediate changes in the
underlying model, causing data loss. By default this support will signal an exception
if this is attempted, though subclasses may choose to suppress that and forcibly
set the new derived model and recreate the underlying model. Subclasses are better advised
to use the exception, and ensure that views of the derived model either handle
it gracefully (e.g. offering the user an opportunity to retry the modification
on the new derived model when it is available, or just beeping), or put the
derived view into a read-only mode temporarily while there is a stale underlying
model so that such a situation cannot arise.
There is a kind of "external clobbering" that can occur if the view does not
update itself promptly after a recreation (generally, after a change in the
derived model leading to a fresh value) but only with some kind of delay. In
that case an attempted change to the derived model may be working with obsolete
data. The support does not try to handle this case; the view is
responsible for detecting it and reacting appropriately.
Another kind of "clobbering" can occur in case the underlying model is not
completely controlled by the lock. For example, it might be the native filesystem,
which can change at any time without acquiring a lock in the JVM. In that case
an attempted mutation may be operating against a model derived from an older
state of the underlying model. Again, this support does not provide a
solution for this problem. Subclasses should attempt to detect such a condition
and recover from it gracefully, e.g. by throwing an exception from
doRecreate or by merging changes. Using TwoWaySupport may not be
appropriate for such cases anyway, since derivation could then cause an existing
reader to see state changes within its read lock, which could violate its
assumptions about the underlying model.
Derivation and recreation may throw checked exceptions. In such cases the
underlying and derived models should be left in a consistent state if at all
possible. If derivation throws an exception, the derived model will be considered
stale, but no attempt to rederive the model will be made unless the underlying
model is invalidated; subsequent calls to
TwoWaySupport.getValueBlocking with the
same underlying model will result in the same exception being thrown repeatedly.
Views should generally put themselves into a read-only mode in this case.
If recreation throws an exception, this is propagated to
TwoWaySupport.mutate but
otherwise nothing is changed.
You may not call any methods of this class from within the dynamic scope of
TwoWaySupport.doDerive or
TwoWaySupport.doRecreate or a listener callback.
You can attach a listener to this class. You will get an event when the
status of the support changes. All events are fired as soon as possible in the
read lock.
author: Jesse Glick |
Inner Class :final protected static class DerivationResult | |
Inner Class :final static class DeriveTask implements Comparable<DeriveTask<DM, UMD, DMD>> | |
Method Summary | |
final public void | addTwoWayListener(TwoWayListener<DM, UMD, DMD> l) Add a listener to lifecycle changes in the support. | abstract protected UMD | composeUnderlyingDeltas(UMD underlyingDelta1, UMD underlyingDelta2) Compute the effect of two sequential changes to the underlying model. | protected Reference<DM> | createReference(DM value, ReferenceQueue q) Create a reference to the derived model. | protected long | delay() Supply an optional delay before rederivation of a model after an invalidation.
If zero (the default), there is no intentional delay. | abstract protected DerivationResult<DM, DMD> | doDerive(DM oldValue, UMD underlyingDelta) Compute the derived model from the underlying model.
This method is called with a read lock held on the lock.
However for derived models with mutable state you may need to acquire an
additional simple lock (monitor) on some part of the model to refresh its
state - this is not a true write, but other readers should be locked out
until it is finished. | abstract protected DM | doRecreate(DM oldValue, DMD derivedDelta) Recreate the underlying model from the derived model.
This method is called with a write lock held on the lock.
It is expected that any changes to the underlying model will be notified
to the relevant listeners within the dynamic scope of this method. | final public RWLock | getLock() Get the associated lock. | final public DM | getStaleValueNonBlocking() Get the value of the derived model, if it is ready (fresh or stale). | final public DM | getValueBlocking() Get the value of the derived model, blocking as needed until it is ready. | final public DM | getValueNonBlocking() Get the value of the derived model, if it is ready and fresh. | final public void | initiate() Initiate creation of the derived model from the underlying model. | protected void | initiating() Called during
TwoWaySupport.initiate .
The default implementation does nothing. | final public void | invalidate(UMD underlyingDelta) Indicate that any current value of the derived model is invalid and
should no longer be used if exact results are desired. | final public DM | mutate(DMD derivedDelta) Change the value of the derived model and correspondingly update the
underlying model. | protected boolean | permitsClobbering() Indicate whether this support permits changes to the derived model via
TwoWaySupport.mutate to "clobber" underived changes to the underlying model. | final public void | removeTwoWayListener(TwoWayListener<DM, UMD, DMD> l) Add a listener to lifecycle changes in the support. |
TwoWaySupport | protected TwoWaySupport(RWLock lock)(Code) | | Create an uninitialized support.
No derivation or recreation is scheduled initially.
Parameters: lock - the associated lock |
addTwoWayListener | final public void addTwoWayListener(TwoWayListener<DM, UMD, DMD> l)(Code) | | Add a listener to lifecycle changes in the support.
A listener may be added multiple times and must be removed once
for each add.
This method may be called from any thread and will not block.
Parameters: l - a listener to add |
composeUnderlyingDeltas | abstract protected UMD composeUnderlyingDeltas(UMD underlyingDelta1, UMD underlyingDelta2)(Code) | | Compute the effect of two sequential changes to the underlying model.
This method is called with a read lock held on the lock.
After this method is called, the first argument is discarded by this support,
so a subclass may implement it by mutating the first argument and returning it.
Parameters: underlyingDelta1 - the older delta Parameters: underlyingDelta2 - the newer delta a delta representing those two changes applied in sequence |
createReference | protected Reference<DM> createReference(DM value, ReferenceQueue q)(Code) | | Create a reference to the derived model.
The support will only retain this reference (though event objects will
strongly refer to the derived model when appropriate).
If the referent is collected, the support returns to an underived state.
This implementation always creates a strong reference that will never
be collected so long as the support itself is not collected.
Parameters: value - a derived model object Parameters: q - a reference queue supplied by the support a reference to the model enqueued on that reference queue |
delay | protected long delay()(Code) | | Supply an optional delay before rederivation of a model after an invalidation.
If zero (the default), there is no intentional delay. The delay is irrelevant
in the case of
TwoWaySupport.getValueBlocking .
a delay in milliseconds (>= 0) |
doDerive | abstract protected DerivationResult<DM, DMD> doDerive(DM oldValue, UMD underlyingDelta) throws Exception(Code) | | Compute the derived model from the underlying model.
This method is called with a read lock held on the lock.
However for derived models with mutable state you may need to acquire an
additional simple lock (monitor) on some part of the model to refresh its
state - this is not a true write, but other readers should be locked out
until it is finished. For purely functional derived models that are
replaced wholesale, this is not necessary.
Note that derivations never run in parallel, even though they are in a
read lock. In this implementation, all derivations in fact run in a dedicated
thread if they are invoked asynchronously using
TwoWaySupport.initiate , but that
may change.
TwoWayListener.derived will be triggered after this method
completes. An implementation is responsible for notifying relevant listeners
of changes to the derived model, but should not do so from within the scope
of this method, as the new value of the derived model will not yet be available;
instead listen for
TwoWayEvent.Derived . Both the derived delta and
final value are made available to that event for this reason.
Parameters: oldValue - the old value of the derived model, or null if it hadnever been calculated before Parameters: underlyingDelta - a change in the underlying model, or null if noparticular change was signalled the new value of the derived model (might be the same object asthe old value) plus the derived delta throws: Exception - (checked only!) if derivation of the model failed |
doRecreate | abstract protected DM doRecreate(DM oldValue, DMD derivedDelta) throws Exception(Code) | | Recreate the underlying model from the derived model.
This method is called with a write lock held on the lock.
It is expected that any changes to the underlying model will be notified
to the relevant listeners within the dynamic scope of this method. An implementation
is responsible for notifying relevant listeners of changes to the derived
model, but should not do so from within the scope of this method, as the
new value of the derived model will not yet be available; instead listen for
TwoWayEvent.Recreated .
Parameters: oldValue - the old value of the derived model, or null if it wasnever derived Parameters: derivedDelta - a change in the derived model the new value of the derived model (might be the same object asthe old value) throws: Exception - (checked only!) if recreation of the underlying model failed |
getLock | final public RWLock getLock()(Code) | | Get the associated lock.
the lock |
getStaleValueNonBlocking | final public DM getStaleValueNonBlocking()(Code) | | Get the value of the derived model, if it is ready (fresh or stale).
This method requires the read lock but otherwise does not block.
the value of the derived model, or null if it has never beencomputed at all |
getValueBlocking | final public DM getValueBlocking() throws InvocationTargetException(Code) | | Get the value of the derived model, blocking as needed until it is ready.
This method requires the read lock and may block further for
TwoWaySupport.doDerive .
the value of the derived model (never null) throws: InvocationTargetException - if doDerive was calledand threw an exception (possibly from anearlier derivation run that is still broken) |
getValueNonBlocking | final public DM getValueNonBlocking()(Code) | | Get the value of the derived model, if it is ready and fresh.
This method requires the read lock but otherwise does not block.
the value of the derived model, or null if it is stale or has neverbeen computed at all |
initiate | final public void initiate()(Code) | | Initiate creation of the derived model from the underlying model.
This is a no-op unless that process has not yet been started or if the
value of the derived model is already fresh and needs no rederivation.
This method does not require the lock nor does it block, except
insofar as
TwoWaySupport.initiating might.
|
initiating | protected void initiating()(Code) | | Called during
TwoWaySupport.initiate .
The default implementation does nothing. Subclasses may choose to initiate
a request for some information from the underlying model, if it is not
immediately accessible.
This method is not called with any lock, so if a read lock is desired,
it must be requested explicitly.
|
invalidate | final public void invalidate(UMD underlyingDelta)(Code) | | Indicate that any current value of the derived model is invalid and
should no longer be used if exact results are desired.
This method requires the read lock but does not block otherwise,
except to call
TwoWaySupport.composeUnderlyingDeltas .
Parameters: underlyingDelta - a change to the underlying model |
permitsClobbering | protected boolean permitsClobbering()(Code) | | Indicate whether this support permits changes to the derived model via
TwoWaySupport.mutate to "clobber" underived changes to the underlying model.
If false (the default), such attempts will throw
ClobberException .
If true, they will be permitted, though a clobber event will be notified
rather than a recreate event.
A subclass must always return the same value from this method.
true to permit clobbering, false to forbid it |
removeTwoWayListener | final public void removeTwoWayListener(TwoWayListener<DM, UMD, DMD> l)(Code) | | Add a listener to lifecycle changes in the support.
This method may be called from any thread and will not block.
Parameters: l - a listener to remove |
|
|