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