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}