/*
* 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.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
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;
/**
* Merged view onto a list of underlying Annotation objects.
* Currently immutable (but reflects changes to underlying objects). Annotations
* near the beginning of the list will have properties that take
* precedence. It is possible to get the ordering of the annotations, or to
* change it by removing and re-adding methods.
* This Annotation implementation is immutable.
*
* @author Thomas Down
* @author Matthew Pocock
* @author Greg Cox
* @author Francois Pepin
* @since 1.2
*
* Use these when you have a list of Annotation instances that
* need to be viewed as one. For example, if you have annotation for a feature
* from a local database, in-memory objects and a web-page, you could build
* three Annotation instances and merge them using a MergeAnnotation.
*/
public class MergeAnnotation
extends
AbstractChangeable
implements
Annotation,
Serializable {
private transient ChangeListener propertyForwarder = null;
private List mergeSet;
{
mergeSet = new ArrayList();
}
/**
* ChangeType of ChangeEvent fired before and after an annotation is added
* to MergeAnnotation.
*
*/
public static final ChangeType ANNOTATION_CHANGED = new ChangeType(
"annotation added",
"org.biojava.bio.MergeAnnotation",
"ANNOTATION_CHANGED"
);
/**
* ChangeType of ChangeEvent fired before and after an annotation is added
* to MergeAnnotation.
*
*/
public static final ChangeType ANNOTATION_ADD = new ChangeType(
"annotation added from List",
"org.biojava.bio.MergeAnnotation",
"ANNOTATION_ADD",
ANNOTATION_CHANGED
);
/**
* ChangeType of ChangeEvent fired before and after an annotation is added
* to MergeAnnotation.
*
*/
public static final ChangeType ANNOTATION_REMOVE = new ChangeType(
"annotation deleted from List",
"org.biojava.bio.MergeAnnotation",
"ANNOTATION_REMOVE",
ANNOTATION_CHANGED
);
/**
* Add a new Annotation to to the end of the list to be merged.
*
* Use this to alter the Annotations being merged
*
* @param ann the Annotation to add
* @throws ChangeVetoException if the annotation could not be added
*/
public void addAnnotation(Annotation ann)
throws ChangeVetoException {
if(!hasListeners())
mergeSet.add(ann);
else{
ChangeEvent ce = new ChangeEvent(this,MergeAnnotation.ANNOTATION_ADD,ann);
ChangeSupport changeSupport = super.getChangeSupport(MergeAnnotation.ANNOTATION_ADD);
synchronized(changeSupport) {
changeSupport.firePreChangeEvent(ce);
mergeSet.add(ann);
changeSupport.firePostChangeEvent(ce);
}
}
}
/**
* Gets an unmodifiable view of the list of Annotations that are part of the
* MergeAnnotation. Lower indices Annotation have precedence if 2
* Annotations share the same property.
*
* @return an unmodifiable List
of the Annotations that form
* this MergeAnnotation.
*/
public List getAnnotations()
{
return Collections.unmodifiableList(mergeSet);
}
/**
* Remove an Annotation from the list. This can be used to change the
* ordering of the Annotations by re-adding it later.
*
* @param ann an Annotation
to be removed.
* @exception ChangeVetoException if an error occurs
*/
public void removeAnnotation(Annotation ann)
throws ChangeVetoException {
if(!hasListeners())
mergeSet.remove(ann);
else{
ChangeEvent ce = new ChangeEvent(this,MergeAnnotation.ANNOTATION_REMOVE,ann);
ChangeSupport changeSupport = super.getChangeSupport(MergeAnnotation.ANNOTATION_REMOVE);
synchronized(changeSupport) {
changeSupport.firePreChangeEvent(ce);
mergeSet.remove(ann);
changeSupport.firePostChangeEvent(ce);
}
}
}
protected ChangeSupport getChangeSupport(ChangeType changeType) {
ChangeSupport changeSupport = super.getChangeSupport(changeType);
if (
(Annotation.PROPERTY.isMatchingType(changeType) || changeType.isMatchingType(Annotation.PROPERTY))
&&
propertyForwarder == null
) {
propertyForwarder = new PropertyForwarder(
MergeAnnotation.this,
changeSupport
);
for (Iterator i = mergeSet.iterator(); i.hasNext();) {
Annotation a = (Annotation) i.next();
a.addChangeListener(propertyForwarder, Annotation.PROPERTY);
}
}
return changeSupport;
}
public void setProperty(Object key, Object value) throws ChangeVetoException {
throw new ChangeVetoException("MergeAnnotations don't allow property setting at the moment");
}
public void removeProperty(Object key) throws ChangeVetoException {
throw new ChangeVetoException("MergeAnnotations don't allow property removal at the moment");
}
public Object getProperty(Object key) {
for (Iterator i = mergeSet.iterator(); i.hasNext();) {
Annotation a = (Annotation) i.next();
if (a.containsProperty(key)) {
return a.getProperty(key);
}
}
throw new NoSuchElementException("Can't find property " + key);
}
public boolean containsProperty(Object key) {
for (Iterator i = mergeSet.iterator(); i.hasNext();) {
Annotation a = (Annotation) i.next();
if (a.containsProperty(key)) {
return true;
}
}
return false;
}
public Set keys() {
Set s = new HashSet();
for (Iterator i = mergeSet.iterator(); i.hasNext();) {
Annotation a = (Annotation) i.next();
s.addAll(a.keys());
}
return s;
}
public Map asMap() {
return new MAMap();
}
private class MAEntrySet extends AbstractSet {
private MAEntrySet() {
super();
}
public Iterator iterator() {
return new Iterator() {
Iterator ksi = MergeAnnotation.this.keys().iterator();
public boolean hasNext() {
return ksi.hasNext();
}
public Object next() {
Object k = ksi.next();
Object v = getProperty(k);
return new MAMapEntry(k, v);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
return MergeAnnotation.this.keys().size();
}
}
private class MAMapEntry implements Map.Entry {
private Object key;
private Object value;
private MAMapEntry(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 MAMap extends AbstractMap {
MAEntrySet es;
private MAMap() {
super();
es = new MAEntrySet();
}
public Set entrySet() {
return es;
}
public Set keySet() {
return MergeAnnotation.this.keys();
}
public Object get(Object key) {
try {
return getProperty(key);
} catch (NoSuchElementException ex) {
}
return null;
}
}
/**
* Listener used to forward changes for any of the underlying annotations to
* listeners on this annotation.
*
* @author Thomas Down
* @author Matthew Pocock
* @since 1.2
*/
protected class PropertyForwarder extends ChangeForwarder {
/**
* Create a new forwarder on behalf of a source using the change support.
* @param source the new source of events
* @param cs the ChangeSupport used to manage listeners
*/
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;
}
}
}