bibliothek.gui.dock.DockHierarchyLock Maven / Gradle / Ivy
/*
* Bibliothek - DockingFrames
* Library built on Java/Swing, allows the user to "drag and drop"
* panels containing any Swing-Component the developer likes to add.
*
* Copyright (C) 2011 Benjamin Sigg
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Benjamin Sigg
* [email protected]
* CH - Switzerland
*/
package bibliothek.gui.dock;
import java.awt.EventQueue;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
/**
* The {@link DockHierarchyLock} allows {@link DockStation}s to defend
* themselves against concurrent modifications of the hierarchy. At any time only
* one {@link DockStation} or a class working with {@link DockStation}s in the realm
* of a {@link DockController} can acquire the lock.
* @author Benjamin Sigg
*/
public class DockHierarchyLock {
/** the current lock */
private volatile Token token = null;
/** whether to throw an exception or just print one */
private boolean hardExceptions = false;
/** if greater than 0, no exception is ever thrown */
private volatile int concurrent = 0;
/** the {@link Runnable}s to execute once a {@link Token} is released */
private Queue onRelease = new LinkedList();
/** whether a {@link Runnable} from {@link #onRelease} is currently executed */
private boolean onReleaseRunning = false;
/**
* Sets whether exceptions should be thrown or only printed.
* @param hardExceptions true
if the exceptions should be thrown
*/
public void setHardExceptions( boolean hardExceptions ){
this.hardExceptions = hardExceptions;
}
/**
* Tells whether hard exceptions should be thrown or only printed.
* @return true
if exceptions should be thrown
* @see #setHardExceptions(boolean)
*/
public boolean isHardExceptions(){
return hardExceptions;
}
/**
* Tells this lock whether concurrent modifications are allowed or not. If allowed no exception will be thrown
* due to a lock being acquired while a lock is already held.
* @param concurrent whether to allow concurrent modification or not
*/
public synchronized void setConcurrent( boolean concurrent ){
if( concurrent ){
this.concurrent++;
}
else{
this.concurrent--;
}
}
/**
* Whether this lock throws exceptions or is silent.
* @return if true
, then this lock is silent
* @see #isConcurrent()
*/
public boolean isConcurrent(){
return concurrent > 0;
}
/**
* Executes run
once no {@link Token} is acquired anymore. The exact order of how and when
* the {@link Runnable}s are executed is:
*
* - Any token acquired while {@link #isConcurrent()} returns
true
will be ignored.
* - There is a queue of {@link Runnable}s,
run
will be added to the end of that queue. The queue
* will be executed from the beginning to the end, this order cannot be changed.
* - If this is a recursive call, then
run
will never be executed directly.
* - If currently no {@link Token} is acquired, then
run
is executed immediately.
* run
is always executed in the EDT, other {@link Thread}s may be blocked until run
* is completed
*
* @param run the {@link Runnable} to executed, not null
* @throws IllegalArgumentException if run
is null
* @throws IllegalStateException if an {@link InterruptedException} is caught
*/
public void onRelease( Runnable run ){
if( run == null ){
throw new IllegalArgumentException( "run must not be null" );
}
synchronized( onRelease ) {
onRelease.add( run );
}
runOnRelease();
}
private void runOnRelease(){
if( EventQueue.isDispatchThread() ){
if( !onReleaseRunning ){
synchronized( this ) {
if( token != null ){
return;
}
}
try{
onReleaseRunning = true;
while( true ){
Runnable run = null;
synchronized( onRelease ){
run = onRelease.poll();
}
if( run == null ){
break;
}
run.run();
}
}
finally{
onReleaseRunning = false;
}
}
}
else{
final AtomicBoolean blocking = new AtomicBoolean( true );
EventQueue.invokeLater( new Runnable(){
public void run(){
runOnRelease();
synchronized(blocking){
blocking.set( false );
blocking.notifyAll();
}
}
});
long timeout = System.currentTimeMillis() + 1000;
synchronized( blocking ){
while( blocking.get() ){
long now = System.currentTimeMillis();
if( timeout <= now ){
break;
}
try {
blocking.wait( timeout - now );
}
catch( InterruptedException e ) {
// ignore
}
}
}
}
}
/**
* The same as calling {@link #acquireLink(DockStation, Dockable)} with the {@link DockHierarchyLock} of
* the {@link DockController} of station
. Returns a fake {@link Token} if station
has
* no {@link DockController}.
* @param station the station which wants to be the new parent of dockable
* @param dockable a dockable with no parent
* @return the acquired token to release the lock
* @throws IllegalStateException if dockable
has a parent or station
* thinks that dockable
is one of its children
*/
public static Token acquireLinking( DockStation station, Dockable dockable ){
DockController controller = station.getController();
if( controller == null ){
return new Token( null, station, dockable, true );
}
else{
return controller.getHierarchyLock().acquireLink( station, dockable );
}
}
/**
* The same as calling {@link #acquireUnlink(DockStation, Dockable)} with the {@link DockHierarchyLock} of
* the {@link DockController} of station
. Returns a fake {@link Token} if station
has
* no {@link DockController}.
* @param station the current parent of dockable
* @param dockable a dockable with station
as parent
* @return the acquired token to release the lock
* @throws IllegalStateException if dockable
is not a child of
* station
*/
public static Token acquireUnlinking( DockStation station, Dockable dockable ){
DockController controller = station.getController();
if( controller == null ){
return new Token( null, station, dockable, true );
}
else{
return controller.getHierarchyLock().acquireUnlink( station, dockable );
}
}
/**
* The same as calling {@link #acquire(DockStation)} with the {@link DockHierarchyLock} of
* the {@link DockController} of station
. Returns a fake {@link Token} if station
has
* no {@link DockController}.
* @param station the station to lock
* @return the acquired token to release the lock
* @throws IllegalStateException if a lock has already been acquired
*/
public static Token acquiring( DockStation station ){
DockController controller = station.getController();
if( controller == null ){
return new Token( null, station );
}
else{
return controller.getHierarchyLock().acquire( station );
}
}
/**
* Acquires a fake token which does not lock anything. This method never throws an exception.
* @return the fake token
*/
public static Token acquireFake(){
return new Token( null, null, null, false );
}
private String defaultMessage(){
return "During an operation the framework attempted to acquire the same lock twice. There are two possible explanations:\n" +
"1. In a multi-threaded application one or both operations are not executed in the EventDispatchThread, or\n" +
"2. The operations are calling each other, which should not happen.\n" +
"Please verify that this application is not accessing the framework from different threads, and fill a bug" +
"report if you feel that this exception is not caused by your application.";
}
/**
* Allows station
to become the new parent of dockable
.
* @param station the station which wants to be the new parent of dockable
* @param dockable a dockable with no parent
* @return the acquired token to release the lock
* @throws IllegalStateException if dockable
has a parent or station
* thinks that dockable
is one of its children
*/
public synchronized Token acquireLink( DockStation station, Dockable dockable ){
if( station == null ){
throw new IllegalArgumentException( "station is null" );
}
if( dockable == null ){
throw new IllegalArgumentException( "dockable is null" );
}
ensureUnlinked( station, dockable );
if( isConcurrent() ){
return new Token( this, station, dockable, true );
}
if( token != null ){
throwException( new IllegalStateException( defaultMessage() ) );
}
token = new Token( this, station, dockable, true );
return token;
}
/**
* Allows station
to remove itself as parent from dockable
.
* @param station the current parent of dockable
* @param dockable a dockable with station
as parent
* @return the acquired token to release the lock
* @throws IllegalStateException if dockable
is not a child of
* station
*/
public synchronized Token acquireUnlink( DockStation station, Dockable dockable ){
if( station == null ){
throw new IllegalArgumentException( "station is null" );
}
if( dockable == null ){
throw new IllegalArgumentException( "dockable is null" );
}
ensureLinked( station, dockable );
if( isConcurrent() ){
return new Token( this, station, dockable, false );
}
if( token != null ){
throwException( new IllegalStateException( defaultMessage() ) );
}
token = new Token( this, station, dockable, false );
return token;
}
/**
* Acquires a lock describing the entire contents of station
. This
* method is intended in situations where the layout of station
gets
* modified and this modification must not be interrupted.
* @param station the station for which a lock is requested
* @return the acquired token to release the lock
* @throws IllegalStateException if the lock is already acquired
*/
public synchronized Token acquire( DockStation station ){
if( isConcurrent() ){
return new Token( this, station );
}
if( token != null ){
throwException( new IllegalStateException( defaultMessage() ) );
}
token = new Token( this, station );
return token;
}
private void ensureLinked( DockStation station, Dockable dockable ){
if( dockable.getDockParent() != station ){
throwException( new IllegalStateException( "the parent of '" + dockable + "' is not '" + station + "' but '" + dockable.getDockParent() + "'" ) );
return;
}
boolean found = false;
for( int i = 0, n = station.getDockableCount(); i
© 2015 - 2025 Weber Informatics LLC | Privacy Policy