/*
* BioJava development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public Licence. This should
* be distributed with the code. If you do not have a copy,
* see:
*
* http://www.gnu.org/copyleft/lesser.html
*
* Copyright for this code is held jointly by the individual
* authors. These should be listed in @author doc comments.
*
* For more information on the BioJava project and its aims,
* or to join the biojava-l mailing list, visit the home page
* at:
*
* http://www.biojava.org/
*
*/
package org.biojava.bio;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.biojava.utils.AbstractChangeable;
import org.biojava.utils.ChangeEvent;
import org.biojava.utils.ChangeForwarder;
import org.biojava.utils.ChangeListener;
import org.biojava.utils.ChangeSupport;
import org.biojava.utils.ChangeType;
import org.biojava.utils.ChangeVetoException;
/**
* Annotation implementation which allows new key-value
* pairs to be layered on top of an underlying Annotation.
* When getProperty
is called, we first check
* for a value stored in the overlay. If this fails, the
* underlying Annotation
is checked. Values
* passed to setProperty
are always stored
* within the overlay.
*
* @author Thomas Down
* @author Matthew Pocock
* @author Greg Cox
* @since 1.1
*
* In the case where you wish to wrap an underlying Annotation in a view that
* will allow it to be edited without altering the original object, but also
* reflect changes in the original object.
*/
public class OverlayAnnotation
extends
AbstractChangeable
implements
Annotation,
Serializable
{
private transient ChangeListener propertyForwarder = null;
private Annotation parent;
private Map overlay = null;
protected ChangeSupport getChangeSupport(ChangeType changeType) {
ChangeSupport changeSupport = super.getChangeSupport(changeType);
if(
(Annotation.PROPERTY.isMatchingType(changeType) || changeType.isMatchingType(Annotation.PROPERTY)) &&
(propertyForwarder == null)
) {
propertyForwarder = new PropertyForwarder(
OverlayAnnotation.this,
changeSupport
);
parent.addChangeListener(
propertyForwarder,
Annotation.PROPERTY
);
}
return changeSupport;
}
/**
* Get the map used for the overlay. Modifying this modifies the Annotation.
*
* @return the overlay Map
*/
protected Map getOverlay() {
if (overlay == null)
overlay = new HashMap();
return overlay;
}
/**
* Construct an annotation which can overlay new key-value
* pairs onto an underlying annotation.
*
* @param par The `parent' annotation, on which new
* key-value pairs can be layered.
*/
public OverlayAnnotation(Annotation par) {
parent = par;
}
public void setProperty(Object key, Object value)
throws ChangeVetoException
{
if(hasListeners()) {
ChangeSupport changeSupport = getChangeSupport(Annotation.PROPERTY);
ChangeEvent ce = new ChangeEvent(
this,
Annotation.PROPERTY,
new Object[] {key, value},
new Object[] {key, getProperty(key)}
);
synchronized(changeSupport) {
changeSupport.firePreChangeEvent(ce);
getOverlay().put(key, value);
changeSupport.firePostChangeEvent(ce);
}
} else {
getOverlay().put(key, value);
}
}
public void removeProperty(Object key)
throws ChangeVetoException
{
if (overlay == null || !overlay.containsKey(key)) {
if (parent.containsProperty(key)) {
throw new ChangeVetoException("Can't remove properties from the parent annotation");
} else {
throw new NoSuchElementException("Property doesn't exist: " + key);
}
}
if(hasListeners()) {
ChangeSupport changeSupport = getChangeSupport(Annotation.PROPERTY);
ChangeEvent ce = new ChangeEvent(
this,
Annotation.PROPERTY,
new Object[] {key, null},
new Object[] {key, getProperty(key)}
);
synchronized(changeSupport) {
changeSupport.firePreChangeEvent(ce);
getOverlay().remove(key);
changeSupport.firePostChangeEvent(ce);
}
} else {
getOverlay().remove(key);
}
}
public Object getProperty(Object key) {
Object val = null;
if (overlay != null)
val = overlay.get(key);
if (val != null) {
return val;
}
return parent.getProperty(key);
}
public boolean containsProperty(Object key) {
if(
(overlay != null) &&
(overlay.containsKey(key))
) {
return true;
} else {
return parent.containsProperty(key);
}
}
/**
* Return a Set
containing all key objects
* visible in this annotation. The Set
is
* unmodifiable, but will dynamically reflect changes made
* to the annotation.
*
* @return the keys as a Set
*/
public Set keys() {
return new OAKeySet();
}
/**
* Return a Map
view onto this annotation.
* The returned Map
is unmodifiable, but will
* dynamically reflect any changes made to this annotation.
*
* @return a view of this Annotation as an immutable Map
*/
public Map asMap() {
return new OAMap();
}
private class OAKeySet extends AbstractSet {
private Set parentKeys;
private OAKeySet() {
super();
parentKeys = parent.keys();
}
public Iterator iterator() {
return new Iterator() {
Iterator oi = (overlay != null) ? overlay.keySet().iterator()
: Collections.EMPTY_SET.iterator();
Iterator pi = parentKeys.iterator();
Object peek = null;
public boolean hasNext() {
if (peek == null)
peek = nextObject();
return (peek != null);
}
public Object next() {
if (peek == null) {
peek = nextObject();
}
if (peek == null) {
throw new NoSuchElementException();
}
Object o = peek;
peek = null;
return o;
}
private Object nextObject() {
if (oi.hasNext()) {
return oi.next();
}
Object po = null;
while (po == null && pi.hasNext()) {
po = pi.next();
if (overlay != null && overlay.containsKey(po)) {
po = null;
}
}
return po;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
int i = 0;
Iterator keys = iterator();
while(keys.hasNext()) {
keys.next();
++i;
}
return i;
}
public boolean contains(Object o) {
return (overlay != null && overlay.containsKey(o)) || parentKeys.contains(o);
}
}
private class OAEntrySet extends AbstractSet {
OAKeySet ks;
private OAEntrySet() {
super();
ks = new OAKeySet();
}
public Iterator iterator() {
return new Iterator() {
Iterator ksi = ks.iterator();
public boolean hasNext() {
return ksi.hasNext();
}
public Object next() {
Object k = ksi.next();
Object v = getProperty(k);
return new OAMapEntry(k, v);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
return ks.size();
}
}
private class OAMapEntry implements Map.Entry {
private Object key;
private Object value;
private OAMapEntry(Object key, Object value) {
this.key = key;
this.value = value;
}
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object v) {
throw new UnsupportedOperationException();
}
public boolean equals(Object o) {
if (! (o instanceof Map.Entry)) {
return false;
}
Map.Entry mo = (Map.Entry) o;
return ((key == null ? mo.getKey() == null : key.equals(mo.getKey())) &&
(value == null ? mo.getValue() == null : value.equals(mo.getValue())));
}
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
}
}
private class OAMap extends AbstractMap {
OAEntrySet es;
OAKeySet ks;
private OAMap() {
super();
ks = new OAKeySet();
es = new OAEntrySet();
}
public Set entrySet() {
return es;
}
public Set keySet() {
return ks;
}
public Object get(Object key) {
try {
return getProperty(key);
} catch (NoSuchElementException ex) {
}
return null;
}
}
/**
* Forwards change events from the underlying Annotation to this one.
*
* @author Thomas Down
* @author Matthew Pocock
*/
protected class PropertyForwarder extends ChangeForwarder {
/**
* Forward on behalf of source using the change support provided.
*
* @param source the source Object
* @param cs the ChangeSupport to use
*/
public PropertyForwarder(Object source, ChangeSupport cs) {
super(source, cs);
}
public ChangeEvent generateEvent(ChangeEvent ce) {
ChangeType ct = ce.getType();
if(ct == Annotation.PROPERTY) {
Object curVal = ce.getChange();
if(curVal instanceof Object[]) {
Object[] cur = (Object []) curVal;
if(cur.length == 2) {
Object key = cur[0];
Object value = cur[0];
if(getProperty(key) != value) {
return new ChangeEvent(
getSource(),
Annotation.PROPERTY,
curVal,
ce.getPrevious(),
ce
);
}
}
}
}
return null;
}
}
}