in progress
[jalview.git] / forester / java / src / org / forester / analysis / AncestralTaxonomyInference.java
1 // forester -- software libraries and applications
2 // for genomics and evolutionary biology research.
3 //
4 // Copyright (C) 2010 Christian M Zmasek
5 // Copyright (C) 2010 Sanford-Burnham Medical Research Institute
6 // All rights reserved
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Lesser General Public
10 // License as published by the Free Software Foundation; either
11 // version 2.1 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Lesser General Public License for more details.
17 //
18 // You should have received a copy of the GNU Lesser General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 //
22 // Contact: phylosoft @ gmail . com
23 // WWW: www.phylosoft.org/forester
24
25 package org.forester.analysis;
26
27 import java.io.IOException;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.SortedSet;
32 import java.util.TreeSet;
33
34 import org.forester.io.parsers.phyloxml.PhyloXmlDataFormatException;
35 import org.forester.phylogeny.Phylogeny;
36 import org.forester.phylogeny.PhylogenyNode;
37 import org.forester.phylogeny.data.Identifier;
38 import org.forester.phylogeny.data.Taxonomy;
39 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
40 import org.forester.util.ForesterUtil;
41 import org.forester.ws.uniprot.UniProtTaxonomy;
42 import org.forester.ws.uniprot.UniProtWsTools;
43
44 public final class AncestralTaxonomyInference {
45
46     private static final int                              MAX_CACHE_SIZE           = 100000;
47     private static final int                              MAX_TAXONOMIES_TO_RETURN = 1000;
48     private static final HashMap<String, UniProtTaxonomy> _sn_up_cache_map         = new HashMap<String, UniProtTaxonomy>();
49     private static final HashMap<String, UniProtTaxonomy> _lineage_up_cache_map    = new HashMap<String, UniProtTaxonomy>();
50     private static final HashMap<String, UniProtTaxonomy> _code_up_cache_map       = new HashMap<String, UniProtTaxonomy>();
51     private static final HashMap<String, UniProtTaxonomy> _cn_up_cache_map         = new HashMap<String, UniProtTaxonomy>();
52     private static final HashMap<String, UniProtTaxonomy> _id_up_cache_map         = new HashMap<String, UniProtTaxonomy>();
53
54     synchronized private static void clearCachesIfTooLarge() {
55         if ( getSnTaxCacheMap().size() > MAX_CACHE_SIZE ) {
56             getSnTaxCacheMap().clear();
57         }
58         if ( getLineageTaxCacheMap().size() > MAX_CACHE_SIZE ) {
59             getLineageTaxCacheMap().clear();
60         }
61         if ( getCnTaxCacheMap().size() > MAX_CACHE_SIZE ) {
62             getCnTaxCacheMap().clear();
63         }
64         if ( getCodeTaxCacheMap().size() > MAX_CACHE_SIZE ) {
65             getCodeTaxCacheMap().clear();
66         }
67         if ( getIdTaxCacheMap().size() > MAX_CACHE_SIZE ) {
68             getIdTaxCacheMap().clear();
69         }
70     }
71
72     synchronized private static HashMap<String, UniProtTaxonomy> getCnTaxCacheMap() {
73         return _cn_up_cache_map;
74     }
75
76     synchronized private static HashMap<String, UniProtTaxonomy> getCodeTaxCacheMap() {
77         return _code_up_cache_map;
78     }
79
80     synchronized private static HashMap<String, UniProtTaxonomy> getIdTaxCacheMap() {
81         return _id_up_cache_map;
82     }
83
84     synchronized private static HashMap<String, UniProtTaxonomy> getSnTaxCacheMap() {
85         return _sn_up_cache_map;
86     }
87
88     synchronized private static HashMap<String, UniProtTaxonomy> getLineageTaxCacheMap() {
89         return _lineage_up_cache_map;
90     }
91
92     synchronized private static UniProtTaxonomy getTaxonomies( final HashMap<String, UniProtTaxonomy> cache,
93                                                                final Object query,
94                                                                final QUERY_TYPE qt ) throws IOException,
95             AncestralTaxonomyInferenceException {
96         if ( cache.containsKey( query ) ) {
97             return cache.get( query ).copy();
98         }
99         else {
100             List<UniProtTaxonomy> up_taxonomies = null;
101             switch ( qt ) {
102                 case ID:
103                     up_taxonomies = getTaxonomiesFromId( ( String ) query );
104                     break;
105                 case CODE:
106                     up_taxonomies = getTaxonomiesFromTaxonomyCode( ( String ) query );
107                     break;
108                 case SN:
109                     up_taxonomies = getTaxonomiesFromScientificName( ( String ) query );
110                     break;
111                 case CN:
112                     up_taxonomies = getTaxonomiesFromCommonName( ( String ) query );
113                     break;
114                 case LIN:
115                     return obtainUniProtTaxonomyFromLineage( ( List<String> ) query );
116                 default:
117                     throw new RuntimeException();
118             }
119             if ( ( up_taxonomies != null ) && ( up_taxonomies.size() == 1 ) ) {
120                 final UniProtTaxonomy up_tax = up_taxonomies.get( 0 );
121                 if ( !ForesterUtil.isEmpty( up_tax.getScientificName() ) ) {
122                     getSnTaxCacheMap().put( up_tax.getScientificName(), up_tax );
123                 }
124                 if ( !ForesterUtil.isEmpty( up_tax.getCode() ) ) {
125                     getCodeTaxCacheMap().put( up_tax.getCode(), up_tax );
126                 }
127                 if ( !ForesterUtil.isEmpty( up_tax.getCommonName() ) ) {
128                     getCnTaxCacheMap().put( up_tax.getCommonName(), up_tax );
129                 }
130                 if ( !ForesterUtil.isEmpty( up_tax.getId() ) ) {
131                     getIdTaxCacheMap().put( up_tax.getId(), up_tax );
132                 }
133                 return up_tax;
134             }
135             else {
136                 return null;
137             }
138         }
139     }
140
141     synchronized private static List<UniProtTaxonomy> getTaxonomiesFromCommonName( final String query )
142             throws IOException {
143         return UniProtWsTools.getTaxonomiesFromCommonNameStrict( query, MAX_TAXONOMIES_TO_RETURN );
144     }
145
146     synchronized private static List<UniProtTaxonomy> getTaxonomiesFromId( final String query ) throws IOException {
147         return UniProtWsTools.getTaxonomiesFromId( query, MAX_TAXONOMIES_TO_RETURN );
148     }
149
150     synchronized private static List<UniProtTaxonomy> getTaxonomiesFromScientificName( final String query )
151             throws IOException {
152         return UniProtWsTools.getTaxonomiesFromScientificNameStrict( query, MAX_TAXONOMIES_TO_RETURN );
153     }
154
155     synchronized private static List<UniProtTaxonomy> getTaxonomiesFromTaxonomyCode( final String query )
156             throws IOException {
157         return UniProtWsTools.getTaxonomiesFromTaxonomyCode( query, MAX_TAXONOMIES_TO_RETURN );
158     }
159
160     synchronized public static void inferTaxonomyFromDescendents( final Phylogeny phy ) throws IOException,
161             AncestralTaxonomyInferenceException {
162         clearCachesIfTooLarge();
163         for( final PhylogenyNodeIterator iter = phy.iteratorPostorder(); iter.hasNext(); ) {
164             final PhylogenyNode node = iter.next();
165             if ( !node.isExternal() ) {
166                 inferTaxonomyFromDescendents( node );
167             }
168         }
169     }
170
171     synchronized private static void inferTaxonomyFromDescendents( final PhylogenyNode n ) throws IOException,
172             AncestralTaxonomyInferenceException {
173         if ( n.isExternal() ) {
174             throw new IllegalArgumentException( "attempt to infer taxonomy from descendants of external node" );
175         }
176         n.getNodeData().setTaxonomy( null );
177         final List<PhylogenyNode> descs = n.getDescendants();
178         final List<String[]> lineages = new ArrayList<String[]>();
179         int shortest_lin_length = Integer.MAX_VALUE;
180         for( final PhylogenyNode desc : descs ) {
181             if ( desc.getNodeData().isHasTaxonomy()
182                     && ( isHasAppropriateId( desc.getNodeData().getTaxonomy() )
183                             || !ForesterUtil.isEmpty( desc.getNodeData().getTaxonomy().getScientificName() )
184                             || !ForesterUtil.isEmpty( desc.getNodeData().getTaxonomy().getLineage() )
185                             || !ForesterUtil.isEmpty( desc.getNodeData().getTaxonomy().getTaxonomyCode() ) || !ForesterUtil
186                             .isEmpty( desc.getNodeData().getTaxonomy().getCommonName() ) ) ) {
187                 final UniProtTaxonomy up_tax = obtainUniProtTaxonomy( desc.getNodeData().getTaxonomy(), null, null );
188                 if ( ( up_tax == null ) && ForesterUtil.isEmpty( desc.getNodeData().getTaxonomy().getLineage() ) ) {
189                     String desc_str = "";
190                     if ( !ForesterUtil.isEmpty( desc.getName() ) ) {
191                         desc_str = "\"" + desc.getName() + "\"";
192                     }
193                     else {
194                         desc_str = "[" + desc.getId() + "]";
195                     }
196                     System.out.println( desc.getNodeData().getTaxonomy().toString() );
197                     System.out.println( ForesterUtil.stringListToString( desc.getNodeData().getTaxonomy().getLineage(),
198                                                                          "  >  " ) );
199                     throw new AncestralTaxonomyInferenceException( "a taxonomy for node " + desc_str
200                             + " could not be established from the database" );
201                 }
202                 String[] lineage = ForesterUtil.stringListToArray( desc.getNodeData().getTaxonomy().getLineage() );
203                 if ( ( lineage == null ) || ( lineage.length < 1 ) ) {
204                     lineage = ForesterUtil.stringListToArray( up_tax.getLineage() );
205                 }
206                 if ( ( lineage == null ) || ( lineage.length < 1 ) ) {
207                     throw new AncestralTaxonomyInferenceException( "a taxonomic lineage for node \""
208                             + desc.getNodeData().getTaxonomy().toString() + "\" could not be established" );
209                 }
210                 if ( lineage.length < shortest_lin_length ) {
211                     shortest_lin_length = lineage.length;
212                 }
213                 lineages.add( lineage );
214             }
215             else {
216                 String node = "";
217                 if ( !ForesterUtil.isEmpty( desc.getName() ) ) {
218                     node = "\"" + desc.getName() + "\"";
219                 }
220                 else {
221                     node = "[" + desc.getId() + "]";
222                 }
223                 //   final List<PhylogenyNode> e = desc.getAllExternalDescendants();
224                 //TODO remove me!
225                 //                System.out.println();
226                 //                int x = 0;
227                 //                for( final PhylogenyNode object : e ) {
228                 //                    System.out.println( x + ":" );
229                 //                    System.out.println( object.getName() + "  " );
230                 //                    x++;
231                 //                }
232                 //                System.out.println();
233                 //
234                 throw new AncestralTaxonomyInferenceException( "node " + node
235                         + " has no or inappropriate taxonomic information" );
236             }
237         }
238         final List<String> last_common_lineage = new ArrayList<String>();
239         String last_common = null;
240         if ( shortest_lin_length > 0 ) {
241             I: for( int i = 0; i < shortest_lin_length; ++i ) {
242                 final String lineage_0 = lineages.get( 0 )[ i ];
243                 for( int j = 1; j < lineages.size(); ++j ) {
244                     if ( !lineage_0.equals( lineages.get( j )[ i ] ) ) {
245                         break I;
246                     }
247                 }
248                 last_common_lineage.add( lineage_0 );
249                 last_common = lineage_0;
250             }
251         }
252         if ( last_common_lineage.isEmpty() ) {
253             String msg = "no common lineage for:\n";
254             int counter = 0;
255             for( final String[] strings : lineages ) {
256                 msg += counter + ": ";
257                 ++counter;
258                 for( final String string : strings ) {
259                     msg += string + " ";
260                 }
261                 msg += "\n";
262             }
263             throw new AncestralTaxonomyInferenceException( msg );
264         }
265         final Taxonomy tax = new Taxonomy();
266         n.getNodeData().setTaxonomy( tax );
267         tax.setScientificName( last_common );
268         final UniProtTaxonomy up_tax = obtainUniProtTaxonomyFromLineage( last_common_lineage );
269         if ( up_tax != null ) {
270             if ( !ForesterUtil.isEmpty( up_tax.getRank() ) ) {
271                 try {
272                     tax.setRank( up_tax.getRank().toLowerCase() );
273                 }
274                 catch ( final PhyloXmlDataFormatException ex ) {
275                     tax.setRank( "" );
276                 }
277             }
278             if ( !ForesterUtil.isEmpty( up_tax.getId() ) ) {
279                 tax.setIdentifier( new Identifier( up_tax.getId(), "uniprot" ) );
280             }
281             if ( !ForesterUtil.isEmpty( up_tax.getCommonName() ) ) {
282                 tax.setCommonName( up_tax.getCommonName() );
283             }
284             if ( !ForesterUtil.isEmpty( up_tax.getSynonym() ) && !tax.getSynonyms().contains( up_tax.getSynonym() ) ) {
285                 tax.getSynonyms().add( up_tax.getSynonym() );
286             }
287             if ( up_tax.getLineage() != null ) {
288                 tax.setLineage( new ArrayList<String>() );
289                 for( final String lin : up_tax.getLineage() ) {
290                     if ( !ForesterUtil.isEmpty( lin ) ) {
291                         tax.getLineage().add( lin );
292                     }
293                 }
294             }
295         }
296         if ( ForesterUtil.isEmpty( tax.getLineage() ) ) {
297             tax.setLineage( new ArrayList<String>() );
298             for( final String lin : last_common_lineage ) {
299                 if ( !ForesterUtil.isEmpty( lin ) ) {
300                     tax.getLineage().add( lin );
301                 }
302             }
303         }
304         for( final PhylogenyNode desc : descs ) {
305             if ( !desc.isExternal() && desc.getNodeData().isHasTaxonomy()
306                     && desc.getNodeData().getTaxonomy().isEqual( tax ) ) {
307                 desc.getNodeData().setTaxonomy( null );
308             }
309         }
310     }
311
312     synchronized private static boolean isHasAppropriateId( final Taxonomy tax ) {
313         return ( ( tax.getIdentifier() != null ) && ( !ForesterUtil.isEmpty( tax.getIdentifier().getValue() ) && ( tax
314                 .getIdentifier().getProvider().equalsIgnoreCase( "ncbi" )
315                 || tax.getIdentifier().getProvider().equalsIgnoreCase( "uniprot" ) || tax.getIdentifier().getProvider()
316                 .equalsIgnoreCase( "uniprotkb" ) ) ) );
317     }
318
319     synchronized public static SortedSet<String> obtainDetailedTaxonomicInformation( final Phylogeny phy,
320                                                                                      final boolean delete )
321             throws IOException, AncestralTaxonomyInferenceException {
322         clearCachesIfTooLarge();
323         final SortedSet<String> not_found = new TreeSet<String>();
324         List<PhylogenyNode> not_found_external_nodes = null;
325         if ( delete ) {
326             not_found_external_nodes = new ArrayList<PhylogenyNode>();
327         }
328         for( final PhylogenyNodeIterator iter = phy.iteratorPostorder(); iter.hasNext(); ) {
329             final PhylogenyNode node = iter.next();
330             final QUERY_TYPE qt = null;
331             Taxonomy tax = null;
332             if ( node.getNodeData().isHasTaxonomy() ) {
333                 tax = node.getNodeData().getTaxonomy();
334             }
335             else if ( node.isExternal() ) {
336                 if ( !ForesterUtil.isEmpty( node.getName() ) ) {
337                     not_found.add( node.getName() );
338                 }
339                 else {
340                     not_found.add( node.toString() );
341                 }
342                 if ( delete ) {
343                     not_found_external_nodes.add( node );
344                 }
345             }
346             UniProtTaxonomy uniprot_tax = null;
347             if ( ( tax != null )
348                     && ( isHasAppropriateId( tax ) || !ForesterUtil.isEmpty( tax.getScientificName() )
349                             || !ForesterUtil.isEmpty( tax.getTaxonomyCode() ) || !ForesterUtil.isEmpty( tax
350                             .getCommonName() ) ) ) {
351                 uniprot_tax = obtainUniProtTaxonomy( tax, null, qt );
352                 if ( uniprot_tax != null ) {
353                     updateTaxonomy( qt, node, tax, uniprot_tax );
354                 }
355                 else {
356                     not_found.add( tax.toString() );
357                     if ( delete && node.isExternal() ) {
358                         not_found_external_nodes.add( node );
359                     }
360                 }
361             }
362         }
363         if ( delete ) {
364             for( final PhylogenyNode node : not_found_external_nodes ) {
365                 phy.deleteSubtree( node, true );
366             }
367             phy.externalNodesHaveChanged();
368             phy.hashIDs();
369             phy.recalculateNumberOfExternalDescendants( true );
370         }
371         return not_found;
372     }
373
374     synchronized private static UniProtTaxonomy obtainUniProtTaxonomy( final Taxonomy tax, Object query, QUERY_TYPE qt )
375             throws IOException, AncestralTaxonomyInferenceException {
376         if ( isHasAppropriateId( tax ) ) {
377             query = tax.getIdentifier().getValue();
378             qt = QUERY_TYPE.ID;
379             return getTaxonomies( getIdTaxCacheMap(), query, qt );
380         }
381         else if ( !ForesterUtil.isEmpty( tax.getScientificName() ) ) {
382             if ( !ForesterUtil.isEmpty( tax.getLineage() ) ) {
383                 query = tax.getLineage();
384                 qt = QUERY_TYPE.LIN;
385                 return getTaxonomies( getLineageTaxCacheMap(), query, qt );
386             }
387             else {
388                 query = tax.getScientificName();
389                 qt = QUERY_TYPE.SN;
390                 return getTaxonomies( getSnTaxCacheMap(), query, qt );
391             }
392         }
393         else if ( !ForesterUtil.isEmpty( tax.getTaxonomyCode() ) ) {
394             query = tax.getTaxonomyCode();
395             qt = QUERY_TYPE.CODE;
396             return getTaxonomies( getCodeTaxCacheMap(), query, qt );
397         }
398         else {
399             query = tax.getCommonName();
400             qt = QUERY_TYPE.CN;
401             return getTaxonomies( getCnTaxCacheMap(), query, qt );
402         }
403     }
404
405     synchronized private static UniProtTaxonomy obtainUniProtTaxonomyFromLineage( final List<String> lineage )
406             throws AncestralTaxonomyInferenceException, IOException {
407         final String lineage_str = ForesterUtil.stringListToString( lineage, ">" );
408         UniProtTaxonomy up_tax = null;
409         if ( getLineageTaxCacheMap().containsKey( lineage_str ) ) {
410             up_tax = getLineageTaxCacheMap().get( lineage_str ).copy();
411         }
412         else {
413             final List<UniProtTaxonomy> up_taxonomies = getTaxonomiesFromScientificName( lineage
414                     .get( lineage.size() - 1 ) );
415             if ( ( up_taxonomies != null ) && ( up_taxonomies.size() > 0 ) ) {
416                 for( final UniProtTaxonomy up_taxonomy : up_taxonomies ) {
417                     boolean match = true;
418                     I: for( int i = 0; i < lineage.size(); ++i ) {
419                         if ( !lineage.get( i ).equalsIgnoreCase( up_taxonomy.getLineage().get( i ) ) ) {
420                             match = false;
421                             break I;
422                         }
423                     }
424                     if ( match ) {
425                         if ( up_tax != null ) {
426                             throw new AncestralTaxonomyInferenceException( "lineage \""
427                                     + ForesterUtil.stringListToString( lineage, " > " ) + "\" is not unique" );
428                         }
429                         up_tax = up_taxonomy;
430                     }
431                 }
432                 if ( up_tax == null ) {
433                     throw new AncestralTaxonomyInferenceException( "lineage \""
434                             + ForesterUtil.stringListToString( lineage, " > " ) + "\" not found" );
435                 }
436                 getLineageTaxCacheMap().put( lineage_str, up_tax );
437                 if ( !ForesterUtil.isEmpty( up_tax.getScientificName() ) ) {
438                     getSnTaxCacheMap().put( up_tax.getScientificName(), up_tax );
439                 }
440                 if ( !ForesterUtil.isEmpty( up_tax.getCode() ) ) {
441                     getCodeTaxCacheMap().put( up_tax.getCode(), up_tax );
442                 }
443                 if ( !ForesterUtil.isEmpty( up_tax.getCommonName() ) ) {
444                     getCnTaxCacheMap().put( up_tax.getCommonName(), up_tax );
445                 }
446                 if ( !ForesterUtil.isEmpty( up_tax.getId() ) ) {
447                     getIdTaxCacheMap().put( up_tax.getId(), up_tax );
448                 }
449             }
450         }
451         return up_tax;
452     }
453
454     synchronized private static void updateTaxonomy( final QUERY_TYPE qt,
455                                                      final PhylogenyNode node,
456                                                      final Taxonomy tax,
457                                                      final UniProtTaxonomy up_tax ) {
458         if ( ( qt != QUERY_TYPE.SN ) && !ForesterUtil.isEmpty( up_tax.getScientificName() )
459                 && ForesterUtil.isEmpty( tax.getScientificName() ) ) {
460             tax.setScientificName( up_tax.getScientificName() );
461         }
462         if ( ( qt != QUERY_TYPE.CODE ) && !ForesterUtil.isEmpty( up_tax.getCode() )
463                 && ForesterUtil.isEmpty( tax.getTaxonomyCode() ) ) {
464             tax.setTaxonomyCode( up_tax.getCode() );
465         }
466         if ( ( qt != QUERY_TYPE.CN ) && !ForesterUtil.isEmpty( up_tax.getCommonName() )
467                 && ForesterUtil.isEmpty( tax.getCommonName() ) ) {
468             tax.setCommonName( up_tax.getCommonName() );
469         }
470         if ( !ForesterUtil.isEmpty( up_tax.getSynonym() ) && !tax.getSynonyms().contains( up_tax.getSynonym() ) ) {
471             tax.getSynonyms().add( up_tax.getSynonym() );
472         }
473         if ( !ForesterUtil.isEmpty( up_tax.getRank() ) && ForesterUtil.isEmpty( tax.getRank() ) ) {
474             try {
475                 tax.setRank( up_tax.getRank().toLowerCase() );
476             }
477             catch ( final PhyloXmlDataFormatException ex ) {
478                 tax.setRank( "" );
479             }
480         }
481         if ( ( qt != QUERY_TYPE.ID ) && !ForesterUtil.isEmpty( up_tax.getId() ) && ( tax.getIdentifier() == null ) ) {
482             tax.setIdentifier( new Identifier( up_tax.getId(), "uniprot" ) );
483         }
484         if ( up_tax.getLineage() != null ) {
485             tax.setLineage( new ArrayList<String>() );
486             for( final String lin : up_tax.getLineage() ) {
487                 if ( !ForesterUtil.isEmpty( lin ) ) {
488                     tax.getLineage().add( lin );
489                 }
490             }
491         }
492     }
493
494     private enum QUERY_TYPE {
495         CODE, SN, CN, ID, LIN;
496     }
497 }