All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bibliothek.gui.dock.DockHierarchyLock Maven / Gradle / Ivy

The newest version!
/*
 * 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
 * themselfs 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 immediatelly.
  • *
  • 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 caought */ 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 - 2024 Weber Informatics LLC | Privacy Policy