~ubuntu-branches/ubuntu/maverick/cdk/maverick

« back to all changes in this revision

Viewing changes to src/org/openscience/cdk/io/MDLWriter.java

  • Committer: Bazaar Package Importer
  • Author(s): Paul Cager
  • Date: 2008-04-09 21:17:53 UTC
  • Revision ID: james.westby@ubuntu.com-20080409211753-46lmjw5z8mx5pd8d
Tags: upstream-1.0.2
ImportĀ upstreamĀ versionĀ 1.0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* $RCSfile$
 
2
 * $Author: egonw $ 
 
3
 * $Date: 2007-08-28 10:29:51 +0200 (Tue, 28 Aug 2007) $
 
4
 * $Revision: 8719 $
 
5
 * 
 
6
 * Copyright (C) 1997-2007  The Chemistry Development Kit (CDK) project
 
7
 * 
 
8
 * Contact: cdk-devel@lists.sourceforge.net
 
9
 * 
 
10
 * This program is free software; you can redistribute it and/or
 
11
 * modify it under the terms of the GNU Lesser General Public License
 
12
 * as published by the Free Software Foundation; either version 2.1
 
13
 * of the License, or (at your option) any later version.
 
14
 * All we ask is that proper credit is given for our work, which includes
 
15
 * - but is not limited to - adding the above copyright notice to the beginning
 
16
 * of your source code files, and to any copyright notice that you may distribute
 
17
 * with programs based on this work.
 
18
 * 
 
19
 * This program is distributed in the hope that it will be useful,
 
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
22
 * GNU Lesser General Public License for more details.
 
23
 * 
 
24
 * You should have received a copy of the GNU Lesser General Public License
 
25
 * along with this program; if not, write to the Free Software
 
26
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 
27
 * 
 
28
 */
 
29
package org.openscience.cdk.io;
 
30
 
 
31
import org.openscience.cdk.CDKConstants;
 
32
import org.openscience.cdk.config.IsotopeFactory;
 
33
import org.openscience.cdk.exception.CDKException;
 
34
import org.openscience.cdk.interfaces.*;
 
35
import org.openscience.cdk.io.formats.IResourceFormat;
 
36
import org.openscience.cdk.io.formats.MDLFormat;
 
37
import org.openscience.cdk.tools.LoggingTool;
 
38
import org.openscience.cdk.tools.manipulator.ChemFileManipulator;
 
39
 
 
40
import java.io.*;
 
41
import java.text.NumberFormat;
 
42
import java.text.SimpleDateFormat;
 
43
import java.util.*;
 
44
 
 
45
/**
 
46
 * Writes MDL mol files and SD files.
 
47
 * <BR><BR>
 
48
 * A MDL mol file contains a single molecule, whereas a MDL SD file contains
 
49
 * one or more molecules. This class is capable of writing both mol files and
 
50
 * SD files. The correct format is automatically chosen:
 
51
 * <ul>
 
52
 * <li>if {@link #write(IChemObject)} is called with a {@link org.openscience.cdk.MoleculeSet MoleculeSet}
 
53
 * as an argument a SD files is written</li>
 
54
 * <li>if one of the two writeMolecule methods (either {@link #writeMolecule(IMolecule) this one} or
 
55
 * {@link #writeMolecule(org.openscience.cdk.interfaces.IMolecule)} that one}) is called the first time, a mol file is written</li>
 
56
 * <li>if one of the two writeMolecule methods is called more than once the output is a SD file</li>
 
57
 * </ul>
 
58
 * 
 
59
 * <p>Thus, to write several molecules to a single SD file you can either use {@link #write(IChemObject)} and pass
 
60
 * a {@link org.openscience.cdk.MoleculeSet MoleculeSet} or you can repeatedly call one of the two
 
61
 * writeMolecule methods.
 
62
 * <p>For writing a MDL molfile you can this code:
 
63
 * <pre>
 
64
 * MDLWriter writer = new MDLWriter(new FileWriter(new File("output.mol")));
 
65
 * writer.write((Molecule)molecule);
 
66
 * writer.close();
 
67
 * </pre>
 
68
 *
 
69
 * See {@cdk.cite DAL92}.
 
70
 *
 
71
 * @cdk.module  io
 
72
 * @cdk.keyword file format, MDL molfile
 
73
 * @cdk.bug     1524466
 
74
 */
 
