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 */ 021package org.biojava.nbio.structure; 022 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.text.DateFormat; 026import java.text.DecimalFormat; 027import java.text.NumberFormat; 028import java.text.SimpleDateFormat; 029import java.util.ArrayList; 030import java.util.Date; 031import java.util.EnumSet; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Locale; 035import java.util.Map; 036import java.util.Set; 037 038import org.biojava.nbio.structure.quaternary.BioAssemblyInfo; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042 043/** 044 * A class that contains PDB Header information. 045 * In contrast to what the name suggests, this class does not represent a 046 * direct mapping of the Header section of the PDB legacy file format. 047 * Instead, it holds the information that is not directly related to the 048 * structure data. Such information may exist in some cases and may not exist in 049 * other cases. 050 * 051 * @author Andreas Prlic 052 * @since 1.6 053 * 054 */ 055public class PDBHeader implements PDBRecord { 056 057 private static final long serialVersionUID = -5834326174085429508L; 058 059 private static final Logger logger = LoggerFactory.getLogger(PDBHeader.class); 060 061 private String title; 062 /**@deprecated This field should not be used. It will be removed later. 063 * Use {@link #getKeywords()} instead. */ 064 private String description; 065 private List<String> keywords; 066 private PdbId pdbId; 067 private String classification; 068 069 private Date depDate; 070 private Date relDate; 071 private Date modDate; 072 073 private Set<ExperimentalTechnique> techniques; 074 private PDBCrystallographicInfo crystallographicInfo; 075 076 private float resolution; 077 private float rFree; 078 private float rWork; 079 080 private JournalArticle journalArticle; 081 private String authors; 082 083 public static final float DEFAULT_RESOLUTION = 99; 084 public static final float DEFAULT_RFREE = 1; // worst possible rfree is the default 085 086 087 private Long id; 088 public static final String newline = System.getProperty("line.separator"); 089 090 private DateFormat dateFormat; 091 092 private Map<Integer,BioAssemblyInfo> bioAssemblies ; 093 094 List<DatabasePDBRevRecord> revisionRecords; 095 096 public PDBHeader(){ 097 098 depDate = new Date(0); 099 modDate = new Date(0); 100 relDate = new Date(0); 101 dateFormat = new SimpleDateFormat("dd-MMM-yy",Locale.US); 102 103 resolution = DEFAULT_RESOLUTION; 104 rFree = DEFAULT_RFREE; 105 rWork = DEFAULT_RFREE; 106 107 bioAssemblies = new LinkedHashMap<Integer, BioAssemblyInfo>(); 108 crystallographicInfo = new PDBCrystallographicInfo(); 109 110 keywords = new ArrayList<>(); 111 112 } 113 114 /** String representation 115 * 116 */ 117 @Override 118 public String toString(){ 119 StringBuilder buf = new StringBuilder(); 120 121 try { 122 123 124 Class<?> c = Class.forName(PDBHeader.class.getName()); 125 Method[] methods = c.getMethods(); 126 127 for (Method m : methods) { 128 String name = m.getName(); 129 130 if (name.substring(0, 3).equals("get")) { 131 if (name.equals("getClass")) { 132 continue; 133 } 134 Object o = m.invoke(this); 135 if (o != null) { 136 buf.append(name.substring(3, name.length())); 137 buf.append(": ").append(o).append(" "); 138 } 139 } 140 } 141 } catch (ClassNotFoundException e) { 142 logger.error("Exception caught while creating toString ",e); 143 } catch (InvocationTargetException e) { 144 logger.error("Exception caught while creating toString ",e); 145 } catch (IllegalAccessException e) { 146 logger.error("Exception caught while creating toString ",e); 147 } 148 149 return buf.toString(); 150 } 151 152 /** Return a PDB representation of the PDB Header 153 * 154 * @return a PDB file style display 155 */ 156 @Override 157 public String toPDB(){ 158 StringBuffer buf = new StringBuffer(); 159 toPDB(buf); 160 return buf.toString(); 161 } 162 163 /** Appends a PDB representation of the PDB header to the provided StringBuffer 164 * 165 * @param buf 166 */ 167 @Override 168 public void toPDB(StringBuffer buf){ 169 // 1 2 3 4 5 6 7 170 //01234567890123456789012345678901234567890123456789012345678901234567890123456789 171 //HEADER COMPLEX (SERINE PROTEASE/INHIBITORS) 06-FEB-98 1A4W 172 //TITLE CRYSTAL STRUCTURES OF THROMBIN WITH THIAZOLE-CONTAINING 173 //TITLE 2 INHIBITORS: PROBES OF THE S1' BINDING SITE 174 175 printHeader(buf); 176 printTitle(buf); 177 printExpdata(buf); 178 printAuthors(buf); 179 printResolution(buf); 180 181 } 182 183 private void printResolution(StringBuffer buf){ 184 185 if (getResolution() == DEFAULT_RESOLUTION){ 186 return; 187 } 188 189 DecimalFormat d2 = (DecimalFormat)NumberFormat.getInstance(java.util.Locale.UK); 190 d2.setMaximumIntegerDigits(2); 191 d2.setMinimumFractionDigits(2); 192 d2.setMaximumFractionDigits(2); 193 194 buf.append("REMARK 2 RESOLUTION. "); 195 String x = d2.format(resolution); 196 buf.append(x); 197 buf.append(" ANGSTROMS."); 198 fillLine(buf,34+x.length()); 199 200 buf.append(newline); 201 } 202 203 private void printExpdata(StringBuffer buf){ 204 Set<ExperimentalTechnique> exp = getExperimentalTechniques(); 205 if ( exp == null ) 206 return; 207 208 209 buf.append("EXPDTA "); 210 211 int length = 0; 212 int i = 0; 213 for (ExperimentalTechnique et:exp) { 214 if (i>0) { 215 buf.append("; "); 216 length+=2; 217 } 218 buf.append(et.getName()); 219 length+=et.getName().length(); 220 i++; 221 } 222 223 // fill up the white space to the right column 224 int l = length + 10; 225 fillLine(buf,l); 226 227 buf.append(newline); 228 229 } 230 231 private void printAuthors(StringBuffer buf){ 232 String authors = getAuthors(); 233 if ( authors == null) 234 return; 235 if ( authors.equals("")){ 236 return; 237 } 238 239 printMultiLine(buf, "AUTHOR ", authors,','); 240 241 } 242 243 private void printMultiLine(StringBuffer buf, String lineStart, String data, char breakChar){ 244 if ( lineStart.length() != 9) 245 logger.info("lineStart != 9, there will be problems :" + lineStart); 246 247 if ( data.length() < 58) { 248 buf.append(lineStart); 249 buf.append(" "); 250 buf.append(data); 251 buf.append(newline); 252 return; 253 } 254 String thisLine = ""; 255 int count = 1; 256 while (data.length() > 57) { 257 // find first whitespace from left 258 // there are 10 chars to the left, so the cutoff position is 56 259 boolean charFound = false; 260 for ( int i =57;i>-1;i--){ 261 char c = data.charAt(i); 262 if (c == breakChar){ 263 // found the whitespace 264 265 thisLine = data.substring(0,i+1); 266 267 // prevent endless loop 268 if (i == 0 ) 269 i++; 270 data = data.substring(i); 271 charFound = true; 272 break; 273 } 274 } 275 // for emergencies... prevents an endless loop 276 if ( ! charFound){ 277 thisLine = data.substring(0,58); 278 data = data.substring(57); 279 } 280 if ( ( breakChar == ',' ) && ( data.charAt(0)== ',')) { 281 data = data.substring(1); 282 } 283 284 //TODO: check structures that have more than 10 lines... 285 // start printing.. 286 287 buf.append(lineStart); 288 if ( count > 1) { 289 buf.append(count); 290 if ( breakChar != ' ' ) 291 buf.append(" "); 292 } 293 else 294 buf.append(" "); 295 buf.append(thisLine); 296 297 // fill up the white space to the right column 298 int l = thisLine.length()+ 10; 299 while (l < 67){ 300 l++; 301 buf.append(" "); 302 } 303 304 buf.append(newline); 305 count++; 306 307 } 308 309 // last line... 310 if (!data.trim().isEmpty()){ 311 buf.append(lineStart); 312 buf.append(count); 313 int filledLeft = 10; 314 if ( breakChar != ' ' ) { 315 buf.append(" "); 316 filledLeft++; 317 } 318 buf.append(data); 319 // fill up the white space to the right column 320 int l = data.length()+ filledLeft; 321 fillLine(buf,l); 322 buf.append(newline); 323 } 324 325 } 326 327 private void fillLine(StringBuffer buf, int currentPos){ 328 int l = currentPos; 329 while (l < 67){ 330 l++; 331 buf.append(" "); 332 } 333 } 334 335 private void printHeader(StringBuffer buf){ 336 337 String classification = getClassification(); 338 339 if (classification == null || classification.isEmpty()) return; 340 341 // we can;t display this line since the classification is not there... 342 343 buf.append("HEADER "); 344 buf.append(classification); 345 buf.append(" "); 346 347 // fill up the white space to the right column 348 int l = classification.length() + 10 ; 349 while (l < 49){ 350 l++; 351 buf.append(" "); 352 } 353 354 Date d = getDepDate(); 355 if ( d != null){ 356 // provide correct display of Dep date... 357 buf.append(dateFormat.format(d)); 358 } else { 359 buf.append(" "); 360 } 361 buf.append(" "); 362 363 String id = getIdCode(); 364 if ( id != null){ 365 buf.append(getIdCode()); 366 buf.append(" "); 367 } 368 else 369 buf.append(" "); 370 buf.append(newline); 371 372 373 } 374 375 private void printTitle(StringBuffer buf) { 376 // 1 2 3 4 5 6 7 377 //01234567890123456789012345678901234567890123456789012345678901234567890123456789 378 379 //HEADER COMPLEX (SERINE PROTEASE/INHIBITORS) 06-FEB-98 1A4W 380 //TITLE CRYSTAL STRUCTURES OF THROMBIN WITH THIAZOLE-CONTAINING 381 //TITLE 2 INHIBITORS: PROBES OF THE S1' BINDING SITE 382 383 String title = getTitle(); 384 385 if ( (title == null) || (title.trim().isEmpty()) ) 386 return; 387 388 printMultiLine(buf, "TITLE ", title,' '); 389 390 } 391 392 /** Get the ID used by Hibernate. 393 * 394 * @return the ID used by Hibernate 395 * @see #setId(Long) 396 */ 397 public Long getId() { 398 return id; 399 } 400 401 /** Set the ID used by Hibernate. 402 * 403 * @param id the id assigned by Hibernate 404 * @see #getId() 405 * 406 */ 407 408 @SuppressWarnings("unused") 409 private void setId(Long id) { 410 this.id = id; 411 } 412 413 /** Compare two PDBHeader objects 414 * 415 * @param other a PDBHeader object to compare this one to. 416 * @return true if they are equal or false if they are not. 417 */ 418 public boolean equals(PDBHeader other){ 419 try { 420 421 Class<?> c = Class.forName(PDBHeader.class.getName()); 422 Method[] methods = c.getMethods(); 423 424 for (Method m : methods) { 425 String name = m.getName(); 426 427 if (name.substring(0, 3).equals("get")) { 428 if (name.equals("getClass")) { 429 continue; 430 } 431 Object a = m.invoke(this); 432 Object b = m.invoke(other); 433 if (a == null) { 434 if (b == null) { 435 continue; 436 } else { 437 logger.warn(name + " a is null, where other is " + b); 438 return false; 439 } 440 } 441 if (b == null) { 442 logger.warn(name + " other is null, where a is " + a); 443 return false; 444 } 445 if (!(a.equals(b))) { 446 logger.warn("mismatch with " + name + " >" + a + "< >" + b + "<"); 447 return false; 448 } 449 } 450 } 451 } catch (ClassNotFoundException e) { 452 logger.error("Exception caught while comparing PDBHeader objects ",e); 453 return false; 454 } catch (InvocationTargetException e) { 455 logger.error("Exception caught while comparing PDBHeader objects ",e); 456 return false; 457 } catch (IllegalAccessException e) { 458 logger.error("Exception caught while comparing PDBHeader objects ",e); 459 return false; 460 } 461 return true; 462 } 463 464 465 /** 466 * The PDB code for this protein structure. 467 * 468 * @return the PDB identifier 469 * @see #setIdCode(String) 470 * @deprecated use {@link #getPdbId()} 471 */ 472 @Deprecated 473 public String getIdCode() { 474 if(this.pdbId == null) 475 return null; 476 return this.pdbId.getId(); 477 } 478 479 /** 480 * The PDB code for this protein structure. 481 * 482 * @param idCode the PDB identifier 483 * @see #getIdCode() 484 * @deprecated use {@link #setPdbId(PdbId)} 485 */ 486 @Deprecated 487 public void setIdCode(String idCode) { 488 if(idCode == null) { 489 this.pdbId = null; 490 }else { 491 this.pdbId = new PdbId(idCode); 492 } 493 } 494 495 /** 496 * Gets the PDB identifier for this protein structure. 497 * 498 * @return the {@link PdbId} PDB identifier 499 * @see #setPdbId(PdbId) 500 * @since 6.0.0 501 */ 502 public PdbId getPdbId() { 503 return pdbId; 504 } 505 506 /** 507 * Sets the PDB identifier code for this protein structure. 508 * 509 * @param pdbId the PDB identifier 510 * @see #getPdbId() 511 * @since 6.0.0 512 */ 513 public void setPdbId(PdbId pdbId) { 514 this.pdbId = pdbId; 515 } 516 517 public String getClassification() { 518 return classification; 519 } 520 521 public void setClassification(String classification) { 522 this.classification = classification; 523 } 524 525 /** 526 * Return the deposition date of the structure in the PDB. 527 * 528 * @return the deposition date 529 */ 530 public Date getDepDate() { 531 return depDate; 532 } 533 534 /** 535 * The deposition date of the structure in the PDB 536 * 537 * @param depDate the deposition date 538 */ 539 public void setDepDate(Date depDate) { 540 this.depDate = depDate; 541 } 542 543 /** 544 * Return the Set of ExperimentalTechniques, usually the set is of size 1 except for hybrid 545 * experimental techniques when the Set will contain 2 or more values 546 * @return the Set of ExperimentalTechniques or null if not set 547 */ 548 public Set<ExperimentalTechnique> getExperimentalTechniques() { 549 return techniques; 550 } 551 552 /** 553 * Adds the experimental technique to the set of experimental techniques of this header. 554 * Note that if input is not a recognised technique string then no errors will be produced but 555 * false will be returned 556 * @param techniqueStr 557 * @return true if the input corresponds to a recognised technique string (see {@link ExperimentalTechnique}) 558 * and it was not already present in the current set of ExperimentalTechniques 559 */ 560 public boolean setExperimentalTechnique(String techniqueStr) { 561 562 ExperimentalTechnique et = ExperimentalTechnique.getByName(techniqueStr); 563 564 if (et==null) return false; 565 566 if (techniques==null) { 567 techniques = EnumSet.of(et); 568 return true; 569 } else { 570 return techniques.add(et); 571 } 572 573 } 574 575 public PDBCrystallographicInfo getCrystallographicInfo() { 576 return crystallographicInfo; 577 } 578 579 public void setCrystallographicInfo(PDBCrystallographicInfo crystallographicInfo) { 580 this.crystallographicInfo = crystallographicInfo; 581 } 582 583 /** 584 * Returns the resolution (or effective resolution) of the experiment. This is 585 * related to <code>_refine.ls_d_res_high</code> (DIFFRACTION) or 586 * <code>_em_3d_reconstruction.resolution</code> (ELECTRON MICROSCOPY) for mmCif 587 * format, or to <code>REMARK 2</code> or <code>REMARK 3</code> for PDB legacy 588 * format. If more than one value is available (in rare cases), the last one is 589 * reported. If no value is available, it defaults to 590 * {@link #DEFAULT_RESOLUTION} ({@value #DEFAULT_RESOLUTION}). 591 * 592 * @return The reported experiment resolution, {@link #DEFAULT_RESOLUTION} 593 * ({@value #DEFAULT_RESOLUTION}) if no value is available. 594 */ 595 public float getResolution() { 596 return resolution; 597 } 598 599 public void setResolution(float resolution) { 600 this.resolution = resolution; 601 } 602 603 public float getRfree() { 604 return rFree; 605 } 606 607 public void setRfree(float rFree) { 608 this.rFree = rFree; 609 } 610 611 /** 612 * Return the latest modification date of the structure. 613 * 614 * @return the latest modification date 615 */ 616 public Date getModDate() { 617 return modDate; 618 } 619 620 /** 621 * The latest modification date of the structure. 622 * 623 * @param modDate the latest modification date 624 */ 625 public void setModDate(Date modDate) { 626 this.modDate = modDate; 627 } 628 629 /** 630 * Return the release date of the structure in the PDB. 631 * 632 * @return the release date 633 */ 634 public Date getRelDate() { 635 return relDate; 636 } 637 638 /** 639 * 640 * The release date of the structure in the PDB. 641 * 642 * @param relDate the release date 643 */ 644 public void setRelDate(Date relDate) { 645 this.relDate = relDate; 646 } 647 648 public String getTitle() { 649 return title; 650 } 651 public void setTitle(String title) { 652 this.title = title; 653 } 654 655 /**@deprecated will be removed later. Use {@link #getKeywords()} if you use 656 * <code>description</code> to keep the keywords. 657 * @return 658 */ 659 @Deprecated 660 public String getDescription() { 661 return description; 662 } 663 /**@deprecated will be removed later. Use {@link #getKeywords()} if you use 664 * <code>description</code> to keep the keywords. 665 * @param description 666 */ 667 @Deprecated 668 public void setDescription(String description) { 669 this.description = description; 670 } 671 672 /** 673 * Return the names of the authors as listed in the AUTHORS section of a PDB file. 674 * Not necessarily the same authors as listed in the AUTH section of the primary citation! 675 * 676 * @return Authors as a string 677 */ 678 public String getAuthors() 679 { 680 return authors; 681 } 682 683 public void setAuthors(String authors) 684 { 685 this.authors = authors; 686 } 687 688 /** 689 * Return whether or not the entry has an associated journal article 690 * or publication. The JRNL section is not mandatory and thus may not be 691 * present. 692 * @return flag if a JournalArticle could be found. 693 */ 694 public boolean hasJournalArticle() { 695 return this.journalArticle != null; 696 } 697 698 /** 699 * Get the associated publication as defined by the JRNL records in a PDB 700 * file. 701 * @return a JournalArticle 702 */ 703 public JournalArticle getJournalArticle() { 704 return this.journalArticle; 705 } 706 707 /** 708 * Set the associated publication as defined by the JRNL records in a PDB 709 * file. 710 * @param journalArticle the article 711 */ 712 public void setJournalArticle(JournalArticle journalArticle) { 713 this.journalArticle = journalArticle; 714 } 715 716 /** 717 * Return the map of biological assemblies. The keys are the 718 * biological assembly identifiers (starting at 1). Non-numerical identifiers 719 * such as PAU or XAU are not supported. 720 * @return 721 */ 722 public Map<Integer,BioAssemblyInfo> getBioAssemblies() { 723 return bioAssemblies ; 724 } 725 726 public void setBioAssemblies(Map<Integer,BioAssemblyInfo> bioAssemblies) { 727 this.bioAssemblies = bioAssemblies; 728 } 729 730 /** 731 * Get the number of biological assemblies available in the PDB header 732 * @return 733 */ 734 public int getNrBioAssemblies() { 735 return this.bioAssemblies.size(); 736 } 737 738 public List<DatabasePDBRevRecord> getRevisionRecords() { 739 return revisionRecords; 740 } 741 742 public void setRevisionRecords(List<DatabasePDBRevRecord> revisionRecords) { 743 this.revisionRecords = revisionRecords; 744 } 745 746 /** 747 * @return the R-work for this structure. 748 */ 749 public float getRwork() { 750 return rWork; 751 } 752 753 /** 754 * @param rWork the R-work for this structure. 755 */ 756 public void setRwork(float rWork) { 757 this.rWork = rWork; 758 } 759 760 /** 761 * Gets the keywords (KEYWODS) record of the structure 762 * @return The keywords in a <code>List<String></code> 763 * @since 6.0.0 764 */ 765 public List<String> getKeywords() { 766 return keywords; 767 } 768 769 /** 770 * Sets the KEYWODS record of the structure. 771 * @param keywords The keywords in a <code>List<String> to set.</code> 772 * @since 6.0.0 773 */ 774 public void setKeywords(List<String> keywords) { 775 this.keywords = keywords; 776 } 777}