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&lt;String&gt;</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&lt;String&gt; to set.</code>
772         * @since 6.0.0
773         */
774        public void setKeywords(List<String> keywords) {
775                this.keywords = keywords;
776        }
777}