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}