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
022/*
023 * SimpleRichAnnotation.java
024 *
025 * Created on July 29, 2005, 10:30 AM
026 */
027
028package org.biojavax;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.Iterator;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Map;
035import java.util.NoSuchElementException;
036import java.util.Set;
037import java.util.TreeMap;
038import java.util.TreeSet;
039
040import org.biojava.bio.Annotatable;
041import org.biojava.ontology.Term;
042import org.biojava.utils.AbstractChangeable;
043import org.biojava.utils.ChangeEvent;
044import org.biojava.utils.ChangeSupport;
045import org.biojava.utils.ChangeVetoException;
046import org.biojavax.ontology.ComparableTerm;
047
048/**
049 * Simple annotation wrapper. All non-Note annotations get a rank of zero.
050 * @author Richard Holland
051 * @author George Waldon - adapted note change firing
052 * @since 1.5
053 */
054public class SimpleRichAnnotation extends AbstractChangeable implements RichAnnotation {
055    
056    private Set<Note> notes = new TreeSet<Note>(); // Keeps them ordered by rank then term
057    
058    /** Creates a new, empty instance of SimpleRichAnnotation */
059    public SimpleRichAnnotation() {}
060    
061    /**
062     * {@inheritDoc}
063     */
064    public void clear() throws ChangeVetoException{ 
065        // Use copy of list in order to prevent concurrent modifications.
066        // Fix for bug #2258.
067        for(Iterator<Note> i = (new ArrayList(this.notes)).iterator(); i.hasNext(); ){
068            this.removeNote(i.next());
069        }
070    }
071    
072    /**
073     * {@inheritDoc}
074     * The map is a copy of the internal structure. It is a map of 
075     * <code>ComparableTerm</code>s to <code>String</code>s corresponding
076     * to the Term and Value of the <code>Note</code>s in the annotation.
077     */
078    public Map asMap() {
079        Map m = new TreeMap();
080        for (Iterator<Note> i = this.notes.iterator(); i.hasNext(); ) {
081            Note n = i.next();
082            m.put(n.getTerm(), n.getValue());
083        }
084        return m;
085    }
086    
087    /**
088     * {@inheritDoc}
089     * In case the note was already here, a call to ChangeEvent.getPrevious()
090     * in the firePostChangeEvent method will return a copy of the original note.
091     */
092    public void addNote(Note note) throws ChangeVetoException {
093        if (note==null) throw new IllegalArgumentException("Note cannot be null");
094        if(!this.hasListeners(Annotatable.ANNOTATION)) {
095            this.notes.add(note);
096        } else {
097            ChangeEvent ce = new ChangeEvent(
098                    this,
099                    Annotatable.ANNOTATION,
100                    note,
101                    null
102                    );
103            ChangeSupport cs = this.getChangeSupport(Annotatable.ANNOTATION);
104            synchronized(cs) {
105                cs.firePreChangeEvent(ce);
106                boolean change = this.notes.add(note);
107                if(!change) {
108                    Note current = null;
109                    Iterator<Note> it = notes.iterator();
110                    while(it.hasNext()) {
111                        current = it.next();
112                        if(note.equals(current))
113                            break;
114                    }
115                    Note clone = new SimpleNote(current.getTerm(),current.getValue(),current.getRank());
116                    current.setValue(note.getValue()); //will fire Note.VALUE
117                    ce = new ChangeEvent(
118                    this,
119                    Annotatable.ANNOTATION,
120                    current,
121                    clone
122                    );
123                }
124                cs.firePostChangeEvent(ce);
125            }
126        }
127    }
128
129    /**
130     * {@inheritDoc}
131     */
132    public boolean contains(Note note) { return this.notes.contains(note); }
133    
134    /**
135     * {@inheritDoc}
136     * @deprecated 
137     */
138    public boolean containsProperty(Object key) { 
139        if (key instanceof Term) key = RichObjectFactory.getDefaultOntology().getOrImportTerm((Term)key);
140        else key = RichObjectFactory.getDefaultOntology().getOrCreateTerm(key.toString());
141        for(Iterator<Note> i = notes.iterator(); i.hasNext();){
142            Note n = i.next();
143            if(n.getTerm().equals(key)) return true;
144        }
145        return false; 
146    }
147    
148    /**
149     * {@inheritDoc}
150     */
151    public Note getNote(Note note) throws NoSuchElementException {
152        if (note==null) throw new IllegalArgumentException("Note cannot be null");
153        for (Iterator<Note> i = this.notes.iterator(); i.hasNext(); ) {
154            Note n = i.next();
155            if (note.equals(n)) return n;
156        }
157        throw new NoSuchElementException("No such property: "+note.getTerm()+", rank "+note.getRank());
158    }
159    
160    /**
161     * {@inheritDoc}
162     * Strictly it will return the first <code>Note</code> which matches the 
163     * <code>key</code> (or a <code>Term</code> made with a <code>String</code> key)..
164     * @see #getProperties(Object key)
165     * @deprecated 
166     */
167    public Object getProperty(Object key) throws NoSuchElementException { 
168        if (key instanceof Term) key = RichObjectFactory.getDefaultOntology().getOrImportTerm((Term)key);
169        else key = RichObjectFactory.getDefaultOntology().getOrCreateTerm(key.toString());
170        for(Iterator<Note> i = notes.iterator(); i.hasNext();){
171            Note n = i.next();
172            if (n.getTerm().equals(key)) return n.getValue();
173        }
174        throw new NoSuchElementException("No such property: "+key); 
175        }
176    
177    /**
178     * {@inheritDoc}
179     * Strictly it will return all <code>Note</code>s which match the 
180     * <code>key</code> (or a <code>Term</code> made with a <code>String</code> key)..
181     * @deprecated 
182     */
183    public Note[] getProperties(Object key){
184        if (key instanceof Term) key = RichObjectFactory.getDefaultOntology().getOrImportTerm((Term)key);
185        else key = RichObjectFactory.getDefaultOntology().getOrCreateTerm(key.toString());
186        List l = new LinkedList();
187        for(Iterator<Note> i = notes.iterator(); i.hasNext();){
188            Note n = i.next();
189            if (n.getTerm().equals(key)) l.add(n);
190        }
191        Collections.sort(l);
192        Note[] na = new Note[l.size()];
193        l.toArray(na);
194        return na;
195    }
196    
197    /**
198     * {@inheritDoc}
199     */
200    public Set keys() { return this.asMap().keySet(); }
201    
202    /**
203     * {@inheritDoc}
204     * In case the note is not found, a call to ChangeEvent.getPrevious()
205     * in the firePostChangeEvent method will return null.
206     */
207    public void removeNote(Note note) throws ChangeVetoException {
208        if (note==null) throw new IllegalArgumentException("Note cannot be null");
209        if(!this.hasListeners(Annotatable.ANNOTATION)) {
210            this.notes.remove(note);
211        } else {
212            ChangeEvent ce = new ChangeEvent(
213                    this,
214                    Annotatable.ANNOTATION,
215                    null,
216                    note
217                    );
218            ChangeSupport cs = this.getChangeSupport(Annotatable.ANNOTATION);
219            synchronized(cs) {
220                cs.firePreChangeEvent(ce);
221                boolean removed = this.notes.remove(note);
222                if(!removed)
223                    ce = new ChangeEvent(
224                    this,
225                    Annotatable.ANNOTATION,
226                    null,
227                    null
228                    );
229                cs.firePostChangeEvent(ce);
230            }
231        }
232    }
233    
234    /**
235     * {@inheritDoc}
236     * Strictly it will remove the first <code>Note</code> which matches the 
237     * <code>key</code> (or a <code>Term</code> made with a <code>String</code> key)..
238     * @deprecated 
239     */
240    public void removeProperty(Object key) throws NoSuchElementException, ChangeVetoException { 
241        if (key instanceof Term) key = RichObjectFactory.getDefaultOntology().getOrImportTerm((Term)key);
242        else key = RichObjectFactory.getDefaultOntology().getOrCreateTerm(key.toString());
243        for(Iterator<Note> i = notes.iterator(); i.hasNext();){
244            Note n = i.next();
245            if (n.getTerm().equals(key)) {
246                this.removeNote(n); 
247                return;
248            }
249        }
250        throw new NoSuchElementException("No such property: "+key); 
251    }
252    
253    /**
254     * {@inheritDoc}
255     * @deprecated 
256     */
257    public void setProperty(Object key, Object value) throws IllegalArgumentException, ChangeVetoException {
258        if(key == null) throw new IllegalArgumentException("Property keys cannot be null");
259        if (key instanceof Term) key = RichObjectFactory.getDefaultOntology().getOrImportTerm((Term)key);
260        else key = RichObjectFactory.getDefaultOntology().getOrCreateTerm(key.toString());
261        this.addNote(new SimpleNote((ComparableTerm)key, (String)(value==null?value:value.toString()), 0));
262    }
263    
264    /**
265     * {@inheritDoc}
266     * <b>Warning</b> this method gives access to the original 
267     * Collection not a copy. This is required by Hibernate. If you
268     * modify the object directly the behaviour may be unpredictable.
269     */
270    public Set<Note> getNoteSet() {  return this.notes; } // original for Hibernate
271    
272    /**
273     * {@inheritDoc}
274     * <b>Warning</b> this method gives access to the original 
275     * Collection not a copy. This is required by Hibernate. If you
276     * modify the object directly the behaviour may be unpredictable.
277     */
278    public void setNoteSet(Set<Note> notes) throws ChangeVetoException { this.notes = notes; } // original for Hibernate
279    
280    /**
281     * {@inheritDoc}
282     * Form: list of "[note]" values separated by commas
283     */
284    public String toString() {
285        StringBuffer sb = new StringBuffer();
286        for (Iterator i = this.notes.iterator(); i.hasNext(); ) {
287            sb.append("[");
288            sb.append(i.next());
289            sb.append("]");
290            if (i.hasNext()) sb.append(",");
291        }
292        return sb.toString();
293    }
294}