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.Collections; 028import java.util.HashMap; 029import java.util.Iterator; 030import java.util.Map; 031import java.util.NoSuchElementException; 032import java.util.Set; 033 034import org.biojava.utils.AbstractChangeable; 035import org.biojava.utils.ChangeEvent; 036import org.biojava.utils.ChangeForwarder; 037import org.biojava.utils.ChangeListener; 038import org.biojava.utils.ChangeSupport; 039import org.biojava.utils.ChangeType; 040import org.biojava.utils.ChangeVetoException; 041 042/** 043 * Annotation implementation which allows new key-value 044 * pairs to be layered on top of an underlying Annotation. 045 * When <code>getProperty</code> is called, we first check 046 * for a value stored in the overlay. If this fails, the 047 * underlying <code>Annotation</code> is checked. Values 048 * passed to <code>setProperty</code> are always stored 049 * within the overlay. 050 * 051 * @author Thomas Down 052 * @author Matthew Pocock 053 * @author Greg Cox 054 * @since 1.1 055 * 056 * In the case where you wish to wrap an underlying Annotation in a view that 057 * will allow it to be edited without altering the original object, but also 058 * reflect changes in the original object. 059 */ 060 061public class OverlayAnnotation 062 extends 063 AbstractChangeable 064 implements 065 Annotation, 066 Serializable 067{ 068 private transient ChangeListener propertyForwarder = null; 069 070 private Annotation parent; 071 private Map overlay = null; 072 073 protected ChangeSupport getChangeSupport(ChangeType changeType) { 074 ChangeSupport changeSupport = super.getChangeSupport(changeType); 075 076 if( 077 (Annotation.PROPERTY.isMatchingType(changeType) || changeType.isMatchingType(Annotation.PROPERTY)) && 078 (propertyForwarder == null) 079 ) { 080 propertyForwarder = new PropertyForwarder( 081 OverlayAnnotation.this, 082 changeSupport 083 ); 084 parent.addChangeListener( 085 propertyForwarder, 086 Annotation.PROPERTY 087 ); 088 } 089 090 return changeSupport; 091 } 092 093 /** 094 * Get the map used for the overlay. Modifying this modifies the Annotation. 095 * 096 * @return the overlay Map 097 */ 098 protected Map getOverlay() { 099 if (overlay == null) 100 overlay = new HashMap(); 101 return overlay; 102 } 103 104 /** 105 * Construct an annotation which can overlay new key-value 106 * pairs onto an underlying annotation. 107 * 108 * @param par The `parent' annotation, on which new 109 * key-value pairs can be layered. 110 */ 111 112 public OverlayAnnotation(Annotation par) { 113 parent = par; 114 } 115 116 public void setProperty(Object key, Object value) 117 throws ChangeVetoException 118 { 119 if(hasListeners()) { 120 ChangeSupport changeSupport = getChangeSupport(Annotation.PROPERTY); 121 ChangeEvent ce = new ChangeEvent( 122 this, 123 Annotation.PROPERTY, 124 new Object[] {key, value}, 125 new Object[] {key, getProperty(key)} 126 ); 127 synchronized(changeSupport) { 128 changeSupport.firePreChangeEvent(ce); 129 getOverlay().put(key, value); 130 changeSupport.firePostChangeEvent(ce); 131 } 132 } else { 133 getOverlay().put(key, value); 134 } 135 } 136 137 public void removeProperty(Object key) 138 throws ChangeVetoException 139 { 140 if (overlay == null || !overlay.containsKey(key)) { 141 if (parent.containsProperty(key)) { 142 throw new ChangeVetoException("Can't remove properties from the parent annotation"); 143 } else { 144 throw new NoSuchElementException("Property doesn't exist: " + key); 145 } 146 } 147 148 if(hasListeners()) { 149 ChangeSupport changeSupport = getChangeSupport(Annotation.PROPERTY); 150 ChangeEvent ce = new ChangeEvent( 151 this, 152 Annotation.PROPERTY, 153 new Object[] {key, null}, 154 new Object[] {key, getProperty(key)} 155 ); 156 synchronized(changeSupport) { 157 changeSupport.firePreChangeEvent(ce); 158 getOverlay().remove(key); 159 changeSupport.firePostChangeEvent(ce); 160 } 161 } else { 162 getOverlay().remove(key); 163 } 164 } 165 166 public Object getProperty(Object key) { 167 Object val = null; 168 if (overlay != null) 169 val = overlay.get(key); 170 if (val != null) { 171 return val; 172 } 173 return parent.getProperty(key); 174 } 175 176 public boolean containsProperty(Object key) { 177 if( 178 (overlay != null) && 179 (overlay.containsKey(key)) 180 ) { 181 return true; 182 } else { 183 return parent.containsProperty(key); 184 } 185 } 186 187 188 /** 189 * Return a <code>Set</code> containing all key objects 190 * visible in this annotation. The <code>Set</code> is 191 * unmodifiable, but will dynamically reflect changes made 192 * to the annotation. 193 * 194 * @return the keys as a Set 195 */ 196 public Set keys() { 197 return new OAKeySet(); 198 } 199 200 /** 201 * Return a <code>Map</code> view onto this annotation. 202 * The returned <code>Map</code> is unmodifiable, but will 203 * dynamically reflect any changes made to this annotation. 204 * 205 * @return a view of this Annotation as an immutable Map 206 */ 207 208 public Map asMap() { 209 return new OAMap(); 210 } 211 212 private class OAKeySet extends AbstractSet { 213 private Set parentKeys; 214 215 private OAKeySet() { 216 super(); 217 parentKeys = parent.keys(); 218 } 219 220 public Iterator iterator() { 221 return new Iterator() { 222 Iterator oi = (overlay != null) ? overlay.keySet().iterator() 223 : Collections.EMPTY_SET.iterator(); 224 Iterator pi = parentKeys.iterator(); 225 Object peek = null; 226 227 public boolean hasNext() { 228 if (peek == null) 229 peek = nextObject(); 230 return (peek != null); 231 } 232 233 public Object next() { 234 if (peek == null) { 235 peek = nextObject(); 236 } 237 if (peek == null) { 238 throw new NoSuchElementException(); 239 } 240 Object o = peek; 241 peek = null; 242 return o; 243 } 244 245 private Object nextObject() { 246 if (oi.hasNext()) { 247 return oi.next(); 248 } 249 Object po = null; 250 while (po == null && pi.hasNext()) { 251 po = pi.next(); 252 if (overlay != null && overlay.containsKey(po)) { 253 po = null; 254 } 255 } 256 return po; 257 } 258 259 public void remove() { 260 throw new UnsupportedOperationException(); 261 } 262 }; 263 } 264 265 public int size() { 266 int i = 0; 267 Iterator keys = iterator(); 268 while(keys.hasNext()) { 269 keys.next(); 270 ++i; 271 } 272 return i; 273 } 274 275 public boolean contains(Object o) { 276 return (overlay != null && overlay.containsKey(o)) || parentKeys.contains(o); 277 } 278 } 279 280 private class OAEntrySet extends AbstractSet { 281 OAKeySet ks; 282 283 private OAEntrySet() { 284 super(); 285 ks = new OAKeySet(); 286 } 287 288 public Iterator iterator() { 289 return new Iterator() { 290 Iterator ksi = ks.iterator(); 291 292 public boolean hasNext() { 293 return ksi.hasNext(); 294 } 295 296 public Object next() { 297 Object k = ksi.next(); 298 Object v = getProperty(k); 299 return new OAMapEntry(k, v); 300 } 301 302 public void remove() { 303 throw new UnsupportedOperationException(); 304 } 305 }; 306 } 307 308 public int size() { 309 return ks.size(); 310 } 311 } 312 313 private class OAMapEntry implements Map.Entry { 314 private Object key; 315 private Object value; 316 317 private OAMapEntry(Object key, Object value) { 318 this.key = key; 319 this.value = value; 320 } 321 322 public Object getKey() { 323 return key; 324 } 325 326 public Object getValue() { 327 return value; 328 } 329 330 public Object setValue(Object v) { 331 throw new UnsupportedOperationException(); 332 } 333 334 public boolean equals(Object o) { 335 if (! (o instanceof Map.Entry)) { 336 return false; 337 } 338 339 Map.Entry mo = (Map.Entry) o; 340 return ((key == null ? mo.getKey() == null : key.equals(mo.getKey())) && 341 (value == null ? mo.getValue() == null : value.equals(mo.getValue()))); 342 } 343 344 public int hashCode() { 345 return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); 346 } 347 } 348 349 private class OAMap extends AbstractMap { 350 OAEntrySet es; 351 OAKeySet ks; 352 353 private OAMap() { 354 super(); 355 ks = new OAKeySet(); 356 es = new OAEntrySet(); 357 } 358 359 public Set entrySet() { 360 return es; 361 } 362 363 public Set keySet() { 364 return ks; 365 } 366 367 public Object get(Object key) { 368 try { 369 return getProperty(key); 370 } catch (NoSuchElementException ex) { 371 } 372 373 return null; 374 } 375 } 376 377 /** 378 * Forwards change events from the underlying Annotation to this one. 379 * 380 * @author Thomas Down 381 * @author Matthew Pocock 382 */ 383 protected class PropertyForwarder extends ChangeForwarder { 384 /** 385 * Forward on behalf of source using the change support provided. 386 * 387 * @param source the source Object 388 * @param cs the ChangeSupport to use 389 */ 390 public PropertyForwarder(Object source, ChangeSupport cs) { 391 super(source, cs); 392 } 393 394 public ChangeEvent generateEvent(ChangeEvent ce) { 395 ChangeType ct = ce.getType(); 396 if(ct == Annotation.PROPERTY) { 397 Object curVal = ce.getChange(); 398 if(curVal instanceof Object[]) { 399 Object[] cur = (Object []) curVal; 400 if(cur.length == 2) { 401 Object key = cur[0]; 402 Object value = cur[0]; 403 if(getProperty(key) != value) { 404 return new ChangeEvent( 405 getSource(), 406 Annotation.PROPERTY, 407 curVal, 408 ce.getPrevious(), 409 ce 410 ); 411 } 412 } 413 } 414 } 415 return null; 416 } 417 } 418}