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