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}