in progress...
[jalview.git] / forester / java / src / org / forester / application / cladinator.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2017 Christian M. Zmasek
6 // Copyright (C) 2017 J. Craig Venter Institute
7 // All rights reserved
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 // Contact: phyloxml @ gmail . com
24 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
25
26 package org.forester.application;
27
28 import java.io.File;
29 import java.io.IOException;
30 import java.text.DecimalFormat;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.SortedMap;
34 import java.util.regex.Pattern;
35 import java.util.regex.PatternSyntaxException;
36
37 import org.forester.clade_analysis.AnalysisMulti;
38 import org.forester.clade_analysis.Prefix;
39 import org.forester.clade_analysis.ResultMulti;
40 import org.forester.io.parsers.PhylogenyParser;
41 import org.forester.io.parsers.util.ParserUtils;
42 import org.forester.phylogeny.Phylogeny;
43 import org.forester.phylogeny.factories.ParserBasedPhylogenyFactory;
44 import org.forester.phylogeny.factories.PhylogenyFactory;
45 import org.forester.util.BasicTable;
46 import org.forester.util.BasicTableParser;
47 import org.forester.util.CommandLineArguments;
48 import org.forester.util.EasyWriter;
49 import org.forester.util.ForesterUtil;
50 import org.forester.util.UserException;
51
52 public final class cladinator {
53
54     final static private String        PRG_NAME                             = "cladinator";
55     final static private String        PRG_VERSION                          = "1.02";
56     final static private String        PRG_DATE                             = "170912";
57     final static private String        PRG_DESC                             = "clades within clades of annotated labels -- analysis of pplacer-type outputs";
58     final static private String        E_MAIL                               = "phyloxml@gmail.com";
59     final static private String        WWW                                  = "https://sites.google.com/site/cmzmasek/home/software/forester";
60     final static private String        HELP_OPTION_1                        = "help";
61     final static private String        HELP_OPTION_2                        = "h";
62     final static private String        SEP_OPTION                           = "s";
63     final static private String        QUERY_PATTERN_OPTION                 = "q";
64     final static private String        SPECIFICS_CUTOFF_OPTION              = "c";
65     final static private String        MAPPING_FILE_OPTION                  = "m";
66     final static private String        EXTRA_PROCESSING_OPTION1             = "x";
67     final static private String        EXTRA_PROCESSING1_SEP_OPTION         = "xs";
68     final static private String        EXTRA_PROCESSING1_KEEP_EXTRA_OPTION  = "xk";
69     final static private String        VERBOSE_OPTION                       = "v";
70     final static private double        SPECIFICS_CUTOFF_DEFAULT             = 0.8;
71     final static private String        SEP_DEFAULT                          = ".";
72     final static private Pattern       QUERY_PATTERN_DEFAULT                = AnalysisMulti.DEFAULT_QUERY_PATTERN_FOR_PPLACER_TYPE;
73     final static private String        EXTRA_PROCESSING1_SEP_DEFAULT        = "|";
74     final static private boolean       EXTRA_PROCESSING1_KEEP_EXTRA_DEFAULT = false;
75     private final static DecimalFormat df                                   = new DecimalFormat( "0.0#######" );
76
77     public static void main( final String args[] ) {
78         try {
79             ForesterUtil.printProgramInformation( PRG_NAME,
80                                                   PRG_DESC,
81                                                   PRG_VERSION,
82                                                   PRG_DATE,
83                                                   E_MAIL,
84                                                   WWW,
85                                                   ForesterUtil.getForesterLibraryInformation() );
86             CommandLineArguments cla = null;
87             try {
88                 cla = new CommandLineArguments( args );
89             }
90             catch ( final Exception e ) {
91                 ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
92             }
93             if ( cla.isOptionSet( HELP_OPTION_1 ) || cla.isOptionSet( HELP_OPTION_2 ) ) {
94                 System.out.println();
95                 print_help();
96                 System.exit( 0 );
97             }
98             if ( ( cla.getNumberOfNames() != 1 ) && ( cla.getNumberOfNames() != 2 ) ) {
99                 print_help();
100                 System.exit( -1 );
101             }
102             final List<String> allowed_options = new ArrayList<>();
103             allowed_options.add( SEP_OPTION );
104             allowed_options.add( QUERY_PATTERN_OPTION );
105             allowed_options.add( SPECIFICS_CUTOFF_OPTION );
106             allowed_options.add( MAPPING_FILE_OPTION );
107             allowed_options.add( EXTRA_PROCESSING_OPTION1 );
108             allowed_options.add( EXTRA_PROCESSING1_SEP_OPTION );
109             allowed_options.add( EXTRA_PROCESSING1_KEEP_EXTRA_OPTION );
110             allowed_options.add( VERBOSE_OPTION );
111             final String dissallowed_options = cla.validateAllowedOptionsAsString( allowed_options );
112             if ( dissallowed_options.length() > 0 ) {
113                 ForesterUtil.fatalError( PRG_NAME, "unknown option(s): " + dissallowed_options );
114             }
115             double cutoff_specifics = SPECIFICS_CUTOFF_DEFAULT;
116             if ( cla.isOptionSet( SPECIFICS_CUTOFF_OPTION ) ) {
117                 if ( cla.isOptionValueSet( SPECIFICS_CUTOFF_OPTION ) ) {
118                     cutoff_specifics = cla.getOptionValueAsDouble( SPECIFICS_CUTOFF_OPTION );
119                     if ( cutoff_specifics < 0 ) {
120                         ForesterUtil.fatalError( PRG_NAME, "cutoff cannot be negative" );
121                     }
122                 }
123                 else {
124                     ForesterUtil.fatalError( PRG_NAME, "no value for cutoff for specifics" );
125                 }
126             }
127             String separator = SEP_DEFAULT;
128             if ( cla.isOptionSet( SEP_OPTION ) ) {
129                 if ( cla.isOptionValueSet( SEP_OPTION ) ) {
130                     separator = cla.getOptionValue( SEP_OPTION );
131                 }
132                 else {
133                     ForesterUtil.fatalError( PRG_NAME, "no value for separator option" );
134                 }
135             }
136             Pattern compiled_query_str = null;
137             if ( cla.isOptionSet( QUERY_PATTERN_OPTION ) ) {
138                 if ( cla.isOptionValueSet( QUERY_PATTERN_OPTION ) ) {
139                     final String query_str = cla.getOptionValue( QUERY_PATTERN_OPTION );
140                     try {
141                         compiled_query_str = Pattern.compile( query_str );
142                     }
143                     catch ( final PatternSyntaxException e ) {
144                         ForesterUtil.fatalError( PRG_NAME, "error in regular expression: " + e.getMessage() );
145                     }
146                 }
147                 else {
148                     ForesterUtil.fatalError( PRG_NAME, "no value for query pattern option" );
149                 }
150             }
151             File mapping_file = null;
152             if ( cla.isOptionSet( MAPPING_FILE_OPTION ) ) {
153                 if ( cla.isOptionValueSet( MAPPING_FILE_OPTION ) ) {
154                     final String mapping_file_str = cla.getOptionValue( MAPPING_FILE_OPTION );
155                     final String error = ForesterUtil.isReadableFile( mapping_file_str );
156                     if ( !ForesterUtil.isEmpty( error ) ) {
157                         ForesterUtil.fatalError( PRG_NAME, error );
158                     }
159                     mapping_file = new File( mapping_file_str );
160                 }
161                 else {
162                     ForesterUtil.fatalError( PRG_NAME, "no value for mapping file" );
163                 }
164             }
165             final Pattern pattern = ( compiled_query_str != null ) ? compiled_query_str : QUERY_PATTERN_DEFAULT;
166             final File intreefile = cla.getFile( 0 );
167             final String error_intreefile = ForesterUtil.isReadableFile( intreefile );
168             if ( !ForesterUtil.isEmpty( error_intreefile ) ) {
169                 ForesterUtil.fatalError( PRG_NAME, error_intreefile );
170             }
171             final File outtablefile;
172             if ( cla.getNumberOfNames() > 1 ) {
173                 outtablefile = cla.getFile( 1 );
174                 final String error_outtablefile = ForesterUtil.isWritableFile( outtablefile );
175                 if ( !ForesterUtil.isEmpty( error_outtablefile ) ) {
176                     ForesterUtil.fatalError( PRG_NAME, error_outtablefile );
177                 }
178             }
179             else {
180                 outtablefile = null;
181             }
182             final BasicTable<String> t;
183             final SortedMap<String, String> map;
184             if ( mapping_file != null ) {
185                 t = BasicTableParser.parse( mapping_file, '\t' );
186                 if ( t.getNumberOfColumns() != 2 ) {
187                     ForesterUtil.fatalError( PRG_NAME,
188                                              "mapping file needs to have 2 tab-separated columns, not "
189                                                      + t.getNumberOfColumns() );
190                 }
191                 map = t.getColumnsAsMap( 0, 1 );
192             }
193             else {
194                 t = null;
195                 map = null;
196             }
197             final boolean extra_processing1;
198             if ( cla.isOptionSet( EXTRA_PROCESSING_OPTION1 ) ) {
199                 extra_processing1 = true;
200             }
201             else {
202                 extra_processing1 = false;
203             }
204             String extra_processing1_sep = EXTRA_PROCESSING1_SEP_DEFAULT;
205             if ( cla.isOptionSet( EXTRA_PROCESSING1_SEP_OPTION ) ) {
206                 if ( !extra_processing1 ) {
207                     ForesterUtil.fatalError( PRG_NAME,
208                                              "extra processing is not enabled, cannot set -"
209                                                      + EXTRA_PROCESSING1_SEP_OPTION + " option" );
210                 }
211                 if ( cla.isOptionValueSet( EXTRA_PROCESSING1_SEP_OPTION ) ) {
212                     extra_processing1_sep = cla.getOptionValue( EXTRA_PROCESSING1_SEP_OPTION );
213                 }
214                 else {
215                     ForesterUtil.fatalError( PRG_NAME, "no value for extra processing separator" );
216                 }
217             }
218             if ( extra_processing1_sep != null && extra_processing1_sep.equals( separator ) ) {
219                 ForesterUtil.fatalError( PRG_NAME,
220                                          "extra processing separator must not be the same the annotation-separator" );
221             }
222             boolean extra_processing1_keep = EXTRA_PROCESSING1_KEEP_EXTRA_DEFAULT;
223             if ( cla.isOptionSet( EXTRA_PROCESSING1_KEEP_EXTRA_OPTION ) ) {
224                 if ( !extra_processing1 ) {
225                     ForesterUtil.fatalError( PRG_NAME,
226                                              "extra processing is not enabled, cannot set -"
227                                                      + EXTRA_PROCESSING1_KEEP_EXTRA_OPTION + " option" );
228                 }
229                 extra_processing1_keep = true;
230             }
231             final boolean verbose;
232             if ( cla.isOptionSet( VERBOSE_OPTION ) ) {
233                 verbose = true;
234             }
235             else {
236                 verbose = false;
237             }
238             System.out.println( "Input tree                 : " + intreefile );
239             System.out.println( "Specific-hit support cutoff: " + cutoff_specifics );
240             if ( mapping_file != null ) {
241                 System.out.println( "Mapping file               : " + mapping_file + " (" + t.getNumberOfRows()
242                         + " rows)" );
243             }
244             System.out.println( "Annotation-separator       : " + separator );
245             System.out.println( "Query pattern              : " + pattern );
246             System.out.println( "Extra processing           : " + extra_processing1 );
247             if ( extra_processing1 ) {
248                 System.out.println( "Extra processing separator : " + extra_processing1_sep );
249                 System.out.println( "Keep extra annotations     : " + extra_processing1_keep );
250             }
251             if ( outtablefile != null ) {
252                 System.out.println( "Output table               : " + outtablefile );
253             }
254             Phylogeny p = null;
255             try {
256                 final PhylogenyFactory factory = ParserBasedPhylogenyFactory.getInstance();
257                 final PhylogenyParser pp = ParserUtils.createParserDependingOnFileType( intreefile, true );
258                 p = factory.create( intreefile, pp )[ 0 ];
259             }
260             catch ( final IOException e ) {
261                 ForesterUtil.fatalError( PRG_NAME, "Could not read \"" + intreefile + "\" [" + e.getMessage() + "]" );
262                 System.exit( -1 );
263             }
264             System.out.println( "Ext. nodes in input tree   : " + p.getNumberOfExternalNodes() );
265             if ( map != null ) {
266                 AnalysisMulti.performMapping( pattern, map, p, verbose );
267             }
268             if ( extra_processing1 ) {
269                 AnalysisMulti.performExtraProcessing1( pattern,
270                                                        p,
271                                                        extra_processing1_sep,
272                                                        extra_processing1_keep,
273                                                        separator,
274                                                        verbose );
275             }
276             final ResultMulti res = AnalysisMulti.execute( p, pattern, separator, cutoff_specifics );
277             printResult( res );
278             if ( outtablefile != null ) {
279                 writeResultToTable( res, outtablefile );
280             }
281         }
282         catch ( final UserException e ) {
283             ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
284         }
285         catch ( final IOException e ) {
286             ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
287         }
288         catch ( final Exception e ) {
289             e.printStackTrace();
290             ForesterUtil.fatalError( PRG_NAME, "Unexpected errror!" );
291         }
292     }
293
294     private final static void printResult( final ResultMulti res ) {
295         System.out.println();
296         System.out.println( "Result:" );
297         System.out.println();
298         if ( ( res.getAllMultiHitPrefixes() == null ) | ( res.getAllMultiHitPrefixes().size() < 1 ) ) {
299             System.out.println( "No match to query pattern!" );
300         }
301         else {
302             System.out.println( "Matching Clade(s):" );
303             for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
304                 System.out.println( prefix );
305             }
306             if ( res.isHasSpecificMultiHitsPrefixes() ) {
307                 System.out.println();
308                 System.out.println( "Specific-hit(s):" );
309                 for( final Prefix prefix : res.getSpecificMultiHitPrefixes() ) {
310                     System.out.println( prefix );
311                 }
312                 System.out.println();
313                 System.out.println( "Matching Clade(s) with Specific-hit(s):" );
314                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
315                     System.out.println( prefix );
316                     for( final Prefix spec : res.getSpecificMultiHitPrefixes() ) {
317                         if ( spec.getPrefix().startsWith( prefix.getPrefix() ) ) {
318                             System.out.println( "    " + spec );
319                         }
320                     }
321                 }
322             }
323             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesDown() ) ) {
324                 System.out.println();
325                 System.out.println( "Matching Down-tree Bracketing Clade(s):" );
326                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesDown() ) {
327                     System.out.println( prefix );
328                 }
329             }
330             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesUp() ) ) {
331                 System.out.println();
332                 System.out.println( "Matching Up-tree Bracketing Clade(s):" );
333                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesUp() ) {
334                     System.out.println( prefix );
335                 }
336             }
337         }
338         System.out.println();
339     }
340
341     private final static void writeResultToTable( final ResultMulti res, final File outtablefile ) throws IOException {
342         final EasyWriter w = ForesterUtil.createEasyWriter( outtablefile );
343         if ( ( res.getAllMultiHitPrefixes() == null ) | ( res.getAllMultiHitPrefixes().size() < 1 ) ) {
344             w.println( "No match to query pattern!" );
345         }
346         else {
347             for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
348                 w.print( "Matching Clades" );
349                 w.print( "\t" );
350                 w.print( prefix.getPrefix() );
351                 w.print( "\t" );
352                 w.print( df.format( prefix.getConfidence() ) );
353                 w.println();
354             }
355             if ( res.isHasSpecificMultiHitsPrefixes() ) {
356                 for( final Prefix prefix : res.getSpecificMultiHitPrefixes() ) {
357                     w.print( "Specific-hits" );
358                     w.print( "\t" );
359                     w.print( prefix.getPrefix() );
360                     w.print( "\t" );
361                     w.print( df.format( prefix.getConfidence() ) );
362                     w.println();
363                 }
364             }
365             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesDown() ) ) {
366                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesDown() ) {
367                     w.print( "Matching Down-tree Bracketing Clades" );
368                     w.print( "\t" );
369                     w.print( prefix.getPrefix() );
370                     w.print( "\t" );
371                     w.print( df.format( prefix.getConfidence() ) );
372                     w.println();
373                 }
374             }
375             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesUp() ) ) {
376                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesUp() ) {
377                     w.print( "Matching Up-tree Bracketing Clades" );
378                     w.print( "\t" );
379                     w.print( prefix.getPrefix() );
380                     w.print( "\t" );
381                     w.print( df.format( prefix.getConfidence() ) );
382                     w.println();
383                 }
384             }
385         }
386         w.flush();
387         w.close();
388     }
389
390     private final static void print_help() {
391         System.out.println( "Usage:" );
392         System.out.println();
393         System.out.println( PRG_NAME + " [options] <input tree file> [output table file]" );
394         System.out.println();
395         System.out.println( " options:" );
396         System.out.println( "  -" + SPECIFICS_CUTOFF_OPTION
397                 + "=<double>       : the cutoff for \"specific-hit\" support values (default: "
398                 + SPECIFICS_CUTOFF_DEFAULT + ")" );
399         System.out.println( "  -" + SEP_OPTION + "=<separator>    : the annotation-separator to be used (default: "
400                 + SEP_DEFAULT + ")" );
401         System.out.println( "  -" + MAPPING_FILE_OPTION
402                 + "=<mapping table>: to map node names to appropriate annotations (tab-separated, two columns) (default: no mapping)" );
403         System.out.println( "  -" + QUERY_PATTERN_OPTION
404                 + "=<query pattern>: the regular expression for the query (default: \"" + QUERY_PATTERN_DEFAULT
405                 + "\" for pplacer output)" );
406         System.out.println( "  -" + EXTRA_PROCESSING_OPTION1
407                 + "                : to enable extra processing of annotations (e.g. \"Q16611|A.1.1\" becomes \"A.1.1\")" );
408         System.out.println( "  -" + EXTRA_PROCESSING1_SEP_OPTION
409                 + "=<separator>   : the separator for extra annotations (default: \"" + EXTRA_PROCESSING1_SEP_DEFAULT
410                 + "\")" );
411         System.out.println( "  -" + EXTRA_PROCESSING1_KEEP_EXTRA_OPTION
412                 + "               : to keep extra annotations (e.g. \"Q16611|A.1.1\" becomes \"A.1.1.Q16611\")" );
413         System.out.println( "  -" + VERBOSE_OPTION + "                : verbose" );
414         System.out.println();
415         System.out.println( "Examples:" );
416         System.out.println();
417         System.out.println( " " + PRG_NAME + " my_tree.nh result.tsv" );
418         System.out.println( " " + PRG_NAME + " -c=0.5 -s=. my_tree.nh result.tsv" );
419         System.out.println( " " + PRG_NAME + " -c=0.9 -s=_ -m=map.tsv my_tree.nh result.tsv" );
420         System.out.println( " " + PRG_NAME + " -x -xs=& -xk my_tree.nh result.tsv" );
421         System.out.println( " " + PRG_NAME + " -x -xs=\"|\" my_tree.nh result.tsv" );
422         System.out.println();
423     }
424 }