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 * Created on Nov 2, 2009
021 * Author: Andreas Prlic
022 *
023 */
024
025package org.biojava.nbio.structure.align.ce;
026
027
028import org.biojava.nbio.structure.Atom;
029import org.biojava.nbio.structure.Structure;
030import org.biojava.nbio.structure.StructureException;
031import org.biojava.nbio.structure.StructureTools;
032import org.biojava.nbio.structure.align.StructureAlignment;
033import org.biojava.nbio.structure.align.model.AFPChain;
034import org.biojava.nbio.structure.align.util.*;
035import org.biojava.nbio.structure.align.xml.AFPChainXMLConverter;
036import org.biojava.nbio.structure.io.LocalPDBDirectory.FetchBehavior;
037import org.biojava.nbio.structure.io.PDBFileReader;
038
039import java.beans.Introspector;
040import java.io.*;
041import java.lang.reflect.InvocationTargetException;
042import java.net.URL;
043import java.util.ArrayList;
044import java.util.Iterator;
045import java.util.List;
046import java.util.stream.Stream;
047
048
049/**
050 * Base class for a new structure alignment CLI.
051 *
052 * <p>To add a new StructureAlignment with a CLI similar to CE or FATCAT,
053 * <ol>
054 * <li>Implement StructureAlignment with the main algorithm
055 * <li>Implement ConfigStrucAligParams. This provides the parameters for the GUI.
056 * <li>Subclass StartupParameters (can be an inner class) with the same parameters
057 *     described in the ConfigStrucAligParams.
058 * <li>Subclass AbstractUserArgumentProcessor, with the getStartupParams() method
059 *     returning a fresh instance of the custom StartupParameters
060 * <li>Implement the getParameters() method to copy values from the StartupParameters
061 *     to the ConfigStrucAligParams.
062 * </ol>
063 *
064 * <p>Note that reflection is used in a number of places, so the CLI argument names
065 * must match the get/set functions in both parameter beans.
066 * <ul>
067 * <li>AlignmentGUI automatically takes parameter names and types from the
068 *     ConfigStrucAligParams
069 * <li>AbstractUserArgumentProcessor also takes parameter names and help descriptions
070 *     from ConfigStrucAligParams, but it saves arguments to the StartupParameter
071 *     bean.
072 * </ul>
073 * This means that both beans should be kept in sync.
074 *
075 * @author Andreas
076 * @author Spencer
077 *
078 */
079public abstract class AbstractUserArgumentProcessor implements UserArgumentProcessor {
080
081        public static String newline = System.getProperty("line.separator");
082
083        protected StartupParameters params ;
084
085        public static final List<String> mandatoryArgs= new ArrayList<String>();
086
087        protected AbstractUserArgumentProcessor(){
088                params = getStartupParametersInstance();
089        }
090
091        /**
092         * StartupParameters is a bean to store all the possible
093         * command line parameters.
094         *
095         * The `StartupParameter` class contains the basic set of CLI parameters
096         * common to all `StructureAligmnent`s. This method should return a subclass
097         * of StartupParameters which has been extended to store values for all
098         * additional parameters.
099         * @return A new instance of the correct StartupParameters subclass
100         */
101        protected abstract StartupParameters getStartupParametersInstance();
102
103        public abstract StructureAlignment getAlgorithm();
104        public abstract Object getParameters();
105        public abstract String getDbSearchLegend();
106
107        @Override
108        public void process(String[] argv){
109
110                printAboutMe();
111
112//              if(argv.length == 0 ) {
113//                      System.out.println(printHelp());
114//                      return;
115//              }
116
117                for (int i = 0 ; i < argv.length; i++){
118                        String arg   = argv[i];
119
120                        // help string
121                        if(arg.equalsIgnoreCase("-h") || arg.equalsIgnoreCase("-help")
122                                        || arg.equalsIgnoreCase("--help") )
123                        {
124                                System.out.println(printHelp());
125                                return;
126                        }
127                        // version
128                        if(arg.equalsIgnoreCase("-version") || arg.equalsIgnoreCase("--version")) {
129                                StructureAlignment alg = getAlgorithm();
130                                System.out.println(alg.getAlgorithmName() + " v." + alg.getVersion() );
131                                return;
132                        }
133
134                        String value = null;
135                        if ( i < argv.length -1)
136                                value = argv[i+1];
137
138                        // if value starts with - then the arg does not have a value.
139                        if (value != null && value.startsWith("-"))
140                                value = null;
141                        else
142                                i++;
143
144
145                        String[] tmp = {arg,value};
146
147                        //System.out.println(arg + " " + value);
148
149                        try {
150
151                                CliTools.configureBean(params, tmp);
152
153                        } catch (ConfigurationException e){
154                                System.err.println("Error: "+e.getLocalizedMessage());
155                                System.exit(1); return;
156                        }
157                }
158
159                if ( params.getPdbFilePath() != null){
160                        System.setProperty(UserConfiguration.PDB_DIR,params.getPdbFilePath());
161                }
162
163                if ( params.getCacheFilePath() != null){
164                        System.setProperty(UserConfiguration.PDB_CACHE_DIR,params.getCacheFilePath());
165                }
166
167                if ( params.isShowMenu()){
168                        System.err.println("showing menu...");
169                        try {
170                                GuiWrapper.showAlignmentGUI();
171                        } catch (Exception e){
172                                System.err.println(e.getMessage());
173                                e.printStackTrace();
174                        }
175                }
176
177                String pdb1  = params.getPdb1();
178                String file1 = params.getFile1();
179
180
181                try {
182                        if (pdb1 != null || file1 != null){
183                                runPairwise();
184                                return;
185                        }
186
187
188                } catch (ConfigurationException e) {
189                        System.err.println(e.getLocalizedMessage());
190                        System.exit(1); return;
191                }
192
193                System.out.println(printHelp());
194                System.err.println("Error: insufficient arguments.");
195                System.exit(1); return;
196        }
197
198
199
200
201
202        public static void printAboutMe() {
203                try {
204                        ResourceManager about = ResourceManager.getResourceManager("about");
205
206                        String version = about.getString("project_version");
207                        String build   = about.getString("build");
208
209                        System.out.println("Protein Comparison Tool " + version + " " + build);
210                } catch (Exception e){
211                        e.printStackTrace();
212                }
213
214
215        }
216
217        private void runAlignPairs(AtomCache cache, String alignPairs,
218                        String outputFile) {
219                try {
220                        File f = new File(alignPairs);
221
222                        BufferedReader is = new BufferedReader (new InputStreamReader(new FileInputStream(f)));
223
224                        BufferedWriter out = new BufferedWriter(new FileWriter(outputFile, true));
225
226                        StructureAlignment algorithm =  getAlgorithm();
227
228                        String header = "# algorithm:" + algorithm.getAlgorithmName();
229                        out.write(header);
230                        out.write(newline);
231
232                        out.write("#Legend: " + newline );
233                        String legend = getDbSearchLegend();
234                        out.write(legend + newline );
235                        System.out.println(legend);
236                        String line = null;
237                        while ( (line = is.readLine()) != null){
238                                if ( line.startsWith("#"))
239                                        continue;
240
241                                String[] spl = line.split(" ");
242
243                                if ( spl.length != 2) {
244                                        System.err.println("wrongly formattted line. Expected format: 4hhb.A 4hhb.B but found " + line);
245                                        continue;
246                                }
247
248                                String pdb1 = spl[0];
249                                String pdb2 = spl[1];
250
251
252                                Structure structure1 = cache.getStructure(pdb1);
253                                Structure structure2 = cache.getStructure(pdb2);
254
255                                Atom[] ca1;
256                                Atom[] ca2;
257
258
259                                ca1 = StructureTools.getRepresentativeAtomArray(structure1);
260                                ca2 = StructureTools.getRepresentativeAtomArray(structure2);
261
262                                Object jparams = getParameters();
263
264                                AFPChain afpChain;
265
266                                afpChain = algorithm.align(ca1, ca2, jparams);
267                                afpChain.setName1(pdb1);
268                                afpChain.setName2(pdb2);
269
270                                String result = getDbSearchResult(afpChain);
271                                out.write(result);
272                                System.out.print(result);
273
274                                checkWriteFile(afpChain,ca1,ca2,true);
275                        }
276
277                        out.close();
278                        is.close();
279                } catch(Exception e){
280                        e.printStackTrace();
281                }
282        }
283
284
285        private void runPairwise() throws ConfigurationException{
286
287                String name1 = params.getPdb1();
288                String file1 = params.getFile1();
289
290                if ( name1 == null && file1 == null){
291                        throw new ConfigurationException("You did not specify the -pdb1 or -file1 parameter. Can not find query PDB id for alignment.");
292                }
293
294                if ( file1 == null) {
295                        if ( name1.length() < 4) {
296                                throw new ConfigurationException("-pdb1 does not look like a PDB ID. Please specify PDB code or PDB.chainName.");
297                        }
298                }
299
300                String name2 = params.getPdb2();
301                String file2 = params.getFile2();
302                if ( name2 == null && file2 == null ){
303                        throw new ConfigurationException("You did not specify the -pdb2 or -file2 parameter. Can not find target PDB id for alignment.");
304                }
305
306                if ( file2 == null ){
307                        if ( name2.length() < 4) {
308                                throw new ConfigurationException("-pdb2 does not look like a PDB ID. Please specify PDB code or PDB.chainName.");
309                        }
310                }
311
312                // first load two example structures
313
314                Structure structure1 = null;
315                Structure structure2 = null;
316
317                String path = params.getPdbFilePath();
318
319                if ( file1 == null || file2 == null) {
320                        if ( path == null){
321                                UserConfiguration c = new UserConfiguration();
322                                path = c.getPdbFilePath();
323                                System.err.println("You did not specify the -pdbFilePath parameter. Defaulting to "+path+".");
324                        }
325
326                        AtomCache cache = new AtomCache(path, path);
327                        if(params.isAutoFetch()) {
328                                cache.setFetchBehavior(FetchBehavior.DEFAULT);
329                        } else {
330                                cache.setFetchBehavior(FetchBehavior.LOCAL_ONLY);
331                        }
332                        structure1 = getStructure(cache, name1, file1);
333                        structure2 = getStructure(cache, name2, file2);
334                } else {
335
336                        structure1 = getStructure(null, name1, file1);
337                        structure2 = getStructure(null, name2, file2);
338                }
339
340
341
342                if ( structure1 == null){
343                        System.err.println("structure 1 is null, can't run alignment.");
344                        System.exit(1); return;
345                }
346
347                if ( structure2 == null){
348                        System.err.println("structure 2 is null, can't run alignment.");
349                        System.exit(1); return;
350                }
351
352                if ( name1 == null) {
353                        name1 = structure1.getName();
354                }
355                if ( name2 == null) {
356                        name2 = structure2.getName();
357                }
358
359                //                   default:      new:
360                // 1buz - 1ali : time: 8.3s eqr 68 rmsd 3.1 score 161 | time 6.4 eqr 58 rmsd 3.0 scre 168
361                // 5pti - 1tap : time: 6.2s eqr 48 rmsd 2.67 score 164 | time 5.2 eqr 49 rmsd 2.9 score 151
362                // 1cdg - 8tim
363                // 1jbe - 1ord
364                // 1nbw.A - 1kid
365                // 1t4y - 1rp5
366
367
368                try {
369
370                        Atom[] ca1;
371                        Atom[] ca2;
372
373                        ca1 = StructureTools.getRepresentativeAtomArray(structure1);
374                        ca2 = StructureTools.getRepresentativeAtomArray(structure2);
375
376                        StructureAlignment algorithm =  getAlgorithm();
377                        Object jparams = getParameters();
378
379                        AFPChain afpChain;
380
381                        afpChain = algorithm.align(ca1, ca2, jparams);
382                        afpChain.setName1(name1);
383                        afpChain.setName2(name2);
384
385                        if ( params.isShow3d()){
386
387                                if (! GuiWrapper.isGuiModuleInstalled()) {
388                                        System.err.println("The biojava-structure-gui module is not installed. Please install!");
389                                } else {
390
391                                        try {
392
393                                                Object jmol = GuiWrapper.display(afpChain,ca1,ca2);
394
395                                                GuiWrapper.showAlignmentImage(afpChain, ca1,ca2,jmol);
396
397                                        } catch (Exception e){
398
399                                                System.err.println(e.getMessage());
400                                                e.printStackTrace();
401                                        }
402                                        //StructureAlignmentJmol jmol = algorithm.display(afpChain,ca1,ca2,hetatms1, nucs1, hetatms2, nucs2);
403
404                                        //String result = afpChain.toFatcat(ca1, ca2);
405                                        //String rot = afpChain.toRotMat();
406
407                                        //DisplayAFP.showAlignmentImage(afpChain, ca1,ca2,jmol);
408                                }
409                        }
410
411
412                        checkWriteFile(afpChain,ca1, ca2, false);
413
414
415
416
417                        if ( params.isPrintXML()){
418                                String fatcatXML = AFPChainXMLConverter.toXML(afpChain,ca1,ca2);
419                                System.out.println(fatcatXML);
420                        }
421                        if ( params.isPrintFatCat()) {
422                                // default output is to XML on sysout...
423                                System.out.println(afpChain.toFatcat(ca1, ca2));
424                        }
425                        if ( params. isPrintCE()){
426                                System.out.println(afpChain.toCE(ca1, ca2));
427                        }
428
429                } catch (IOException e) {
430                        e.printStackTrace();
431                        System.exit(1); return;
432                } catch (ClassNotFoundException e) {
433                        e.printStackTrace();
434                        System.exit(1); return;
435                } catch (NoSuchMethodException e) {
436                        e.printStackTrace();
437                        System.exit(1); return;
438                } catch (InvocationTargetException e) {
439                        e.printStackTrace();
440                        System.exit(1); return;
441                } catch (IllegalAccessException e) {
442                        e.printStackTrace();
443                        System.exit(1); return;
444                } catch (StructureException e) {
445                        e.printStackTrace();
446                        System.exit(1); return;
447                }
448        }
449
450        /**
451         * check if the result should be written to the local file system
452         *
453         * @param afpChain
454         * @param ca1
455         * @param ca2
456         * @throws IOException If an error occurs when writing the afpChain to XML
457         * @throws ClassNotFoundException If an error occurs when invoking jmol
458         * @throws NoSuchMethodException If an error occurs when invoking jmol
459         * @throws InvocationTargetException If an error occurs when invoking jmol
460         * @throws IllegalAccessException If an error occurs when invoking jmol
461         * @throws StructureException
462         */
463        private void checkWriteFile( AFPChain afpChain, Atom[] ca1, Atom[] ca2, boolean dbsearch) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, StructureException
464                        {
465                String output = null;
466                if ( params.isOutputPDB()){
467                        if (! GuiWrapper.isGuiModuleInstalled()) {
468                                System.err.println("The biojava-structure-gui module is not installed. Please install!");
469                                output = AFPChainXMLConverter.toXML(afpChain,ca1,ca2);
470                        } else {
471
472                                Structure tmp = AFPAlignmentDisplay.createArtificalStructure(afpChain, ca1, ca2);
473                                output = "TITLE  " + afpChain.getAlgorithmName() + " " + afpChain.getVersion()  + " ";
474                                output += afpChain.getName1() + " vs. " + afpChain.getName2();
475                                output += newline;
476                                output += tmp.toPDB();
477                        }
478                } else  if ( params.getOutFile() != null) {
479                        // output by default is XML
480                        // write the XML to a file...
481                        output = AFPChainXMLConverter.toXML(afpChain,ca1,ca2);
482
483                } else if ( params.getSaveOutputDir() != null){
484                        output = AFPChainXMLConverter.toXML(afpChain,ca1,ca2);
485                }
486
487                // no output requested.
488                if ( output == null)
489                        return;
490
491                String fileName = null;
492
493                if ( dbsearch ){
494                        if ( params.getSaveOutputDir() != null) {
495
496                                // we currently don't have a naming convention for how to store results for custom files
497                                // they will be re-created on the fly
498                                if ( afpChain.getName1().startsWith("file:") || afpChain.getName2().startsWith("file:"))
499                                        return;
500                                fileName = params.getSaveOutputDir();
501                                fileName += getAutoFileName(afpChain);
502
503                        } else {
504                                return;
505                        }
506
507                        //
508                        //else {
509                        //      fileName = getAutoFileName(afpChain);
510                        //}
511                } else
512
513                        if ( params.getOutFile() != null) {
514                                fileName = params.getOutFile();
515                        }
516
517                if (fileName == null) {
518                        System.err.println("Can't write outputfile. Either provide a filename using -outFile or set -autoOutputFile to true .");
519                        System.exit(1); return;
520                }
521                //System.out.println("writing results to " + fileName + " " + params.getSaveOutputDir());
522
523                FileOutputStream out; // declare a file output object
524                PrintStream p; // declare a print stream object
525
526                // Create a new file output stream
527                out = new FileOutputStream(fileName);
528
529                // Connect print stream to the output stream
530                p = new PrintStream( out );
531
532                p.println (output);
533
534                p.close();
535        }
536
537        private String getAutoFileName(AFPChain afpChain){
538                String fileName =afpChain.getName1()+"_" + afpChain.getName2()+"_"+afpChain.getAlgorithmName();
539
540                if (params.isOutputPDB() )
541                        fileName += ".pdb";
542                else
543                        fileName += ".xml";
544                return fileName;
545        }
546
547        private Structure getStructure(AtomCache cache, String name1, String file)
548        {
549
550                PDBFileReader reader = new PDBFileReader();
551                if ( file != null ){
552                        try {
553                                // check if it is a URL:
554                                try {
555                                        URL url = new URL(file);
556                                        System.out.println(url);
557
558                                        Structure s = reader.getStructure(url);
559
560                                        return fixStructureName(s,file);
561
562                                } catch ( Exception e){
563                                        System.err.println(e.getMessage());
564                                }
565                                File f= new File(file);
566                                System.out.println("file from local " + f.getAbsolutePath());
567                                Structure s= reader.getStructure(f);
568                                return fixStructureName(s, file);
569                        } catch (Exception e){
570                                System.err.println("general exception:" + e.getMessage());
571                                System.err.println("unable to load structure from " + file);
572                                return null;
573                        }
574                }
575                try {
576                        Structure s = cache.getStructure(name1);
577                        return s;
578                } catch ( Exception e){
579                        System.err.println(e.getMessage());
580                        System.err.println("unable to load structure from dir: " + cache.getPath() + "/"+ name1);
581                        return null;
582                }
583
584        }
585
586        /** apply a number of rules to fix the name of the structure if it did not get set during loading.
587         *
588         * @param s
589         * @param file
590         * @return
591         */
592        private Structure fixStructureName(Structure s, String file) {
593
594                if ( s.getName() != null && (! s.getName().equals("")))
595                        return s;
596
597                s.setName(s.getPDBCode());
598
599                if ( s.getName() == null || s.getName().equals("")){
600                        File f = new File(file);
601                        s.setName(f.getName());
602                }
603                return s;
604        }
605
606        public String getDbSearchResult(AFPChain afpChain){
607                return afpChain.toDBSearchResult();
608        }
609
610        @Override
611        public String printHelp() {
612                StringBuffer buf = new StringBuffer();
613                StructureAlignment alg = getAlgorithm();
614
615                buf.append("-------------------").append(newline);
616                buf.append(alg.getAlgorithmName() + " v." + alg.getVersion() + " help: " + newline);
617                buf.append("-------------------").append(newline);
618                buf.append(newline);
619
620                buf.append(alg.getAlgorithmName()).append(" accepts the following parameters:").append(newline);
621                buf.append(newline);
622
623                buf.append("--- pairwise alignments ---").append(newline);
624                buf.append(" two files to align can be specified by providing a path to a file, or a URL:").append(newline);
625                buf.append("   -file1 the first file to align").append(newline);
626                buf.append("   -file2 the second file to align").append(newline);
627                buf.append(" alternatively you can specify PDB files by their PDB ids:").append(newline);
628                buf.append("   -pdbFilePath  Path to the directory in your file system that contains the PDB files.").append(newline);
629                buf.append("   -pdb1  PDB ID of target structure. Chain IDs are optional. In order to specify chain IDs write e.g: 5pti.A").append(newline);
630                buf.append("   -pdb2  PDB ID of query structure. Chain IDs are optional. In order to specify chain IDs write e.g: 5pti.A").append(newline);
631                buf.append(newline);
632
633                buf.append("   -h / -help / --help : print this help string.").append(newline);
634                buf.append("   -version: print version info").append(newline);
635                buf.append("   -printXML true/false print the XML representation of the alignment on stdout.").append(newline);
636                buf.append("   -printFatCat true/false print the original FATCAT output to stdout.").append(newline);
637                buf.append("   -printCE true/false print the result in CE style").append(newline);
638                buf.append("   -show3d print a 3D visualisation of the alignment (requires jmolapplet.jar in classpath)").append(newline);
639                buf.append("   -outFile file to write the output to (default: writes XML representation).").append(newline);
640                buf.append("   -outputPDB use this flag together with -outFile to dump the PDB file of the aligned structures, instead of the XML representation, instead of XML").append(newline);
641                buf.append("   -autoFetch true/false if set to true PDB files will automatically get downloaded and stored in the right location. (default: false)").append(newline);
642                buf.append("   -showMenu displays the menu that allows to run alignments through a user interface.").append(newline);
643                buf.append(newline);
644
645                buf.append("--- custom searches ---").append(newline);
646                buf.append("   -alignPairs (mandatory) path to a file that contains a set of pairs to compair").append(newline);
647                buf.append("   -outFile (mandatory) a file that will contain the summary of all the pairwise alignments").append(newline);
648                buf.append(newline);
649
650                ConfigStrucAligParams params = alg.getParameters();
651                List<String> paramNames = params.getUserConfigParameters();
652                List<String> paramHelp = params.getUserConfigHelp();
653
654                assert(paramNames.size() == paramHelp.size());
655
656                int size = Math.min(paramNames.size(), paramHelp.size());
657                if(size > 0) {
658                        Iterator<String> namesIt = paramNames.iterator();
659                        Iterator<String> helpIt = paramHelp.iterator();
660
661                        buf.append("--- ").append(alg.getAlgorithmName()).append(" parameters: ---").append(newline);
662                        Stream.iterate(0, n -> n + 1).limit(size)
663                                                                                                                .map(i -> namesIt.next())
664                                                                                                                .forEach(name -> buf.append("   -").append(Introspector.decapitalize(name)).append(" ").append(helpIt.next()).append(newline));
665                }
666                buf.append(newline);
667
668                buf.append(" For boolean arguments: if neither the text >true< or >false< is provided it is assumed to mean >true<. Instead of >-argument false< it is also possible to write -noArgument.").append(newline);
669                buf.append(newline);
670
671                buf.append("--- How to specify what to align ---").append(newline);
672                buf.append(" If only a PDB code is provided, the whole structure will be used for the alignment.").append(newline);
673                buf.append(" To specify a particular chain write as: 4hhb.A (chain IDs are case sensitive, PDB ids are not)").append(newline);
674                buf.append(" To specify that the 1st chain in a structure should be used write: 4hhb:0 .").append(newline);
675                buf.append(" In order to align SCOP domains, provide pdb1/pdb2 as: d4hhba_ Note: if SCOP is not installed at the -pdbFilePath, will automatically download and install.").append(newline);
676                buf.append(newline);
677
678                return buf.toString();
679        }
680
681}