/* Copyright (C) 1997-2007 Christoph Steinbeck <steinbeck@users.sourceforge.net>
* 2010 Egon Willighagen <egonw@users.sourceforge.net>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
* All we ask is that proper credit is given for our work, which includes
* - but is not limited to - adding the above copyright notice to the beginning
* of your source code files, and to any copyright notice that you may distribute
* with programs based on this work.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.cdk.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.vecmath.Point2d;
import javax.vecmath.Point3d;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.config.IsotopeFactory;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemFile;
import org.openscience.cdk.interfaces.IChemModel;
import org.openscience.cdk.interfaces.IChemObject;
import org.openscience.cdk.interfaces.IChemSequence;
import org.openscience.cdk.interfaces.IIsotope;
import org.openscience.cdk.interfaces.IMolecule;
import org.openscience.cdk.interfaces.IMoleculeSet;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.io.formats.IResourceFormat;
import org.openscience.cdk.io.formats.MDLV2000Format;
import org.openscience.cdk.io.setting.BooleanIOSetting;
import org.openscience.cdk.io.setting.IOSetting;
import org.openscience.cdk.tools.ILoggingTool;
import org.openscience.cdk.tools.LoggingToolFactory;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
/**
* Reads a molecule from an MDL MOL or SDF file {@cdk.cite DAL92}. An SD files
* is read into a {@link IChemSequence} of {@link IChemModel}'s. Each IChemModel will contain one
* Molecule.
*
* <p>From the Atom block it reads atomic coordinates, element types and
* formal charges. From the Bond block it reads the bonds and the orders.
* Additionally, it reads 'M CHG', 'G ', 'M RAD' and 'M ISO' lines from the
* property block.
*
* <p>If all z coordinates are 0.0, then the xy coordinates are taken as
* 2D, otherwise the coordinates are read as 3D.
*
* <p>The title of the MOL file is read and can be retrieved with:
* <pre>
* molecule.getProperty(CDKConstants.TITLE);
* </pre>
*
* <p>RGroups which are saved in the MDL molfile as R#, are renamed according to their appearance,
* e.g. the first R# is named R1. With PseudAtom.getLabel() "R1" is returned (instead of R#).
* This is introduced due to the SAR table generation procedure of Scitegics PipelinePilot.
*
* @cdk.module io
* @cdk.githash
*
* @author steinbeck
* @author Egon Willighagen
* @cdk.created 2000-10-02
* @cdk.keyword file format, MDL molfile
* @cdk.keyword file format, SDF
* @cdk.bug 1587283
*/
@TestClass("org.openscience.cdk.io.MDLV2000ReaderTest")
public class MDLV2000Reader extends DefaultChemObjectReader {
BufferedReader input = null;
private static ILoggingTool logger =
LoggingToolFactory.createLoggingTool(MDLV2000Reader.class);
private BooleanIOSetting forceReadAs3DCoords;
private BooleanIOSetting interpretHydrogenIsotopes;
//Keep track of atoms and the lines they were on in the atom block.
private List<IAtom> atomsByLinePosition;
public MDLV2000Reader() {
this(new StringReader(""));
}
/**
* Constructs a new MDLReader that can read Molecule from a given InputStream.
*
*@param in The InputStream to read from
*/
public MDLV2000Reader(InputStream in) {
this(new InputStreamReader(in));
}
public MDLV2000Reader(InputStream in, Mode mode) {
this(new InputStreamReader(in), mode);
}
/**
* Contructs a new MDLReader that can read Molecule from a given Reader.
*
*@param in The Reader to read from
*/
public MDLV2000Reader(Reader in) {
this(in, Mode.RELAXED);
}
public MDLV2000Reader(Reader in, Mode mode) {
input = new BufferedReader(in);
initIOSettings();
super.mode = mode;
}
@TestMethod("testGetFormat")
public IResourceFormat getFormat() {
return MDLV2000Format.getInstance();
}
@TestMethod("testSetReader_Reader")
public void setReader(Reader input) throws CDKException {
if (input instanceof BufferedReader) {
this.input = (BufferedReader)input;
} else {
this.input = new BufferedReader(input);
}
}
@TestMethod("testSetReader_InputStream")
public void setReader(InputStream input) throws CDKException {
setReader(new InputStreamReader(input));
}
@TestMethod("testAccepts")
public boolean accepts(Class<? extends IChemObject> classObject) {
Class<?>[] interfaces = classObject.getInterfaces();
for (int i=0; i<interfaces.length; i++) {
if (IChemFile.class.equals(interfaces[i])) return true;
if (IChemModel.class.equals(interfaces[i])) return true;
if (IMolecule.class.equals(interfaces[i])) return true;
}
Class superClass = classObject.getSuperclass();
if (superClass != null) return this.accepts(superClass);
return false;
}
/**
* Takes an object which subclasses IChemObject, e.g. Molecule, and will read
* this (from file, database, internet etc). If the specific implementation
* does not support a specific IChemObject it will throw an Exception.
*
*@param object The object that subclasses
* IChemObject
*@return The IChemObject read
*@exception CDKException
*/
public <T extends IChemObject> T read(T object) throws CDKException {
if (object instanceof IChemFile) {
return (T)readChemFile((IChemFile)object);
} else if (object instanceof IChemModel) {
return (T)readChemModel((IChemModel)object);
} else if (object instanceof IMolecule) {
return (T)readMolecule((IMolecule)object);
} else {
throw new CDKException("Only supported are ChemFile and Molecule.");
}
}
private IChemModel readChemModel(IChemModel chemModel) throws CDKException {
IMoleculeSet setOfMolecules = chemModel.getMoleculeSet();
if (setOfMolecules == null) {
setOfMolecules = chemModel.getBuilder().newMoleculeSet();
}
IMolecule m = readMolecule(chemModel.getBuilder().newMolecule());
if (m != null) {
setOfMolecules.addMolecule(m);
}
chemModel.setMoleculeSet(setOfMolecules);
return chemModel;
}
/**
* Read a ChemFile from a file in MDL SDF format.
*
* @return The ChemFile that was read from the MDL file.
*/
private IChemFile readChemFile(IChemFile chemFile) throws CDKException {
IChemSequence chemSequence = chemFile.getBuilder().newChemSequence();
IChemModel chemModel = chemFile.getBuilder().newChemModel();
IMoleculeSet setOfMolecules = chemFile.getBuilder().newMoleculeSet();
IMolecule m = readMolecule(chemFile.getBuilder().newMolecule());
if (m != null) {
setOfMolecules.addMolecule(m);
}
chemModel.setMoleculeSet(setOfMolecules);
chemSequence.addChemModel(chemModel);
setOfMolecules = chemFile.getBuilder().newMoleculeSet();
chemModel = chemFile.getBuilder().newChemModel();
String str;
try {
String line;
while ((line = input.readLine()) != null) {
logger.debug("line: ", line);
// apparently, this is a SDF file, continue with
// reading mol files
str = new String(line);
if (str.equals("$$$$")) {
m = readMolecule(chemFile.getBuilder().newMolecule());
if (m != null) {
setOfMolecules.addMolecule(m);
chemModel.setMoleculeSet(setOfMolecules);
chemSequence.addChemModel(chemModel);
setOfMolecules = chemFile.getBuilder().newMoleculeSet();
chemModel = chemFile.getBuilder().newChemModel();
}
} else {
// here the stuff between 'M END' and '$$$$'
if (m != null) {
// ok, the first lines should start with '>'
String fieldName = null;
if (str.startsWith("> ")) {
// ok, should extract the field name
str.substring(2); // String content =
int index = str.indexOf("<");
if (index != -1) {
int index2 = str.substring(index).indexOf(">");
if (index2 != -1) {
fieldName = str.substring(
index+1,
index+index2
);
}
}
}
if (line == null) {
throw new CDKException("Expecting data line here, but found null!");
}
StringBuilder data = new StringBuilder();
int dataLineCount = 0;
boolean lineIsContinued = false;
while ((line = input.readLine()) != null &&
line.trim().length() > 0) {
if (line.equals("$$$$")) {
logger.error("Expecting data line here, but found end of molecule: ", line);
break;
}
logger.debug("data line: ", line);
lineIsContinued = false; // reset property
dataLineCount++;
// preserve newlines, unless the line is exactly 80 chars;
// in that case it is assumed to continue on the next line.
// See MDL documentation.
if (!lineIsContinued && dataLineCount > 1)
data.append(System.getProperty("line.separator"));
// add the data line
data.append(line);
// check if the line will be continued on the next line
if (line.length() == 80) lineIsContinued = true;
}
if (fieldName != null) {
logger.info("fieldName, data: ", fieldName, ", ", data);
m.setProperty(fieldName, data.toString());
}
}
}
}
} catch (CDKException cdkexc) {
throw cdkexc;
} catch (Exception exception) {
String error = "Error while parsing SDF";
logger.error(error);
logger.debug(exception);
throw new CDKException(error, exception);
}
try {
input.close();
} catch (Exception exc) {
String error = "Error while closing file: " + exc.getMessage();
logger.error(error);
throw new CDKException(error, exc);
}
chemFile.addChemSequence(chemSequence);
return chemFile;
}
/**
* Read a Molecule from a file in MDL sd format
*
*@return The Molecule that was read from the MDL file.
*/
private IMolecule readMolecule(IMolecule molecule) throws CDKException {
logger.debug("Reading new molecule");
int linecount = 0;
int atoms = 0;
int bonds = 0;
int atom1 = 0;
int atom2 = 0;
int order = 0;
IBond.Stereo stereo = (IBond.Stereo)CDKConstants.UNSET;
int RGroupCounter=1;
int Rnumber=0;
String [] rGroup=null;
double x = 0.0;
double y = 0.0;
double z = 0.0;
double totalX = 0.0;
double totalY = 0.0;
double totalZ = 0.0;
//int[][] conMat = new int[0][0];
//String help;
IAtom atom;
String line = "";
//A map to keep track of R# atoms so that RGP line can be parsed
Map<Integer,IPseudoAtom> rAtoms = new HashMap<Integer,IPseudoAtom>();
try {
IsotopeFactory isotopeFactory = IsotopeFactory.getInstance(molecule.getBuilder());
logger.info("Reading header");
line = input.readLine(); linecount++;
if (line == null) {
return null;
}
logger.debug("Line " + linecount + ": " + line);
if (line.startsWith("$$$$")) {
logger.debug("File is empty, returning empty molecule");
return molecule;
}
if (line.length() > 0) {
molecule.setProperty(CDKConstants.TITLE, line);
}
line = input.readLine(); linecount++;
logger.debug("Line " + linecount + ": " + line);
line = input.readLine(); linecount++;
logger.debug("Line " + linecount + ": " + line);
if (line.length() > 0) {
molecule.setProperty(CDKConstants.REMARK, line);
}
logger.info("Reading rest of file");
line = input.readLine(); linecount++;
logger.debug("Line " + linecount + ": " + line);
// if the line is empty we hav a problem - either a malformed
// molecule entry or just extra new lines at the end of the file
if (line.length() == 0) {
// read till the next $$$$ or EOF
while (true) {
line = input.readLine(); linecount++;
if (line == null) {
return null;
}
if (line.startsWith("$$$$")) {
return molecule; // an empty molecule
}
}
}
// check the CT block version
if (line.contains("V3000") || line.contains("v3000")) {
handleError("This file must be read with the MDLV3000Reader.");
} else if (!line.contains("V2000") && !line.contains("v2000")) {
handleError("This file must be read with the MDLReader.");
}
atoms = Integer.parseInt(line.substring(0, 3).trim());
logger.debug("Atomcount: " + atoms);
bonds = Integer.parseInt(line.substring(3, 6).trim());
logger.debug("Bondcount: " + bonds);
// read ATOM block
logger.info("Reading atom block");
atomsByLinePosition = new ArrayList<IAtom>();
atomsByLinePosition.add(null); // 0 is not a valid position
int atomBlockLineNumber=0;
for (int f = 0; f < atoms; f++) {
line = input.readLine(); linecount++; atomBlockLineNumber++;
x = Double.parseDouble(line.substring(0, 10).trim());
y = Double.parseDouble(line.substring(10, 20).trim());
z = Double.parseDouble(line.substring(20, 30).trim());
// *all* values should be zero, not just the sum
totalX += Math.abs(x);
totalY += Math.abs(y);
totalZ += Math.abs(z);
logger.debug("Coordinates: " + x + "; " + y + "; " + z);
String element = line.substring(31,34).trim();
logger.debug("Atom type: ", element);
if (isotopeFactory.isElement(element)) {
atom = isotopeFactory.configure(molecule.getBuilder().newAtom(element));
} else if ("A".equals(element)) {
atom = molecule.getBuilder().newPseudoAtom(element);
} else if ("Q".equals(element)) {
atom = molecule.getBuilder().newPseudoAtom(element);
} else if ("*".equals(element)) {
atom = molecule.getBuilder().newPseudoAtom(element);
} else if ("LP".equals(element)) {
atom = molecule.getBuilder().newPseudoAtom(element);
} else if ("L".equals(element)) {
atom = molecule.getBuilder().newPseudoAtom(element);
} else if ( element.equals("R") ||
(element.length() > 0 && element.charAt(0) == 'R')){
logger.debug("Atom ", element, " is not an regular element. Creating a PseudoAtom.");
//check if the element is R
rGroup=element.split("^R");
atom=null;
if (rGroup.length >1){
try{
Rnumber= Integer.valueOf(rGroup[(rGroup.length - 1)]);
RGroupCounter=Rnumber;
element="R"+Rnumber;
atom = molecule.getBuilder().newPseudoAtom(element);
}catch(Exception ex){
// This happens for atoms labeled "R#".
// The Rnumber may be set later on, using RGP line
atom = molecule.getBuilder().newPseudoAtom("R");
rAtoms.put(atomBlockLineNumber,(IPseudoAtom)atom);
}
}
else {
atom = molecule.getBuilder().newPseudoAtom("R");
}
} else {
handleError(
"Invalid element type. Must be an existing " +
"element, or one in: A, Q, L, LP, *.",
linecount, 32, 35
);
atom = molecule.getBuilder().newPseudoAtom(element);
}
// store as 3D for now, convert to 2D (if totalZ == 0.0) later
atom.setPoint3d(new Point3d(x, y, z));
// parse further fields
String massDiffString = line.substring(34,36).trim();
logger.debug("Mass difference: ", massDiffString);
if (!(atom instanceof IPseudoAtom)) {
try {
int massDiff = Integer.parseInt(massDiffString);
if (massDiff != 0) {
IIsotope major = IsotopeFactory.getInstance(molecule.getBuilder()).getMajorIsotope(element);
atom.setMassNumber(major.getMassNumber() + massDiff);
}
} catch (Exception exception) {
handleError(
"Could not parse mass difference field.",
linecount, 35, 37,
exception
);
}
} else {
logger.error("Cannot set mass difference for a non-element!");
}
String chargeCodeString = line.substring(36,39).trim();
logger.debug("Atom charge code: ", chargeCodeString);
int chargeCode = Integer.parseInt(chargeCodeString);
if (chargeCode == 0) {
// uncharged species
} else if (chargeCode == 1) {
atom.setFormalCharge(+3);
} else if (chargeCode == 2) {
atom.setFormalCharge(+2);
} else if (chargeCode == 3) {
atom.setFormalCharge(+1);
} else if (chargeCode == 4) {
} else if (chargeCode == 5) {
atom.setFormalCharge(-1);
} else if (chargeCode == 6) {
atom.setFormalCharge(-2);
} else if (chargeCode == 7) {
atom.setFormalCharge(-3);
}
try {
// read the mmm field as position 61-63
String reactionAtomIDString = line.substring(60,63).trim();
logger.debug("Parsing mapping id: ", reactionAtomIDString);
try {
int reactionAtomID = Integer.parseInt(reactionAtomIDString);
if (reactionAtomID != 0) {
atom.setID(reactionAtomIDString);
}
} catch (Exception exception) {
logger.error("Mapping number ", reactionAtomIDString, " is not an integer.");
logger.debug(exception);
}
} catch (Exception exception) {
// older mol files don't have all these fields...
logger.warn("A few fields are missing. Older MDL MOL file?");
}
//shk3: This reads shifts from after the molecule. I don't think this is an official format, but I saw it frequently 80=>78 for alk
if(line.length()>=78){
double shift=Double.parseDouble(line.substring(69,80).trim());
atom.setProperty("first shift", shift);
}
if(line.length()>=87){
double shift=Double.parseDouble(line.substring(79,87).trim());
atom.setProperty("second shift", shift);
}
molecule.addAtom(atom);
atomsByLinePosition.add(atom);
}
// convert to 2D, if totalZ == 0
if (totalX == 0.0 && totalY == 0.0 && totalZ == 0.0) {
logger.info("All coordinates are 0.0");
for (IAtom atomToUpdate : molecule.atoms()) {
atomToUpdate.setPoint3d(null);
}
} else if (totalZ == 0.0 && !forceReadAs3DCoords.isSet()) {
logger.info("Total 3D Z is 0.0, interpreting it as a 2D structure");
Iterator<IAtom> atomsToUpdate = molecule.atoms().iterator();
while (atomsToUpdate.hasNext()) {
IAtom atomToUpdate = atomsToUpdate.next();
Point3d p3d = atomToUpdate.getPoint3d();
if (p3d != null) {
atomToUpdate.setPoint2d(new Point2d(p3d.x, p3d.y));
atomToUpdate.setPoint3d(null);
}
}
}
// read BOND block
logger.info("Reading bond block");
for (int f = 0; f < bonds; f++) {
line = input.readLine(); linecount++;
atom1 = Integer.parseInt(line.substring(0, 3).trim());
atom2 = Integer.parseInt(line.substring(3, 6).trim());
order = Integer.parseInt(line.substring(6, 9).trim());
if (line.length() >= 12) {
int mdlStereo = line.length() > 12
? Integer.parseInt(line.substring(9, 12).trim())
: Integer.parseInt(line.substring(9).trim());
if (mdlStereo == 1) {
// MDL up bond
stereo = IBond.Stereo.UP;
} else if (mdlStereo == 6) {
// MDL down bond
stereo = IBond.Stereo.DOWN;
} else if (mdlStereo == 0) {
if (order == 2) {
// double bond stereo defined by coordinates
stereo = IBond.Stereo.E_Z_BY_COORDINATES;
} else {
// bond has no stereochemistry
stereo = IBond.Stereo.NONE;
}
} else if (mdlStereo == 3 && order == 2) {
// unknown E/Z stereochemistry
stereo = IBond.Stereo.E_OR_Z;
} else if (mdlStereo == 4) {
//MDL bond undefined
stereo = (IBond.Stereo)CDKConstants.UNSET;
}
} else {
handleError(
"Missing expected stereo field at line: ",
linecount, 10, 12
);
}
if (logger.isDebugEnabled()) {
logger.debug("Bond: " + atom1 + " - " + atom2 + "; order " + order);
}
// interpret CTfile's special bond orders
IAtom a1 = molecule.getAtom(atom1 - 1);
IAtom a2 = molecule.getAtom(atom2 - 1);
IBond newBond = null;
if (order == 1) {
newBond = molecule.getBuilder().newBond(a1, a2, IBond.Order.SINGLE, stereo);
} else if (order == 2) {
newBond = molecule.getBuilder().newBond(a1, a2, IBond.Order.DOUBLE, stereo);
} else if (order == 3) {
newBond = molecule.getBuilder().newBond(a1, a2, IBond.Order.TRIPLE, stereo);
} else if (order == 4) {
// aromatic bond
newBond = molecule.getBuilder().newBond(a1, a2, IBond.Order.SINGLE, stereo);
// mark both atoms and the bond as aromatic
newBond.setFlag(CDKConstants.ISAROMATIC, true);
a1.setFlag(CDKConstants.ISAROMATIC, true);
a2.setFlag(CDKConstants.ISAROMATIC, true);
}
else {
throw new CDKException
("Detected 'query bond type ' (value="+order +")."+
" Could not create regular molecule.");
}
molecule.addBond(newBond);
}
// read PROPERTY block
logger.info("Reading property block");
while (true) {
line = input.readLine(); linecount++;
if (line == null) {
handleError(
"The expected property block is missing!",
linecount, 0, 0
);
}
if (line.startsWith("M END")) break;
boolean lineRead = false;
if (line.startsWith("M CHG")) {
// FIXME: if this is encountered for the first time, all
// atom charges should be set to zero first!
int infoCount = Integer.parseInt(line.substring(6,9).trim());
StringTokenizer st = new StringTokenizer(line.substring(9));
for (int i=1; i <= infoCount; i++) {
String token = st.nextToken();
int atomNumber = Integer.parseInt(token.trim());
token = st.nextToken();
int charge = Integer.parseInt(token.trim());
molecule.getAtom(atomNumber - 1).setFormalCharge(charge);
}
} else if (line.matches("^A \\d+")) {
// Reads the pseudo atom property from the mol file
// The atom number of the to replaced atom
int aliasAtomNumber = Integer.parseInt(line.replaceFirst("^A ", "")) - RGroupCounter;
line = input.readLine(); linecount++;
String[] aliasArray = line.split("\\\\");
// name of the alias atom like R1 odr R2 etc.
String alias = "";
for (int i = 0; i < aliasArray.length; i++) {
alias += aliasArray[i];
}
IAtom aliasAtom = molecule.getAtom(aliasAtomNumber);
IAtom newPseudoAtom = molecule.getBuilder().newPseudoAtom(alias);
if(aliasAtom.getPoint2d() != null) {
newPseudoAtom.setPoint2d(aliasAtom.getPoint2d());
}
if(aliasAtom.getPoint3d() != null) {
newPseudoAtom.setPoint3d(aliasAtom.getPoint3d());
}
molecule.addAtom(newPseudoAtom);
List<IBond> bondsOfAliasAtom = molecule.getConnectedBondsList(aliasAtom);
for (int i = 0; i < bondsOfAliasAtom.size(); i++) {
IBond bondOfAliasAtom = bondsOfAliasAtom.get(i);
IAtom connectedToAliasAtom = bondOfAliasAtom.getConnectedAtom(aliasAtom);
IBond newBond = bondOfAliasAtom.getBuilder().newBond();
newBond.setAtoms(new IAtom[] {connectedToAliasAtom, newPseudoAtom});
newBond.setOrder(bondOfAliasAtom.getOrder());
molecule.addBond(newBond);
molecule.removeBond(aliasAtom, connectedToAliasAtom);
}
molecule.removeAtom(aliasAtom);
RGroupCounter++;
} else if (line.startsWith("M ISO")) {
try {
String countString = line.substring(6,10).trim();
int infoCount = Integer.parseInt(countString);
StringTokenizer st = new StringTokenizer(line.substring(10));
for (int i=1; i <= infoCount; i++) {
int atomNumber = Integer.parseInt(st.nextToken().trim());
int absMass = Integer.parseInt(st.nextToken().trim());
if (absMass != 0) {
IAtom isotope = molecule.getAtom(atomNumber - 1);
isotope.setMassNumber(absMass);
}
}
} catch (NumberFormatException exception) {
String error = "Error (" + exception.getMessage() + ") while parsing line "
+ linecount + ": " + line + " in property block.";
logger.error(error);
handleError(
"NumberFormatException in isotope information.",
linecount, 7, 11,
exception
);
}
} else if (line.startsWith("M RAD")) {
try {
String countString = line.substring(6,9).trim();
int infoCount = Integer.parseInt(countString);
StringTokenizer st = new StringTokenizer(line.substring(9));
for (int i=1; i <= infoCount; i++) {
int atomNumber = Integer.parseInt(st.nextToken().trim());
int spinMultiplicity = Integer.parseInt(st.nextToken().trim());
if (spinMultiplicity > 1) {
IAtom radical = molecule.getAtom(atomNumber - 1);
for (int j=2; j <= spinMultiplicity; j++) {
// 2 means doublet -> one unpaired electron
// 3 means triplet -> two unpaired electron
molecule.addSingleElectron(molecule.getBuilder().newSingleElectron(radical));
}
}
}
} catch (NumberFormatException exception) {
String error = "Error (" + exception.getMessage() + ") while parsing line "
+ linecount + ": " + line + " in property block.";
logger.error(error);
handleError(
"NumberFormatException in radical information",
linecount, 7, 10,
exception
);
}
} else if (line.startsWith("G ")) {
try {
String atomNumberString = line.substring(3,6).trim();
int atomNumber = Integer.parseInt(atomNumberString);
//String whatIsThisString = line.substring(6,9).trim();
String atomName = input.readLine();
// convert Atom into a PseudoAtom
IAtom prevAtom = molecule.getAtom(atomNumber - 1);
IPseudoAtom pseudoAtom = molecule.getBuilder().newPseudoAtom(atomName);
if (prevAtom.getPoint2d() != null) {
pseudoAtom.setPoint2d(prevAtom.getPoint2d());
}
if (prevAtom.getPoint3d() != null) {
pseudoAtom.setPoint3d(prevAtom.getPoint3d());
}
AtomContainerManipulator.replaceAtomByAtom(molecule, prevAtom, pseudoAtom);
} catch (NumberFormatException exception) {
String error = "Error (" + exception.toString() + ") while parsing line "
+ linecount + ": " + line + " in property block.";
logger.error(error);
handleError(
"NumberFormatException in group information",
linecount, 4, 7,
exception
);
}
} else if (line.startsWith("M RGP")) {
StringTokenizer st = new StringTokenizer(line);
//Ignore first 3 tokens (overhead).
st.nextToken(); st.nextToken(); st.nextToken();
//Process the R group numbers as defined in RGP line.
while (st.hasMoreTokens()) {
Integer position = new Integer(st.nextToken());
Rnumber = new Integer(st.nextToken());
IPseudoAtom pseudoAtom = rAtoms.get(position);
if (pseudoAtom!=null) {
pseudoAtom.setLabel("R"+Rnumber);
}
}
}
if (!lineRead) {
logger.warn("Skipping line in property block: ", line);
}
}
} catch (CDKException exception) {
String error = "Error while parsing line " + linecount + ": " + line + " -> " + exception.getMessage();
logger.error(error);
logger.debug(exception);
throw exception;
} catch (Exception exception) {
exception.printStackTrace();
String error = "Error while parsing line " + linecount + ": " + line + " -> " + exception.getMessage();
logger.error(error);
logger.debug(exception);
handleError(
"Error while parsing line: " + line,
linecount, 0, 0,
exception
);
}
if (interpretHydrogenIsotopes.isSet()) {
fixHydrogenIsotopes(molecule);
}
return molecule;
}
private void fixHydrogenIsotopes(IMolecule molecule) {
Iterator<IAtom> atoms = molecule.atoms().iterator();
while (atoms.hasNext()) {
IAtom atom = atoms.next();
if (atom instanceof IPseudoAtom) {
IPseudoAtom pseudo = (IPseudoAtom)atom;
Integer massNumber = atom.getMassNumber();
if ("D".equals(pseudo.getLabel()) &&
massNumber != null && massNumber == 2) {
IAtom newAtom = molecule.getBuilder().newAtom(atom);
newAtom.setSymbol("H");
AtomContainerManipulator.replaceAtomByAtom(molecule, atom, newAtom);
} else if ("T".equals(pseudo.getLabel()) &&
massNumber != null && massNumber == 3) {
IAtom newAtom = molecule.getBuilder().newAtom(atom);
newAtom.setSymbol("H");
AtomContainerManipulator.replaceAtomByAtom(molecule, atom, newAtom);
}
}
}
}
@TestMethod("testClose")
public void close() throws IOException {
input.close();
}
private void initIOSettings() {
forceReadAs3DCoords = new BooleanIOSetting("ForceReadAs3DCoordinates", IOSetting.LOW,
"Should coordinates always be read as 3D?",
"false");
interpretHydrogenIsotopes = new BooleanIOSetting("InterpretHydrogenIsotopes", IOSetting.LOW,
"Should D and T be interpreted as hydrogen isotopes?",
"true");
}
public void customizeJob() {
fireIOSettingQuestion(forceReadAs3DCoords);
fireIOSettingQuestion(interpretHydrogenIsotopes);
}
public IOSetting[] getIOSettings() {
IOSetting[] settings = new IOSetting[2];
settings[0] = forceReadAs3DCoords;
settings[1] = interpretHydrogenIsotopes;
return settings;
}
public List<IAtom> getAtomsByLinePosition() {
return atomsByLinePosition;
}
}