001 /* 002 * Copyright 2003-2004 The Apache Software Foundation 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.apache.commons.events.observable; 017 018 import java.util.Collection; 019 import java.util.Iterator; 020 021 import org.apache.commons.collections.collection.AbstractCollectionDecorator; 022 import org.apache.commons.collections.iterators.AbstractIteratorDecorator; 023 import org.apache.commons.events.observable.standard.StandardModificationHandler; 024 025 /** 026 * Decorates a <code>Collection</code> implementation to observe modifications. 027 * <p> 028 * Each modifying method call made on this <code>Collection</code> is forwarded to a 029 * {@link ModificationHandler}. 030 * The handler manages the event, notifying listeners and optionally vetoing changes. 031 * The default handler is {@link StandardModificationHandler}. 032 * See this class for details of configuration available. 033 * 034 * @since Commons Events 1.0 035 * @version $Revision: 155443 $ $Date: 2005-02-26 13:19:51 +0000 (Sat, 26 Feb 2005) $ 036 * 037 * @author Stephen Colebourne 038 */ 039 public class ObservableCollection extends AbstractCollectionDecorator { 040 041 /** The list of registered factories, checked in reverse order */ 042 private static ModificationHandlerFactory[] factories = new ModificationHandlerFactory[] { 043 ModificationHandler.FACTORY, 044 StandardModificationHandler.FACTORY 045 }; 046 047 /** The handler to delegate event handling to */ 048 protected final ModificationHandler handler; 049 050 // ObservableCollection factories 051 //----------------------------------------------------------------------- 052 /** 053 * Factory method to create an observable collection. 054 * <p> 055 * A {@link StandardModificationHandler} will be created. 056 * This can be accessed by {@link #getHandler()} to add listeners. 057 * 058 * @param coll the collection to decorate, must not be null 059 * @return the observed collection 060 * @throws IllegalArgumentException if the collection is null 061 */ 062 public static ObservableCollection decorate(final Collection coll) { 063 return new ObservableCollection(coll, null); 064 } 065 066 /** 067 * Factory method to create an observable collection using a listener or a handler. 068 * <p> 069 * A lot of functionality is available through this method. 070 * If you don't need the extra functionality, simply implement the 071 * {@link org.apache.commons.events.observable.standard.StandardModificationListener} 072 * interface and pass it in as the second parameter. 073 * <p> 074 * Internally, an <code>ObservableCollection</code> relies on a {@link ModificationHandler}. 075 * The handler receives all the events and processes them, typically by 076 * calling listeners. Different handler implementations can be plugged in 077 * to provide a flexible event system. 078 * <p> 079 * The handler implementation is determined by the listener parameter via 080 * the registered factories. The listener may be a manually configured 081 * <code>ModificationHandler</code> instance. 082 * <p> 083 * The listener is defined as an Object for maximum flexibility. 084 * It does not have to be a listener in the classic JavaBean sense. 085 * It is entirely up to the factory and handler as to how the parameter 086 * is interpretted. An IllegalArgumentException is thrown if no suitable 087 * handler can be found for this listener. 088 * <p> 089 * A <code>null</code> listener will create a {@link StandardModificationHandler}. 090 * 091 * @param coll the collection to decorate, must not be null 092 * @param listener collection listener, may be null 093 * @return the observed collection 094 * @throws IllegalArgumentException if the collection is null 095 * @throws IllegalArgumentException if there is no valid handler for the listener 096 */ 097 public static ObservableCollection decorate( 098 final Collection coll, 099 final Object listener) { 100 101 if (coll == null) { 102 throw new IllegalArgumentException("Collection must not be null"); 103 } 104 return new ObservableCollection(coll, listener); 105 } 106 107 // Register for ModificationHandlerFactory 108 //----------------------------------------------------------------------- 109 /** 110 * Registers a handler factory to be used for looking up a listener to 111 * a handler. 112 * <p> 113 * This method is used to add your own event handler to the supplied ones. 114 * Registering the factory will enable the {@link #decorate(Collection, Object)} 115 * method to create your handler. 116 * <p> 117 * Each handler added becomes the first in the lookup chain. Thus it is 118 * possible to override the default setup. 119 * Obviously this should be done with care in a shared web environment! 120 * <p> 121 * This method is not guaranteed to be threadsafe. 122 * It should only be called during initialization. 123 * Problems will occur if two threads call this method at the same time. 124 * 125 * @param factory the factory to add, may be null 126 */ 127 public static void registerFactory(final ModificationHandlerFactory factory) { 128 if (factory != null) { 129 // add at end, as checked in reverse order 130 ModificationHandlerFactory[] array = new ModificationHandlerFactory[factories.length + 1]; 131 System.arraycopy(factories, 0, array, 0, factories.length); 132 array[factories.length] = factory; 133 factories = array; // atomic operation 134 } 135 } 136 137 // Constructors 138 //----------------------------------------------------------------------- 139 /** 140 * Constructor that wraps (not copies) and takes a handler. 141 * <p> 142 * The handler implementation is determined by the listener parameter via 143 * the registered factories. The listener may be a manually configured 144 * <code>ModificationHandler</code> instance. 145 * 146 * @param coll the collection to decorate, must not be null 147 * @param listener the observing handler, may be null 148 * @throws IllegalArgumentException if the collection is null 149 */ 150 protected ObservableCollection( 151 final Collection coll, 152 final Object listener) { 153 super(coll); 154 this.handler = createHandler(coll, listener); 155 this.handler.init(this, coll); 156 } 157 158 /** 159 * Constructor used by subclass views, such as subList. 160 * 161 * @param handler the observing handler, may be null 162 * @param coll the collection to decorate, must not be null 163 * @throws IllegalArgumentException if the collection is null 164 */ 165 protected ObservableCollection( 166 final ModificationHandler handler, 167 final Collection coll) { 168 super(coll); 169 this.handler = handler; 170 } 171 172 /** 173 * Creates a handler subclass based on the specified listener. 174 * <p> 175 * The method is defined in terms of an Object to allow for unusual 176 * listeners, such as a Swing model object. 177 * 178 * @param listener a listener object to create a handler for 179 * @return an instantiated handler with the listener attached 180 * @throws IllegalArgumentException if no suitable handler 181 */ 182 protected ModificationHandler createHandler(final Collection coll, final Object listener) { 183 if (listener == null) { 184 return new StandardModificationHandler(); 185 } 186 ModificationHandlerFactory[] array = factories; // atomic operation 187 for (int i = array.length - 1; i >= 0 ; i--) { 188 ModificationHandler handler = array[i].createHandler(coll, listener); 189 if (handler != null) { 190 return handler; 191 } 192 } 193 throw new IllegalArgumentException("Unrecognised listener type: " + 194 (listener == null ? "null" : listener.getClass().getName())); 195 } 196 197 // Handler access 198 //----------------------------------------------------------------------- 199 /** 200 * Gets the handler that is observing this collection. 201 * 202 * @return the observing handler, never null 203 */ 204 public ModificationHandler getHandler() { 205 return handler; 206 } 207 208 // Collection 209 //----------------------------------------------------------------------- 210 public boolean add(Object object) { 211 boolean result = false; 212 if (handler.preAdd(object)) { 213 result = collection.add(object); 214 handler.postAdd(object, result); 215 } 216 return result; 217 } 218 219 public boolean addAll(Collection coll) { 220 boolean result = false; 221 if (handler.preAddAll(coll)) { 222 result = collection.addAll(coll); 223 handler.postAddAll(coll, result); 224 } 225 return result; 226 } 227 228 public void clear() { 229 if (handler.preClear()) { 230 collection.clear(); 231 handler.postClear(); 232 } 233 } 234 235 public Iterator iterator() { 236 return new ObservableIterator(collection.iterator()); 237 } 238 239 public boolean remove(Object object) { 240 boolean result = false; 241 if (handler.preRemove(object)) { 242 result = collection.remove(object); 243 handler.postRemove(object, result); 244 } 245 return result; 246 } 247 248 public boolean removeAll(Collection coll) { 249 boolean result = false; 250 if (handler.preRemoveAll(coll)) { 251 result = collection.removeAll(coll); 252 handler.postRemoveAll(coll, result); 253 } 254 return result; 255 } 256 257 public boolean retainAll(Collection coll) { 258 boolean result = false; 259 if (handler.preRetainAll(coll)) { 260 result = collection.retainAll(coll); 261 handler.postRetainAll(coll, result); 262 } 263 return result; 264 } 265 266 // Iterator 267 //----------------------------------------------------------------------- 268 /** 269 * Inner class Iterator for the ObservableCollection. 270 */ 271 protected class ObservableIterator extends AbstractIteratorDecorator { 272 273 protected int lastIndex = -1; 274 protected Object last; 275 276 protected ObservableIterator(Iterator iterator) { 277 super(iterator); 278 } 279 280 public Object next() { 281 last = super.next(); 282 lastIndex++; 283 return last; 284 } 285 286 public void remove() { 287 if (handler.preRemoveIterated(lastIndex, last)) { 288 iterator.remove(); 289 handler.postRemoveIterated(lastIndex, last); 290 lastIndex--; 291 } 292 } 293 } 294 295 }