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}