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