001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022package org.biojava.bio; 023 024import java.io.Serializable; 025import java.util.Collections; 026import java.util.Iterator; 027import java.util.Map; 028import java.util.NoSuchElementException; 029import java.util.Set; 030 031import org.biojava.utils.AbstractChangeable; 032import org.biojava.utils.ChangeEvent; 033import org.biojava.utils.ChangeSupport; 034import org.biojava.utils.ChangeVetoException; 035 036/** 037 * A utility class to ease the problem of implementing an Annotation to that of 038 * providing an apropreate implementation of Map. Where possible implementations 039 * 040 * This class is only intended as a way to implement 041 * Annotation. If you are not trying to do that, then don't read on. If you 042 * are reading the documentation for an Annotation implementation that extends 043 * this, then don't read on. There is nothing to see here. 044 * 045 * If you are still reading this, then you must be trying to 046 * implement Annotation. To do that, extend this class and implement 047 * <code>getProperties()</code> and <code>propertiesAllocated()</code>. 048 * Where possible implementations should be backed with a 049 * <code>LinkedHashMap</code> or similar so properties are iterated in the order 050 * they were added. 051 * 052 * @author Matthew Pocock 053 * @author Greg Cox 054 * 055 * @since 1.0 056 */ 057public abstract class AbstractAnnotation 058 extends 059 AbstractChangeable 060 implements 061 Annotation, 062 Serializable 063{ 064 /** 065 * Implement this to return the Map delegate. Modifying this return value will 066 * modify the properties associated with this annotation. 067 * 068 * From code in the 1.2 version of AbstractAnnotation 069 * This is required for the implementation of an Annotation that 070 * extends AbstractAnnotation. Where possible implementations 071 * should be backed with a 072 * <code>LinkedHashMap</code> or similar so properties are iterated in the order 073 * they were added. 074 * 075 * @return a Map containing all properties 076 */ 077 protected abstract Map getProperties(); 078 079 /** 080 * A convenience method to see if we have allocated the properties 081 * Map. 082 * This is required for the implementation of an Annotation that 083 * extends AbstractAnnotation. 084 * @return true if the properties have been allocated, false otherwise 085 * 086 */ 087 protected abstract boolean propertiesAllocated(); 088 089 090 public Object getProperty(Object key) throws NoSuchElementException { 091 if(propertiesAllocated()) { 092 Map prop = getProperties(); 093 if(prop.containsKey(key)) { 094 return prop.get(key); 095 } 096 } 097 throw new NoSuchElementException("Property " + key + " unknown"); 098 } 099 100 public void setProperty(Object key, Object value) 101 throws ChangeVetoException { 102 if(!hasListeners()) { 103 getProperties().put(key, value); 104 } else { 105 Map properties = getProperties(); 106 ChangeEvent ce = new ChangeEvent( 107 this, 108 Annotation.PROPERTY, 109 new Object[] { key, value }, 110 new Object[] { key, properties.get(key)} 111 ); 112 ChangeSupport cs = getChangeSupport(Annotation.PROPERTY); 113 synchronized(cs) { 114 cs.firePreChangeEvent(ce); 115 properties.put(key, value); 116 cs.firePostChangeEvent(ce); 117 } 118 } 119 } 120 121 public void removeProperty(Object key) 122 throws ChangeVetoException, NoSuchElementException 123 { 124 if (!getProperties().containsKey(key)) { 125 throw new NoSuchElementException("Can't remove key " + key.toString()); 126 } 127 128 if(!hasListeners()) { 129 getProperties().remove(key); 130 } else { 131 Map properties = getProperties(); 132 ChangeEvent ce = new ChangeEvent( 133 this, 134 Annotation.PROPERTY, 135 new Object[] { key, null }, 136 new Object[] { key, properties.get(key)} 137 ); 138 ChangeSupport cs = getChangeSupport(Annotation.PROPERTY); 139 synchronized(cs) { 140 cs.firePreChangeEvent(ce); 141 properties.remove(key); 142 cs.firePostChangeEvent(ce); 143 } 144 } 145 } 146 147 public boolean containsProperty(Object key) { 148 if(propertiesAllocated()) { 149 return getProperties().containsKey(key); 150 } else { 151 return false; 152 } 153 } 154 155 public Set keys() { 156 if(propertiesAllocated()) { 157 return getProperties().keySet(); 158 } else { 159 return Collections.EMPTY_SET; 160 } 161 } 162 163 public String toString() { 164 StringBuffer sb = new StringBuffer("{"); 165 Map prop = getProperties(); 166 Iterator i = prop.keySet().iterator(); 167 if(i.hasNext()) { 168 Object key = i.next(); 169 sb.append(key + "=" + prop.get(key)); 170 } 171 while(i.hasNext()) { 172 Object key = i.next(); 173 sb.append("," + key + "=" + prop.get(key)); 174 } 175 sb.append("}"); 176 return sb.substring(0); 177 } 178 179 public Map asMap() { 180 return Collections.unmodifiableMap(getProperties()); 181 } 182 183 /** 184 * Protected no-args constructor intended for sub-classes. This class is 185 * abstract and can not be directly instantiated. 186 */ 187 protected AbstractAnnotation() { 188 } 189 190 /** 191 * Copy-constructor. 192 * 193 * <p> 194 * This does a shallow copy of the annotation. The result is an annotation 195 * with the same properties and values, but which is independant of the 196 * original annotation. 197 * </p> 198 * 199 * @param ann the Annotation to copy 200 */ 201 protected AbstractAnnotation(Annotation ann) { 202 if(ann == null) { 203 throw new NullPointerException( 204 "Null annotation not allowed. Use Annotation.EMPTY_ANNOTATION instead." 205 ); 206 } 207 if(ann == Annotation.EMPTY_ANNOTATION) { 208 return; 209 } 210 Map properties = getProperties(); 211 for(Iterator i = ann.keys().iterator(); i.hasNext(); ) { 212 Object key = i.next(); 213 try { 214 properties.put(key, ann.getProperty(key)); 215 } catch (IllegalArgumentException iae) { 216 throw new BioError( 217 "Property was there and then disappeared: " + key, iae 218 ); 219 } 220 } 221 } 222 223 /** 224 * Create a new Annotation by copying the key-value pairs from a map. The 225 * resulting Annotation is independant of the map. 226 * 227 * @param annMap the Map to copy from. 228 */ 229 public AbstractAnnotation(Map annMap) { 230 if(annMap == null) { 231 throw new IllegalArgumentException( 232 "Null annotation Map not allowed. Use an empy map instead." 233 ); 234 } 235 if(annMap.isEmpty()) { 236 return; 237 } 238 239 Map properties = getProperties(); 240 for(Iterator i = annMap.keySet().iterator(); i.hasNext(); ) { 241 Object key = i.next(); 242 properties.put(key, annMap.get(key)); 243 } 244 } 245 246 247 public int hashCode() { 248 return asMap().hashCode(); 249 } 250 251 public boolean equals(Object o) { 252 if(o == this){ 253 return true; 254 } 255 if (! (o instanceof Annotation)) { 256 return false; 257 } 258 259 return ((Annotation) o).asMap().equals(asMap()); 260 } 261}