1 /* Copyright (C) 2000-2003  The Jmol Development Team
2  * Copyright (C) 2003-2007  The CDK Project
3  *
4  * Contact: cdk-devel@lists.sf.net
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2.1 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 package org.openscience.cdk.io;
21 
22 import java.io.BufferedWriter;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.OutputStreamWriter;
26 import java.io.StringWriter;
27 import java.io.Writer;
28 import java.util.Iterator;
29 import java.util.List;
30 
31 import javax.vecmath.Point3d;
32 import javax.vecmath.Vector3d;
33 
34 import org.openscience.cdk.CDKConstants;
35 import org.openscience.cdk.exception.CDKException;
36 import org.openscience.cdk.geometry.CrystalGeometryTools;
37 import org.openscience.cdk.interfaces.IAtom;
38 import org.openscience.cdk.interfaces.IAtomContainer;
39 import org.openscience.cdk.interfaces.IChemFile;
40 import org.openscience.cdk.interfaces.IChemModel;
41 import org.openscience.cdk.interfaces.IChemObject;
42 import org.openscience.cdk.interfaces.IChemSequence;
43 import org.openscience.cdk.interfaces.ICrystal;
44 import org.openscience.cdk.io.formats.IResourceFormat;
45 import org.openscience.cdk.io.formats.PDBFormat;
46 import org.openscience.cdk.io.setting.BooleanIOSetting;
47 import org.openscience.cdk.io.setting.IOSetting;
48 import org.openscience.cdk.tools.FormatStringBuffer;
49 import org.openscience.cdk.tools.manipulator.ChemModelManipulator;
50 
51 /**
52  * Saves small molecules in a rudimentary PDB format. It does not allow
53  * writing of PDBProtein data structures.
54  *
55  * @author Gilleain Torrance <gilleain.torrance@gmail.com>
56  * @cdk.module pdb
57  * @cdk.iooptions
58  * @cdk.githash
59  */
60 public class PDBWriter extends DefaultChemObjectWriter {
61 
62     public final String      SERIAL_FORMAT    = "%5d";
63     public final String      ATOM_NAME_FORMAT = "%-5s";
64     public final String      POSITION_FORMAT  = "%8.3f";
65     public final String      RESIDUE_FORMAT   = "%s";
66 
67     private BooleanIOSetting writeAsHET;
68     private BooleanIOSetting useElementSymbolAsAtomName;
69     private BooleanIOSetting writeCONECTRecords;
70     private BooleanIOSetting writeTERRecord;
71     private BooleanIOSetting writeENDRecord;
72 
73     private BufferedWriter   writer;
74 
PDBWriter()75     public PDBWriter() {
76         this(new StringWriter());
77     }
78 
79     /**
80      * Creates a PDB writer.
81      *
82      * @param out the stream to write the PDB file to.
83      */
PDBWriter(Writer out)84     public PDBWriter(Writer out) {
85         try {
86             if (out instanceof BufferedWriter) {
87                 writer = (BufferedWriter) out;
88             } else {
89                 writer = new BufferedWriter(out);
90             }
91         } catch (Exception exc) {
92         }
93         writeAsHET = addSetting(new BooleanIOSetting("WriteAsHET", IOSetting.Importance.LOW,
94                 "Should the output file use HETATM", "false"));
95         useElementSymbolAsAtomName = addSetting(new BooleanIOSetting("UseElementSymbolAsAtomName",
96                 IOSetting.Importance.LOW, "Should the element symbol be written as the atom name", "false"));
97         writeCONECTRecords = addSetting(new BooleanIOSetting("WriteCONECT", IOSetting.Importance.LOW,
98                 "Should the bonds be written as CONECT records?", "true"));
99         writeTERRecord = addSetting(new BooleanIOSetting("WriteTER", IOSetting.Importance.LOW,
100                 "Should a TER record be put at the end of the atoms?", "false"));
101         writeENDRecord = addSetting(new BooleanIOSetting("WriteEND", IOSetting.Importance.LOW,
102                 "Should an END record be put at the end of the file?", "true"));
103     }
104 
PDBWriter(OutputStream output)105     public PDBWriter(OutputStream output) {
106         this(new OutputStreamWriter(output));
107     }
108 
109     @Override
getFormat()110     public IResourceFormat getFormat() {
111         return PDBFormat.getInstance();
112     }
113 
114     @Override
setWriter(Writer out)115     public void setWriter(Writer out) throws CDKException {
116         if (out instanceof BufferedWriter) {
117             writer = (BufferedWriter) out;
118         } else {
119             writer = new BufferedWriter(out);
120         }
121     }
122 
123     @Override
setWriter(OutputStream output)124     public void setWriter(OutputStream output) throws CDKException {
125         setWriter(new OutputStreamWriter(output));
126     }
127 
128     @Override
accepts(Class<? extends IChemObject> classObject)129     public boolean accepts(Class<? extends IChemObject> classObject) {
130         if (IChemFile.class.equals(classObject)) return true;
131         if (ICrystal.class.equals(classObject)) return true;
132         if (IAtomContainer.class.equals(classObject)) return true;
133         Class<?>[] interfaces = classObject.getInterfaces();
134         for (int i = 0; i < interfaces.length; i++) {
135             if (ICrystal.class.equals(interfaces[i])) return true;
136             if (IAtomContainer.class.equals(interfaces[i])) return true;
137             if (IChemFile.class.equals(interfaces[i])) return true;
138         }
139         Class superClass = classObject.getSuperclass();
140         if (superClass != null) return this.accepts(superClass);
141         return false;
142     }
143 
144     @Override
write(IChemObject object)145     public void write(IChemObject object) throws CDKException {
146         if (object instanceof ICrystal) {
147             writeCrystal((ICrystal) object);
148         } else if (object instanceof IAtomContainer) {
149             writeMolecule((IAtomContainer) object);
150         } else if (object instanceof IChemFile) {
151             IChemFile chemFile = (IChemFile) object;
152             IChemSequence sequence = chemFile.getChemSequence(0);
153             if (sequence != null) {
154                 IChemModel model = sequence.getChemModel(0);
155                 if (model != null) {
156                     ICrystal crystal = model.getCrystal();
157                     if (crystal != null) {
158                         write(crystal);
159                     } else {
160                         Iterator<IAtomContainer> containers = ChemModelManipulator.getAllAtomContainers(model)
161                                 .iterator();
162                         while (containers.hasNext()) {
163                             writeMolecule(model.getBuilder().newInstance(IAtomContainer.class, containers.next()));
164                         }
165                     }
166                 }
167             }
168         } else {
169             throw new CDKException("Only supported is writing of Molecule, Crystal and ChemFile objects.");
170         }
171     }
172 
173     /**
174      * Writes a single frame in PDB format to the Writer.
175      *
176      * @param molecule the Molecule to write
177      */
writeMolecule(IAtomContainer molecule)178     public void writeMolecule(IAtomContainer molecule) throws CDKException {
179 
180         try {
181             writeHeader();
182             int atomNumber = 1;
183 
184             String hetatmRecordName = (writeAsHET.isSet()) ? "HETATM" : "ATOM  ";
185             String id = molecule.getID();
186             String residueName = (id == null || id.equals("")) ? "MOL" : id;
187             String terRecordName = "TER";
188 
189             // Loop through the atoms and write them out:
190             StringBuffer buffer = new StringBuffer();
191             Iterator<IAtom> atoms = molecule.atoms().iterator();
192             FormatStringBuffer fsb = new FormatStringBuffer("");
193             String[] connectRecords = null;
194             if (writeCONECTRecords.isSet()) {
195                 connectRecords = new String[molecule.getAtomCount()];
196             }
197             while (atoms.hasNext()) {
198                 buffer.setLength(0);
199                 buffer.append(hetatmRecordName);
200                 fsb.reset(SERIAL_FORMAT).format(atomNumber);
201                 buffer.append(fsb.toString());
202                 buffer.append(' ');
203                 IAtom atom = atoms.next();
204                 String name;
205                 if (useElementSymbolAsAtomName.isSet()) {
206                     name = atom.getSymbol();
207                 } else {
208                     if (atom.getID() == null || atom.getID().equals("")) {
209                         name = atom.getSymbol();
210                     } else {
211                         name = atom.getID();
212                     }
213                 }
214                 fsb.reset(ATOM_NAME_FORMAT).format(name);
215                 buffer.append(fsb.toString());
216                 fsb.reset(RESIDUE_FORMAT).format(residueName);
217                 buffer.append(fsb).append("     0    ");
218                 Point3d position = atom.getPoint3d();
219                 fsb.reset(POSITION_FORMAT).format(position.x);
220                 buffer.append(fsb.toString());
221                 fsb.reset(POSITION_FORMAT).format(position.y);
222                 buffer.append(fsb.toString());
223                 fsb.reset(POSITION_FORMAT).format(position.z);
224                 buffer.append(fsb.toString());
225 
226                 buffer.append("  1.00  0.00           ") // occupancy + temperature factor
227                       .append(atom.getSymbol());
228                 Integer formalCharge = atom.getFormalCharge();
229                 if (formalCharge == CDKConstants.UNSET) {
230                     buffer.append("+0");
231                 } else {
232                     if (formalCharge < 0) {
233                         buffer.append(formalCharge);
234                     } else {
235                         buffer.append('+').append(formalCharge);
236                     }
237                 }
238 
239                 if (connectRecords != null && writeCONECTRecords.isSet()) {
240                     List<IAtom> neighbours = molecule.getConnectedAtomsList(atom);
241                     if (neighbours.size() != 0) {
242                         StringBuffer connectBuffer = new StringBuffer("CONECT");
243                         connectBuffer.append(String.format("%5d", atomNumber));
244                         for (IAtom neighbour : neighbours) {
245                             int neighbourNumber = molecule.indexOf(neighbour) + 1;
246                             connectBuffer.append(String.format("%5d", neighbourNumber));
247                         }
248                         connectRecords[atomNumber - 1] = connectBuffer.toString();
249                     } else {
250                         connectRecords[atomNumber - 1] = null;
251                     }
252                 }
253 
254                 writer.write(buffer.toString(), 0, buffer.length());
255                 writer.write('\n');
256                 ++atomNumber;
257             }
258 
259             if (writeTERRecord.isSet()) {
260                 writer.write(terRecordName, 0, terRecordName.length());
261                 writer.write('\n');
262             }
263 
264             if (connectRecords != null && writeCONECTRecords.isSet()) {
265                 for (String connectRecord : connectRecords) {
266                     if (connectRecord != null) {
267                         writer.write(connectRecord);
268                         writer.write('\n');
269                     }
270                 }
271             }
272 
273             if (writeENDRecord.isSet()) {
274                 writer.write("END   ");
275                 writer.write('\n');
276             }
277 
278         } catch (IOException exception) {
279             throw new CDKException("Error while writing file: " + exception.getMessage(), exception);
280         }
281     }
282 
writeHeader()283     private void writeHeader() throws IOException {
284         writer.write("HEADER created with the CDK (http://cdk.sf.net/)");
285         writer.write('\n');
286     }
287 
writeCrystal(ICrystal crystal)288     public void writeCrystal(ICrystal crystal) throws CDKException {
289         try {
290             writeHeader();
291             Vector3d a = crystal.getA();
292             Vector3d b = crystal.getB();
293             Vector3d c = crystal.getC();
294             double[] ucParams = CrystalGeometryTools.cartesianToNotional(a, b, c);
295             final String LENGTH_FORMAT = "%4.3f";
296             final String ANGLE_FORMAT = "%3.3f";
297             FormatStringBuffer fsb = new FormatStringBuffer("");
298             fsb.reset(LENGTH_FORMAT).format(ucParams[0]);
299             writer.write("CRYST1 " + fsb.toString());
300             fsb.reset(LENGTH_FORMAT).format(ucParams[1]);
301             writer.write(fsb.toString());
302             fsb.reset(LENGTH_FORMAT).format(ucParams[2]);
303             writer.write(fsb.toString());
304             fsb.reset(ANGLE_FORMAT).format(ucParams[3]);
305             writer.write(fsb.toString());
306             fsb.reset(ANGLE_FORMAT).format(ucParams[4]);
307             writer.write(fsb.toString());
308             fsb.reset(ANGLE_FORMAT).format(ucParams[4]);
309             writer.write(fsb.toString());
310             writer.write('\n');
311 
312             // before saving the atoms, we need to create cartesian coordinates
313             Iterator<IAtom> atoms = crystal.atoms().iterator();
314             while (atoms.hasNext()) {
315                 IAtom atom = atoms.next();
316                 //            	logger.debug("PDBWriter: atom -> " + atom);
317                 // if it got 3D coordinates, use that. If not, try fractional coordinates
318                 if (atom.getPoint3d() == null && atom.getFractionalPoint3d() != null) {
319                     Point3d frac = new Point3d(atom.getFractionalPoint3d());
320                     Point3d cart = CrystalGeometryTools.fractionalToCartesian(a, b, c, frac);
321                     atom.setPoint3d(cart);
322                 }
323             }
324             writeMolecule(crystal.getBuilder().newInstance(IAtomContainer.class, crystal));
325         } catch (IOException exception) {
326             throw new CDKException("Error while writing file: " + exception.getMessage(), exception);
327         }
328     }
329 
330     /**
331       * Flushes the output and closes this object.
332       */
333     @Override
close()334     public void close() throws IOException {
335         writer.close();
336     }
337 
338 }
339