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.biojavax.bio.taxa;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.Set;
026import java.util.TreeMap;
027import java.util.TreeSet;
028
029import org.biojava.utils.AbstractChangeable;
030import org.biojava.utils.ChangeEvent;
031import org.biojava.utils.ChangeSupport;
032import org.biojava.utils.ChangeVetoException;
033import org.biojavax.RichObjectFactory;
034
035/**
036 * Reference implementation of NCBITaxon.
037 * @author Richard Holland
038 * @author Mark Schreiber
039 * @author David Scott
040 * @since 1.5
041 */
042public class SimpleNCBITaxon extends AbstractChangeable implements NCBITaxon {
043    
044    protected Set names = new TreeSet();
045    private Map namesMap = new TreeMap();
046    private Integer parent;
047    private int NCBITaxID;
048    private String nodeRank;
049    private Integer geneticCode;
050    private Integer mitoGeneticCode;
051    private Integer leftValue;
052    private Integer rightValue;
053        private boolean isTaxonHidden = false;
054         
055        private final static String TAXONISHIDDEN = "X";
056        private final static int ROOTNCBIID = 1;
057   
058    /**
059     * Creates a new instance of SimpleNCBITaxon based on the given taxon ID.
060     * @param NCBITaxID the underlying taxon ID from NCBI.
061     */
062    public SimpleNCBITaxon(int NCBITaxID) {
063        this.NCBITaxID = NCBITaxID;
064    }
065    
066    /**
067     * Creates a new instance of SimpleNCBITaxon based on the given taxon ID.
068     * It may not be null, else you'll get exceptions.
069     * @param NCBITaxID the underlying taxon ID from NCBI.
070     */
071    public SimpleNCBITaxon(Integer NCBITaxID) {
072        this.NCBITaxID = NCBITaxID.intValue();
073    }
074    
075    // Hibernate requirement - not for public use.
076    protected SimpleNCBITaxon() {}
077    
078    /**
079     * {@inheritDoc}
080     * NCBITaxon objects are compared only by their NCBITaxID fields.
081     */
082    public int compareTo(Object o) {
083        if (o==this) return 0;
084        NCBITaxon them = (NCBITaxon)o;
085        return this.NCBITaxID-them.getNCBITaxID();
086    }
087    
088    /**
089     * {@inheritDoc}
090     * NCBITaxon objects are equal if their NCBITaxID fields match.
091     */
092    public boolean equals(Object obj) {
093        if (this == obj) return true;
094        if (obj==null || !(obj instanceof NCBITaxon)) return false;
095        NCBITaxon them = (NCBITaxon)obj;
096        return this.NCBITaxID==them.getNCBITaxID();
097    }
098    
099    /**
100     * {@inheritDoc}
101     */
102    public int hashCode() {
103        return this.NCBITaxID;
104    }
105    
106    /**
107     * {@inheritDoc}
108     */
109    public Set getNameClasses() { return this.namesMap.keySet(); }
110    
111    /**
112     * {@inheritDoc}
113     */
114    public Set getNames(String nameClass) throws IllegalArgumentException {
115        if (nameClass==null) throw new IllegalArgumentException("Name class cannot be null");
116        Set items = (Set)this.namesMap.get(nameClass);
117        Set n = new TreeSet();
118        if (items!=null) for (Iterator j = items.iterator(); j.hasNext(); ) {
119            SimpleNCBITaxonName name = (SimpleNCBITaxonName)j.next();
120            n.add(name.getName());
121        }
122        return n;
123    }
124    
125    // Hibernate requirement - not for public use.
126    protected Set getNameSet() { return this.names; } // original for Hibernate
127    
128    // Hibernate requirement - not for public use.
129    void setNameSet(Set names) {
130        this.names = names; // original for Hibernate
131        // convert set to map
132        this.namesMap.clear();
133        for (Iterator i = names.iterator(); i.hasNext(); ) {
134            SimpleNCBITaxonName n = (SimpleNCBITaxonName)i.next();
135            try {
136                this.addName(n.getNameClass(), n.getName());
137            } catch (ChangeVetoException e) {
138                throw new RuntimeException("Database contents don't add up",e);
139            }
140        }
141    }
142    
143    /**
144     * {@inheritDoc}
145     */
146    public void addName(String nameClass, String name) throws IllegalArgumentException,ChangeVetoException {
147        if (name==null) throw new IllegalArgumentException("Name cannot be null");
148        if (nameClass==null) throw new IllegalArgumentException("Name class cannot be null");
149        SimpleNCBITaxonName n = new SimpleNCBITaxonName(nameClass, name);
150        if(!this.hasListeners(NCBITaxon.NAMES)) {
151            if (!this.namesMap.containsKey(nameClass)) this.namesMap.put(nameClass,new TreeSet());
152            ((Set)this.namesMap.get(nameClass)).add(n);
153            this.names.add(n);
154        } else {
155            ChangeEvent ce = new ChangeEvent(
156                    this,
157                    NCBITaxon.NAMES,
158                    name,
159                    ((Set)this.namesMap.get(nameClass)).contains(n)?name:null
160                    );
161            ChangeSupport cs = this.getChangeSupport(NCBITaxon.NAMES);
162            synchronized(cs) {
163                cs.firePreChangeEvent(ce);
164                if (!this.namesMap.containsKey(nameClass)) this.namesMap.put(nameClass,new TreeSet());
165                ((Set)this.namesMap.get(nameClass)).add(n);
166                this.names.add(n);
167                cs.firePostChangeEvent(ce);
168            }
169        }
170    }
171    
172    /**
173     * {@inheritDoc}
174     */
175    public boolean removeName(String nameClass, String name) throws IllegalArgumentException,ChangeVetoException {
176        if (name==null) throw new IllegalArgumentException("Name cannot be null");
177        if (nameClass==null) throw new IllegalArgumentException("Name class cannot be null");
178        SimpleNCBITaxonName n = new SimpleNCBITaxonName(nameClass, name);
179        if (!this.namesMap.containsKey(nameClass)) return false;
180        boolean results;
181        if(!this.hasListeners(NCBITaxon.NAMES)) {
182            results = ((Set)this.namesMap.get(nameClass)).remove(n);
183            this.names.remove(n);
184        } else {
185            ChangeEvent ce = new ChangeEvent(
186                    this,
187                    NCBITaxon.NAMES,
188                    null,
189                    name
190                    );
191            ChangeSupport cs = this.getChangeSupport(NCBITaxon.NAMES);
192            synchronized(cs) {
193                cs.firePreChangeEvent(ce);
194                results = ((Set)this.namesMap.get(nameClass)).remove(n);
195                this.names.remove(n);
196                cs.firePostChangeEvent(ce);
197            }
198        }
199        return results;
200    }
201    
202    /**
203     * {@inheritDoc}
204     */
205    public boolean containsName(String nameClass, String name) throws IllegalArgumentException {
206        if (name==null) throw new IllegalArgumentException("Name cannot be null");
207        if (nameClass==null) throw new IllegalArgumentException("Name class cannot be null");
208        if (!this.namesMap.containsKey(nameClass)) return false;
209        SimpleNCBITaxonName n = new SimpleNCBITaxonName(nameClass, name);
210        return ((Set)this.namesMap.get(nameClass)).contains(n);
211    }
212    
213    protected final Map getNamesMap() {
214        return namesMap;
215    }
216    
217    /**
218     * {@inheritDoc}
219     */
220    public Integer getParentNCBITaxID() { return this.parent; }
221    
222    /**
223     * {@inheritDoc}
224     */
225    public void setParentNCBITaxID(Integer parent) throws ChangeVetoException {
226        if(!this.hasListeners(NCBITaxon.PARENT)) {
227            this.parent = parent;
228        } else {
229            ChangeEvent ce = new ChangeEvent(
230                    this,
231                    NCBITaxon.PARENT,
232                    parent,
233                    this.parent
234                    );
235            ChangeSupport cs = this.getChangeSupport(NCBITaxon.PARENT);
236            synchronized(cs) {
237                cs.firePreChangeEvent(ce);
238                this.parent = parent;
239                cs.firePostChangeEvent(ce);
240            }
241        }
242    }
243    
244    /**
245     * {@inheritDoc}
246     */
247    public int getNCBITaxID() { return this.NCBITaxID; }
248    
249    // Hibernate requirement - not for public use.
250    void setNCBITaxID(int NCBITaxID) { this.NCBITaxID = NCBITaxID; }
251    
252    /**
253     * {@inheritDoc}
254     */
255    public String getNodeRank() { return this.nodeRank; }
256    
257    /**
258     * Setter for property nodeRank.
259     * @param nodeRank New value of property nodeRank.
260     * @throws org.biojava.utils.ChangeVetoException in case of objections.
261     */
262    public void setNodeRank(String nodeRank) throws ChangeVetoException {
263        if(!this.hasListeners(NCBITaxon.NODERANK)) {
264            this.nodeRank = nodeRank;
265        } else {
266            ChangeEvent ce = new ChangeEvent(
267                    this,
268                    NCBITaxon.NODERANK,
269                    nodeRank,
270                    this.nodeRank
271                    );
272            ChangeSupport cs = this.getChangeSupport(NCBITaxon.NODERANK);
273            synchronized(cs) {
274                cs.firePreChangeEvent(ce);
275                this.nodeRank = nodeRank;
276                cs.firePostChangeEvent(ce);
277            }
278        }
279    }
280    
281    /**
282     * {@inheritDoc}
283     */
284    public Integer getGeneticCode() { return this.geneticCode; }
285    
286    /**
287     * {@inheritDoc}
288     */
289    public void setGeneticCode(Integer geneticCode) throws ChangeVetoException {
290        if(!this.hasListeners(NCBITaxon.GENETICCODE)) {
291            this.geneticCode = geneticCode;
292        } else {
293            ChangeEvent ce = new ChangeEvent(
294                    this,
295                    NCBITaxon.GENETICCODE,
296                    nodeRank,
297                    this.nodeRank
298                    );
299            ChangeSupport cs = this.getChangeSupport(NCBITaxon.GENETICCODE);
300            synchronized(cs) {
301                cs.firePreChangeEvent(ce);
302                this.geneticCode = geneticCode;
303                cs.firePostChangeEvent(ce);
304            }
305        }
306    }
307    
308    /**
309     * Getter for property mitoGeneticCode. Returns Persistent.NULL_INTEGER if null.
310     * @return Value of property mitoGeneticCode.
311     */
312    public Integer getMitoGeneticCode() { return this.mitoGeneticCode; }
313    
314    /**
315     * {@inheritDoc}
316     */
317    public void setMitoGeneticCode(Integer mitoGeneticCode) throws ChangeVetoException {
318        if(!this.hasListeners(NCBITaxon.MITOGENETICCODE)) {
319            this.mitoGeneticCode = mitoGeneticCode;
320        } else {
321            ChangeEvent ce = new ChangeEvent(
322                    this,
323                    NCBITaxon.MITOGENETICCODE,
324                    mitoGeneticCode,
325                    this.mitoGeneticCode
326                    );
327            ChangeSupport cs = this.getChangeSupport(NCBITaxon.MITOGENETICCODE);
328            synchronized(cs) {
329                cs.firePreChangeEvent(ce);
330                this.mitoGeneticCode = mitoGeneticCode;
331                cs.firePostChangeEvent(ce);
332            }
333        }
334    }
335    
336    /**
337     * {@inheritDoc}
338     */
339    public Integer getLeftValue() { return this.leftValue; }
340    
341    /**
342     * {@inheritDoc}
343     */
344    public void setLeftValue(Integer leftValue) throws ChangeVetoException {
345        if(!this.hasListeners(NCBITaxon.LEFTVALUE)) {
346            this.leftValue = leftValue;
347        } else {
348            ChangeEvent ce = new ChangeEvent(
349                    this,
350                    NCBITaxon.LEFTVALUE,
351                    leftValue,
352                    this.leftValue
353                    );
354            ChangeSupport cs = this.getChangeSupport(NCBITaxon.LEFTVALUE);
355            synchronized(cs) {
356                cs.firePreChangeEvent(ce);
357                this.leftValue = leftValue;
358                cs.firePostChangeEvent(ce);
359            }
360        }
361    }
362    
363    /**
364     * {@inheritDoc}
365     */
366    public Integer getRightValue() { return this.rightValue; }
367    
368    /**
369     * {@inheritDoc}
370     */
371    public void setRightValue(Integer rightValue) throws ChangeVetoException {
372        if(!this.hasListeners(NCBITaxon.RIGHTVALUE)) {
373            this.rightValue = rightValue;
374        } else {
375            ChangeEvent ce = new ChangeEvent(
376                    this,
377                    NCBITaxon.RIGHTVALUE,
378                    rightValue,
379                    this.rightValue
380                    );
381            ChangeSupport cs = this.getChangeSupport(NCBITaxon.RIGHTVALUE);
382            synchronized(cs) {
383                cs.firePreChangeEvent(ce);
384                this.rightValue = rightValue;
385                cs.firePostChangeEvent(ce);
386            }
387        }
388    }
389    
390    /**
391     * Returns the name of this taxon entry in the form:
392     *   scientific (common)
393     * or if there is no common name:
394     *   scientific
395     * or if there are no scientific names at all, the empty string.
396     * @return the display name as described above.
397     */
398    public String getDisplayName() {
399        StringBuffer sb = new StringBuffer();
400        Set sciNames = this.getNames(NCBITaxon.SCIENTIFIC);
401        Set comNames = this.getNames(NCBITaxon.COMMON);
402        if (sciNames.size()>0) {
403            sb.append((String)sciNames.iterator().next());
404            if (comNames.size()>0) {
405                sb.append(" (");
406                sb.append((String)comNames.iterator().next());
407                sb.append(")");
408            }
409        }
410        return sb.toString();
411    }
412    
413    /**
414     * Returns the taxonomy hierarchy of this taxon entry in the form:
415     *   most specific; less specific; ...; least specific.
416     * It follows the chain up the tree as far as it can, and will stop
417     * at the first one it comes to that returns null for getParentNCBITaxID().
418     * If this taxon entry has no scientific name, you will get the string ".".
419     * @return the display name as described above.
420     */
421    /*
422     * (non-Javadoc)
423     * @see com.bioperception.bio.taxa.NCBITaxon2#isTaxonHidden()
424     */
425    public final boolean isTaxonHidden() {
426        return isTaxonHidden;
427    }
428    
429    /*
430     * (non-Javadoc)
431     * @see com.bioperception.bio.taxa.NCBITaxon2#setTaxonHidden(boolean)
432     */
433    public final void setTaxonHidden(final boolean isTaxonHidden) throws ChangeVetoException {
434        if(!this.hasListeners(HIDDEN)) {
435            this.isTaxonHidden = isTaxonHidden;
436        } else {
437            final ChangeEvent ce = new ChangeEvent( this, HIDDEN, new Boolean(isTaxonHidden), new Boolean(this.isTaxonHidden));
438            final ChangeSupport cs = this.getChangeSupport(HIDDEN);
439            synchronized(cs) {
440                cs.firePreChangeEvent(ce);
441                this.isTaxonHidden = isTaxonHidden;
442                cs.firePostChangeEvent(ce);
443            }
444        }
445    }
446    
447    // Hibernate requirement - not for public use.
448    String getTaxonHiddenChar() {
449        return isTaxonHidden()?TAXONISHIDDEN:null;
450    }
451
452    // Hibernate requirement - not for public use.
453    void setTaxonHiddenChar(final String isHiddenChar) throws ChangeVetoException {
454        setTaxonHidden(isHiddenChar!=null || (isHiddenChar!=null && isHiddenChar.length() > 0));// any character will set
455    }
456
457    /**
458     * Returns the taxonomy hierarchy of this taxon entry in the form:
459     *   most specific; less specific; ...; least specific.
460     * It follows the chain up the tree as far as it can, and will stop
461     * at the first one it comes to that returns null for getParentNCBITaxID().
462     * If this taxon entry has no scientific name, you will get the string ".".
463     * @return the display name as described above.
464     */
465    public String getNameHierarchy() {
466        StringBuffer sb = new StringBuffer();
467        boolean first = true;
468        Integer parent = this.getParentNCBITaxID();
469        while (parent!=null) {
470                NCBITaxon t = (NCBITaxon)RichObjectFactory.getObject(SimpleNCBITaxon.class, new Object[]{parent});
471                Set sciNames = t.getNames(NCBITaxon.SCIENTIFIC);
472//              System.out.println("SimpleNCBITaxon2.getNameHierarchy-t:"+t+", t.isTaxonHidden? "+t.isTaxonHidden()+", isRoot? "+isRoot(t)+", sciNames:"+sciNames);
473                if (sciNames.size()>0) {
474                        if (!t.isTaxonHidden() && !isRoot(t)) {// root is NOT hidden - but also not displayed
475                                if (!first) sb.insert(0,"; ");
476                                else first = false;
477                                sb.insert(0,(String)sciNames.iterator().next());
478                        }
479                        // Don't get into endless loop if child's parent is itself.
480                        if (t.getParentNCBITaxID().equals(new Integer(t.getNCBITaxID()))) parent = null;
481                        else parent = t.getParentNCBITaxID();
482                }
483                // Also don't go up past a parent that doesn't have a name.
484                else parent = null;
485        }
486        sb.append(".");
487        return sb.toString();
488    }
489    
490    
491    private final static boolean isRoot(final NCBITaxon theTaxon) {
492        return isRoot(theTaxon.getNCBITaxID());
493    }
494    
495    private final static boolean isRoot(final int theNCBIId) {
496        return theNCBIId==ROOTNCBIID;
497    }
498    
499    /**
500     * {@inheritDoc}
501     * Form: "taxid:[name,name...]"
502     */
503    public String toString() {
504        StringBuffer sb = new StringBuffer();
505        sb.append("taxid:");
506        sb.append(this.NCBITaxID);
507        sb.append("[");
508        for (Iterator i = this.getNameSet().iterator(); i.hasNext(); ) {
509            SimpleNCBITaxonName n = (SimpleNCBITaxonName)i.next();
510            sb.append(n);
511            if (i.hasNext()) sb.append(",");
512        }
513        sb.append("]");
514        return sb.toString();
515    }
516    
517    // Hibernate requirement - not for public use.
518    private Integer id;
519    
520    /**
521     * Gets the Hibernate ID. Should be used with caution.
522     * @return the Hibernate ID, if using Hibernate.
523     */
524    public Integer getId() { return this.id; }
525    
526    /**
527     * Sets the Hibernate ID. Should be used with caution.
528     * @param id the Hibernate ID, if using Hibernate.
529     */
530    public void setId(Integer id) { this.id = id;}
531    
532}
533