/*
* @(#) src/net/sf/ivmaidns/util/ObservedCore.java --
* Root class for observable objects.
**
* Copyright (c) 2000 Ivan Maidanski <ivmai@mail.ru>
* All rights reserved.
*/
/*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
**
* This software 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
* General Public License (GPL) for more details.
**
* Linking this library statically or dynamically with other modules is
* making a combined work based on this library. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
**
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under
* terms of your choice, provided that you also meet, for each linked
* independent module, the terms and conditions of the license of that
* module. An independent module is a module which is not derived from
* or based on this library. If you modify this library, you may extend
* this exception to your version of the library, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package net.sf.ivmaidns.util;
/**
* Root class for observable objects.
**
* This is an implementation of <CODE>MultiObservable</CODE>. 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
* <CODE>notifyObservers(this, argument)</CODE> to notify every
* registered observer agent (in an unspecified order) about the
* event, where <VAR>argument</VAR> 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
* <CODE>Object</CODE>.
**
* @see Notifiable
**
* @version 2.0
* @author Ivan Maidanski
*/
public class ObservedCore
implements MultiObservable, TrimToSizeable, Verifiable
{
/**
* An empty <CODE>private</CODE> 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-<CODE>null</CODE> agent of this array is notified when
* <CODE>notifyObservers(observed, argument)</CODE> is called.
* <VAR>observers</VAR> must be non-<CODE>null</CODE>. This array is
* set to <VAR>EMPTY_OBSERVERS</VAR> on creation and cloning of
* <CODE>this</CODE> object. No <CODE>public</CODE> access (even
* read-only) should be provided to <VAR>observers</VAR> elements
* (except through notification). In future, <VAR>observers</VAR>
* may be implemented as an array of weak references.
**
* @see #clone()
* @see #trimToSize()
*/
private Notifiable[] observers = EMPTY_OBSERVERS;
/**
* Constructs an observable object.
**
* @see #addObserver(net.sf.ivmaidns.util.Notifiable)
*/
public ObservedCore() {}
/**
* Frees extra memory.
**
* This method re-allocates internal <VAR>observers</VAR> 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(net.sf.ivmaidns.util.Notifiable)
* @see #removeObserver(net.sf.ivmaidns.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 <VAR>agent</VAR> will be
* updated (notified) each time <CODE>this</CODE> 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 <VAR>observers</VAR> array may be
* re-allocated (to have at least enough space for holding all
* registered agents). If an exception is thrown then state of
* <CODE>this</CODE> object is not changed. This method must be
* synchronized outside. Important notes: registered observers are
* not accessible for other objects, not copied when
* <CODE>this</CODE> object is cloned, and not serialized.
**
* @param agent
* the observer agent (must be non-<CODE>null</CODE>) to be
* registered.
* @exception NullPointerException
* if <VAR>agent</VAR> is <CODE>null</CODE>.
* @exception OutOfMemoryError
* if there is not enough memory.
**
* @see #removeObserver(net.sf.ivmaidns.util.Notifiable)
* @see #notifyObservers(net.sf.ivmaidns.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 <VAR>agent</VAR> is <CODE>null</CODE> or
* <CODE>equals(agent)</CODE> is <CODE>false</CODE> for every
* registered agent then nothing is performed. Else the specified
* agent is removed from <VAR>observers</VAR> of <CODE>this</CODE>
* observable object (this action is just the opposite to the agent
* registration). Internal <VAR>observers</VAR> array is not
* re-allocated. This method must be synchronized outside.
**
* @param agent
* the observer agent (may be <CODE>null</CODE>) to be unregistered.
**
* @see #addObserver(net.sf.ivmaidns.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 <CODE>this</CODE> observable object has any
* observers.
**
* If the result is <CODE>false</CODE> then there is no observers
* which must be updated so at this moment it is useless to call
* <CODE>notifyObservers(MultiObservable, Object)</CODE> method.
**
* @return
* <CODE>false</CODE> only if no observer agents registered.
**
* @see #addObserver(net.sf.ivmaidns.util.Notifiable)
* @see #removeObserver(net.sf.ivmaidns.util.Notifiable)
* @see #notifyObservers(net.sf.ivmaidns.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
* <CODE>update(observed, argument)</CODE> for every agent which is
* in observers list of <CODE>this</CODE> observable object. The
* order of notification is undefined. Important notes: notification
* should be done after committing of the occurred modification of
* <VAR>observed</VAR>; <VAR>argument</VAR> object should provide
* minimum yet enough information to effectively find out new state
* of the object; <CODE>RuntimeException</CODE> (and
* <CODE>OutOfMemoryError</CODE>) 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 <CODE>public</CODE> dummy method).
**
* @param observed
* the observed object (may be <CODE>null</CODE>, but normally
* <CODE>this</CODE>).
* @param argument
* the argument (may be <CODE>null</CODE>), 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(net.sf.ivmaidns.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 <CODE>this</CODE> object.
**
* This method overrides <CODE>clone()</CODE> of <CODE>Object</CODE>
* 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 <CODE>Cloneable</CODE> interface.
* This method may be overridden and made <CODE>public</CODE> in
* the subclasses if needed.
**
* @return
* a copy (may be <CODE>null</CODE>) of <CODE>this</CODE> instance.
* @exception CloneNotSupportedException
* if <CODE>Cloneable</CODE> 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 <CODE>this</CODE> object for its integrity.
**
* Observer agents of <CODE>this</CODE> 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");
}
}