75
public class MDLWriter extends DefaultChemObjectWriter {
 
76
 
 
77
    private BufferedWriter writer;
 
78
    private LoggingTool logger;
 
79
    private int moleculeNumber;
 
80
    public Map sdFields=null;
 
81
    //private boolean writeAromatic=true;
 
82
    
 
83
 
 
84
    
 
85
    /**
 
86
     * Contructs a new MDLWriter that can write an array of 
 
87
     * Molecules to a Writer.
 
88
     *
 
89
     * @param   out  The Writer to write to
 
90
     */
 
91
    public MDLWriter(Writer out) {
 
92
        logger = new LoggingTool(this);
 
93
        try {
 
94
                if (out instanceof BufferedWriter) {
 
95
                writer = (BufferedWriter)out;
 
96
            } else {
 
97
                writer = new BufferedWriter(out);
 
98
            }
 
99
        } catch (Exception exc) {
 
100
        }
 
101
        this.moleculeNumber = 1;
 
102
    }
 
103
 
 
104
    /**
 
105
     * Contructs a new MDLWriter that can write an array of
 
106
     * Molecules to a given OutputStream.
 
107
     *
 
108
     * @param   output  The OutputStream to write to
 
109
     */
 
110
    public MDLWriter(OutputStream output) {
 
111
        this(new OutputStreamWriter(output));
 
112
    }
 
113
    
 
114
    public MDLWriter() {
 
115
        this(new StringWriter());
 
116
    }
 
117
 
 
118
    public IResourceFormat getFormat() {
 
119
        return MDLFormat.getInstance();
 
120
    }
 
121
    
 
122
    public void setWriter(Writer out) throws CDKException {
 
123
        if (out instanceof BufferedWriter) {
 
124
            writer = (BufferedWriter)out;
 
125
        } else {
 
126
            writer = new BufferedWriter(out);
 
127
        }
 
128
    }
 
129
 
 
130
    public void setWriter(OutputStream output) throws CDKException {
 
131
        setWriter(new OutputStreamWriter(output));
 
132
    }
 
133
    
 
134
    /**
 
135
     * 
 
136
     * Method does not do anything until now.
 
137
     *
 
138
     */
 
139
    public void dontWriteAromatic(){
 
140
      //writeAromatic=false;
 
141
    }
 
142
    
 
143
    /**
 
144
     * Here you can set a map which will be used to build sd fields in the file.
 
145
     * The entries will be translated to sd fields like this:<br>
 
146
     * &gt; &lt;key&gt;<br>
 
147
     * &gt; value<br>
 
148
     * empty line<br>
 
149
     *
 
150
     * @param  map The map to be used, map of String-String pairs
 
151
     */
 
152
    public void setSdFields(Map map){
 
153
      sdFields=map;
 
154
    }
 
155
    
 
156
    /**
 
157
     * Flushes the output and closes this object.
 
158
     */
 
159
    public void close() throws IOException {
 
160
        writer.close();
 
161
    }
 
162
 
 
163
        public boolean accepts(Class classObject) {
 
164
                Class[] interfaces = classObject.getInterfaces();
 
165
                for (int i=0; i<interfaces.length; i++) {
 
166
                        if (IMolecule.class.equals(interfaces[i])) return true;
 
167
                        if (IChemFile.class.equals(interfaces[i])) return true;
 
168
                        if (IChemModel.class.equals(interfaces[i])) return true;
 
169
                        if (IMoleculeSet.class.equals(interfaces[i])) return true;
 
170
                }
 
171
                return false;
 
172
        }
 
173
 
 
174
    /**
 
175
     * Writes a IChemObject to the MDL molfile formated output. 
 
176
     * It can only output ChemObjects of type ChemFile, Molecule and
 
177
     * MoleculeSet.
 
178
     *
 
179
     * @param object class must be of type ChemFile, Molecule or MoleculeSet.
 
180
     *
 
181
     * @see org.openscience.cdk.ChemFile
 
182
     */
 
183
        public void write(IChemObject object) throws CDKException {
 
184
                try {
 
185
                        if (object instanceof IMoleculeSet) {
 
186
                                writeMoleculeSet((IMoleculeSet)object);
 
187
                                return;
 
188
                        } else if (object instanceof IChemFile) {
 
189
                                writeChemFile((IChemFile)object);
 
190
                                return;
 
191
                        } else if (object instanceof IChemModel) {
 
192
                                IChemFile file = object.getBuilder().newChemFile();
 
193
                                IChemSequence sequence = object.getBuilder().newChemSequence();
 
194
                                sequence.addChemModel((IChemModel)object);
 
195
                                file.addChemSequence(sequence);
 
196
                                writeChemFile((IChemFile)file);
 
197
                                return;
 
198
                        } else if (object instanceof IMolecule) {
 
199
                                writeMolecule((IMolecule)object);
 
200
                                return;
 
201
                        }
 
202
                } catch (Exception ex) {
 
203
                        logger.error(ex.getMessage());
 
204
                        logger.debug(ex);
 
205
                        throw new CDKException("Exception while writing MDL file: " + ex.getMessage(), ex);
 
206
                }
 
207
                throw new CDKException("Only supported is writing of ChemFile, MoleculeSet, AtomContainer and Molecule objects.");
 
208
        }
 
209
        
 
210
        /**
 
211
         * Writes an array of Molecules to an OutputStream in MDL sdf format.
 
212
         *
 
213
         * @param   som  Array of Molecules that is written to an OutputStream
 
214
         */
 
215
        private void writeMoleculeSet(IMoleculeSet som)
 
216
        {
 
217
                java.util.Iterator molecules = som.molecules();
 
218
                while (molecules.hasNext())
 
219
                {
 
220
                        IMolecule mol = (IMolecule)molecules.next();
 
221
                        try
 
222
                        {
 
223
                                boolean[] isVisible=new boolean[mol.getAtomCount()];
 
224
                                for(int k=0;k<isVisible.length;k++){
 
225
                                        isVisible[k]=true;
 
226
                                }
 
227
                                writeMolecule(mol);
 
228
                        }
 
229
                        catch (Exception exc)
 
230
                        {
 
231
                        }
 
232
                }
 
233
        }
 
234
        
 
235
        private void writeChemFile(IChemFile file) throws Exception {
 
236
                List moleculesList = ChemFileManipulator.getAllAtomContainers(file);
 
237
                for (int i=0; i<moleculesList.size(); i++) {
 
238
                        writeMolecule(file.getBuilder().newMolecule((IAtomContainer)moleculesList.get(i)));
 
239
                }
 
240
        }
 
241
        
 
242
 
 
243
        /**
 
244
         * Writes a Molecule to an OutputStream in MDL sdf format.
 
245
         *
 
246
         * @param   container  Molecule that is written to an OutputStream
 
247
         */
 
248
    public void writeMolecule(IMolecule container) throws Exception {
 
249
        String line = "";
 
250
        // taking care of the $$$$ signs:
 
251
        // we do not write such a sign at the end of the first molecule, thus we have to write on BEFORE the second molecule
 
252
        if(moleculeNumber == 2) {
 
253
          writer.write("$$$$");
 
254
          writer.newLine();
 
255
        }
 
256
        // write header block
 
257
        // lines get shortened to 80 chars, that's in the spec
 
258
        String title = (String)container.getProperty(CDKConstants.TITLE);
 
259
        if (title == null) title = "";
 
260
        if(title.length()>80)
 
261
          title=title.substring(0,80);
 
262
        writer.write(title + "\n");
 
263
        
 
264
        /* From CTX spec
 
265
         * This line has the format:
 
266
         * IIPPPPPPPPMMDDYYHHmmddSSssssssssssEEEEEEEEEEEERRRRRR
 
267
         * (FORTRAN: A2<--A8--><---A10-->A2I2<--F10.5-><---F12.5--><-I6-> )
 
268
         * User's first and last initials (l), program name (P),
 
269
         * date/time (M/D/Y,H:m), dimensional codes (d), scaling factors (S, s), 
 
270
         * energy (E) if modeling program input, internal registry number (R) 
 
271
         * if input through MDL form.
 
272
         * A blank line can be substituted for line 2.
 
273
         */
 
274
        writer.write("  CDK    ");
 
275
        writer.write(new SimpleDateFormat("M/d/y,H:m",Locale.US).format(
 
276
                             Calendar.getInstance(TimeZone.getDefault()).getTime())
 
277
        );
 
278
        writer.write('\n');
 
279
        
 
280
        String comment = (String)container.getProperty(CDKConstants.REMARK);
 
281
        if (comment == null) comment = "";
 
282
        if(comment.length()>80)
 
283
          comment=comment.substring(0,80);
 
284
        writer.write(comment + "\n");
 
285
        
 
286
        // write Counts line
 
287
        line += formatMDLInt(container.getAtomCount(), 3);
 
288
        line += formatMDLInt(container.getBondCount(), 3);
 
289
        line += "  0  0  0  0  0  0  0  0999 V2000\n";
 
290
        writer.write(line);
 
291
 
 
292
        // write Atom block
 
293
        for (int f = 0; f < container.getAtomCount(); f++) {
 
294
                IAtom atom = container.getAtom(f);
 
295
                line = "";
 
296
                if (atom.getPoint3d() != null) {
 
297
                        line += formatMDLFloat((float) atom.getPoint3d().x);
 
298
                        line += formatMDLFloat((float) atom.getPoint3d().y);
 
299
                        line += formatMDLFloat((float) atom.getPoint3d().z) + " ";
 
300
                } else if (atom.getPoint2d() != null) {
 
301
                        line += formatMDLFloat((float) atom.getPoint2d().x);
 
302
                        line += formatMDLFloat((float) atom.getPoint2d().y);
 
303
                        line += "    0.0000 ";
 
304
                } else {
 
305
                        // if no coordinates available, then output a number
 
306
                        // of zeros
 
307
                        line += formatMDLFloat((float)0.0);
 
308
                        line += formatMDLFloat((float)0.0);
 
309
                        line += formatMDLFloat((float)0.0) + " ";
 
310
                }
 
311
                if(container.getAtom(f) instanceof IPseudoAtom)
 
312
                        line += formatMDLString(((IPseudoAtom) container.getAtom(f)).getLabel(), 3);
 
313
                else
 
314
                        line += formatMDLString(container.getAtom(f).getSymbol(), 3); 
 
315
                line += " 0  0  0  0  0  0  0  0  0  0  0  0";
 
316
                writer.write(line);
 
317
                writer.newLine();
 
318
        }
 
319
 
 
320
        // write Bond block
 
321
        Iterator bonds = container.bonds();
 
322
        while (bonds.hasNext()) {
 
323
            IBond bond = (IBond) bonds.next();
 
324
 
 
325
                if (bond.getAtomCount() != 2) {
 
326
                        logger.warn("Skipping bond with more/less than two atoms: " + bond);
 
327
                } else {
 
328
                        if (bond.getStereo() == CDKConstants.STEREO_BOND_UP_INV || 
 
329
                                        bond.getStereo() == CDKConstants.STEREO_BOND_DOWN_INV) {
 
330
                                // turn around atom coding to correct for inv stereo
 
331
                                line = formatMDLInt(container.getAtomNumber(bond.getAtom(1)) + 1,3);
 
332
                                line += formatMDLInt(container.getAtomNumber(bond.getAtom(0)) + 1,3);
 
333
                        } else {
 
334
                                line = formatMDLInt(container.getAtomNumber(bond.getAtom(0)) + 1,3);
 
335
                                line += formatMDLInt(container.getAtomNumber(bond.getAtom(1)) + 1,3);
 
336
                        }
 
337
                        line += formatMDLInt((int)bond.getOrder(),3);
 
338
                        line += "  ";
 
339
                        switch(bond.getStereo()){
 
340
                        case CDKConstants.STEREO_BOND_UP:
 
341
                                line += "1";
 
342
                                break;
 
343
                        case CDKConstants.STEREO_BOND_UP_INV:
 
344
                                line += "1";
 
345
                                break;
 
346
                        case CDKConstants.STEREO_BOND_DOWN:
 
347
                                line += "6";
 
348
                                break;
 
349
                        case CDKConstants.STEREO_BOND_DOWN_INV:
 
350
                                line += "6";
 
351
                                break;
 
352
                        default:
 
353
                                line += "0";
 
354
                        }
 
355
                        line += "  0  0  0 ";
 
356
                        writer.write(line);
 
357
                        writer.newLine();
 
358
                }
 
359
        }
 
360
 
 
361
        // write formal atomic charges
 
362
        for (int i = 0; i < container.getAtomCount(); i++) {
 
363
                IAtom atom = container.getAtom(i);
 
364
            int charge = atom.getFormalCharge();
 
365
            if (charge != 0) {
 
366
                writer.write("M  CHG  1 ");
 
367
                writer.write(formatMDLInt(i+1,3));
 
368
                writer.write(" ");
 
369
                writer.write(formatMDLInt(charge,3));
 
370
                writer.newLine();
 
371
            }
 
372
        }
 
373
        
 
374
        // write formal isotope information
 
375
        for (int i = 0; i < container.getAtomCount(); i++) {
 
376
                IAtom atom = container.getAtom(i);
 
377
            if (!(atom instanceof IPseudoAtom)) {
 
378
                int atomicMass = atom.getMassNumber();
 
379
                int majorMass = IsotopeFactory.getInstance(atom.getBuilder()).getMajorIsotope(atom.getSymbol()).getMassNumber();
 
380
                if (atomicMass != 0 && atomicMass != majorMass) {
 
381
                    writer.write("M  ISO  1 ");
 
382
                    writer.write(formatMDLInt(i+1,3));
 
383
                    writer.write(" ");
 
384
                    writer.write(formatMDLInt(atomicMass,3));
 
385
                    writer.newLine();
 
386
                }
 
387
            }
 
388
        }
 
389
        
 
390
        // close molecule
 
391
        writer.write("M  END");
 
392
        writer.newLine();
 
393
        //write sdfields, if any
 
394
        if(sdFields!=null){
 
395
          Set set = sdFields.keySet();
 
396
          Iterator iterator = set.iterator();
 
397
          while (iterator.hasNext()) {
 
398
            Object element = iterator.next();
 
399
            writer.write("> <"+(String)element+">");
 
400
            writer.newLine();
 
401
            writer.write(sdFields.get(element).toString());
 
402
            writer.newLine();
 
403
            writer.newLine();
 
404
          }
 
405
        }
 
406
        // taking care of the $$$$ signs:
 
407
        // we write such a sign at the end of all except the first molecule
 
408
        if(moleculeNumber != 1) {
 
409
          writer.write("$$$$");
 
410
          writer.newLine();
 
411
        }
 
412
        moleculeNumber++;
 
413
        writer.flush();
 
414
    }
 
415
 
 
416
        /**
 
417
         * Formats an int to fit into the connectiontable and changes it 
 
418
     * to a String.
 
419
         *
 
420
         * @param   i  The int to be formated
 
421
         * @param   l  Length of the String
 
422
         * @return     The String to be written into the connectiontable
 
423
         */
 
424
    private String formatMDLInt(int i, int l) {
 
425
        String s = "", fs = "";
 
426
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.ENGLISH);
 
427
        nf.setParseIntegerOnly(true);
 
428
        nf.setMinimumIntegerDigits(1);
 
429
        nf.setMaximumIntegerDigits(l);
 
430
        nf.setGroupingUsed(false);
 
431
        s = nf.format(i);
 
432
        l = l - s.length();
 
433
        for (int f = 0; f < l; f++)
 
434
            fs += " ";
 
435
        fs += s;
 
436
        return fs;
 
437
    }
 
