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}