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