| /* |
| * Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed 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. |
| */ |
| package org.apache.directory.server.protocol.shared.chain.impl; |
| |
| |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.lang.reflect.Method; |
| import java.util.AbstractCollection; |
| import java.util.AbstractSet; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.directory.server.protocol.shared.chain.Context; |
| |
| |
| /** |
| * <p>Convenience base class for {@link Context} implementations.</p> |
| * |
| * <p>In addition to the minimal functionality required by the {@link Context} |
| * interface, this class implements the recommended support for |
| * <em>Attribute-Property Transparency</p>. This is implemented by |
| * analyzing the available JavaBeans properties of this class (or its |
| * subclass), exposes them as key-value pairs in the <code>Map</code>, |
| * with the key being the name of the property itself.</p> |
| * |
| * <p><strong>IMPLEMENTATION NOTE</strong> - Because <code>empty</code> is a |
| * read-only property defined by the <code>Map</code> interface, it may not |
| * be utilized as an attribute key or property name.</p> |
| * |
| * @author Craig R. McClanahan |
| * @version $Revision$ $Date$ |
| */ |
| public class ContextBase extends HashMap implements Context |
| { |
| // ------------------------------------------------------------ Constructors |
| |
| private static final long serialVersionUID = 5536081240350960868L; |
| |
| |
| /** |
| * Default, no argument constructor. |
| */ |
| public ContextBase() |
| { |
| super(); |
| initialize(); |
| } |
| |
| |
| /** |
| * <p>Initialize the contents of this {@link Context} by copying the |
| * values from the specified <code>Map</code>. Any keys in <code>map</code> |
| * that correspond to local properties will cause the setter method for |
| * that property to be called.</p> |
| * |
| * @param map Map whose key-value pairs are added |
| * |
| * @exception IllegalArgumentException if an exception is thrown |
| * writing a local property value |
| * @exception UnsupportedOperationException if a local property does not |
| * have a write method. |
| */ |
| public ContextBase(Map map) |
| { |
| super( map ); |
| initialize(); |
| putAll( map ); |
| } |
| |
| // ------------------------------------------------------ Instance Variables |
| |
| /** |
| * <p>The <code>PropertyDescriptor</code>s for all JavaBeans properties |
| * of this {@link Context} implementation class, keyed by property name. |
| * This collection is allocated only if there are any JavaBeans |
| * properties.</p> |
| */ |
| private Map descriptors = null; |
| |
| /** |
| * <p>The same <code>PropertyDescriptor</code>s as an array.</p> |
| */ |
| private PropertyDescriptor[] pd = null; |
| |
| /** |
| * <p>Distinguished singleton value that is stored in the map for each |
| * key that is actually a property. This value is used to ensure that |
| * <code>equals()</code> comparisons will always fail.</p> |
| */ |
| private static Object singleton; |
| |
| static |
| { |
| singleton = new Object() |
| { |
| public boolean equals( Object object ) |
| { |
| return ( false ); |
| } |
| }; |
| } |
| |
| /** |
| * <p>Zero-length array of parameter values for calling property getters. |
| * </p> |
| */ |
| private static Object[] zeroParams = new Object[0]; |
| |
| |
| // ------------------------------------------------------------- Map Methods |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to clear all keys and |
| * values except those corresponding to JavaBeans properties.</p> |
| */ |
| public void clear() |
| { |
| if ( descriptors == null ) |
| { |
| super.clear(); |
| } |
| else |
| { |
| Iterator keys = keySet().iterator(); |
| while ( keys.hasNext() ) |
| { |
| Object key = keys.next(); |
| if ( !descriptors.containsKey( key ) ) |
| { |
| keys.remove(); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to return |
| * <code>true</code> if the specified value is present in either the |
| * underlying <code>Map</code> or one of the local property values.</p> |
| * |
| * @exception IllegalArgumentException if a property getter |
| * throws an exception |
| */ |
| public boolean containsValue( Object value ) |
| { |
| // Case 1 -- no local properties |
| if ( descriptors == null ) |
| { |
| return ( super.containsValue( value ) ); |
| } |
| // Case 2 -- value found in the underlying Map |
| else if ( super.containsValue( value ) ) |
| { |
| return ( true ); |
| } |
| |
| // Case 3 -- check the values of our readable properties |
| for ( int i = 0; i < pd.length; i++ ) |
| { |
| if ( pd[i].getReadMethod() != null ) |
| { |
| Object prop = readProperty( pd[i] ); |
| if ( value == null ) |
| { |
| if ( prop == null ) |
| { |
| return ( true ); |
| } |
| } |
| else if ( value.equals( prop ) ) |
| { |
| return ( true ); |
| } |
| } |
| } |
| |
| return ( false ); |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to return a |
| * <code>Set</code> that meets the specified default behavior except |
| * for attempts to remove the key for a property of the {@link Context} |
| * implementation class, which will throw |
| * <code>UnsupportedOperationException</code>.</p> |
| */ |
| public Set entrySet() |
| { |
| return ( new EntrySetImpl() ); |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to return the value |
| * of a local property if the specified key matches a local property name. |
| * </p> |
| * |
| * <p><strong>IMPLEMENTATION NOTE</strong> - If the specified |
| * <code>key</code> identifies a write-only property, <code>null</code> |
| * will arbitrarily be returned, in order to avoid difficulties implementing |
| * the contracts of the <code>Map</code> interface.</p> |
| * |
| * @param key Key of the value to be returned |
| * |
| * @exception IllegalArgumentException if an exception is thrown |
| * reading this local property value |
| * @exception UnsupportedOperationException if this local property does not |
| * have a read method. |
| */ |
| public Object get( Object key ) |
| { |
| // Case 1 -- no local properties |
| if ( descriptors == null ) |
| { |
| return ( super.get( key ) ); |
| } |
| |
| // Case 2 -- this is a local property |
| if ( key != null ) |
| { |
| PropertyDescriptor descriptor = ( PropertyDescriptor ) descriptors.get( key ); |
| if ( descriptor != null ) |
| { |
| if ( descriptor.getReadMethod() != null ) |
| { |
| return ( readProperty( descriptor ) ); |
| } |
| else |
| { |
| return ( null ); |
| } |
| } |
| } |
| |
| // Case 3 -- retrieve value from our underlying Map |
| return ( super.get( key ) ); |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to return |
| * <code>true</code> if the underlying <code>Map</code> only contains |
| * key-value pairs for local properties (if any).</p> |
| */ |
| public boolean isEmpty() |
| { |
| // Case 1 -- no local properties |
| if ( descriptors == null ) |
| { |
| return ( super.isEmpty() ); |
| } |
| |
| // Case 2 -- compare key count to property count |
| return ( super.size() <= descriptors.size() ); |
| |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to return a |
| * <code>Set</code> that meets the specified default behavior except |
| * for attempts to remove the key for a property of the {@link Context} |
| * implementation class, which will throw |
| * <code>UnsupportedOperationException</code>.</p> |
| */ |
| public Set keySet() |
| { |
| return ( super.keySet() ); |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to set the value |
| * of a local property if the specified key matches a local property name. |
| * </p> |
| * |
| * @param key Key of the value to be stored or replaced |
| * @param value New value to be stored |
| * |
| * @exception IllegalArgumentException if an exception is thrown |
| * reading or wrting this local property value |
| * @exception UnsupportedOperationException if this local property does not |
| * have both a read method and a write method |
| */ |
| public Object put( Object key, Object value ) |
| { |
| // Case 1 -- no local properties |
| if ( descriptors == null ) |
| { |
| return ( super.put( key, value ) ); |
| } |
| |
| // Case 2 -- this is a local property |
| if ( key != null ) |
| { |
| PropertyDescriptor descriptor = ( PropertyDescriptor ) descriptors.get( key ); |
| if ( descriptor != null ) |
| { |
| Object previous = null; |
| if ( descriptor.getReadMethod() != null ) |
| { |
| previous = readProperty( descriptor ); |
| } |
| writeProperty( descriptor, value ); |
| return ( previous ); |
| } |
| } |
| |
| // Case 3 -- store or replace value in our underlying map |
| return ( super.put( key, value ) ); |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to call the |
| * <code>put()</code> method individually for each key-value pair |
| * in the specified <code>Map</code>.</p> |
| * |
| * @param map <code>Map</code> containing key-value pairs to store |
| * (or replace) |
| * |
| * @exception IllegalArgumentException if an exception is thrown |
| * reading or wrting a local property value |
| * @exception UnsupportedOperationException if a local property does not |
| * have both a read method and a write method |
| */ |
| public void putAll( Map map ) |
| { |
| Iterator pairs = map.entrySet().iterator(); |
| while ( pairs.hasNext() ) |
| { |
| Map.Entry pair = ( Map.Entry ) pairs.next(); |
| put( pair.getKey(), pair.getValue() ); |
| } |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to throw |
| * <code>UnsupportedOperationException</code> on any attempt to |
| * remove a key that is the name of a local property.</p> |
| * |
| * @param key Key to be removed |
| * |
| * @exception UnsupportedOperationException if the specified |
| * <code>key</code> matches the name of a local property |
| */ |
| public Object remove( Object key ) |
| { |
| // Case 1 -- no local properties |
| if ( descriptors == null ) |
| { |
| return ( super.remove( key ) ); |
| } |
| |
| // Case 2 -- this is a local property |
| if ( key != null ) |
| { |
| PropertyDescriptor descriptor = ( PropertyDescriptor ) descriptors.get( key ); |
| if ( descriptor != null ) |
| { |
| throw new UnsupportedOperationException( "Local property '" + key + "' cannot be removed" ); |
| } |
| } |
| |
| // Case 3 -- remove from underlying Map |
| return ( super.remove( key ) ); |
| } |
| |
| |
| /** |
| * <p>Override the default <code>Map</code> behavior to return a |
| * <code>Collection</code> that meets the specified default behavior except |
| * for attempts to remove the key for a property of the {@link Context} |
| * implementation class, which will throw |
| * <code>UnsupportedOperationException</code>.</p> |
| */ |
| public Collection values() |
| { |
| return ( new ValuesImpl() ); |
| } |
| |
| |
| // --------------------------------------------------------- Private Methods |
| |
| /** |
| * <p>Eliminate the specified property descriptor from the list of |
| * property descriptors in <code>pd</code>.</p> |
| * |
| * @param name Name of the property to eliminate |
| * |
| * @exception IllegalArgumentException if the specified property name |
| * is not present |
| */ |
| private void eliminate( String name ) |
| { |
| int j = -1; |
| for ( int i = 0; i < pd.length; i++ ) |
| { |
| if ( name.equals( pd[i].getName() ) ) |
| { |
| j = i; |
| break; |
| } |
| } |
| |
| if ( j < 0 ) |
| { |
| throw new IllegalArgumentException( "Property '" + name + "' is not present" ); |
| } |
| |
| PropertyDescriptor[] results = new PropertyDescriptor[pd.length - 1]; |
| System.arraycopy( pd, 0, results, 0, j ); |
| System.arraycopy( pd, j + 1, results, j, pd.length - ( j + 1 ) ); |
| pd = results; |
| } |
| |
| |
| /** |
| * <p>Return an <code>Iterator</code> over the set of <code>Map.Entry</code> |
| * objects representing our key-value pairs.</p> |
| */ |
| private Iterator entriesIterator() |
| { |
| return ( new EntrySetIterator() ); |
| } |
| |
| |
| /** |
| * <p>Return a <code>Map.Entry</code> for the specified key value, if it |
| * is present; otherwise, return <code>null</code>.</p> |
| * |
| * @param key Attribute key or property name |
| */ |
| private Map.Entry entry( Object key ) |
| { |
| if ( containsKey( key ) ) |
| { |
| return ( new MapEntryImpl( key, get( key ) ) ); |
| } |
| else |
| { |
| return ( null ); |
| } |
| } |
| |
| |
| /** |
| * <p>Customize the contents of our underlying <code>Map</code> so that |
| * it contains keys corresponding to all of the JavaBeans properties of |
| * the {@link Context} implementation class.</p> |
| * |
| * |
| * @exception IllegalArgumentException if an exception is thrown |
| * writing this local property value |
| * @exception UnsupportedOperationException if this local property does not |
| * have a write method. |
| */ |
| private void initialize() |
| { |
| // Retrieve the set of property descriptors for this Context class |
| try |
| { |
| pd = Introspector.getBeanInfo( getClass() ).getPropertyDescriptors(); |
| } |
| catch ( IntrospectionException e ) |
| { |
| pd = new PropertyDescriptor[0]; // Should never happen |
| } |
| eliminate( "class" ); // Because of "getClass()" |
| eliminate( "empty" ); // Because of "isEmpty()" |
| |
| // Initialize the underlying Map contents |
| if ( pd.length > 0 ) |
| { |
| descriptors = new HashMap(); |
| for ( int i = 0; i < pd.length; i++ ) |
| { |
| descriptors.put( pd[i].getName(), pd[i] ); |
| super.put( pd[i].getName(), singleton ); |
| } |
| } |
| |
| } |
| |
| |
| /** |
| * <p>Get and return the value for the specified property.</p> |
| * |
| * @param descriptor <code>PropertyDescriptor</code> for the |
| * specified property |
| * |
| * @exception IllegalArgumentException if an exception is thrown |
| * reading this local property value |
| * @exception UnsupportedOperationException if this local property does not |
| * have a read method. |
| */ |
| private Object readProperty( PropertyDescriptor descriptor ) |
| { |
| try |
| { |
| Method method = descriptor.getReadMethod(); |
| if ( method == null ) |
| { |
| throw new UnsupportedOperationException( "Property '" + descriptor.getName() + "' is not readable" ); |
| } |
| return ( method.invoke( this, zeroParams ) ); |
| } |
| catch ( Exception e ) |
| { |
| throw new UnsupportedOperationException( "Exception reading property '" + descriptor.getName() + "': " |
| + e.getMessage() ); |
| } |
| } |
| |
| |
| /** |
| * <p>Remove the specified key-value pair, if it exists, and return |
| * <code>true</code>. If this pair does not exist, return |
| * <code>false</code>.</p> |
| * |
| * @param entry Key-value pair to be removed |
| * |
| * @exception UnsupportedOperationException if the specified key |
| * identifies a property instead of an attribute |
| */ |
| private boolean remove( Map.Entry entry ) |
| { |
| Map.Entry actual = entry( entry.getKey() ); |
| if ( actual == null ) |
| { |
| return ( false ); |
| } |
| else if ( !entry.equals( actual ) ) |
| { |
| return ( false ); |
| } |
| else |
| { |
| remove( entry.getKey() ); |
| return ( true ); |
| } |
| } |
| |
| |
| /** |
| * <p>Return an <code>Iterator</code> over the set of values in this |
| * <code>Map</code>.</p> |
| */ |
| private Iterator valuesIterator() |
| { |
| return ( new ValuesIterator() ); |
| } |
| |
| |
| /** |
| * <p>Set the value for the specified property.</p> |
| * |
| * @param descriptor <code>PropertyDescriptor</code> for the |
| * specified property |
| * @param value The new value for this property (must be of the |
| * correct type) |
| * |
| * @exception IllegalArgumentException if an exception is thrown |
| * writing this local property value |
| * @exception UnsupportedOperationException if this local property does not |
| * have a write method. |
| */ |
| private void writeProperty( PropertyDescriptor descriptor, Object value ) |
| { |
| try |
| { |
| Method method = descriptor.getWriteMethod(); |
| if ( method == null ) |
| { |
| throw new UnsupportedOperationException( "Property '" + descriptor.getName() + "' is not writeable" ); |
| } |
| method.invoke( this, new Object[] |
| { value } ); |
| } |
| catch ( Exception e ) |
| { |
| throw new UnsupportedOperationException( "Exception writing property '" + descriptor.getName() + "': " |
| + e.getMessage() ); |
| } |
| } |
| |
| // --------------------------------------------------------- Private Classes |
| |
| /** |
| * <p>Private implementation of <code>Set</code> that implements the |
| * semantics required for the value returned by <code>entrySet()</code>.</p> |
| */ |
| private class EntrySetImpl extends AbstractSet |
| { |
| public void clear() |
| { |
| ContextBase.this.clear(); |
| } |
| |
| |
| public boolean contains( Object obj ) |
| { |
| if ( !( obj instanceof Map.Entry ) ) |
| { |
| return ( false ); |
| } |
| Map.Entry entry = ( Map.Entry ) obj; |
| Entry actual = ContextBase.this.entry( entry.getKey() ); |
| if ( actual != null ) |
| { |
| return ( actual.equals( entry ) ); |
| } |
| else |
| { |
| return ( false ); |
| } |
| } |
| |
| |
| public boolean isEmpty() |
| { |
| return ( ContextBase.this.isEmpty() ); |
| } |
| |
| |
| public Iterator iterator() |
| { |
| return ( ContextBase.this.entriesIterator() ); |
| } |
| |
| |
| public boolean remove( Object obj ) |
| { |
| if ( obj instanceof Map.Entry ) |
| { |
| return ( ContextBase.this.remove( ( Map.Entry ) obj ) ); |
| } |
| else |
| { |
| return ( false ); |
| } |
| } |
| |
| |
| public int size() |
| { |
| return ( ContextBase.this.size() ); |
| } |
| } |
| |
| /** |
| * <p>Private implementation of <code>Iterator</code> for the |
| * <code>Set</code> returned by <code>entrySet()</code>.</p> |
| */ |
| private class EntrySetIterator implements Iterator |
| { |
| Map.Entry entry = null; |
| private Iterator keys = ContextBase.this.keySet().iterator(); |
| |
| |
| public boolean hasNext() |
| { |
| return ( keys.hasNext() ); |
| } |
| |
| |
| public Object next() |
| { |
| entry = ContextBase.this.entry( keys.next() ); |
| return ( entry ); |
| } |
| |
| |
| public void remove() |
| { |
| ContextBase.this.remove( entry ); |
| } |
| |
| } |
| |
| /** |
| * <p>Private implementation of <code>Map.Entry</code> for each item in |
| * <code>EntrySetImpl</code>.</p> |
| */ |
| private class MapEntryImpl implements Map.Entry |
| { |
| MapEntryImpl(Object key, Object value) |
| { |
| this.key = key; |
| this.value = value; |
| } |
| |
| private Object key; |
| private Object value; |
| |
| |
| public boolean equals( Object obj ) |
| { |
| if ( obj == null ) |
| { |
| return ( false ); |
| } |
| else if ( !( obj instanceof Map.Entry ) ) |
| { |
| return ( false ); |
| } |
| Map.Entry entry = ( Map.Entry ) obj; |
| if ( key == null ) |
| { |
| return ( entry.getKey() == null ); |
| } |
| if ( key.equals( entry.getKey() ) ) |
| { |
| if ( value == null ) |
| { |
| return ( entry.getValue() == null ); |
| } |
| else |
| { |
| return ( value.equals( entry.getValue() ) ); |
| } |
| } |
| else |
| { |
| return ( false ); |
| } |
| } |
| |
| |
| public Object getKey() |
| { |
| return ( this.key ); |
| } |
| |
| |
| public Object getValue() |
| { |
| return ( this.value ); |
| } |
| |
| |
| public int hashCode() |
| { |
| return ( ( ( key == null ) ? 0 : key.hashCode() ) ^ ( ( value == null ) ? 0 : value.hashCode() ) ); |
| } |
| |
| |
| public Object setValue( Object value ) |
| { |
| Object previous = this.value; |
| ContextBase.this.put( this.key, value ); |
| this.value = value; |
| return ( previous ); |
| } |
| } |
| |
| /** |
| * <p>Private implementation of <code>Collection</code> that implements the |
| * semantics required for the value returned by <code>values()</code>.</p> |
| */ |
| private class ValuesImpl extends AbstractCollection |
| { |
| public void clear() |
| { |
| ContextBase.this.clear(); |
| } |
| |
| |
| public boolean contains( Object obj ) |
| { |
| if ( !( obj instanceof Map.Entry ) ) |
| { |
| return ( false ); |
| } |
| Map.Entry entry = ( Map.Entry ) obj; |
| return ( ContextBase.this.containsValue( entry.getValue() ) ); |
| } |
| |
| |
| public boolean isEmpty() |
| { |
| return ( ContextBase.this.isEmpty() ); |
| } |
| |
| |
| public Iterator iterator() |
| { |
| return ( ContextBase.this.valuesIterator() ); |
| } |
| |
| |
| public boolean remove( Object obj ) |
| { |
| if ( obj instanceof Map.Entry ) |
| { |
| return ( ContextBase.this.remove( ( Map.Entry ) obj ) ); |
| } |
| else |
| { |
| return ( false ); |
| } |
| } |
| |
| |
| public int size() |
| { |
| return ( ContextBase.this.size() ); |
| } |
| } |
| |
| /** |
| * <p>Private implementation of <code>Iterator</code> for the |
| * <code>Collection</code> returned by <code>values()</code>.</p> |
| */ |
| private class ValuesIterator implements Iterator |
| { |
| Map.Entry entry = null; |
| private Iterator keys = ContextBase.this.keySet().iterator(); |
| |
| |
| public boolean hasNext() |
| { |
| return ( keys.hasNext() ); |
| } |
| |
| |
| public Object next() |
| { |
| entry = ContextBase.this.entry( keys.next() ); |
| return ( entry.getValue() ); |
| } |
| |
| |
| public void remove() |
| { |
| ContextBase.this.remove( entry ); |
| } |
| } |
| } |