/* * 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) 2007 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 * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock.util; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import bibliothek.gui.DockController; /** * A set of properties that are used at different places all over the framework. * The map uses a {@link Priority} based system, allowing clients to override * behavior of themes or set default values in case a theme does not set one. * @author Benjamin Sigg */ public class DockProperties { /** the map of values */ private Map<PropertyKey<?>, Entry<?>> map = new HashMap<PropertyKey<?>, Entry<?>>(); /** the owner of this map */ private DockController controller; /** * Creates a new map. * @param controller the owner of this map */ public DockProperties( DockController controller ){ if( controller == null ){ throw new IllegalArgumentException( "controller must not be null" ); } this.controller = controller; } /** * Gets the owner of this {@link DockProperties}. * @return the owner, not <code>null</code> */ public DockController getController(){ return controller; } /** * Sets a value. This is equivalent to calling <code>set( key, value, Priority.CLIENT )</code>. * @param <A> the type of the value * @param key the key to access the value * @param value the value, can be <code>null</code> */ public <A> void set( PropertyKey<A> key, A value ){ set( key, value, Priority.CLIENT ); } /** * Sets a value. * @param <A> the type of the value * @param key the key to access the value * @param value the value, can be <code>null</code> * @param priority the priority of the new value */ public <A> void set( PropertyKey<A> key, A value, Priority priority ){ Entry<A> entry = getEntry( key, true ); entry.setValue( value, priority ); check( entry ); } /** * Ensures that the value behind <code>key</code> will never be * changed. Should be used with care: any attempt to set the value afterwards * will be responded with an {@link IllegalStateException}. Most times it * is much better to just use {@link Priority#CLIENT} to mark some setting * as important. * @param <A> the type of the value * @param key the key to protect */ public <A> void finalize( PropertyKey<A> key ){ Entry<A> entry = getEntry( key, true ); entry.lock(); } /** * Either sets the property <code>key</code> to <code>value</code> or * set the default value for <code>key</code>. * @param <A> the type of the value * @param key the key to access the value * @param value the new value, if <code>null</code> then the default * value will be set * @param priority the priority of the value to remove */ public <A> void setOrRemove( PropertyKey<A> key, A value, Priority priority ){ if( value == null ) unset( key, priority ); else set( key, value, priority ); } /** * Tells the entry <code>key</code> that the user has never set its value. * This is equivalent to calling <code>unset( key, Priority.CLIENT )</code>. * @param key the key to access the entry */ public void unset( PropertyKey<?> key ){ unset( key, Priority.CLIENT ); } /** * Tells the entry <code>key</code> that the user has never set its value. * Also removes the old value of the entry. * @param key the key to access the entry * @param priority the priority for which to remove the value */ public void unset( PropertyKey<?> key, Priority priority ){ Entry<?> entry = getEntry( key, true ); entry.unsetValue( priority ); check( entry ); } /** * Gets the value accessed by <code>key</code>. If the value in the * properties is not set, then the {@link PropertyKey#getDefault(DockProperties) default} * value is returned. * @param <A> the type of the value * @param key the key to search * @return the value or <code>null</code> */ public <A> A get( PropertyKey<A> key ){ Entry<A> entry = getEntry( key, true ); return entry.getValue(); } /** * Gets the value of <code>key</code> for the given <code>priority</code>. * @param <A> the kind of value * @param key some key, not <code>null</code> * @param priority the priority, not <code>null</code> * @return the value, might be <code>null</code> even if {@link #get(PropertyKey)} * returns a non-<code>null</code> value */ public <A> A get( PropertyKey<A> key, Priority priority ){ Entry<A> entry = getEntry( key, false ); if( entry == null ) return null; return entry.getValue( priority ); } /** * Tells whether there is value set for <code>key</code> with <code>priority</code>. * @param <A> the kind of value * @param key the key to check * @param priority the priority for which something might be set * @return <code>true</code> if there is a value set */ public <A> boolean isSet( PropertyKey<A> key, Priority priority ){ Entry<A> entry = getEntry( key, false ); if( entry == null ) return false; return entry.value.isSet( priority ); } /** * Tells whether there is value set for <code>key</code>. * @param <A> the kind of value * @param key the key to check * @return <code>true</code> if there is a value set */ public <A> boolean isSet( PropertyKey<A> key ){ Entry<A> entry = getEntry( key, false ); if( entry == null ) return false; return entry.value.isSomethingSet(); } /** * Adds a listener that will be informed whenever the value accessed * through <code>key</code> changes. * @param <A> the type of the value * @param key the key that accesses the value * @param listener the new listener */ public <A> void addListener( PropertyKey<A> key, DockPropertyListener<A> listener ){ if( listener == null ) throw new IllegalArgumentException( "Listener must not be null" ); getEntry( key, true ).addListener( listener ); } /** * Removes an earlier added listener. * @param <A> the type of value observed by the listener * @param key the key to access the observed entry * @param listener the listener to remove */ public <A> void removeListener( PropertyKey<A> key, DockPropertyListener<A> listener ){ Entry<A> entry = getEntry( key, false ); if( entry != null ){ entry.removeListener( listener ); check( entry ); } } /** * Gets the entry for <code>key</code>. * @param <A> the type of the entry * @param key the name of the entry * @param createIfNull <code>true</code> if <code>null</code> is not a valid * result. * @return the entry or <code>null</code>, but only if <code>createIfNull</code> * is <code>false</code> */ @SuppressWarnings( "unchecked" ) private <A> Entry<A> getEntry( PropertyKey<A> key, boolean createIfNull ){ Entry<?> entry = map.get( key ); if( entry == null && createIfNull ){ entry = new Entry<A>( key ); map.put( key, entry ); } return (Entry<A>)entry; } /** * Checks whether <code>entry</code> has to be stored any longer. * @param entry the entry that may be deleted */ private void check( Entry<?> entry ){ if( entry.removeable() ){ map.remove( entry.getKey() ); } } /** * An entry that contains key, listeners and a value. * @author Benjamin Sigg * * @param <A> the type of the value */ private class Entry<A>{ /** the name of this entry */ private PropertyKey<A> key; /** listeners to this entry */ private List<DockPropertyListener<A>> listeners = new ArrayList<DockPropertyListener<A>>(); /** the value stored in this entry */ private NullPriorityValue<A> value = new NullPriorityValue<A>(); /** default value of this entry */ private A defaultValue; /** whether the default value was ever needed and has been set */ private boolean defaultValueSet = false; /** whether changes of this entry are allowed */ private boolean locked = false; /** * Creates a new entry. * @param key the name of this entry */ public Entry( PropertyKey<A> key ){ this.key = key; } /** * If called makes this entry immutable. */ public void lock(){ locked = true; } /** * Sets the new value of this entry. * @param value the new value * @param priority the priority of the new value */ @SuppressWarnings( "unchecked" ) public void setValue( A value, Priority priority ){ if( locked ){ throw new IllegalStateException( "this entry is immutable" ); } A oldValue = getValue(); this.value.set( priority, value ); A newValue = getValue(); if( (oldValue == null && newValue != null) || (oldValue != null && newValue == null) || (oldValue != null && !oldValue.equals( newValue ))){ for( DockPropertyListener<A> listener : (DockPropertyListener<A>[])listeners.toArray( new DockPropertyListener<?>[ listeners.size() ] )) listener.propertyChanged( DockProperties.this, key, oldValue, newValue ); } } /** * Removes a value from this entry * @param priority the priority of the value to unset */ @SuppressWarnings("unchecked") public void unsetValue( Priority priority ){ if( locked ){ throw new IllegalStateException( "this entry is immutable" ); } A oldValue = getValue(); this.value.unset( priority ); A newValue = getValue(); if( (oldValue == null && newValue != null) || (oldValue != null && newValue == null) || (oldValue != null && !oldValue.equals( newValue ))){ for( DockPropertyListener<A> listener : (DockPropertyListener<A>[])listeners.toArray( new DockPropertyListener<?>[ listeners.size() ] )) listener.propertyChanged( DockProperties.this, key, oldValue, newValue ); } } /** * Gets the value of this entry. * @return the value */ public A getValue(){ A value = this.value.get(); if( value == null && (!this.value.isSomethingSet() || key.isNullValueReplacedByDefault()) ){ return getDefault(); } return value; } /** * Gets the value stored for <code>priority</code>. * @param priority the priority, not <code>null</code> * @return the value, might be <code>null</code> */ public A getValue( Priority priority ){ return value.get( priority ); } /** * Gets the default value of this property. * @return the default value, may be <code>null</code> */ public A getDefault(){ if( !defaultValueSet ){ defaultValue = key.getDefault( DockProperties.this ); defaultValueSet = true; } return defaultValue; } /** * Gets the name of this entry. * @return the name */ public PropertyKey<A> getKey(){ return key; } /** * Adds a new listener to this entry. * @param listener the new listener */ public void addListener( DockPropertyListener<A> listener ){ listeners.add( listener ); } /** * Removes a listener from this entry. * @param listener the listener to remove */ public void removeListener( DockPropertyListener<A> listener ){ listeners.remove( listener ); } /** * Tells whether this entry is needed any longer or not. * @return <code>true</code> if this entry can be deleted safely. */ public boolean removeable(){ if( locked ) return false; if( !listeners.isEmpty() ) return false; if( defaultValueSet || value.isSomethingSet() ) return false; return true; } } }