From 074afb474e343a9c54aed63adb0ad98938b36524 Mon Sep 17 00:00:00 2001 From: John Mayfield Date: Fri, 17 Dec 2021 15:24:02 +0000 Subject: [PATCH] First pass at moving from JNA to JNI inchi - some tests need adjusting. --- storage/inchi/pom.xml | 21 +- .../java/net/sf/jniinchi/INCHI_OPTION.java | 102 ++++++ .../main/java/net/sf/jniinchi/INCHI_RET.java | 48 +++ .../openscience/cdk/inchi/InChIGenerator.java | 295 +++++++++--------- .../cdk/inchi/InChIGeneratorFactory.java | 7 - .../cdk/inchi/InChIOptionParser.java | 102 ++++++ .../cdk/inchi/InChIToStructure.java | 266 ++++++++-------- .../cdk/inchi/JniInChIInputAdapter.java | 139 --------- .../invariant/InChINumbersToolsTest.java | 2 +- .../cdk/inchi/InChIGeneratorTest.java | 3 +- 10 files changed, 527 insertions(+), 458 deletions(-) create mode 100644 storage/inchi/src/main/java/net/sf/jniinchi/INCHI_OPTION.java create mode 100644 storage/inchi/src/main/java/net/sf/jniinchi/INCHI_RET.java create mode 100644 storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIOptionParser.java delete mode 100644 storage/inchi/src/main/java/org/openscience/cdk/inchi/JniInChIInputAdapter.java diff --git a/storage/inchi/pom.xml b/storage/inchi/pom.xml index 1eebb95a7e2..54ec9a32a0b 100644 --- a/storage/inchi/pom.xml +++ b/storage/inchi/pom.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - + cdk-storage org.openscience.cdk @@ -11,7 +11,7 @@ cdk-inchi - + cdk-inchi @@ -19,14 +19,9 @@ vecmath - net.sf.jni-inchi - jni-inchi - - - log4j - log4j - - + io.github.dan2097 + jna-inchi-core + 1.0.1 org.apache.logging.log4j @@ -95,7 +90,7 @@ cdk-data ${project.parent.version} test-jar - test + test ${project.groupId} @@ -108,7 +103,7 @@ cdk-test ${project.parent.version} test-jar - test + test ${project.groupId} @@ -127,7 +122,7 @@ cdk-testdata ${project.parent.version} test-jar - test + test diff --git a/storage/inchi/src/main/java/net/sf/jniinchi/INCHI_OPTION.java b/storage/inchi/src/main/java/net/sf/jniinchi/INCHI_OPTION.java new file mode 100644 index 00000000000..8d2f563ddcd --- /dev/null +++ b/storage/inchi/src/main/java/net/sf/jniinchi/INCHI_OPTION.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 John Mayfield + * + * 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. + * + * 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 net.sf.jniinchi; + +import io.github.dan2097.jnainchi.InchiFlag; + +/** + * This class provides backwards compatibility of JNA-INCHI with JNI-INCHI, this enum was exposed in the CDK API. + * @author John Mayfield + */ +public enum INCHI_OPTION { + SUCF, + ChiralFlagON, + ChiralFlagOFF, + SNon, + SAbs, + SRel, + SRac, + SUU, + NEWPS, + RecMet, + FixedH, + AuxNone, + NoADP, + Compress, + DoNotAddH, + Wnumber, + OutputSDF, + WarnOnEmptyStructure, + FixSp3Bug, + FB, + SPXYZ, + SAsXYZ; + + public static INCHI_OPTION wrap(InchiFlag flag) { + switch (flag) { + case SUCF: return SUCF; + case ChiralFlagON: return ChiralFlagON; + case ChiralFlagOFF: return ChiralFlagOFF; + case SNon: return SNon; + case SRel: return SRel; + case SRac: return SRac; + case SUU: return SUU; + case RecMet: return RecMet; + case FixedH: return FixedH; + case AuxNone: return AuxNone; + case DoNotAddH: return DoNotAddH; + case WarnOnEmptyStructure: return WarnOnEmptyStructure; + + default: throw new IllegalArgumentException(flag + " not supported?"); + } + } + + public static InchiFlag wrap(INCHI_OPTION flag) { + switch (flag) { + case SUCF: return InchiFlag.SUCF; + case ChiralFlagON: return InchiFlag.ChiralFlagON; + case ChiralFlagOFF: return InchiFlag.ChiralFlagOFF; + case SNon: return InchiFlag.SNon; + case SRel: return InchiFlag.SRel; + case SRac: return InchiFlag.SRac; + case SUU: return InchiFlag.SUU; + case RecMet: return InchiFlag.RecMet; + case FixedH: return InchiFlag.FixedH; + case AuxNone: return InchiFlag.AuxNone; + case DoNotAddH: return InchiFlag.DoNotAddH; + case WarnOnEmptyStructure: return InchiFlag.WarnOnEmptyStructure; + default: + System.err.println("Unsupported flag: " + flag); + return null; + } + + // case SAbs: return SAbs; + // case NEWPS: return NEWPS; + // case NoADP: return NoADP; + // case Compress: return Compress; + // case Wnumber: return Wnumber; + // case OutputSDF: return OutputSDF; + // case FixSp3Bug: return FixSp3Bug; +// case FB: return FB; +// case SPXYZ: return SPXYZ; +// case SAsXYZ: return SAsXYZ; + } +} diff --git a/storage/inchi/src/main/java/net/sf/jniinchi/INCHI_RET.java b/storage/inchi/src/main/java/net/sf/jniinchi/INCHI_RET.java new file mode 100644 index 00000000000..0173d67f30d --- /dev/null +++ b/storage/inchi/src/main/java/net/sf/jniinchi/INCHI_RET.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 John Mayfield + * + * 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. + * + * 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 net.sf.jniinchi; + +import io.github.dan2097.jnainchi.InchiStatus; + +/** + * This class provides backwards compatibility of JNA-INCHI with JNI-INCHI, this enum was exposed in the CDK API. + * @author John Mayfield + */ +public enum INCHI_RET { + SKIP, + EOF, + OKAY, + WARNING, + ERROR, + FATAL, + UNKNOWN, + BUSY; + + public static INCHI_RET wrap(InchiStatus status) { + switch (status) { + case SUCCESS: return INCHI_RET.OKAY; + case WARNING: return INCHI_RET.WARNING; + case ERROR: return INCHI_RET.ERROR; + default: + throw new IllegalArgumentException("Unexpected status!"); + } + } +} diff --git a/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGenerator.java b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGenerator.java index ea486b3512e..c3368c72f02 100644 --- a/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGenerator.java +++ b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGenerator.java @@ -18,36 +18,23 @@ */ package org.openscience.cdk.inchi; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Collections; - -import javax.vecmath.Point2d; -import javax.vecmath.Point3d; - -import net.sf.jniinchi.INCHI_BOND_STEREO; -import net.sf.jniinchi.INCHI_BOND_TYPE; -import net.sf.jniinchi.INCHI_KEY; import net.sf.jniinchi.INCHI_OPTION; -import net.sf.jniinchi.INCHI_PARITY; -import net.sf.jniinchi.INCHI_RADICAL; import net.sf.jniinchi.INCHI_RET; -import net.sf.jniinchi.INCHI_STEREOTYPE; -import net.sf.jniinchi.JniInchiAtom; -import net.sf.jniinchi.JniInchiBond; -import net.sf.jniinchi.JniInchiException; -import net.sf.jniinchi.JniInchiInput; -import net.sf.jniinchi.JniInchiOutput; -import net.sf.jniinchi.JniInchiOutputKey; -import net.sf.jniinchi.JniInchiStereo0D; -import net.sf.jniinchi.JniInchiWrapper; - +import io.github.dan2097.jnainchi.InchiAtom; +import io.github.dan2097.jnainchi.InchiBond; +import io.github.dan2097.jnainchi.InchiBondStereo; +import io.github.dan2097.jnainchi.InchiBondType; +import io.github.dan2097.jnainchi.InchiFlag; +import io.github.dan2097.jnainchi.InchiInput; +import io.github.dan2097.jnainchi.InchiKeyOutput; +import io.github.dan2097.jnainchi.InchiKeyStatus; +import io.github.dan2097.jnainchi.InchiOptions; +import io.github.dan2097.jnainchi.InchiOutput; +import io.github.dan2097.jnainchi.InchiRadical; +import io.github.dan2097.jnainchi.InchiStereo; +import io.github.dan2097.jnainchi.InchiStereoParity; +import io.github.dan2097.jnainchi.JnaInchi; import org.openscience.cdk.CDKConstants; -import org.openscience.cdk.config.Isotopes; -import org.openscience.cdk.config.IsotopeFactory; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IAtomContainer; @@ -61,6 +48,14 @@ import org.openscience.cdk.tools.ILoggingTool; import org.openscience.cdk.tools.LoggingToolFactory; +import javax.vecmath.Point2d; +import javax.vecmath.Point3d; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + /** *

This class generates the IUPAC International Chemical Identifier (InChI) for * a CDK IAtomContainer. It places calls to a JNI wrapper for the InChI C++ library. @@ -103,9 +98,16 @@ */ public class InChIGenerator { - protected JniInchiInput input; + private static final InchiOptions DEFAULT_OPTIONS = new InchiOptions.InchiOptionsBuilder() + .withFlag(InchiFlag.AuxNone) + .withTimeoutMilliSeconds(5000) + .build(); + + protected InchiOptions options; - protected JniInchiOutput output; + protected InchiInput input; + + protected InchiOutput output; private final boolean auxNone; @@ -116,6 +118,17 @@ public class InChIGenerator { */ protected IAtomContainer atomContainer; + protected InChIGenerator(IAtomContainer atomContainer, + InchiOptions options, + boolean ignoreAromaticBonds) throws CDKException { + this.input = new InchiInput(); + this.options = options; + if (options == null) + this.options = DEFAULT_OPTIONS; + generateInchiFromCDKAtomContainer(atomContainer, ignoreAromaticBonds); + auxNone = this.options.getFlags().contains(InchiFlag.AuxNone); + } + /** *

Constructor. Generates InChI from CDK AtomContainer. * @@ -128,7 +141,7 @@ public class InChIGenerator { * error during InChI generation */ protected InChIGenerator(IAtomContainer atomContainer, boolean ignoreAromaticBonds) throws CDKException { - this(atomContainer, Collections.singletonList(INCHI_OPTION.AuxNone), ignoreAromaticBonds); + this(atomContainer, DEFAULT_OPTIONS, ignoreAromaticBonds); } /** @@ -138,21 +151,25 @@ protected InChIGenerator(IAtomContainer atomContainer, boolean ignoreAromaticBon * InChI library requires, then calls the library. * * @param atomContainer AtomContainer to generate InChI for. - * @param options Space delimited string of options to pass to InChI library. + * @param optStr Space delimited string of options to pass to InChI library. * Each option may optionally be preceded by a command line * switch (/ or -). * @param ignoreAromaticBonds if aromatic bonds should be treated as bonds of type single and double * @throws CDKException */ - protected InChIGenerator(IAtomContainer atomContainer, String options, boolean ignoreAromaticBonds) + protected InChIGenerator(IAtomContainer atomContainer, String optStr, boolean ignoreAromaticBonds) throws CDKException { - try { - input = new JniInChIInputAdapter(options); - generateInchiFromCDKAtomContainer(atomContainer, ignoreAromaticBonds); - auxNone = input.getOptions() != null && input.getOptions().contains("AuxNone"); - } catch (JniInchiException jie) { - throw new CDKException("InChI generation failed: " + jie.getMessage(), jie); + this(atomContainer, InChIOptionParser.parseString(optStr), ignoreAromaticBonds); + } + + private static InchiOptions convertJniToJnaOpts(List jniOpts) { + InchiOptions.InchiOptionsBuilder builder = new InchiOptions.InchiOptionsBuilder(); + for (INCHI_OPTION jniOpt : jniOpts) { + InchiFlag flag = INCHI_OPTION.wrap(jniOpt); + if (flag != null) + builder.withFlag(flag); } + return builder.build(); } /** @@ -162,19 +179,14 @@ protected InChIGenerator(IAtomContainer atomContainer, String options, boolean i * InChI library requires, then calls the library. * * @param atomContainer AtomContainer to generate InChI for. - * @param options List of INCHI_OPTION. + * @param opts List of INCHI_OPTION. * @param ignoreAromaticBonds if aromatic bonds should be treated as bonds of type single and double * @throws CDKException */ - protected InChIGenerator(IAtomContainer atomContainer, List options, boolean ignoreAromaticBonds) + @Deprecated + protected InChIGenerator(IAtomContainer atomContainer, List opts, boolean ignoreAromaticBonds) throws CDKException { - try { - input = new JniInChIInputAdapter(options); - generateInchiFromCDKAtomContainer(atomContainer, ignoreAromaticBonds); - auxNone = input.getOptions() != null && input.getOptions().contains("AuxNone"); - } catch (JniInchiException jie) { - throw new CDKException("InChI generation failed: " + jie.getMessage(), jie); - } + this(atomContainer, convertJniToJnaOpts(opts), ignoreAromaticBonds); } /** @@ -183,6 +195,7 @@ protected InChIGenerator(IAtomContainer atomContainer, List option * the InChI. * * @param atomContainer AtomContainer to generate InChI for. + * @param ignore Ignore aromatic bonds * @throws CDKException */ private void generateInchiFromCDKAtomContainer(IAtomContainer atomContainer, boolean ignore) throws CDKException { @@ -203,7 +216,7 @@ private void generateInchiFromCDKAtomContainer(IAtomContainer atomContainer, boo } } - Map atomMap = new HashMap(); + Map atomMap = new HashMap(); atoms = atomContainer.atoms().iterator(); while (atoms.hasNext()) { IAtom atom = atoms.next(); @@ -231,7 +244,8 @@ private void generateInchiFromCDKAtomContainer(IAtomContainer atomContainer, boo String el = atom.getSymbol(); // Generate InChI atom - JniInchiAtom iatom = input.addAtom(new JniInchiAtom(x, y, z, el)); + InchiAtom iatom = new InchiAtom(el, x, y, z); + input.addAtom(iatom); atomMap.put(atom, iatom); // Check if charged @@ -253,23 +267,23 @@ private void generateInchiFromCDKAtomContainer(IAtomContainer atomContainer, boo Integer implicitH = atom.getImplicitHydrogenCount(); // set implicit hydrogen count, -1 tells the inchi to determine it - iatom.setImplicitH(implicitH != null ? implicitH : -1); + iatom.setImplicitHydrogen(implicitH != null ? implicitH : -1); // Check if radical int count = atomContainer.getConnectedSingleElectronsCount(atom); if (count == 1) { - iatom.setRadical(INCHI_RADICAL.DOUBLET); + iatom.setRadical(InchiRadical.DOUBLET); } else if (count == 2) { Enum spin = atom.getProperty(CDKConstants.SPIN_MULTIPLICITY); if (spin != null) { // cdk-ctab:SPIN_MULTIPLICITY not accessible by can access via Enum API although // a little brittle if (spin.name().equals("DivalentSinglet")) - iatom.setRadical(INCHI_RADICAL.SINGLET); + iatom.setRadical(InchiRadical.SINGLET); else - iatom.setRadical(INCHI_RADICAL.TRIPLET); + iatom.setRadical(InchiRadical.TRIPLET); } else { - iatom.setRadical(INCHI_RADICAL.TRIPLET); + iatom.setRadical(InchiRadical.TRIPLET); } } else if (count != 0) { throw new CDKException("Unrecognised radical type"); @@ -279,64 +293,47 @@ private void generateInchiFromCDKAtomContainer(IAtomContainer atomContainer, boo // Process bonds for (IBond bond : atomContainer.bonds()) { // Assumes 2 centre bond - JniInchiAtom at0 = (JniInchiAtom) atomMap.get(bond.getBegin()); - JniInchiAtom at1 = (JniInchiAtom) atomMap.get(bond.getEnd()); + InchiAtom at0 = atomMap.get(bond.getBegin()); + InchiAtom at1 = atomMap.get(bond.getEnd()); // Get bond order - INCHI_BOND_TYPE order; + InchiBondType order; Order bo = bond.getOrder(); - if (!ignore && bond.getFlag(CDKConstants.ISAROMATIC)) { - order = INCHI_BOND_TYPE.ALTERN; + if (!ignore && bond.isAromatic()) { + order = InchiBondType.ALTERN; } else if (bo == Order.SINGLE) { - order = INCHI_BOND_TYPE.SINGLE; + order = InchiBondType.SINGLE; } else if (bo == Order.DOUBLE) { - order = INCHI_BOND_TYPE.DOUBLE; + order = InchiBondType.DOUBLE; } else if (bo == Order.TRIPLE) { - order = INCHI_BOND_TYPE.TRIPLE; + order = InchiBondType.TRIPLE; } else { throw new CDKException("Failed to generate InChI: Unsupported bond type"); } // Create InChI bond - JniInchiBond ibond = new JniInchiBond(at0, at1, order); - input.addBond(ibond); + // Check for bond stereo definitions - IBond.Stereo stereo = bond.getStereo(); - // No stereo definition - if (stereo == IBond.Stereo.NONE) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.NONE); - } - // Bond ending (fat end of wedge) below the plane - else if (stereo == IBond.Stereo.DOWN) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_1DOWN); - } - // Bond ending (fat end of wedge) above the plane - else if (stereo == IBond.Stereo.UP) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_1UP); - } - // Bond starting (pointy end of wedge) below the plane - else if (stereo == IBond.Stereo.DOWN_INVERTED) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_2DOWN); - } - // Bond starting (pointy end of wedge) above the plane - else if (stereo == IBond.Stereo.UP_INVERTED) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_2UP); - } else if (stereo == IBond.Stereo.E_OR_Z) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.DOUBLE_EITHER); - } else if (stereo == IBond.Stereo.UP_OR_DOWN) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_1EITHER); - } else if (stereo == IBond.Stereo.UP_OR_DOWN_INVERTED) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_2EITHER); - } - // Bond with undefined stereochemistry - else if (stereo == CDKConstants.UNSET) { - if (order == INCHI_BOND_TYPE.SINGLE) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.SINGLE_1EITHER); - } else if (order == INCHI_BOND_TYPE.DOUBLE) { - ibond.setStereoDefinition(INCHI_BOND_STEREO.DOUBLE_EITHER); - } + IBond.Stereo display = bond.getStereo(); + final InchiBondStereo iDisplay; + switch (display) { + case UP: iDisplay = InchiBondStereo.SINGLE_1UP; break; + case UP_INVERTED: iDisplay = InchiBondStereo.SINGLE_2UP; break; + case DOWN: iDisplay = InchiBondStereo.SINGLE_1DOWN; break; + case DOWN_INVERTED: iDisplay = InchiBondStereo.SINGLE_2DOWN; break; + case UP_OR_DOWN: iDisplay = InchiBondStereo.SINGLE_1EITHER; break; + case UP_OR_DOWN_INVERTED: iDisplay = InchiBondStereo.SINGLE_2EITHER; break; + case E_OR_Z: iDisplay = InchiBondStereo.DOUBLE_EITHER; break; + default: iDisplay = InchiBondStereo.NONE; break; } + + /* + TODO: old code would set single/double either if no display? + */ + + InchiBond ibond = new InchiBond(at0, at1, order, iDisplay); + input.addBond(ibond); } // Process tetrahedral stereo elements @@ -346,23 +343,22 @@ else if (stereo == CDKConstants.UNSET) { IAtom[] surroundingAtoms = chirality.getLigands(); Stereo stereoType = chirality.getStereo(); - JniInchiAtom atC = (JniInchiAtom) atomMap.get(chirality.getChiralAtom()); - JniInchiAtom at0 = (JniInchiAtom) atomMap.get(surroundingAtoms[0]); - JniInchiAtom at1 = (JniInchiAtom) atomMap.get(surroundingAtoms[1]); - JniInchiAtom at2 = (JniInchiAtom) atomMap.get(surroundingAtoms[2]); - JniInchiAtom at3 = (JniInchiAtom) atomMap.get(surroundingAtoms[3]); - INCHI_PARITY p = INCHI_PARITY.UNKNOWN; + InchiAtom atC = atomMap.get(chirality.getChiralAtom()); + InchiAtom at0 = atomMap.get(surroundingAtoms[0]); + InchiAtom at1 = atomMap.get(surroundingAtoms[1]); + InchiAtom at2 = atomMap.get(surroundingAtoms[2]); + InchiAtom at3 = atomMap.get(surroundingAtoms[3]); + InchiStereoParity p; if (stereoType == Stereo.ANTI_CLOCKWISE) { - p = INCHI_PARITY.ODD; + p = InchiStereoParity.ODD; } else if (stereoType == Stereo.CLOCKWISE) { - p = INCHI_PARITY.EVEN; + p = InchiStereoParity.EVEN; } else { throw new CDKException("Unknown tetrahedral chirality"); } - JniInchiStereo0D jniStereo = new JniInchiStereo0D(atC, at0, at1, at2, at3, - INCHI_STEREOTYPE.TETRAHEDRAL, p); - input.addStereo0D(jniStereo); + InchiStereo jniStereo = InchiStereo.createTetrahedralStereo(atC, at0, at1, at2, at3, p); + input.addStereo(jniStereo); } else if (stereoElem instanceof IDoubleBondStereochemistry) { IDoubleBondStereochemistry dbStereo = (IDoubleBondStereochemistry) stereoElem; IBond[] surroundingBonds = dbStereo.getBonds(); @@ -372,42 +368,40 @@ else if (stereo == CDKConstants.UNSET) { .getStereo(); IBond stereoBond = dbStereo.getStereoBond(); - JniInchiAtom at0 = null; - JniInchiAtom at1 = null; - JniInchiAtom at2 = null; - JniInchiAtom at3 = null; + InchiAtom at0 = null; + InchiAtom at1 = null; + InchiAtom at2 = null; + InchiAtom at3 = null; // TODO: I should check for two atom bonds... or maybe that should happen when you // create a double bond stereochemistry if (stereoBond.contains(surroundingBonds[0].getBegin())) { // first atom is A - at1 = (JniInchiAtom) atomMap.get(surroundingBonds[0].getBegin()); - at0 = (JniInchiAtom) atomMap.get(surroundingBonds[0].getEnd()); + at1 = atomMap.get(surroundingBonds[0].getBegin()); + at0 = atomMap.get(surroundingBonds[0].getEnd()); } else { // first atom is X - at0 = (JniInchiAtom) atomMap.get(surroundingBonds[0].getBegin()); - at1 = (JniInchiAtom) atomMap.get(surroundingBonds[0].getEnd()); + at0 = atomMap.get(surroundingBonds[0].getBegin()); + at1 = atomMap.get(surroundingBonds[0].getEnd()); } if (stereoBond.contains(surroundingBonds[1].getBegin())) { // first atom is B - at2 = (JniInchiAtom) atomMap.get(surroundingBonds[1].getBegin()); - at3 = (JniInchiAtom) atomMap.get(surroundingBonds[1].getEnd()); + at2 = atomMap.get(surroundingBonds[1].getBegin()); + at3 = atomMap.get(surroundingBonds[1].getEnd()); } else { // first atom is Y - at2 = (JniInchiAtom) atomMap.get(surroundingBonds[1].getEnd()); - at3 = (JniInchiAtom) atomMap.get(surroundingBonds[1].getBegin()); + at2 = atomMap.get(surroundingBonds[1].getEnd()); + at3 = atomMap.get(surroundingBonds[1].getBegin()); } - INCHI_PARITY p = INCHI_PARITY.UNKNOWN; + InchiStereoParity p = InchiStereoParity.UNKNOWN; if (stereoType == org.openscience.cdk.interfaces.IDoubleBondStereochemistry.Conformation.TOGETHER) { - p = INCHI_PARITY.ODD; + p = InchiStereoParity.ODD; } else if (stereoType == org.openscience.cdk.interfaces.IDoubleBondStereochemistry.Conformation.OPPOSITE) { - p = INCHI_PARITY.EVEN; + p = InchiStereoParity.EVEN; } else { throw new CDKException("Unknown double bond stereochemistry"); } - JniInchiStereo0D jniStereo = new JniInchiStereo0D(null, at0, at1, at2, at3, - INCHI_STEREOTYPE.DOUBLEBOND, p); - input.addStereo0D(jniStereo); + input.addStereo(InchiStereo.createDoubleBondStereo(at0, at1, at2, at3, p)); } else if (stereoElem instanceof ExtendedTetrahedral) { ExtendedTetrahedral extendedTetrahedral = (ExtendedTetrahedral) stereoElem; @@ -421,6 +415,7 @@ else if (stereo == CDKConstants.UNSET) { // t0 = f = t1 // / \ // p1 p3 + IAtom focus = extendedTetrahedral.getFocus(); IAtom[] terminals = extendedTetrahedral.findTerminalAtoms(atomContainer); IAtom[] peripherals = extendedTetrahedral.peripherals(); @@ -471,26 +466,21 @@ else if (stereo == CDKConstants.UNSET) { } } - INCHI_PARITY parity = INCHI_PARITY.UNKNOWN; + InchiStereoParity parity = InchiStereoParity.UNKNOWN; if (winding == Stereo.ANTI_CLOCKWISE) - parity = INCHI_PARITY.ODD; + parity = InchiStereoParity.ODD; else if (winding == Stereo.CLOCKWISE) - parity = INCHI_PARITY.EVEN; + parity = InchiStereoParity.EVEN; else throw new CDKException("Unknown extended tetrahedral chirality"); - JniInchiStereo0D jniStereo = new JniInchiStereo0D(atomMap.get(extendedTetrahedral.focus()), + input.addStereo(InchiStereo.createAllenalStereo(atomMap.get(focus), atomMap.get(peripherals[0]), atomMap.get(peripherals[1]), atomMap.get(peripherals[2]), - atomMap.get(peripherals[3]), INCHI_STEREOTYPE.ALLENE, parity); - input.addStereo0D(jniStereo); + atomMap.get(peripherals[3]), parity)); } } - try { - output = JniInchiWrapper.getInchi(input); - } catch (JniInchiException jie) { - throw new CDKException("Failed to generate InChI: " + jie.getMessage(), jie); - } + output = JnaInchi.toInchi(input, options); } private static List onlySingleBonded(List bonds) { @@ -513,54 +503,47 @@ private static void swap(Object[] objs, int i, int j) { * has failed. */ public INCHI_RET getReturnStatus() { - return (output.getReturnStatus()); + return INCHI_RET.wrap(output.getStatus()); } /** * Gets generated InChI string. */ public String getInchi() { - return (output.getInchi()); + return output.getInchi(); } /** * Gets generated InChIKey string. */ public String getInchiKey() throws CDKException { - JniInchiOutputKey key; - try { - key = JniInchiWrapper.getInchiKey(output.getInchi()); - if (key.getReturnStatus() == INCHI_KEY.OK) { - return key.getKey(); - } else { - throw new CDKException("Error while creating InChIKey: " + key.getReturnStatus()); - } - } catch (JniInchiException exception) { - throw new CDKException("Error while creating InChIKey: " + exception.getMessage(), exception); - } + InchiKeyOutput inchiKeyOutput = JnaInchi.inchiToInchiKey(getInchi()); + if (inchiKeyOutput.getStatus() == InchiKeyStatus.OK) + return inchiKeyOutput.getInchiKey(); + else + throw new CDKException("Error while creating InChIKey: " + inchiKeyOutput.getStatus()); } /** * Gets auxillary information. */ public String getAuxInfo() { - if (auxNone) { + if (auxNone) LOGGER.warn("AuxInfo requested but AuxNone option is set (default)."); - } - return (output.getAuxInfo()); + return output.getAuxInfo(); } /** * Gets generated (error/warning) messages. */ public String getMessage() { - return (output.getMessage()); + return output.getMessage(); } /** * Gets generated log. */ public String getLog() { - return (output.getLog()); + return output.getLog(); } } diff --git a/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGeneratorFactory.java b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGeneratorFactory.java index 0b65bc351c7..c888fb00fa5 100644 --- a/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGeneratorFactory.java +++ b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIGeneratorFactory.java @@ -23,8 +23,6 @@ import java.util.List; import net.sf.jniinchi.INCHI_OPTION; -import net.sf.jniinchi.JniInchiWrapper; -import net.sf.jniinchi.LoadNativeLibraryException; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.interfaces.IAtomContainer; @@ -80,11 +78,6 @@ public class InChIGeneratorFactory { * @throws CDKException if unable to load native code */ private InChIGeneratorFactory() throws CDKException { - try { - JniInchiWrapper.loadLibrary(); - } catch (LoadNativeLibraryException lnle) { - throw new CDKException("Unable to load native code; " + lnle.getMessage(), lnle); - } } /** diff --git a/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIOptionParser.java b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIOptionParser.java new file mode 100644 index 00000000000..cfe83515fdc --- /dev/null +++ b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIOptionParser.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 John Mayfield + * + * 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. + * + * 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.inchi; + +import io.github.dan2097.jnainchi.InchiFlag; +import io.github.dan2097.jnainchi.InchiOptions; +import org.openscience.cdk.tools.ILoggingTool; +import org.openscience.cdk.tools.LoggingToolFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides parsing of InChI options from a string. Using the JNA InchiOptions builder directly. + * @author John Mayfield + */ +final class InChIOptionParser { + + private final ILoggingTool logger = LoggingToolFactory.createLoggingTool(InChIOptionParser.class); + private final Map optMap = new HashMap<>(); + private final InchiOptions.InchiOptionsBuilder options; + + private InChIOptionParser() { + for (InchiFlag flag : InchiFlag.values()) + optMap.put(flag.name(), flag); + optMap.put("15T", InchiFlag.OneFiveT); + options = new InchiOptions.InchiOptionsBuilder(); + } + + private void processString(String optstr) { + int pos = 0; + while (pos < optstr.length()) { + switch (optstr.charAt(pos)) { + case ' ': + case '-': + case '/': + case ',': + pos++; // skip + break; + case 'W': // timeout + pos++; + int next = optstr.indexOf(',', pos); + if (next < 0) + next = optstr.length(); + String substring = optstr.substring(pos, next); + try { + // Note: locale sensitive e.g. 0,01 but we can not pass in milliseconds so doesn't matter so much + options.withTimeoutMilliSeconds((int)(1000*Double.parseDouble(substring))); + } catch (NumberFormatException ex) { + logger.warn("Invalid timtoue:" + substring); + } + break; + default: + next = optstr.indexOf(',', pos); + if (next < 0) + next = optstr.length(); + InchiFlag flag = optMap.get(optstr.substring(pos, next)); + if (flag != null) + options.withFlag(flag); + else + logger.warn("Ignore unrecognized InChI flag:" + flag); + pos = next; + } + } + } + + static InchiOptions parseString(String str) { + if (str == null) + return null; + InChIOptionParser parser = new InChIOptionParser(); + parser.processString(str); + return parser.options.build(); + } + + static InchiOptions parseStrings(List strs) { + if (strs == null) + return null; + InChIOptionParser parser = new InChIOptionParser(); + for (String str : strs) + parser.processString(str); + return parser.options.build(); + } +} diff --git a/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIToStructure.java b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIToStructure.java index e515676ad0a..3e8a9bce4e3 100644 --- a/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIToStructure.java +++ b/storage/inchi/src/main/java/org/openscience/cdk/inchi/InChIToStructure.java @@ -18,40 +18,36 @@ */ package org.openscience.cdk.inchi; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.sf.jniinchi.INCHI_BOND_STEREO; -import net.sf.jniinchi.INCHI_BOND_TYPE; -import net.sf.jniinchi.INCHI_PARITY; +import io.github.dan2097.jnainchi.InchiAtom; +import io.github.dan2097.jnainchi.InchiBond; +import io.github.dan2097.jnainchi.InchiBondStereo; +import io.github.dan2097.jnainchi.InchiBondType; +import io.github.dan2097.jnainchi.InchiInput; +import io.github.dan2097.jnainchi.InchiStereo; +import io.github.dan2097.jnainchi.InchiStereoParity; +import io.github.dan2097.jnainchi.InchiStereoType; +import net.sf.jniinchi.INCHI_OPTION; import net.sf.jniinchi.INCHI_RET; -import net.sf.jniinchi.INCHI_STEREOTYPE; -import net.sf.jniinchi.JniInchiAtom; -import net.sf.jniinchi.JniInchiBond; -import net.sf.jniinchi.JniInchiException; -import net.sf.jniinchi.JniInchiInputInchi; -import net.sf.jniinchi.JniInchiOutputStructure; -import net.sf.jniinchi.JniInchiStereo0D; -import net.sf.jniinchi.JniInchiWrapper; - -import org.openscience.cdk.CDKConstants; +import io.github.dan2097.jnainchi.InchiInputFromInchiOutput; +import io.github.dan2097.jnainchi.InchiOptions; +import io.github.dan2097.jnainchi.JnaInchi; +import org.openscience.cdk.config.Elements; import org.openscience.cdk.config.Isotopes; import org.openscience.cdk.exception.CDKException; import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.interfaces.IAtomContainer; import org.openscience.cdk.interfaces.IBond; -import org.openscience.cdk.interfaces.IBond.Order; import org.openscience.cdk.interfaces.IChemObjectBuilder; import org.openscience.cdk.interfaces.IStereoElement; import org.openscience.cdk.interfaces.ITetrahedralChirality; import org.openscience.cdk.stereo.DoubleBondStereochemistry; import org.openscience.cdk.stereo.ExtendedCisTrans; import org.openscience.cdk.stereo.ExtendedTetrahedral; -import org.openscience.cdk.tools.periodictable.PeriodicTable; -import static org.openscience.cdk.interfaces.IDoubleBondStereochemistry.Conformation; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** *

This class generates a CDK IAtomContainer from an InChI string. It places @@ -91,9 +87,9 @@ */ public class InChIToStructure { - protected JniInchiInputInchi input; + protected InchiInputFromInchiOutput output; - protected JniInchiOutputStructure output; + protected InchiOptions options; protected IAtomContainer molecule; @@ -112,17 +108,25 @@ public class InChIToStructure { * @param inchi * @throws CDKException */ - protected InChIToStructure(String inchi, IChemObjectBuilder builder) throws CDKException { + protected InChIToStructure(String inchi, IChemObjectBuilder builder, InchiOptions options) throws CDKException { if (inchi == null) - throw new NullPointerException(); - try { - input = new JniInchiInputInchi(inchi, ""); - } catch (JniInchiException jie) { - throw new CDKException("Failed to convert InChI to molecule: " + jie.getMessage(), jie); - } + throw new IllegalArgumentException("Null InChI string provided"); + if (options == null) + throw new IllegalArgumentException("Null options provided"); + this.output = JnaInchi.getInchiInputFromInchi(inchi); + this.options = options; generateAtomContainerFromInchi(builder); } + /** + * Constructor. Generates CDK AtomContainer from InChI. + * @param inchi + * @throws CDKException + */ + protected InChIToStructure(String inchi, IChemObjectBuilder builder) throws CDKException { + this(inchi, builder, new InchiOptions.InchiOptionsBuilder().build()); + } + /** * Constructor. Generates CMLMolecule from InChI. * @param inchi @@ -130,14 +134,7 @@ protected InChIToStructure(String inchi, IChemObjectBuilder builder) throws CDKE * @throws CDKException */ protected InChIToStructure(String inchi, IChemObjectBuilder builder, String options) throws CDKException { - if (inchi == null) - throw new NullPointerException(); - try { - input = new JniInchiInputInchi(inchi, options); - } catch (JniInchiException jie) { - throw new CDKException("Failed to convert InChI to molecule: " + jie.getMessage(), jie); - } - generateAtomContainerFromInchi(builder); + this(inchi, builder, InChIOptionParser.parseString(options)); } /** @@ -147,14 +144,7 @@ protected InChIToStructure(String inchi, IChemObjectBuilder builder, String opti * @throws CDKException */ protected InChIToStructure(String inchi, IChemObjectBuilder builder, List options) throws CDKException { - if (inchi == null) - throw new NullPointerException(); - try { - input = new JniInchiInputInchi(inchi, options); - } catch (JniInchiException jie) { - throw new CDKException("Failed to convert InChI to molecule: " + jie.getMessage()); - } - generateAtomContainerFromInchi(builder); + this(inchi, builder, InChIOptionParser.parseStrings(options)); } /** @@ -172,26 +162,23 @@ private void flip(IBond bond) { * @throws CDKException */ protected void generateAtomContainerFromInchi(IChemObjectBuilder builder) throws CDKException { - try { - output = JniInchiWrapper.getStructureFromInchi(input); - } catch (JniInchiException jie) { - throw new CDKException("Failed to convert InChI to molecule: " + jie.getMessage(), jie); - } + + InchiInput input = output.getInchiInput(); //molecule = new AtomContainer(); molecule = builder.newInstance(IAtomContainer.class); - Map inchiCdkAtomMap = new HashMap(); + Map inchiCdkAtomMap = new HashMap(); - for (int i = 0; i < output.getNumAtoms(); i++) { - JniInchiAtom iAt = output.getAtom(i); + List atoms = input.getAtoms(); + for (int i = 0; i < atoms.size(); i++) { + InchiAtom iAt = atoms.get(i); IAtom cAt = builder.newInstance(IAtom.class); inchiCdkAtomMap.put(iAt, cAt); cAt.setID("a" + i); - cAt.setSymbol(iAt.getElementType()); - cAt.setAtomicNumber(PeriodicTable.getAtomicNumber(cAt.getSymbol())); + cAt.setAtomicNumber(Elements.ofString(iAt.getElName()).number()); // Ignore coordinates - all zero - unless aux info was given... but // the CDK doesn't have an API to provide that @@ -199,7 +186,7 @@ protected void generateAtomContainerFromInchi(IChemObjectBuilder builder) throws // InChI does not have unset properties so we set charge, // hydrogen count (implicit) and isotopic mass cAt.setFormalCharge(iAt.getCharge()); - cAt.setImplicitHydrogenCount(iAt.getImplicitH()); + cAt.setImplicitHydrogenCount(iAt.getImplicitHydrogen()); int isotopicMass = iAt.getIsotopicMass(); if (isotopicMass != 0) { @@ -217,90 +204,73 @@ protected void generateAtomContainerFromInchi(IChemObjectBuilder builder) throws molecule.addAtom(cAt); cAt = molecule.getAtom(molecule.getAtomCount()-1); - for (int j = 0; j < iAt.getImplicitDeuterium(); j++) { - IAtom deut = builder.newInstance(IAtom.class); - deut.setAtomicNumber(1); - deut.setSymbol("H"); - deut.setMassNumber(2); - deut.setImplicitHydrogenCount(0); - molecule.addAtom(deut); - deut = molecule.getAtom(molecule.getAtomCount()-1); - IBond bond = builder.newInstance(IBond.class, cAt, deut, Order.SINGLE); - molecule.addBond(bond); - } - for (int j = 0; j < iAt.getImplicitTritium(); j++) { - IAtom trit = builder.newInstance(IAtom.class); - trit.setAtomicNumber(1); - trit.setSymbol("H"); - trit.setMassNumber(3); - trit.setImplicitHydrogenCount(0); - molecule.addAtom(trit); - trit = molecule.getAtom(molecule.getAtomCount()-1); - IBond bond = builder.newInstance(IBond.class, cAt, trit, Order.SINGLE); - molecule.addBond(bond); - } + addHydrogenIsotopes(builder, cAt, 2, iAt.getImplicitDeuterium()); + addHydrogenIsotopes(builder, cAt, 3, iAt.getImplicitTritium()); } - for (int i = 0; i < output.getNumBonds(); i++) { - JniInchiBond iBo = output.getBond(i); + List bonds = input.getBonds(); + for (int i = 0; i < bonds.size(); i++) { + InchiBond iBo = bonds.get(i); IBond cBo = builder.newInstance(IBond.class); - IAtom atO = inchiCdkAtomMap.get(iBo.getOriginAtom()); - IAtom atT = inchiCdkAtomMap.get(iBo.getTargetAtom()); - IAtom[] atoms = new IAtom[2]; - atoms[0] = atO; - atoms[1] = atT; - cBo.setAtoms(atoms); - - INCHI_BOND_TYPE type = iBo.getBondType(); - if (type == INCHI_BOND_TYPE.SINGLE) { - cBo.setOrder(Order.SINGLE); - } else if (type == INCHI_BOND_TYPE.DOUBLE) { - cBo.setOrder(Order.DOUBLE); - } else if (type == INCHI_BOND_TYPE.TRIPLE) { - cBo.setOrder(Order.TRIPLE); - } else if (type == INCHI_BOND_TYPE.ALTERN) { - cBo.setFlag(CDKConstants.ISAROMATIC, true); - } else { - throw new CDKException("Unknown bond type: " + type); + IAtom atO = inchiCdkAtomMap.get(iBo.getStart()); + IAtom atT = inchiCdkAtomMap.get(iBo.getEnd()); + cBo.setAtoms(new IAtom[]{atO, atT}); + + InchiBondType type = iBo.getType(); + switch (type) { + case SINGLE: + cBo.setOrder(IBond.Order.SINGLE); + break; + case DOUBLE: + cBo.setOrder(IBond.Order.DOUBLE); + break; + case TRIPLE: + cBo.setOrder(IBond.Order.TRIPLE); + break; + case ALTERN: + cBo.setIsInRing(true); + break; + default: + throw new CDKException("Unknown bond type: " + type); } - INCHI_BOND_STEREO stereo = iBo.getBondStereo(); - - // No stereo definition - if (stereo == INCHI_BOND_STEREO.NONE) { - cBo.setStereo(IBond.Stereo.NONE); - } - // Bond ending (fat end of wedge) below the plane - else if (stereo == INCHI_BOND_STEREO.SINGLE_1DOWN) { - cBo.setStereo(IBond.Stereo.DOWN); - } - // Bond ending (fat end of wedge) above the plane - else if (stereo == INCHI_BOND_STEREO.SINGLE_1UP) { - cBo.setStereo(IBond.Stereo.UP); - } - // Bond starting (pointy end of wedge) below the plane - else if (stereo == INCHI_BOND_STEREO.SINGLE_2DOWN) { - cBo.setStereo(IBond.Stereo.DOWN_INVERTED); - } - // Bond starting (pointy end of wedge) above the plane - else if (stereo == INCHI_BOND_STEREO.SINGLE_2UP) { - cBo.setStereo(IBond.Stereo.UP_INVERTED); - } - // Bond with undefined stereochemistry - else if (stereo == INCHI_BOND_STEREO.SINGLE_1EITHER || stereo == INCHI_BOND_STEREO.DOUBLE_EITHER) { - cBo.setStereo((IBond.Stereo) CDKConstants.UNSET); + InchiBondStereo stereo = iBo.getStereo(); + + switch (stereo) { + case NONE: + cBo.setStereo(IBond.Stereo.NONE); + break; + case SINGLE_1DOWN: + cBo.setStereo(IBond.Stereo.DOWN); + break; + case SINGLE_1UP: + cBo.setStereo(IBond.Stereo.UP); + break; + case SINGLE_2DOWN: + cBo.setStereo(IBond.Stereo.DOWN_INVERTED); + break; + case SINGLE_2UP: + cBo.setStereo(IBond.Stereo.UP_INVERTED); + break; + case SINGLE_1EITHER: + cBo.setStereo(IBond.Stereo.UP_OR_DOWN); + break; + case SINGLE_2EITHER: + cBo.setStereo(IBond.Stereo.UP_OR_DOWN_INVERTED); + break; } molecule.addBond(cBo); } - for (int i = 0; i < output.getNumStereo0D(); i++) { - JniInchiStereo0D stereo0d = output.getStereo0D(i); - if (stereo0d.getStereoType() == INCHI_STEREOTYPE.TETRAHEDRAL - || stereo0d.getStereoType() == INCHI_STEREOTYPE.ALLENE) { - JniInchiAtom central = stereo0d.getCentralAtom(); - JniInchiAtom[] neighbours = stereo0d.getNeighbors(); + List stereos = input.getStereos(); + for (int i = 0; i < stereos.size(); i++) { + InchiStereo stereo0d = stereos.get(i); + if (stereo0d.getType() == InchiStereoType.Tetrahedral + || stereo0d.getType() == InchiStereoType.Allene) { + InchiAtom central = stereo0d.getCentralAtom(); + InchiAtom[] neighbours = stereo0d.getAtoms(); IAtom focus = inchiCdkAtomMap.get(central); IAtom[] neighbors = new IAtom[]{inchiCdkAtomMap.get(neighbours[0]), inchiCdkAtomMap.get(neighbours[1]), @@ -309,9 +279,9 @@ else if (stereo == INCHI_BOND_STEREO.SINGLE_1EITHER || stereo == INCHI_BOND_STER // as per JNI InChI doc even is clockwise and odd is // anti-clockwise - if (stereo0d.getParity() == INCHI_PARITY.ODD) { + if (stereo0d.getParity() == InchiStereoParity.ODD) { stereo = ITetrahedralChirality.Stereo.ANTI_CLOCKWISE; - } else if (stereo0d.getParity() == INCHI_PARITY.EVEN) { + } else if (stereo0d.getParity() == InchiStereoParity.EVEN) { stereo = ITetrahedralChirality.Stereo.CLOCKWISE; } else { // CDK Only supports parities of + or - @@ -320,9 +290,9 @@ else if (stereo == INCHI_BOND_STEREO.SINGLE_1EITHER || stereo == INCHI_BOND_STER IStereoElement stereoElement = null; - if (stereo0d.getStereoType() == INCHI_STEREOTYPE.TETRAHEDRAL) { + if (stereo0d.getType() == InchiStereoType.Tetrahedral) { stereoElement = builder.newInstance(ITetrahedralChirality.class, focus, neighbors, stereo); - } else if (stereo0d.getStereoType() == INCHI_STEREOTYPE.ALLENE) { + } else if (stereo0d.getType() == InchiStereoType.Allene) { // The periphals (p) and terminals (t) are refering to // the following atoms. The focus (f) is also shown. @@ -363,9 +333,9 @@ else if (stereo == INCHI_BOND_STEREO.SINGLE_1EITHER || stereo == INCHI_BOND_STER assert stereoElement != null; molecule.addStereoElement(stereoElement); - } else if (stereo0d.getStereoType() == INCHI_STEREOTYPE.DOUBLEBOND) { + } else if (stereo0d.getType() == InchiStereoType.DoubleBond) { boolean extended = false; - JniInchiAtom[] neighbors = stereo0d.getNeighbors(); + InchiAtom[] neighbors = stereo0d.getAtoms(); // from JNI InChI doc // neighbor[4] : {#X,#A,#B,#Y} in this order @@ -396,7 +366,7 @@ else if (stereo == INCHI_BOND_STEREO.SINGLE_1EITHER || stereo == INCHI_BOND_STER } int config = IStereoElement.TOGETHER; - if (stereo0d.getParity() == INCHI_PARITY.EVEN) + if (stereo0d.getParity() == InchiStereoParity.EVEN) config = IStereoElement.OPPOSITE; if (extended) { @@ -412,6 +382,20 @@ else if (stereo == INCHI_BOND_STEREO.SINGLE_1EITHER || stereo == INCHI_BOND_STER } } + private void addHydrogenIsotopes(IChemObjectBuilder builder, IAtom cAt, int mass, int count) { + for (int j = 0; j < count; j++) { + IAtom deut = builder.newInstance(IAtom.class); + deut.setAtomicNumber(1); + deut.setSymbol("H"); + deut.setMassNumber(mass); + deut.setImplicitHydrogenCount(0); + molecule.addAtom(deut); + deut = molecule.getAtom(molecule.getAtomCount()-1); + IBond bond = builder.newInstance(IBond.class, cAt, deut, IBond.Order.SINGLE); + molecule.addBond(bond); + } + } + /** * Finds a neighbor attached to 'atom' that is singley bonded and isn't * 'exclude'. If no such atom exists, the 'atom' is returned. @@ -443,21 +427,21 @@ public IAtomContainer getAtomContainer() { * has failed. */ public INCHI_RET getReturnStatus() { - return (output.getReturnStatus()); + return INCHI_RET.wrap(output.getStatus()); } /** * Gets generated (error/warning) messages. */ public String getMessage() { - return (output.getMessage()); + return output.getMessage(); } /** * Gets generated log. */ public String getLog() { - return (output.getLog()); + return output.getLog(); } /** @@ -470,7 +454,7 @@ public String getLog() { *
y=0 => Fixed-H layer */ public long[][] getWarningFlags() { - return (output.getWarningFlags()); + return output.getWarningFlags(); } } diff --git a/storage/inchi/src/main/java/org/openscience/cdk/inchi/JniInChIInputAdapter.java b/storage/inchi/src/main/java/org/openscience/cdk/inchi/JniInChIInputAdapter.java deleted file mode 100644 index eecfc4438cc..00000000000 --- a/storage/inchi/src/main/java/org/openscience/cdk/inchi/JniInChIInputAdapter.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2016 John May - * - * 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 U - */ - -package org.openscience.cdk.inchi; - -import net.sf.jniinchi.INCHI_OPTION; -import net.sf.jniinchi.JniInchiException; -import net.sf.jniinchi.JniInchiInput; - -import java.util.List; -import java.util.StringTokenizer; - -public class JniInChIInputAdapter extends JniInchiInput { - - /** - * Flag indicating windows or linux. - */ - private static final boolean IS_WINDOWS = System.getProperty("os.name", "").toLowerCase().startsWith("windows"); - - /** - * Switch character for passing options. / in windows, - on other systems. - */ - private static final String FLAG_CHAR = IS_WINDOWS ? "/" : "-"; - - public static final String FIVE_SECOND_TIMEOUT = FLAG_CHAR + "W5"; - - public JniInChIInputAdapter(String options) throws JniInchiException { - this.options = options == null ? "" : checkOptions(options); - } - - public JniInChIInputAdapter(List options) throws JniInchiException { - this.options = options == null ? "" : checkOptions(options); - } - - private static boolean isTimeoutOptions(String op) { - if (op == null || op.length() < 2) return false; - return op.charAt(0) == 'W'; - - } - - - private static String checkOptions(final String ops) throws JniInchiException { - if (ops == null) { - throw new IllegalArgumentException("Null options"); - } - StringBuilder sbOptions = new StringBuilder(); - - - boolean hasUserSpecifiedTimeout = false; - - StringTokenizer tok = new StringTokenizer(ops); - while (tok.hasMoreTokens()) { - String op = tok.nextToken(); - - if (op.startsWith("-") || op.startsWith("/")) { - op = op.substring(1); - } - - INCHI_OPTION option = INCHI_OPTION.valueOfIgnoreCase(op); - if (option != null) { - sbOptions.append(FLAG_CHAR).append(option.name()); - if (tok.hasMoreTokens()) { - sbOptions.append(" "); - } - } else if (isTimeoutOptions(op)) { - final double time = Math.ceil(Double.parseDouble(op.substring(1))); - // fix #653: safer to use whole seconds, rounded to next bigger integer - if (time >= 0.0) { - sbOptions.append(FLAG_CHAR).append(String.format("W%.0f", time)); - hasUserSpecifiedTimeout = true; - if (tok.hasMoreTokens()) { - sbOptions.append(" "); - } - } - } - // 1,5 tautomer option - else if ("15T".equals(op)) { - sbOptions.append(FLAG_CHAR).append("15T"); - if (tok.hasMoreTokens()) { - sbOptions.append(" "); - } - } - // keto-enol tautomer option - else if ("KET".equals(op)) { - sbOptions.append(FLAG_CHAR).append("KET"); - if (tok.hasMoreTokens()) { - sbOptions.append(" "); - } - } else { - throw new JniInchiException("Unrecognised InChI option"); - } - } - - if (!hasUserSpecifiedTimeout) { - if (sbOptions.length() > 0) - sbOptions.append(' '); - sbOptions.append(FIVE_SECOND_TIMEOUT); - } - - return sbOptions.toString(); - } - - private static String checkOptions(List ops) throws JniInchiException { - if (ops == null) { - throw new IllegalArgumentException("Null options"); - } - StringBuilder sbOptions = new StringBuilder(); - - for (INCHI_OPTION op : ops) { - sbOptions.append(FLAG_CHAR).append(op.name()).append(" "); - } - - if (sbOptions.length() > 0) - sbOptions.append(' '); - sbOptions.append(FIVE_SECOND_TIMEOUT); - - return sbOptions.toString(); - } -} diff --git a/storage/inchi/src/test/java/org/openscience/cdk/graph/invariant/InChINumbersToolsTest.java b/storage/inchi/src/test/java/org/openscience/cdk/graph/invariant/InChINumbersToolsTest.java index f65e1f32138..5a1401da8dd 100644 --- a/storage/inchi/src/test/java/org/openscience/cdk/graph/invariant/InChINumbersToolsTest.java +++ b/storage/inchi/src/test/java/org/openscience/cdk/graph/invariant/InChINumbersToolsTest.java @@ -103,7 +103,7 @@ public void testGlycine_uSmiles() throws Exception { public void fixedH() throws Exception { SmilesParser parser = new SmilesParser(DefaultChemObjectBuilder.getInstance()); IAtomContainer atomContainer = parser.parseSmiles("N1C=NC2=CC=CC=C12"); - String auxInfo = InChINumbersTools.auxInfo(atomContainer, INCHI_OPTION.FixedH); + String auxInfo = InChINumbersTools.auxInfo(atomContainer, INCHI_OPTION.FixedH, INCHI_OPTION.ChiralFlagON); String expected = "AuxInfo=1/1/" + "N:6,7,5,8,2,4,9,3,1/" + "E:(1,2)(3,4)(6,7)(8,9)/" + "F:7,6,8,5,2,9,4,1,3/" + "rA:9NCNCCCCCC/" + "rB:s1;d2;s3;d4;s5;d6;s7;s1s4d8;/" + "rC:;;;;;;;;;"; assertThat(auxInfo, is(expected)); diff --git a/storage/inchi/src/test/java/org/openscience/cdk/inchi/InChIGeneratorTest.java b/storage/inchi/src/test/java/org/openscience/cdk/inchi/InChIGeneratorTest.java index d9d11711b5c..bed9a2b1a3e 100644 --- a/storage/inchi/src/test/java/org/openscience/cdk/inchi/InChIGeneratorTest.java +++ b/storage/inchi/src/test/java/org/openscience/cdk/inchi/InChIGeneratorTest.java @@ -34,6 +34,7 @@ import net.sf.jniinchi.INCHI_OPTION; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.openscience.cdk.Atom; import org.openscience.cdk.AtomContainer; @@ -991,6 +992,6 @@ public void testFiveSecondTimeoutFlag() throws Exception { InChIGenerator generator = factory.getInChIGenerator(ac); String flagChar = System.getProperty("os.name", "").toLowerCase().startsWith("windows") ? "/" : "-"; - assertThat(generator.input.getOptions(), containsString(flagChar + "W5")); + assertThat(generator.options.getTimeoutMilliSeconds(), is(5000)); } }