438
        
 
439
        
 
440
 
 
441
 
 
442
        /**
 
443
         * Formats a float to fit into the connectiontable and changes it
 
444
     * to a String.
 
445
         *
 
446
         * @param   fl  The float to be formated
 
447
         * @return      The String to be written into the connectiontable
 
448
         */
 
449
    private String formatMDLFloat(float fl) {
 
450
        String s = "", fs = "";
 
451
        int l;
 
452
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.ENGLISH);
 
453
        nf.setMinimumIntegerDigits(1);
 
454
        nf.setMaximumIntegerDigits(4);
 
455
        nf.setMinimumFractionDigits(4);
 
456
        nf.setMaximumFractionDigits(4);
 
457
        nf.setGroupingUsed(false);
 
458
        s = nf.format(fl);
 
459
        l = 10 - s.length();
 
460
        for (int f = 0; f < l; f++)
 
461
            fs += " ";
 
462
        fs += s;
 
463
        return fs;
 
464
    }
 
465
 
 
466
 
 
467
 
 
468
        /**
 
469
         * Formats a String to fit into the connectiontable.
 
470
         *
 
471
         * @param   s    The String to be formated
 
472
         * @param   le   The length of the String
 
473
         * @return       The String to be written in the connectiontable
 
474
         */
 
475
    private String formatMDLString(String s, int le) {
 
476
        s = s.trim();
 
477
        if (s.length() > le)
 
478
            return s.substring(0, le);
 
479
        int l;
 
480
        l = le - s.length();
 
481
        for (int f = 0; f < l; f++)
 
482
            s += " ";
 
483
        return s;
 
484
    }
 
485
 
 
486
}
 
487
 
 
488