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.AbstractMap; 026import java.util.AbstractSet; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.HashSet; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Map; 033import java.util.NoSuchElementException; 034import java.util.Set; 035 036import org.biojava.utils.AbstractChangeable; 037import org.biojava.utils.ChangeEvent; 038import org.biojava.utils.ChangeForwarder; 039import org.biojava.utils.ChangeListener; 040import org.biojava.utils.ChangeSupport; 041import org.biojava.utils.ChangeType; 042import org.biojava.utils.ChangeVetoException; 043 044/** 045 * Merged view onto a list of underlying Annotation objects. 046 * Currently immutable (but reflects changes to underlying objects). Annotations 047 * near the beginning of the list will have properties that take 048 * precedence. It is possible to get the ordering of the annotations, or to 049 * change it by removing and re-adding methods. 050 * This Annotation implementation is immutable. 051 * 052 * @author Thomas Down 053 * @author Matthew Pocock 054 * @author Greg Cox 055 * @author Francois Pepin 056 * @since 1.2 057 * 058 * Use these when you have a list of Annotation instances that 059 * need to be viewed as one. For example, if you have annotation for a feature 060 * from a local database, in-memory objects and a web-page, you could build 061 * three Annotation instances and merge them using a MergeAnnotation. 062 */ 063 064public class MergeAnnotation 065 extends 066 AbstractChangeable 067 implements 068 Annotation, 069 Serializable { 070 private transient ChangeListener propertyForwarder = null; 071 072 private List mergeSet; 073 074 { 075 mergeSet = new ArrayList(); 076 } 077 078 /** 079 * ChangeType of ChangeEvent fired before and after an annotation is added 080 * to MergeAnnotation. 081 * 082 */ 083 public static final ChangeType ANNOTATION_CHANGED = new ChangeType( 084 "annotation added", 085 "org.biojava.bio.MergeAnnotation", 086 "ANNOTATION_CHANGED" 087 ); 088 089 /** 090 * ChangeType of ChangeEvent fired before and after an annotation is added 091 * to MergeAnnotation. 092 * 093 */ 094 public static final ChangeType ANNOTATION_ADD = new ChangeType( 095 "annotation added from List", 096 "org.biojava.bio.MergeAnnotation", 097 "ANNOTATION_ADD", 098 ANNOTATION_CHANGED 099 ); 100 101 /** 102 * ChangeType of ChangeEvent fired before and after an annotation is added 103 * to MergeAnnotation. 104 * 105 */ 106 public static final ChangeType ANNOTATION_REMOVE = new ChangeType( 107 "annotation deleted from List", 108 "org.biojava.bio.MergeAnnotation", 109 "ANNOTATION_REMOVE", 110 ANNOTATION_CHANGED 111 ); 112 113 114 115 /** 116 * Add a new Annotation to to the end of the list to be merged. 117 * 118 * Use this to alter the Annotations being merged 119 * 120 * @param ann the Annotation to add 121 * @throws ChangeVetoException if the annotation could not be added 122 */ 123 public void addAnnotation(Annotation ann) 124 throws ChangeVetoException { 125 if(!hasListeners()) 126 mergeSet.add(ann); 127 else{ 128 ChangeEvent ce = new ChangeEvent(this,MergeAnnotation.ANNOTATION_ADD,ann); 129 ChangeSupport changeSupport = super.getChangeSupport(MergeAnnotation.ANNOTATION_ADD); 130 synchronized(changeSupport) { 131 changeSupport.firePreChangeEvent(ce); 132 mergeSet.add(ann); 133 changeSupport.firePostChangeEvent(ce); 134 } 135 } 136 } 137 138 /** 139 * Gets an unmodifiable view of the list of Annotations that are part of the 140 * MergeAnnotation. Lower indices Annotation have precedence if 2 141 * Annotations share the same property. 142 * 143 * @return an unmodifiable <code>List</code> of the Annotations that form 144 * this MergeAnnotation. 145 */ 146 public List getAnnotations() 147 { 148 return Collections.unmodifiableList(mergeSet); 149 } 150 151 /** 152 * Remove an Annotation from the list. This can be used to change the 153 * ordering of the Annotations by re-adding it later. 154 * 155 * @param ann an <code>Annotation</code> to be removed. 156 * @exception ChangeVetoException if an error occurs 157 */ 158 public void removeAnnotation(Annotation ann) 159 throws ChangeVetoException { 160 if(!hasListeners()) 161 mergeSet.remove(ann); 162 else{ 163 ChangeEvent ce = new ChangeEvent(this,MergeAnnotation.ANNOTATION_REMOVE,ann); 164 ChangeSupport changeSupport = super.getChangeSupport(MergeAnnotation.ANNOTATION_REMOVE); 165 synchronized(changeSupport) { 166 changeSupport.firePreChangeEvent(ce); 167 mergeSet.remove(ann); 168 changeSupport.firePostChangeEvent(ce); 169 } 170 } 171 } 172 173 174 protected ChangeSupport getChangeSupport(ChangeType changeType) { 175 ChangeSupport changeSupport = super.getChangeSupport(changeType); 176 177 if ( 178 (Annotation.PROPERTY.isMatchingType(changeType) || changeType.isMatchingType(Annotation.PROPERTY)) 179 && 180 propertyForwarder == null 181 ) { 182 propertyForwarder = new PropertyForwarder( 183 MergeAnnotation.this, 184 changeSupport 185 ); 186 for (Iterator i = mergeSet.iterator(); i.hasNext();) { 187 Annotation a = (Annotation) i.next(); 188 189 a.addChangeListener(propertyForwarder, Annotation.PROPERTY); 190 } 191 } 192 193 return changeSupport; 194 } 195 196 public void setProperty(Object key, Object value) throws ChangeVetoException { 197 throw new ChangeVetoException("MergeAnnotations don't allow property setting at the moment"); 198 } 199 200 public void removeProperty(Object key) throws ChangeVetoException { 201 throw new ChangeVetoException("MergeAnnotations don't allow property removal at the moment"); 202 } 203 204 public Object getProperty(Object key) { 205 for (Iterator i = mergeSet.iterator(); i.hasNext();) { 206 Annotation a = (Annotation) i.next(); 207 if (a.containsProperty(key)) { 208 return a.getProperty(key); 209 } 210 } 211 throw new NoSuchElementException("Can't find property " + key); 212 } 213 214 public boolean containsProperty(Object key) { 215 for (Iterator i = mergeSet.iterator(); i.hasNext();) { 216 Annotation a = (Annotation) i.next(); 217 if (a.containsProperty(key)) { 218 return true; 219 } 220 } 221 222 return false; 223 } 224 225 public Set keys() { 226 Set s = new HashSet(); 227 for (Iterator i = mergeSet.iterator(); i.hasNext();) { 228 Annotation a = (Annotation) i.next(); 229 s.addAll(a.keys()); 230 } 231 return s; 232 } 233 234 public Map asMap() { 235 return new MAMap(); 236 } 237 238 private class MAEntrySet extends AbstractSet { 239 private MAEntrySet() { 240 super(); 241 } 242 243 public Iterator iterator() { 244 return new Iterator() { 245 Iterator ksi = MergeAnnotation.this.keys().iterator(); 246 247 public boolean hasNext() { 248 return ksi.hasNext(); 249 } 250 251 public Object next() { 252 Object k = ksi.next(); 253 Object v = getProperty(k); 254 return new MAMapEntry(k, v); 255 } 256 257 public void remove() { 258 throw new UnsupportedOperationException(); 259 } 260 }; 261 } 262 263 public int size() { 264 return MergeAnnotation.this.keys().size(); 265 } 266 } 267 268 private class MAMapEntry implements Map.Entry { 269 private Object key; 270 private Object value; 271 272 private MAMapEntry(Object key, Object value) { 273 this.key = key; 274 this.value = value; 275 } 276 277 public Object getKey() { 278 return key; 279 } 280 281 public Object getValue() { 282 return value; 283 } 284 285 public Object setValue(Object v) { 286 throw new UnsupportedOperationException(); 287 } 288 289 public boolean equals(Object o) { 290 if (!(o instanceof Map.Entry)) { 291 return false; 292 } 293 294 Map.Entry mo = (Map.Entry) o; 295 return ((key == null ? mo.getKey() == null : key.equals(mo.getKey())) && 296 (value == null ? mo.getValue() == null : value.equals(mo.getValue()))); 297 } 298 299 public int hashCode() { 300 return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); 301 } 302 } 303 304 private class MAMap extends AbstractMap { 305 MAEntrySet es; 306 307 private MAMap() { 308 super(); 309 es = new MAEntrySet(); 310 } 311 312 public Set entrySet() { 313 return es; 314 } 315 316 public Set keySet() { 317 return MergeAnnotation.this.keys(); 318 } 319 320 public Object get(Object key) { 321 try { 322 return getProperty(key); 323 } catch (NoSuchElementException ex) { 324 } 325 326 return null; 327 } 328 } 329 330 /** 331 * Listener used to forward changes for any of the underlying annotations to 332 * listeners on this annotation. 333 * 334 * @author Thomas Down 335 * @author Matthew Pocock 336 * @since 1.2 337 */ 338 protected class PropertyForwarder extends ChangeForwarder { 339 /** 340 * Create a new forwarder on behalf of a source using the change support. 341 * @param source the new source of events 342 * @param cs the ChangeSupport used to manage listeners 343 */ 344 public PropertyForwarder(Object source, ChangeSupport cs) { 345 super(source, cs); 346 } 347 348 public ChangeEvent generateEvent(ChangeEvent ce) { 349 ChangeType ct = ce.getType(); 350 if (ct == Annotation.PROPERTY) { 351 Object curVal = ce.getChange(); 352 if (curVal instanceof Object[]) { 353 Object[] cur = (Object[]) curVal; 354 if (cur.length == 2) { 355 Object key = cur[0]; 356 Object value = cur[0]; 357 if (getProperty(key) != value) { 358 return new ChangeEvent( 359 getSource(), 360 Annotation.PROPERTY, 361 curVal, 362 ce.getPrevious(), 363 ce 364 ); 365 } 366 } 367 } 368 } 369 return null; 370 } 371 } 372}