/*
* 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.Collections;
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.ChangeSupport;
import org.biojava.utils.ChangeVetoException;
/**
* A utility class to ease the problem of implementing an Annotation to that of
* providing an apropreate implementation of Map. Where possible implementations
*
* This class is only intended as a way to implement
* Annotation. If you are not trying to do that, then don't read on. If you
* are reading the documentation for an Annotation implementation that extends
* this, then don't read on. There is nothing to see here.
*
* If you are still reading this, then you must be trying to
* implement Annotation. To do that, extend this class and implement
* getProperties()
and propertiesAllocated()
.
* Where possible implementations should be backed with a
* LinkedHashMap
or similar so properties are iterated in the order
* they were added.
*
* @author Matthew Pocock
* @author Greg Cox
*
* @since 1.0
*/
public abstract class AbstractAnnotation
extends
AbstractChangeable
implements
Annotation,
Serializable
{
/**
* Implement this to return the Map delegate. Modifying this return value will
* modify the properties associated with this annotation.
*
* From code in the 1.2 version of AbstractAnnotation
* This is required for the implementation of an Annotation that
* extends AbstractAnnotation. Where possible implementations
* should be backed with a
* LinkedHashMap
or similar so properties are iterated in the order
* they were added.
*
* @return a Map containing all properties
*/
protected abstract Map getProperties();
/**
* A convenience method to see if we have allocated the properties
* Map.
* This is required for the implementation of an Annotation that
* extends AbstractAnnotation.
* @return true if the properties have been allocated, false otherwise
*
*/
protected abstract boolean propertiesAllocated();
public Object getProperty(Object key) throws NoSuchElementException {
if(propertiesAllocated()) {
Map prop = getProperties();
if(prop.containsKey(key)) {
return prop.get(key);
}
}
throw new NoSuchElementException("Property " + key + " unknown");
}
public void setProperty(Object key, Object value)
throws ChangeVetoException {
if(!hasListeners()) {
getProperties().put(key, value);
} else {
Map properties = getProperties();
ChangeEvent ce = new ChangeEvent(
this,
Annotation.PROPERTY,
new Object[] { key, value },
new Object[] { key, properties.get(key)}
);
ChangeSupport cs = getChangeSupport(Annotation.PROPERTY);
synchronized(cs) {
cs.firePreChangeEvent(ce);
properties.put(key, value);
cs.firePostChangeEvent(ce);
}
}
}
public void removeProperty(Object key)
throws ChangeVetoException, NoSuchElementException
{
if (!getProperties().containsKey(key)) {
throw new NoSuchElementException("Can't remove key " + key.toString());
}
if(!hasListeners()) {
getProperties().remove(key);
} else {
Map properties = getProperties();
ChangeEvent ce = new ChangeEvent(
this,
Annotation.PROPERTY,
new Object[] { key, null },
new Object[] { key, properties.get(key)}
);
ChangeSupport cs = getChangeSupport(Annotation.PROPERTY);
synchronized(cs) {
cs.firePreChangeEvent(ce);
properties.remove(key);
cs.firePostChangeEvent(ce);
}
}
}
public boolean containsProperty(Object key) {
if(propertiesAllocated()) {
return getProperties().containsKey(key);
} else {
return false;
}
}
public Set keys() {
if(propertiesAllocated()) {
return getProperties().keySet();
} else {
return Collections.EMPTY_SET;
}
}
public String toString() {
StringBuffer sb = new StringBuffer("{");
Map prop = getProperties();
Iterator i = prop.keySet().iterator();
if(i.hasNext()) {
Object key = i.next();
sb.append(key + "=" + prop.get(key));
}
while(i.hasNext()) {
Object key = i.next();
sb.append("," + key + "=" + prop.get(key));
}
sb.append("}");
return sb.substring(0);
}
public Map asMap() {
return Collections.unmodifiableMap(getProperties());
}
/**
* Protected no-args constructor intended for sub-classes. This class is
* abstract and can not be directly instantiated.
*/
protected AbstractAnnotation() {
}
/**
* Copy-constructor.
*
*
* This does a shallow copy of the annotation. The result is an annotation * with the same properties and values, but which is independant of the * original annotation. *
* * @param ann the Annotation to copy */ protected AbstractAnnotation(Annotation ann) { if(ann == null) { throw new NullPointerException( "Null annotation not allowed. Use Annotation.EMPTY_ANNOTATION instead." ); } if(ann == Annotation.EMPTY_ANNOTATION) { return; } Map properties = getProperties(); for(Iterator i = ann.keys().iterator(); i.hasNext(); ) { Object key = i.next(); try { properties.put(key, ann.getProperty(key)); } catch (IllegalArgumentException iae) { throw new BioError( "Property was there and then disappeared: " + key, iae ); } } } /** * Create a new Annotation by copying the key-value pairs from a map. The * resulting Annotation is independant of the map. * * @param annMap the Map to copy from. */ public AbstractAnnotation(Map annMap) { if(annMap == null) { throw new IllegalArgumentException( "Null annotation Map not allowed. Use an empy map instead." ); } if(annMap.isEmpty()) { return; } Map properties = getProperties(); for(Iterator i = annMap.keySet().iterator(); i.hasNext(); ) { Object key = i.next(); properties.put(key, annMap.get(key)); } } public int hashCode() { return asMap().hashCode(); } public boolean equals(Object o) { if(o == this){ return true; } if (! (o instanceof Annotation)) { return false; } return ((Annotation) o).asMap().equals(asMap()); } }