/* * @(#) ObservedCore.java - Root class for observable objects. * (c) 2000 Ivan Maidanski http://ivmai.chat.ru * Freeware class library sources. All rights reserved. ** * Language: Java [pure] * Tested with: JDK v1.1.6 * Last modified: 2000-12-15 14:05:00 GMT+03:00 */ /* * This software is the proprietary information of the author. ** * Permission to use, copy, and distribute this software and its * documentation for non-commercial purposes and without fee is * hereby granted provided that this copyright notice appears in all * copies. ** * This software should not be modified in any way; any found bug * should be reported to the author. ** * The author disclaims all warranties with regard to this software, * including all implied warranties of merchantability and fitness. * In no event shall the author be liable for any special, indirect * or consequential damages or any damages whatsoever resulting from * loss of use, data or profits, whether in an action of contract, * negligence or other tortuous action, arising out of or in * connection with the use or performance of this software. */ package ivmai.util; /** * Root class for observable objects. ** * This is an implementation of MultiObservable. An * observable object (which class extends or includes a variable of * this class) represents mutable 'data' in the model-view paradigm. * Each time an observable object is changed, it must call * notifyObservers(this, argument) to notify every * registered observer agent (in an unspecified order) about the * event, where argument describes the occurred changes * (as it must be specified for a particular object). Important * notes: agents must not modify observed object anyhow; * notification should be performed just after changes; this * notification mechanism has nothing to do with threads and is * completely separate from the 'wait-notify' mechanism of * Object. ** * @see Notifiable ** * @version 2.0 * @author Ivan Maidanski */ public class ObservedCore implements MultiObservable, TrimToSizeable, Verifiable { /** * An empty private list of observers. ** * This constant is used to avoid empty array allocation on * instantiation of this class (and on trimming to its minimal * size). ** * @see #clone() * @see #trimToSize() */ private static final Notifiable[] EMPTY_OBSERVERS = {}; /** * Array (list) of registered observer agents. ** * Each non-null agent of this array is notified when * notifyObservers(observed, argument) is called. * observers must be non-null. This array is * set to EMPTY_OBSERVERS on creation and cloning of * this object. No public access (even * read-only) should be provided to observers elements * (except through notification). In future, observers * may be implemented as an array of weak references. ** * @see #clone() * @see #trimToSize() */ private Notifiable[] observers = EMPTY_OBSERVERS; /** * Constructs an observable object. ** * @see #addObserver(ivmai.util.Notifiable) */ public ObservedCore() {} /** * Frees extra memory. ** * This method re-allocates internal observers list, * setting its length to the current possible minimum. Observer * agents are not modified. Observers order is not changed. This * method must be synchronized outside. ** * @see #addObserver(ivmai.util.Notifiable) * @see #removeObserver(ivmai.util.Notifiable) */ public void trimToSize() { Notifiable[] observers = this.observers; int count = 0, index = observers.length, len = index; while (index > 0) if (observers[--index] != null) count++; if (count < len) { Notifiable[] newObservers = EMPTY_OBSERVERS; if (count > 0) { try { newObservers = new Notifiable[count]; } catch (OutOfMemoryError e) { return; } int offset = 0; do { Notifiable agent; if ((agent = observers[index]) != null) { newObservers[offset] = agent; if (++offset >= count) break; } } while (++index < len); } this.observers = newObservers; } } /** * Registers one more observer. ** * An observer registration means that agent will be * updated (notified) each time this observable object * is changed somehow. If the specified agent is already registered * here then the registration of this agent is not performed (no * duplicate agents). Internal observers array may be * re-allocated (to have at least enough space for holding all * registered agents). If an exception is thrown then state of * this object is not changed. This method must be * synchronized outside. Important notes: registered observers are * not accessible for other objects, not copied when * this object is cloned, and not serialized. ** * @param agent * the observer agent (must be non-null) to be * registered. * @exception NullPointerException * if agent is null. * @exception OutOfMemoryError * if there is not enough memory. ** * @see #removeObserver(ivmai.util.Notifiable) * @see #notifyObservers(ivmai.util.MultiObservable, * java.lang.Object) */ public void addObserver(Notifiable agent) throws NullPointerException { agent.equals(agent); Notifiable[] observers = this.observers; int index = observers.length, count = index; Notifiable curAgent; while (index-- > 0) if ((curAgent = observers[index]) != null && curAgent.equals(agent)) return; while (++index < count && observers[index] != null); if (index >= count) { if ((count = (count >> 1) + count + 1) <= index) count = -1 >>> 1; Notifiable[] newObservers; System.arraycopy(observers, 0, newObservers = new Notifiable[count], 0, index); this.observers = observers = newObservers; } observers[index] = agent; } /** * Unregisters a particular observer. ** * If agent is null or * equals(agent) is false for every * registered agent then nothing is performed. Else the specified * agent is removed from observers of this * observable object (this action is just the opposite to the agent * registration). Internal observers array is not * re-allocated. This method must be synchronized outside. ** * @param agent * the observer agent (may be null) to be unregistered. ** * @see #addObserver(ivmai.util.Notifiable) */ public void removeObserver(Notifiable agent) { if (agent != null) { Notifiable[] observers = this.observers; Notifiable curAgent; boolean hasObservers = false; for (int index = 0, count = observers.length; index < count; index++) if ((curAgent = observers[index]) != null) { if (curAgent.equals(agent)) { observers[index] = null; if (hasObservers) break; while (++index < count) if (observers[index] != null) return; this.observers = EMPTY_OBSERVERS; break; } hasObservers = true; } hasObservers = false; } } /** * Tests whether this observable object has any * observers. ** * If the result is false then there is no observers * which must be updated so at this moment it is useless to call * notifyObservers(MultiObservable, Object) method. ** * @return * false only if no observer agents registered. ** * @see #addObserver(ivmai.util.Notifiable) * @see #removeObserver(ivmai.util.Notifiable) * @see #notifyObservers(ivmai.util.MultiObservable, * java.lang.Object) */ public final boolean hasObservers() { return this.observers.length > 0; } /** * Notifies each registered observer agent on the event that just * occurred. ** * Agents notification means calling * update(observed, argument) for every agent which is * in observers list of this observable object. The * order of notification is undefined. Important notes: notification * should be done after committing of the occurred modification of * observed; argument object should provide * minimum yet enough information to effectively find out new state * of the object; RuntimeException (and * OutOfMemoryError) should be handled properly (since * some of the agents may have already been notified before the * exception is thrown); this method should be called only from the * observable object (so, in subclasses this method should be * overridden with a public dummy method). ** * @param observed * the observed object (may be null, but normally * this). * @param argument * the argument (may be null), describing the occurred * event. * @exception RuntimeException * if the notification process for some registered agent has failed * (a custom exception, not all of the agents may have been * notified). * @exception OutOfMemoryError * if there is not enough memory to complete notification (not all * of the agents may have been notified). ** * @see #addObserver(ivmai.util.Notifiable) */ public void notifyObservers(MultiObservable observed, Object argument) throws RuntimeException { int index = 0; Notifiable[] observers = this.observers; int len = observers.length; for (Notifiable agent; index < len; index++) if ((agent = observers[index]) != null) agent.update(observed, argument); } /** * Creates and returns a copy of this object. ** * This method overrides clone() of Object * to prevent copying of observers list (it is set empty in the * returned observable object). Of course, this method works only * for subclasses which implement Cloneable interface. * This method may be overridden and made public in * the subclasses if needed. ** * @return * a copy (may be null) of this instance. * @exception CloneNotSupportedException * if Cloneable interface is not implemented (in a * subclass). * @exception OutOfMemoryError * if there is not enough memory. ** * @since 1.1 */ protected Object clone() throws CloneNotSupportedException { Object obj; if ((obj = super.clone()) instanceof ObservedCore && obj != this) ((ObservedCore)obj).observers = EMPTY_OBSERVERS; return obj; } /** * Verifies this object for its integrity. ** * Observer agents of this observable are not checked. * For debug purpose only. ** * @exception InternalError * if integrity violation is detected. ** * @since 2.0 */ public void integrityCheck() { if (this.observers == null) throw new InternalError("observers: null"); } }