diff --git a/storage/smiles/src/main/java/org/openscience/cdk/smiles/CDKToBeam.java b/storage/smiles/src/main/java/org/openscience/cdk/smiles/CDKToBeam.java index 03e6216e625..570c011f8e5 100644 --- a/storage/smiles/src/main/java/org/openscience/cdk/smiles/CDKToBeam.java +++ b/storage/smiles/src/main/java/org/openscience/cdk/smiles/CDKToBeam.java @@ -26,7 +26,6 @@ import com.google.common.collect.Maps; -import org.openscience.cdk.CDK; import org.openscience.cdk.CDKConstants; import org.openscience.cdk.config.Isotopes; import org.openscience.cdk.config.IsotopeFactory; @@ -91,15 +90,33 @@ final class CDKToBeam { * Whether to convert the molecule with isotope and stereo information - * Isomeric SMILES. */ - private final int options; + private final int flavour; /** Create a isomeric and aromatic converter. */ CDKToBeam() { - this(SmiOpt.Isotope | SmiOpt.AtomAtomMap | SmiOpt.UseAromaticSymbols); + this(SmiFlavour.AtomicMass | SmiFlavour.AtomAtomMap | SmiFlavour.UseAromaticSymbols); } - CDKToBeam(int options) { - this.options = options; + CDKToBeam(int flavour) { + this.flavour = flavour; + } + + Graph toBeamGraph(IAtomContainer ac) throws CDKException { + return toBeamGraph(ac, flavour); + } + + Atom toBeamAtom(IAtom atom) throws CDKException { + return toBeamAtom(atom, flavour); + } + + Edge toBeamEdge(IBond b, Map indices) throws CDKException { + + checkArgument(b.getAtomCount() == 2, "Invalid number of atoms on bond"); + + int u = indices.get(b.getAtom(0)); + int v = indices.get(b.getAtom(1)); + + return toBeamEdgeLabel(b, this.flavour).edge(u, v); } /** @@ -110,7 +127,7 @@ final class CDKToBeam { * @param ac an atom container instance * @return the Beam ChemicalGraph for additional manipulation */ - Graph toBeamGraph(IAtomContainer ac) throws CDKException { + static Graph toBeamGraph(IAtomContainer ac, int flavour) throws CDKException { int order = ac.getAtomCount(); @@ -119,23 +136,23 @@ Graph toBeamGraph(IAtomContainer ac) throws CDKException { for (IAtom a : ac.atoms()) { indices.put(a, indices.size()); - gb.add(toBeamAtom(a)); + gb.add(toBeamAtom(a, flavour)); } for (IBond b : ac.bonds()) { - gb.add(toBeamEdge(b, indices)); + gb.add(toBeamEdge(b, flavour, indices)); } // configure stereo-chemistry by encoding the stereo-elements - if (SmiOpt.isSet(options, SmiOpt.Stereo)) { + if (SmiFlavour.isSet(flavour, SmiFlavour.Stereo)) { for (IStereoElement se : ac.stereoElements()) { - if (SmiOpt.isSet(options, SmiOpt.StereoTetrahedral) && + if (SmiFlavour.isSet(flavour, SmiFlavour.StereoTetrahedral) && se instanceof ITetrahedralChirality) { addTetrahedralConfiguration((ITetrahedralChirality) se, gb, indices); - } else if (SmiOpt.isSet(options, SmiOpt.StereoCisTrans) && + } else if (SmiFlavour.isSet(flavour, SmiFlavour.StereoCisTrans) && se instanceof IDoubleBondStereochemistry) { - addGeometricConfiguration((IDoubleBondStereochemistry) se, gb, indices); - } else if (SmiOpt.isSet(options, SmiOpt.StereoExTetrahedral) && + addGeometricConfiguration((IDoubleBondStereochemistry) se, flavour, gb, indices); + } else if (SmiFlavour.isSet(flavour, SmiFlavour.StereoExTetrahedral) && se instanceof ExtendedTetrahedral) { addExtendedTetrahedralConfiguration((ExtendedTetrahedral) se, gb, indices); } @@ -156,9 +173,9 @@ Graph toBeamGraph(IAtomContainer ac) throws CDKException { * @throws NullPointerException the atom had an undefined symbol or implicit * hydrogen count */ - Atom toBeamAtom(final IAtom a) { + static Atom toBeamAtom(final IAtom a, final int flavour) { - final boolean aromatic = SmiOpt.isSet(options, SmiOpt.UseAromaticSymbols) && a.getFlag(CDKConstants.ISAROMATIC); + final boolean aromatic = SmiFlavour.isSet(flavour, SmiFlavour.UseAromaticSymbols) && a.getFlag(CDKConstants.ISAROMATIC); final Integer charge = a.getFormalCharge(); final String symbol = checkNotNull(a.getSymbol(), "An atom had an undefined symbol"); @@ -178,7 +195,7 @@ Atom toBeamAtom(final IAtom a) { if (charge != null) ab.charge(charge); // use the mass number to specify isotope? - if (SmiOpt.isSet(options, SmiOpt.Isotope)) { + if (SmiFlavour.isSet(flavour, SmiFlavour.AtomicMass)) { Integer massNumber = a.getMassNumber(); if (massNumber != null) { // XXX: likely causing some overhead but okay for now @@ -193,7 +210,7 @@ Atom toBeamAtom(final IAtom a) { } Integer atomClass = a.getProperty(ATOM_ATOM_MAPPING); - if (SmiOpt.isSet(options, SmiOpt.AtomAtomMap) && atomClass != null) { + if (SmiFlavour.isSet(flavour, SmiFlavour.AtomAtomMap) && atomClass != null) { ab.atomClass(atomClass); } @@ -210,14 +227,14 @@ Atom toBeamAtom(final IAtom a) { * unsupported order * @throws NullPointerException the bond order was undefined */ - Edge toBeamEdge(IBond b, Map indices) throws CDKException { + static Edge toBeamEdge(IBond b, int flavour, Map indices) throws CDKException { checkArgument(b.getAtomCount() == 2, "Invalid number of atoms on bond"); int u = indices.get(b.getAtom(0)); int v = indices.get(b.getAtom(1)); - return toBeamEdgeLabel(b).edge(u, v); + return toBeamEdgeLabel(b, flavour).edge(u, v); } /** @@ -229,9 +246,9 @@ Edge toBeamEdge(IBond b, Map indices) throws CDKException { * not-aromatic * @throws IllegalArgumentException the bond order could not be converted */ - private Bond toBeamEdgeLabel(IBond b) throws CDKException { + private static Bond toBeamEdgeLabel(IBond b, int flavour) throws CDKException { - if (SmiOpt.isSet(options, SmiOpt.UseAromaticSymbols) && b.getFlag(CDKConstants.ISAROMATIC)) return Bond.AROMATIC; + if (SmiFlavour.isSet(flavour, SmiFlavour.UseAromaticSymbols) && b.getFlag(CDKConstants.ISAROMATIC)) return Bond.AROMATIC; if (b.getOrder() == null) throw new CDKException("A bond had undefined order, possible query bond?"); @@ -247,7 +264,7 @@ private Bond toBeamEdgeLabel(IBond b) throws CDKException { case QUADRUPLE: return Bond.QUADRUPLE; default: - if (!SmiOpt.isSet(options, SmiOpt.UseAromaticSymbols) && b.getFlag(CDKConstants.ISAROMATIC)) + if (!SmiFlavour.isSet(flavour, SmiFlavour.UseAromaticSymbols) && b.getFlag(CDKConstants.ISAROMATIC)) throw new CDKException("Cannot write Kekulé SMILES output due to aromatic bond with unset bond order - molecule should be Kekulized"); throw new CDKException("Unsupported bond order: " + order); } @@ -260,13 +277,13 @@ private Bond toBeamEdgeLabel(IBond b) throws CDKException { * @param gb the current graph builder * @param indices atom indices */ - private void addGeometricConfiguration(IDoubleBondStereochemistry dbs, GraphBuilder gb, Map indices) { + private static void addGeometricConfiguration(IDoubleBondStereochemistry dbs, int flavour, GraphBuilder gb, Map indices) { IBond db = dbs.getStereoBond(); IBond[] bs = dbs.getBonds(); // don't try to set a configuration on aromatic bonds - if (SmiOpt.isSet(options, SmiOpt.UseAromaticSymbols) && db.getFlag(CDKConstants.ISAROMATIC)) return; + if (SmiFlavour.isSet(flavour, SmiFlavour.UseAromaticSymbols) && db.getFlag(CDKConstants.ISAROMATIC)) return; int u = indices.get(db.getAtom(0)); int v = indices.get(db.getAtom(1)); @@ -289,7 +306,7 @@ private void addGeometricConfiguration(IDoubleBondStereochemistry dbs, GraphBuil * @param gb the current graph builder * @param indices atom indices */ - private void addTetrahedralConfiguration(ITetrahedralChirality tc, GraphBuilder gb, Map indices) { + private static void addTetrahedralConfiguration(ITetrahedralChirality tc, GraphBuilder gb, Map indices) { IAtom[] ligands = tc.getLigands(); @@ -308,7 +325,7 @@ private void addTetrahedralConfiguration(ITetrahedralChirality tc, GraphBuilder * @param gb the current graph builder * @param indices atom indices */ - private void addExtendedTetrahedralConfiguration(ExtendedTetrahedral et, GraphBuilder gb, + private static void addExtendedTetrahedralConfiguration(ExtendedTetrahedral et, GraphBuilder gb, Map indices) { IAtom[] ligands = et.peripherals(); diff --git a/storage/smiles/src/main/java/org/openscience/cdk/smiles/CxSmilesGenerator.java b/storage/smiles/src/main/java/org/openscience/cdk/smiles/CxSmilesGenerator.java index f9d68514232..e1893d6705c 100644 --- a/storage/smiles/src/main/java/org/openscience/cdk/smiles/CxSmilesGenerator.java +++ b/storage/smiles/src/main/java/org/openscience/cdk/smiles/CxSmilesGenerator.java @@ -23,21 +23,16 @@ package org.openscience.cdk.smiles; -import com.google.common.collect.Lists; -import org.openscience.cdk.interfaces.IAtom; import org.openscience.cdk.smiles.CxSmilesState.PolymerSgroup; import java.text.DecimalFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; public class CxSmilesGenerator { @@ -75,7 +70,7 @@ private static int compare(Comparator comp, List a, List a, List b) { } // Atom Labels - if (SmiOpt.isSet(opts, SmiOpt.CxAtomLabel) && + if (SmiFlavour.isSet(opts, SmiFlavour.CxAtomLabel) && state.atomLabels != null && !state.atomLabels.isEmpty()) { if (sb.length() > 2) @@ -167,7 +162,7 @@ public int compare(List a, List b) { } // 2D/3D Coordinates - if (SmiOpt.isSet(opts, SmiOpt.CxCoordinates) && + if (SmiFlavour.isSet(opts, SmiFlavour.CxCoordinates) && state.atomCoords != null && !state.atomCoords.isEmpty()) { DecimalFormat fmt = new DecimalFormat("#.##"); if (sb.length() > 2) sb.append(','); @@ -188,7 +183,7 @@ public int compare(List a, List b) { } // Multicenter/Positional variation bonds - if (SmiOpt.isSet(opts, SmiOpt.CxMulticenter) && + if (SmiFlavour.isSet(opts, SmiFlavour.CxMulticenter) && state.positionVar != null && !state.positionVar.isEmpty()) { if (sb.length() > 2) sb.append(','); @@ -221,7 +216,7 @@ public int compare(Map.Entry> a, // *CCO* |$_AP1;;;;_AP2$,Sg:n:1,2,3::ht| - if (SmiOpt.isSet(opts, SmiOpt.CxPolymer) && + if (SmiFlavour.isSet(opts, SmiFlavour.CxPolymer) && state.sgroups != null && !state.sgroups.isEmpty()) { List sgroups = new ArrayList<>(state.sgroups); @@ -254,7 +249,7 @@ public int compare(PolymerSgroup a, PolymerSgroup b) { } // [C]1[CH][CH]CCC1 |^1:1,2,^3:0| - if (SmiOpt.isSet(opts, SmiOpt.CxRadical) && + if (SmiFlavour.isSet(opts, SmiFlavour.CxRadical) && state.atomRads != null && !state.atomRads.isEmpty()) { Map> radinv = new TreeMap<>(); for (Map.Entry e : state.atomRads.entrySet()) { diff --git a/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmiFlavour.java b/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmiFlavour.java new file mode 100644 index 00000000000..5541493be07 --- /dev/null +++ b/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmiFlavour.java @@ -0,0 +1,185 @@ +/* + * 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.smiles; + +import org.openscience.cdk.interfaces.IAtom; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.interfaces.IPseudoAtom; + +/** + * Flags for customising SMILES generation. + */ +public final class SmiFlavour { + + private SmiFlavour() { + } + + /** + * Output SMILES in a canonical order. The order is not guaranteed to be + * equivalent between releases. + */ + public static final int Canonical = 0x001; + + /** + * Output SMILES in a canonical order using the InChI labelling algorithm. + * @see #UniversalSmiles + */ + public static final int InChILabelling = 0x003; + + /** + * Output atom-atom mapping for reactions and atom classes for molecules. The + * map index is set on an atom with property {@link org.openscience.cdk.CDKConstants#ATOM_ATOM_MAPPING} + * using {@link org.openscience.cdk.interfaces.IAtom#setProperty(Object, Object)}. + */ + public static final int AtomAtomMap = 0x004; + + /** + * Output atomic mass on atoms. + */ + public static final int AtomicMass = 0x008; + + /** + * Writes aromatic atoms as lower case letters. For portability + * this option is not recomended. + */ + public static final int UseAromaticSymbols = 0x010; + + // public static final int SuppressHydrogens = 0x020; + + /** + * Output tetrahedral stereochemistry on atoms as @ and @@. + * @see #Stereo + */ + public static final int StereoTetrahedral = 0x100; + + /** + * Output cis-trans stereochemistry specified by directional \ + * of / bonds. + * @see #Stereo + */ + public static final int StereoCisTrans = 0x200; + + /** + * Output extended tetrahedral stereochemistry on atoms as @ and + * @@. Extended tetrahedral captures rotations around a cumulated + * carbon: CC=[C@]=CC. + * @see #Stereo + */ + public static final int StereoExTetrahedral = 0x400; + + /** + * Output supported stereochemistry types. + * @see #StereoTetrahedral + * @see #StereoCisTrans + * @see #StereoExTetrahedral + */ + public static final int Stereo = StereoTetrahedral | StereoCisTrans | StereoExTetrahedral; + + /** + * Output 2D coordinates. + */ + public static final int Cx2dCoordinates = 0x001000; + + /** + * Output 3D coordinates. + */ + public static final int Cx3dCoordinates = 0x002000; + + /** + * Output either 2D/3D coordinates. + */ + public static final int CxCoordinates = Cx3dCoordinates | Cx2dCoordinates; + + /** + * Output atom labels, atom labels are specified by {@link IPseudoAtom#getLabel()}. + */ + public static final int CxAtomLabel = 0x008000; + + /** + * Output atom values, atom values are specified by {@link IPseudoAtom#getLabel()}. + */ + public static final int CxAtomValue = 0x010000; + + /** + * Output radicals, radicals are specified by {@link IAtomContainer#getConnectedSingleElectronsCount(IAtom)} + */ + public static final int CxRadical = 0x020000; + + /** + * Output multicenter bonds, positional variation is specified with {@link org.openscience.cdk.sgroup.Sgroup}s + * of the type {@link org.openscience.cdk.sgroup.SgroupType#ExtMulticenter}. + */ + public static final int CxMulticenter = 0x040000; + + /** + * Output polymer repeat units is specified with {@link org.openscience.cdk.sgroup.Sgroup}s. + */ + public static final int CxPolymer = 0x080000; + + /** + * Output fragment grouping for reactions. + */ + public static final int CxFragmentGroup = 0x100000; + + /** + * Output CXSMILES layers. + */ + public static final int CxSmiles = CxAtomLabel | CxAtomValue | CxRadical | CxFragmentGroup | CxMulticenter | CxPolymer; + + /** + * Output CXSMILES layers and coordinates. + */ + public static final int CxSmilesWithCoords = CxSmiles | CxCoordinates; + + /** + * Output non-canonical SMILES without stereochemistry, atomic masses. + */ + public static final int Generic = 0; + + /** + * Output canonical SMILES without stereochemistry, atomic masses. + */ + public static final int Unique = Canonical; + + /** + * Output non-canonical SMILES with stereochemistry, atomic masses. + */ + public static final int Isomeric = Stereo | AtomicMass; + + /** + * Output canonical SMILES with stereochemistry, atomic masses. + */ + public static final int Absolute = Canonical | Isomeric; + + /** + * Output canonical SMILES with stereochemistry, atomic masses using the + * InChI labelling algorithm {@cite OBoyle12}. With delocalised charges + * the generated SMILES can be non-canonical. + */ + public static final int UniversalSmiles = InChILabelling | Isomeric; + + static boolean isSet(int opts, int opt) { + return (opts & opt) != 0; + } +} diff --git a/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmiOpt.java b/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmiOpt.java deleted file mode 100644 index 1266361db2f..00000000000 --- a/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmiOpt.java +++ /dev/null @@ -1,66 +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.smiles; - -import org.openscience.cdk.interfaces.IAtomContainer; - -public final class SmiOpt { - - private SmiOpt() { - } - - public static final int Canonical = 0x001; - public static final int InChILabelling = 0x003; - public static final int AtomAtomMap = 0x004; - public static final int Isotope = 0x008; - public static final int UseAromaticSymbols = 0x010; - public static final int SuppressHydrogens = 0x020; - - public static final int StereoTetrahedral = 0x100; - public static final int StereoCisTrans = 0x200; - public static final int StereoExTetrahedral = 0x400; - public static final int Stereo = StereoTetrahedral | StereoCisTrans | StereoExTetrahedral; - - public static final int Cx2dCoordinates = 0x001000; - public static final int Cx3dCoordinates = 0x002000; - public static final int CxCoordinates = Cx3dCoordinates | Cx2dCoordinates; - public static final int CxAtomLabel = 0x008000; - public static final int CxAtomValue = 0x010000; - public static final int CxRadical = 0x020000; - public static final int CxMulticenter = 0x040000; - public static final int CxPolymer = 0x080000; - public static final int CxFragmentGroup = 0x100000; - public static final int CxSmiles = CxAtomLabel | CxAtomValue | CxRadical | CxFragmentGroup | CxMulticenter | CxPolymer; - public static final int CxSmilesWithCoords = CxSmiles | CxCoordinates; - - public static final int UniversalSmiles = InChILabelling | Stereo | Isotope; - public static final int Absolute = UniversalSmiles; - public static final int Isomeric = Stereo | Isotope; - public static final int Unique = Canonical; - public static final int Generic = 0; - - static boolean isSet(int opts, int opt) { - return (opts & opt) != 0; - } -} diff --git a/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmilesGenerator.java b/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmilesGenerator.java index 937477b41d7..e4b729eabab 100644 --- a/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmilesGenerator.java +++ b/storage/smiles/src/main/java/org/openscience/cdk/smiles/SmilesGenerator.java @@ -46,7 +46,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -56,32 +55,50 @@ import java.util.Set; /** - * Generate a SMILES {@cdk.cite WEI88, WEI89} string for a provided structure. - * The generator can produce several flavour of SMILES. - *

+ * SMILES {@cdk.cite WEI88, WEI89} provides a compact representation of + * chemical structures and reactions. + *
+ * Different flavours of SMILES can be generated and are fully configurable. + * The standard flavours of SMILES defined by Daylight are: *

    - *
  • generic - non-canonical SMILES string, different atom ordering + *
  • Generic - non-canonical SMILES string, different atom ordering * produces different SMILES. No isotope or stereochemistry encoded. *
  • - *
  • unique - canonical SMILES string, different atom ordering + *
  • Unique - canonical SMILES string, different atom ordering * produces the same* SMILES. No isotope or stereochemistry encoded. *
  • - *
  • isomeric - non-canonical SMILES string, different atom ordering + *
  • Isomeric - non-canonical SMILES string, different atom ordering * produces different SMILES. Isotope and stereochemistry is encoded. *
  • - *
  • absolute - canonical SMILES string, different atom ordering + *
  • Absolute - canonical SMILES string, different atom ordering * produces the same SMILES. Isotope and stereochemistry is encoded.
  • *
* + * To output a given flavour the flags in {@link SmiFlavour} are used: + * + *
+ * SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.Isomeric);
+ * 
+ * {@link SmiFlavour} provides more fine grained control, for example, + * for the following is equivalent to {@link SmiFlavour#Isomeric}: + *
+ * SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.Stereo |
+ *                                              SmiFlavour.AtomicMass);
+ * 
+ * Bitwise logic can be used such that we can remove options: + * {@link SmiFlavour#Isomeric} ^ {@link SmiFlavour#AtomicMass} + * will generate isomeric SMILES without atomic mass. + * + * *

* A generator instance is created using one of the static methods, the SMILES * are then created by invoking {@link #create(IAtomContainer)}. *

  * IAtomContainer  ethanol = ...;
- * SmilesGenerator sg      = SmilesGenerator.generic();
- * String          smi     = sg.create(ethanol); // CCO or OCC
+ * SmilesGenerator sg      = new SmilesGenerator(SmiFlavour.Generic);
+ * String          smi     = sg.create(ethanol); // CCO, C(C)O, C(O)C, or OCC
  *
- * SmilesGenerator sg      = SmilesGenerator.unique();
+ * SmilesGenerator sg      = new SmilesGenerator(SmiFlavour.Unique);
  * String          smi     = sg.create(ethanol); // only CCO
  * 
* @@ -97,60 +114,45 @@ * * By default the generator will not write aromatic SMILES. Kekulé SMILES are * generally preferred for compatibility and aromaticity can easily be - * reperceived. Modifying a generator to produce {@link #aromatic()} SMILES - * will use the {@link org.openscience.cdk.CDKConstants#ISAROMATIC} flags. - * These flags can be set manually or with the - * {@link org.openscience.cdk.aromaticity.Aromaticity} utility. + * re-perceived by most tool kits whilst kekulisation may fail. If you + * really want aromatic SMILES the following code demonstrates + * *
  * IAtomContainer  benzene = ...;
  *
- * // with no flags set the output is always kekule
- * SmilesGenerator sg      = SmilesGenerator.generic();
+ * // 'benzene' molecule has no arom flags, we always get Kekulé output
+ * SmilesGenerator sg      = new SmilesGenerator(SmiFlavour.Generic);
  * String          smi     = sg.create(benzene); // C1=CC=CC=C1
  *
- * SmilesGenerator sg      = SmilesGenerator.generic()
- *                                          .aromatic();
- * String          smi     = sg.create(ethanol); // C1=CC=CC=C1
+ * SmilesGenerator sg      = new SmilesGenerator(SmiFlavour.Generic |
+ *                                               SmiFlavour.UseAromaticSymbols);
+ * String          smi     = sg.create(benzene); // C1=CC=CC=C1 flags not set!
  *
+ * // Note, in practice we'd use an aromaticity algorithm
  * for (IAtom a : benzene.atoms())
- *     a.setFlag(CDKConstants.ISAROMATIC, true);
+ *     a.setIsAromatic(true);
  * for (IBond b : benzene.bond())
- *     b.setFlag(CDKConstants.ISAROMATIC, true);
- *
- * // with flags set, the aromatic generator encodes this information
- * SmilesGenerator sg      = SmilesGenerator.generic();
- * String          smi     = sg.create(benzene); // C1=CC=CC=C1
- *
- * SmilesGenerator sg      = SmilesGenerator.generic()
- *                                          .aromatic();
- * String          smi     = sg.create(ethanol); // c1ccccc1
- * 
- *

- * By default atom classes are not written. Atom classes can be written but - * creating a generator {@link #withAtomClasses()}. - * - *

- * IAtomContainer  benzene = ...;
- *
- * // see CDKConstants for property key
- * benzene.getAtom(3)
- *        .setProperty(ATOM_ATOM_MAPPING, 42);
+ *     a.setIsAromatic(true);
  *
- * SmilesGenerator sg      = SmilesGenerator.generic();
+ * // 'benzene' molecule now has arom flags, we always get aromatic SMILES if we request it
+ * SmilesGenerator sg      = new SmilesGenerator(SmiFlavour.Generic);
  * String          smi     = sg.create(benzene); // C1=CC=CC=C1
  *
- * SmilesGenerator sg      = SmilesGenerator.generic()
- *                                          .withAtomClasses();
- * String          smi     = sg.create(ethanol); // C1=CC=[CH:42]C=C1
+ * SmilesGenerator sg      = new SmilesGenerator(SmiFlavour.Generic |
+ *                                               SmiFlavour.UseAromaticSymbols);
+ * String          smi     = sg.create(benzene); // c1ccccc1
  * 
*

* - * Auxiliary data can be stored with SMILES by knowing the output order of - * atoms. The following example demonstrates the storage of 2D coordinates. + * It can be useful to know the output order of SMILES. On input the order of the atoms + * reflects the atom index. If we know this order we can refer to atoms by index and + * associate data with the SMILES string. + * The output order is obtained by parsing in an auxiliary array during creation. The + * following snippet demonstrates how we can write coordinates in order. * *

  * IAtomContainer  mol = ...;
- * SmilesGenerator sg  = SmilesGenerator.generic();
+ * SmilesGenerator sg  = new SmilesGenerator(SmiFlavor.Generic);
  *
  * int   n     = mol.getAtomCount();
  * int[] order = new int[n];
@@ -169,7 +171,18 @@
  *
  * 
* - * * the unique SMILES generation uses a fast equitable labelling procedure + * Using the output order of SMILES forms the basis of + * + * ChemAxon Extended SMILES (CXSMILES) which can also be generated. Extended SMILES + * allows additional structure data to be serialized including, atom labels/values, fragment + * grouping (for salts in reactions), polymer repeats, multi center bonds, and coordinates. + * The CXSMILES layer is appended after the SMILES so that parser which don't interpret it + * can ignore it. + *

+ * The two aggregate flavours are {@link SmiFlavour#CxSmiles} and {@link SmiFlavour#CxSmilesWithCoords}. + * As with other flavours, fine grain control is possible {@see SmiFlavour}. + *

+ * * the unique SMILES generation uses a fast equitable labelling procedure * and as such there are some structures which may not be unique. The number * of such structures is generally minimal. * @@ -190,41 +203,45 @@ */ public final class SmilesGenerator { - private final int options; - private final CDKToBeam converter; + private final int flavour; /** * Create the generic SMILES generator. * @see #generic() - * @deprecated use {@link #SmilesGenerator(int)} configuring with {@link SmiOpt}. + * @deprecated use {@link #SmilesGenerator(int)} configuring with {@link SmiFlavour}. */ @Deprecated public SmilesGenerator() { this(0); } - public SmilesGenerator(int options) { - this.options = options; - this.converter = new CDKToBeam(options); - } - /** - * The generator should write aromatic (lower-case) SMILES. This option is - * not recommended as different parsers can interpret where bonds should be - * placed. + * Create a SMILES generator with the specified {@link SmiFlavour}. * *

-     * IAtomContainer  container = ...;
-     * SmilesGenerator smilesGen = SmilesGenerator.unique()
-     *                                            .aromatic();
-     * smilesGen.createSMILES(container);
+     * SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.Stereo |
+     *                                              SmiFlavour.Canonical);
      * 
* + * @param flavour SMILES flavour flags {@see SmiFlavour} + */ + public SmilesGenerator(int flavour) { + this.flavour = flavour; + } + + /** + * Derived a new generator that writes aromatic atoms in lower case. + * The preferred way of doing this is now to use the {@link #SmilesGenerator(int)} constructor: + * + *
+     * SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.UseAromaticSymbols);
+     * 
+ * * @return a generator for aromatic SMILES - * @deprecated configure with {@link SmiOpt} + * @deprecated configure with {@link SmiFlavour} */ public SmilesGenerator aromatic() { - return new SmilesGenerator(this.options | SmiOpt.UseAromaticSymbols); + return new SmilesGenerator(this.flavour | SmiFlavour.UseAromaticSymbols); } /** @@ -240,11 +257,11 @@ public SmilesGenerator aromatic() { * * * @return a generator for SMILES with atom classes - * @deprecated configure with {@link SmiOpt} + * @deprecated configure with {@link SmiFlavour} */ @Deprecated public SmilesGenerator withAtomClasses() { - return new SmilesGenerator(this.options | SmiOpt.AtomAtomMap); + return new SmilesGenerator(this.flavour | SmiFlavour.AtomAtomMap); } /** @@ -256,7 +273,7 @@ public SmilesGenerator withAtomClasses() { * @return a new arbitrary SMILES generator */ public static SmilesGenerator generic() { - return new SmilesGenerator(SmiOpt.Generic); + return new SmilesGenerator(SmiFlavour.Generic); } /** @@ -267,7 +284,7 @@ public static SmilesGenerator generic() { * @return a new isomeric SMILES generator */ public static SmilesGenerator isomeric() { - return new SmilesGenerator(SmiOpt.Isomeric); + return new SmilesGenerator(SmiFlavour.Isomeric); } /** @@ -277,7 +294,7 @@ public static SmilesGenerator isomeric() { * @return a new unique SMILES generator */ public static SmilesGenerator unique() { - return new SmilesGenerator(SmiOpt.Unique); + return new SmilesGenerator(SmiFlavour.Unique); } /** @@ -289,7 +306,7 @@ public static SmilesGenerator unique() { * @return a new absolute SMILES generator */ public static SmilesGenerator absolute() { - return new SmilesGenerator(SmiOpt.Absolute); + return new SmilesGenerator(SmiFlavour.Absolute); } /** @@ -342,16 +359,17 @@ public String create(IAtomContainer molecule) throws CDKException { } /** - * Create a SMILES string and obtain the order which the atoms were - * written. The output order allows one to arrange auxiliary atom data in the + * Creates a SMILES string of the flavour specified in the constructor + * and write the output order to the provided array. + *
+ * The output order allows one to arrange auxiliary atom data in the * order that a SMILES string will be read. A simple example is seen below - * where 2D coordinates are stored with a SMILES string. In reality a more - * compact binary encoding would be used instead of printing the coordinates - * as a string. + * where 2D coordinates are stored with a SMILES string. This method + * forms the basis of CXSMILES. * *
      * IAtomContainer  mol = ...;
-     * SmilesGenerator sg  = SmilesGenerator.generic();
+     * SmilesGenerator sg  = new SmilesGenerator();
      *
      * int   n     = mol.getAtomCount();
      * int[] order = new int[n];
@@ -376,20 +394,21 @@ public String create(IAtomContainer molecule) throws CDKException {
      * @throws CDKException SMILES could not be created
      */
     public String create(IAtomContainer molecule, int[] order) throws CDKException {
-        return create(molecule, this.options, order);
+        return create(molecule, this.flavour, order);
     }
 
     /**
-     * Create a SMILES string and obtain the order which the atoms were
-     * written. The output order allows one to arrange auxiliary atom data in the
+     * Creates a SMILES string of the flavour specified as a parameter
+     * and write the output order to the provided array.
+     * 
+ * The output order allows one to arrange auxiliary atom data in the * order that a SMILES string will be read. A simple example is seen below - * where 2D coordinates are stored with a SMILES string. In reality a more - * compact binary encoding would be used instead of printing the coordinates - * as a string. + * where 2D coordinates are stored with a SMILES string. This method + * forms the basis of CXSMILES. * *
      * IAtomContainer  mol = ...;
-     * SmilesGenerator sg  = SmilesGenerator.generic();
+     * SmilesGenerator sg  = new SmilesGenerator();
      *
      * int   n     = mol.getAtomCount();
      * int[] order = new int[n];
@@ -411,25 +430,25 @@ public String create(IAtomContainer molecule, int[] order) throws CDKException {
      * @param molecule the molecule to write
      * @param order    array to store the output order of atoms
      * @return the SMILES string
-     * @throws CDKException SMILES could not be created
+     * @throws CDKException a valid SMILES could not be created
      */
-    public String create(IAtomContainer molecule, int options, int[] order) throws CDKException {
+    public static String create(IAtomContainer molecule, int flavour, int[] order) throws CDKException {
         try {
             if (order.length != molecule.getAtomCount())
                 throw new IllegalArgumentException("the array for storing output order should be"
                         + "the same length as the number of atoms");
 
-            Graph g = converter.toBeamGraph(molecule);
+            Graph g = CDKToBeam.toBeamGraph(molecule, flavour);
 
             // apply the canonical labelling
-            if (SmiOpt.isSet(options, SmiOpt.Canonical)) {
+            if (SmiFlavour.isSet(flavour, SmiFlavour.Canonical)) {
 
                 // determine the output order
-                int[] labels = labels(molecule);
+                int[] labels = labels(flavour, molecule);
 
                 g = g.permute(labels).resonate();
 
-                if (SmiOpt.isSet(options, SmiOpt.StereoCisTrans)) {
+                if (SmiFlavour.isSet(flavour, SmiFlavour.StereoCisTrans)) {
 
                     // FIXME: required to ensure canonical double bond labelling
                     g.sort(new Graph.VisitHighOrderFirst());
@@ -452,17 +471,17 @@ public String create(IAtomContainer molecule, int options, int[] order) throws C
                     canorder[i] = order[labels[i]];
                 System.arraycopy(canorder, 0, order, 0, order.length);
 
-                if (SmiOpt.isSet(options, SmiOpt.CxSmilesWithCoords)) {
-                    smiles += CxSmilesGenerator.generate(getCxSmilesState(molecule),
-                                                         options, null, order);
+                if (SmiFlavour.isSet(flavour, SmiFlavour.CxSmilesWithCoords)) {
+                    smiles += CxSmilesGenerator.generate(getCxSmilesState(flavour, molecule),
+                                                         flavour, null, order);
                 }
 
                 return smiles;
             } else {
                 String smiles = g.toSmiles(order);
 
-                if (SmiOpt.isSet(options, SmiOpt.CxSmilesWithCoords)) {
-                    smiles += CxSmilesGenerator.generate(getCxSmilesState(molecule), options, null, order);
+                if (SmiFlavour.isSet(flavour, SmiFlavour.CxSmilesWithCoords)) {
+                    smiles += CxSmilesGenerator.generate(getCxSmilesState(flavour, molecule), flavour, null, order);
                 }
 
                 return smiles;
@@ -472,15 +491,30 @@ public String create(IAtomContainer molecule, int options, int[] order) throws C
         }
     }
 
+    /**
+     * Create a SMILES for a reaction.
+     *
+     * @param reaction CDK reaction instance
+     * @return reaction SMILES
+     * @deprecated use {@link #create(IAtomContainer)}
+     * @throws CDKException a valid SMILES could not be created
+     */
     @Deprecated
     public String createReactionSMILES(IReaction reaction) throws CDKException {
         return create(reaction);
     }
 
+    /**
+     * Create a SMILES for a reaction of the flavour specified in the constructor.
+     *
+     * @param reaction CDK reaction instance
+     * @return reaction SMILES
+     */
     public String create(IReaction reaction) throws CDKException {
         return create(reaction, new int[ReactionManipulator.getAtomCount(reaction)]);
     }
 
+    // utility method that safely collects the Sgroup from a molecule
     private void safeAddSgroups(List sgroups, IAtomContainer mol) {
         List molSgroups = mol.getProperty(CDKConstants.CTAB_SGROUPS);
         if (molSgroups != null)
@@ -488,11 +522,11 @@ private void safeAddSgroups(List sgroups, IAtomContainer mol) {
     }
 
     /**
-     * Generate a SMILES for the given Reaction.
+     * Create a SMILES for a reaction of the flavour specified in the constructor and
+     * write the output order to the provided array.
      *
-     * @param reaction the reaction in question
-     * @return the SMILES representation of the reaction
-     * @throws org.openscience.cdk.exception.CDKException if there is an error during SMILES generation
+     * @param reaction CDK reaction instance
+     * @return reaction SMILES
      */
     public String create(IReaction reaction, int[] ordering) throws CDKException {
 
@@ -530,9 +564,9 @@ public String create(IReaction reaction, int[] ordering) throws CDKException {
         }
 
         // we need to make sure we generate without the CXSMILES layers
-        String smi = create(reactantPart, options ^ SmiOpt.CxSmilesWithCoords, reactantOrder) + ">" +
-                     create(agentPart,    options ^ SmiOpt.CxSmilesWithCoords, agentOrder) + ">" +
-                     create(productPart,  options ^ SmiOpt.CxSmilesWithCoords, productOrder);
+        String smi = create(reactantPart, flavour ^ SmiFlavour.CxSmilesWithCoords, reactantOrder) + ">" +
+                     create(agentPart, flavour ^ SmiFlavour.CxSmilesWithCoords, agentOrder) + ">" +
+                     create(productPart, flavour ^ SmiFlavour.CxSmilesWithCoords, productOrder);
 
         // copy ordering back to unified array and adjust values
         int agentBeg = reactantOrder.length;
@@ -546,7 +580,7 @@ public String create(IReaction reaction, int[] ordering) throws CDKException {
         for (int i = agentEnd; i < prodEnd; i++)
             ordering[i] += agentEnd;
 
-        if (SmiOpt.isSet(options, SmiOpt.CxSmilesWithCoords)) {
+        if (SmiFlavour.isSet(flavour, SmiFlavour.CxSmilesWithCoords)) {
             IAtomContainer unified = reaction.getBuilder().newInstance(IAtomContainer.class);
             unified.add(reactantPart);
             unified.add(agentPart);
@@ -554,12 +588,12 @@ public String create(IReaction reaction, int[] ordering) throws CDKException {
             unified.setProperty(CDKConstants.CTAB_SGROUPS, sgroups);
 
             // base CXSMILES state information
-            final CxSmilesState cxstate = getCxSmilesState(unified);
+            final CxSmilesState cxstate = getCxSmilesState(flavour, unified);
 
             int[] components = null;
 
             // extra state info on fragment grouping, specific to reactions
-            if (SmiOpt.isSet(options, SmiOpt.CxFragmentGroup)) {
+            if (SmiFlavour.isSet(flavour, SmiFlavour.CxFragmentGroup)) {
 
                 cxstate.fragGroups = new ArrayList<>();
 
@@ -601,7 +635,7 @@ public String create(IReaction reaction, int[] ordering) throws CDKException {
             }
 
 
-            smi += CxSmilesGenerator.generate(cxstate, options, components, ordering);
+            smi += CxSmilesGenerator.generate(cxstate, flavour, components, ordering);
         }
 
         return smi;
@@ -631,10 +665,10 @@ public void setUseAromaticityFlag(boolean useAromaticityFlag) {
      * @return the permutation
      * @see Canon
      */
-    private int[] labels(final IAtomContainer molecule) throws CDKException {
+    private static int[] labels(int flavour, final IAtomContainer molecule) throws CDKException {
         // FIXME: use SmiOpt.InChiLabelling
-        long[] labels = SmiOpt.isSet(options, SmiOpt.Isomeric) ? inchiNumbers(molecule)
-                                                               : Canon.label(molecule, GraphUtil.toAdjList(molecule));
+        long[] labels = SmiFlavour.isSet(flavour, SmiFlavour.Isomeric) ? inchiNumbers(molecule)
+                                                                       : Canon.label(molecule, GraphUtil.toAdjList(molecule));
         int[] cpy = new int[labels.length];
         for (int i = 0; i < labels.length; i++)
             cpy[i] = (int) labels[i] - 1;
@@ -651,7 +685,7 @@ private int[] labels(final IAtomContainer molecule) throws CDKException {
      * @return the inchi numbers
      * @throws CDKException the inchi numbers could not be obtained
      */
-    private long[] inchiNumbers(IAtomContainer container) throws CDKException {
+    private static long[] inchiNumbers(IAtomContainer container) throws CDKException {
         // TODO: create an interface so we don't have to dynamically load the
         // class each time
         String cname = "org.openscience.cdk.graph.invariant.InChINumbersTools";
@@ -672,20 +706,23 @@ private long[] inchiNumbers(IAtomContainer container) throws CDKException {
         }
     }
 
+    // utility safety check to guard against invalid state
     private static Integer ensureNotNull(Integer x) {
         if (x == null)
-            throw new IllegalArgumentException("Inconsistent CXSMILES state! Check the SGroups.");
+            throw new IllegalStateException("Inconsistent CXSMILES state! Check the SGroups.");
         return x;
     }
 
-    private List toAtomIdxs(Collection atoms, Map atomidx) {
+    // utility method maps the atoms to their indicies using the provided map.
+    private static List toAtomIdxs(Collection atoms, Map atomidx) {
         List idxs = new ArrayList<>(atoms.size());
         for (IAtom atom : atoms)
             idxs.add(ensureNotNull(atomidx.get(atom)));
         return idxs;
     }
 
-    private CxSmilesState getCxSmilesState(IAtomContainer mol) {
+    // Creates a CxSmilesState from a molecule with atom labels, repeat units, multicenter bonds etc
+    private static CxSmilesState getCxSmilesState(int flavour, IAtomContainer mol) {
         CxSmilesState state = new CxSmilesState();
         state.atomCoords = new ArrayList<>();
         state.coordFlag = false;
@@ -719,13 +756,13 @@ private CxSmilesState getCxSmilesState(IAtomContainer mol) {
             Point2d p2 = atom.getPoint2d();
             Point3d p3 = atom.getPoint3d();
 
-            if (SmiOpt.isSet(options, SmiOpt.Cx2dCoordinates) && p2 != null) {
+            if (SmiFlavour.isSet(flavour, SmiFlavour.Cx2dCoordinates) && p2 != null) {
                 state.atomCoords.add(new double[]{p2.x, p2.y, 0});
                 state.coordFlag = true;
-            } else if (SmiOpt.isSet(options, SmiOpt.Cx3dCoordinates) && p3 != null) {
+            } else if (SmiFlavour.isSet(flavour, SmiFlavour.Cx3dCoordinates) && p3 != null) {
                 state.atomCoords.add(new double[]{p3.x, p3.y, p3.z});
                 state.coordFlag = true;
-            } else if (SmiOpt.isSet(options, SmiOpt.CxCoordinates)) {
+            } else if (SmiFlavour.isSet(flavour, SmiFlavour.CxCoordinates)) {
                 state.atomCoords.add(new double[3]);
             }
         }
diff --git a/storage/smiles/src/test/java/org/openscience/cdk/smiles/CDKToBeamTest.java b/storage/smiles/src/test/java/org/openscience/cdk/smiles/CDKToBeamTest.java
index a3dc563acc5..d6b8c96fcf8 100644
--- a/storage/smiles/src/test/java/org/openscience/cdk/smiles/CDKToBeamTest.java
+++ b/storage/smiles/src/test/java/org/openscience/cdk/smiles/CDKToBeamTest.java
@@ -289,7 +289,7 @@ public void benzene_kekule() throws Exception {
     @Test
     public void benzene() throws Exception {
         IAtomContainer ac = TestMoleculeFactory.makeBenzene();
-        Graph g = convert(ac, true, SmiOpt.UseAromaticSymbols);
+        Graph g = convert(ac, true, SmiFlavour.UseAromaticSymbols);
         assertThat(g.toSmiles(), is("c1ccccc1"));
     }
 
@@ -301,7 +301,7 @@ public void imidazole_kekule() throws Exception {
 
     @Test
     public void imidazole() throws Exception {
-        Graph g = convert(TestMoleculeFactory.makeImidazole(), true, SmiOpt.UseAromaticSymbols);
+        Graph g = convert(TestMoleculeFactory.makeImidazole(), true, SmiFlavour.UseAromaticSymbols);
         assertThat(g.toSmiles(), is("c1[nH]cnc1"));
     }
 
@@ -317,7 +317,7 @@ public void C13_isomeric() throws Exception {
         IAtom a = new Atom("C");
         a.setMassNumber(13);
         ac.addAtom(a);
-        Graph g = convert(ac, SmiOpt.Isotope);
+        Graph g = convert(ac, SmiFlavour.AtomicMass);
         assertThat(g.atom(0).isotope(), is(13));
         assertThat(g.toSmiles(), is("[13CH4]"));
     }
@@ -385,7 +385,7 @@ public void e_1_2_difluoroethene() throws Exception {
 
         ac.addStereoElement(new DoubleBondStereochemistry(ac.getBond(1), new IBond[]{ac.getBond(0), ac.getBond(2)},
                 OPPOSITE));
-        Graph g = convert(ac, SmiOpt.StereoCisTrans);
+        Graph g = convert(ac, SmiFlavour.StereoCisTrans);
         assertThat(g.toSmiles(), is("F/C=C/F"));
     }
 
@@ -408,7 +408,7 @@ public void z_1_2_difluoroethene() throws Exception {
 
         ac.addStereoElement(new DoubleBondStereochemistry(ac.getBond(1), new IBond[]{ac.getBond(0), ac.getBond(2)},
                 TOGETHER));
-        Graph g = convert(ac, SmiOpt.StereoCisTrans);
+        Graph g = convert(ac, SmiFlavour.StereoCisTrans);
         assertThat(g.toSmiles(), is("F/C=C\\F"));
     }
 
@@ -439,7 +439,7 @@ public void _2R_butan_2_ol() throws Exception {
                 ac.getAtom(5), // H
         }, CLOCKWISE));
 
-        Graph g = convert(ac, SmiOpt.StereoTetrahedral);
+        Graph g = convert(ac, SmiFlavour.StereoTetrahedral);
         assertThat(g.toSmiles(), is("CC[C@@](C)(O)[H]"));
     }
 
@@ -470,7 +470,7 @@ public void _2S_butan_2_ol() throws Exception {
                 ac.getAtom(5), // H
         }, ANTI_CLOCKWISE));
 
-        Graph g = convert(ac, SmiOpt.StereoTetrahedral);
+        Graph g = convert(ac, SmiFlavour.StereoTetrahedral);
         assertThat(g.toSmiles(), is("CC[C@](C)(O)[H]"));
     }
 
@@ -497,7 +497,7 @@ public void z_1_2_difluoroethene_aromatic() throws Exception {
 
         ac.addStereoElement(new DoubleBondStereochemistry(ac.getBond(1), new IBond[]{ac.getBond(0), ac.getBond(2)},
                 TOGETHER));
-        Graph g = convert(ac, SmiOpt.UseAromaticSymbols);
+        Graph g = convert(ac, SmiFlavour.UseAromaticSymbols);
         assertThat(g.toSmiles(), is("F[CH]:[CH]F"));
     }
 
@@ -517,7 +517,7 @@ public void writeAtomClass() throws Exception {
         ac.getAtom(0).setProperty(CDKConstants.ATOM_ATOM_MAPPING, 3);
         ac.getAtom(1).setProperty(CDKConstants.ATOM_ATOM_MAPPING, 1);
         ac.getAtom(2).setProperty(CDKConstants.ATOM_ATOM_MAPPING, 2);
-        assertThat(convert(ac, SmiOpt.AtomAtomMap).toSmiles(), is("[CH3:3][CH2:1][OH:2]"));
+        assertThat(convert(ac, SmiFlavour.AtomAtomMap).toSmiles(), is("[CH3:3][CH2:1][OH:2]"));
     }
 
     @Test
@@ -537,7 +537,7 @@ public void r_penta_2_3_diene_impl_h() throws Exception {
                 m.getAtom(3), m.getAtom(4)}, ANTI_CLOCKWISE);
         m.setStereoElements(Collections.singletonList(element));
 
-        assertThat(convert(m, SmiOpt.Stereo).toSmiles(), is("CC=[C@]=CC"));
+        assertThat(convert(m, SmiFlavour.Stereo).toSmiles(), is("CC=[C@]=CC"));
     }
 
     @Test
@@ -557,7 +557,7 @@ public void s_penta_2_3_diene_impl_h() throws Exception {
                 m.getAtom(3), m.getAtom(4)}, CLOCKWISE);
         m.setStereoElements(Collections.singletonList(element));
 
-        assertThat(convert(m, SmiOpt.Stereo).toSmiles(), is("CC=[C@@]=CC"));
+        assertThat(convert(m, SmiFlavour.Stereo).toSmiles(), is("CC=[C@@]=CC"));
     }
 
     @Test
@@ -588,7 +588,7 @@ public void r_penta_2_3_diene_expl_h() throws Exception {
                     m.getAtom(atoms[i][1]), m.getAtom(atoms[i][2]), m.getAtom(atoms[i][3])}, stereos[i]);
             m.setStereoElements(Collections.singletonList(element));
 
-            assertThat(convert(m, SmiOpt.Stereo).toSmiles(), is("CC(=[C@@]=C(C)[H])[H]"));
+            assertThat(convert(m, SmiFlavour.Stereo).toSmiles(), is("CC(=[C@@]=C(C)[H])[H]"));
 
         }
     }
@@ -621,7 +621,7 @@ public void s_penta_2_3_diene_expl_h() throws Exception {
                     m.getAtom(atoms[i][1]), m.getAtom(atoms[i][2]), m.getAtom(atoms[i][3])}, stereos[i]);
             m.setStereoElements(Collections.singletonList(element));
 
-            assertThat(convert(m, SmiOpt.Stereo).toSmiles(), is("CC(=[C@]=C(C)[H])[H]"));
+            assertThat(convert(m, SmiFlavour.Stereo).toSmiles(), is("CC(=[C@]=C(C)[H])[H]"));
 
         }
     }
diff --git a/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesGeneratorTest.java b/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesGeneratorTest.java
index eb6dd8d29dd..00274e1fbf7 100644
--- a/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesGeneratorTest.java
+++ b/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesGeneratorTest.java
@@ -37,7 +37,7 @@ public class CxSmilesGeneratorTest {
     @Test
     public void emptyCXSMILES() {
         CxSmilesState state = new CxSmilesState();
-        assertThat(CxSmilesGenerator.generate(state, SmiOpt.CxSmiles, new int[0], new int[0]),
+        assertThat(CxSmilesGenerator.generate(state, SmiFlavour.CxSmiles, new int[0], new int[0]),
                    is(""));
     }
 
@@ -47,7 +47,7 @@ public void multicenter() {
         state.positionVar = new HashMap<>();
         state.positionVar.put(0, Arrays.asList(4, 5, 6, 7));
         state.positionVar.put(2, Arrays.asList(4, 6, 5, 7));
-        assertThat(CxSmilesGenerator.generate(state, SmiOpt.CxMulticenter, new int[0], new int[]{7,6,5,4,3,2,1,0}),
+        assertThat(CxSmilesGenerator.generate(state, SmiFlavour.CxMulticenter, new int[0], new int[]{7, 6, 5, 4, 3, 2, 1, 0}),
                    is(" |m:5:0.1.2.3,7:0.1.2.3|"));
     }
 
@@ -57,7 +57,7 @@ public void coords2d() {
         state.atomCoords = Arrays.asList(new double[]{0, 1.5, 0},
                                          new double[]{0, 3, 0},
                                          new double[]{1.5, 1.5, 0});
-        assertThat(CxSmilesGenerator.generate(state, SmiOpt.CxCoordinates, new int[0], new int[]{1, 2, 0}),
+        assertThat(CxSmilesGenerator.generate(state, SmiFlavour.CxCoordinates, new int[0], new int[]{1, 2, 0}),
                    is(" |(,3,;1.5,1.5,;,1.5,)|"));
     }
 
@@ -67,7 +67,7 @@ public void sgroups() {
         state.sgroups = new ArrayList<>(1);
         state.sgroups.add(new CxSmilesState.PolymerSgroup("n", Arrays.asList(2,3), "n", "ht"));
         state.sgroups.add(new CxSmilesState.PolymerSgroup("n", Arrays.asList(5), "m", "ht"));
-        assertThat(CxSmilesGenerator.generate(state, SmiOpt.CxPolymer, new int[0], new int[]{7,6,5,4,3,2,1,0}),
+        assertThat(CxSmilesGenerator.generate(state, SmiFlavour.CxPolymer, new int[0], new int[]{7, 6, 5, 4, 3, 2, 1, 0}),
                    is(" |Sg:n:2:m:ht,Sg:n:4,5:n:ht|"));
     }
 
@@ -78,7 +78,7 @@ public void radicals() {
         state.atomRads.put(2, CxSmilesState.Radical.Monovalent);
         state.atomRads.put(6, CxSmilesState.Radical.Monovalent);
         state.atomRads.put(4, CxSmilesState.Radical.Divalent);
-        assertThat(CxSmilesGenerator.generate(state, SmiOpt.CxSmiles, new int[0], new int[]{7,6,5,4,3,2,1,0}),
+        assertThat(CxSmilesGenerator.generate(state, SmiFlavour.CxSmiles, new int[0], new int[]{7, 6, 5, 4, 3, 2, 1, 0}),
                    is(" |^1:1,5,^2:3|"));
     }
 
diff --git a/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesTest.java b/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesTest.java
index c5c4b680656..9647ed3b7f7 100644
--- a/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesTest.java
+++ b/storage/smiles/src/test/java/org/openscience/cdk/smiles/CxSmilesTest.java
@@ -230,7 +230,7 @@ public void atomValues() throws InvalidSmilesException {
         mol.getAtom(2).setImplicitHydrogenCount(0);
         mol.addBond(0, 1, IBond.Order.SINGLE);
         mol.addBond(1, 2, IBond.Order.SINGLE);
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxAtomLabel);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxAtomLabel);
         String smi = smigen.create(mol);
         assertThat(smi, is("CC* |$;;R1$|"));
     }
@@ -245,8 +245,8 @@ public void atomValues() throws InvalidSmilesException {
         mol.getAtom(2).setImplicitHydrogenCount(0);
         mol.addBond(0, 1, IBond.Order.SINGLE);
         mol.addBond(1, 2, IBond.Order.SINGLE);
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.Canonical |
-                                                     SmiOpt.CxAtomLabel);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.Canonical |
+                                                     SmiFlavour.CxAtomLabel);
         String smi = smigen.create(mol);
         assertThat(smi, is("*CC |$R1$|"));
     }
@@ -255,8 +255,8 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("c1ccccc1.*Cl |m:6:0.1.2.3.4.5|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.UseAromaticSymbols |
-                                                     SmiOpt.CxMulticenter);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.UseAromaticSymbols |
+                                                     SmiFlavour.CxMulticenter);
         String smi = smigen.create(mol);
         assertThat(smi, is("c1ccccc1.*Cl |m:6:0.1.2.3.4.5|"));
     }
@@ -265,9 +265,9 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("c1ccccc1.*Cl |m:6:0.1.2.3.4.5|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.UseAromaticSymbols |
-                                                     SmiOpt.CxMulticenter |
-                                                     SmiOpt.Canonical);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.UseAromaticSymbols |
+                                                     SmiFlavour.CxMulticenter |
+                                                     SmiFlavour.Canonical);
         String smi = smigen.create(mol);
         assertThat(smi, is("*Cl.c1ccccc1 |m:0:2.3.4.5.6.7|"));
     }
@@ -277,7 +277,7 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("CCCOCCO |Sg:n:1,2,3::ht|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxPolymer);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxPolymer);
         String smi = smigen.create(mol);
         assertThat(smi, is("CCCOCCO |Sg:n:1,2,3:n:ht|"));
     }
@@ -286,8 +286,8 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("CCCOCCO |Sg:n:1,2,3::ht|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.Canonical |
-                                                     SmiOpt.CxPolymer);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.Canonical |
+                                                     SmiFlavour.CxPolymer);
         String smi = smigen.create(mol);
         assertThat(smi, is("OCCOCCC |Sg:n:3,4,5:n:ht|"));
     }
@@ -296,7 +296,7 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("CCO |(,,;1,1,;2,2,)|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxCoordinates);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxCoordinates);
         String smi = smigen.create(mol);
         assertThat(smi, is("CCO |(,,;1,1,;2,2,)|"));
     }
@@ -305,8 +305,8 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("CCO |(,,;1,1,;2,2,)|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.Canonical |
-                                                     SmiOpt.CxCoordinates);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.Canonical |
+                                                     SmiFlavour.CxCoordinates);
         String smi = smigen.create(mol);
         assertThat(smi, is("OCC |(2,2,;1,1,;,,)|"));
     }
@@ -324,7 +324,7 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("CCO");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxCoordinates);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxCoordinates);
         String smi = smigen.create(mol);
         assertThat(smi, is("CCO"));
     }
@@ -333,7 +333,7 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("[C]1C[CH][CH]OC1 |^1:2,3,^2:0|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxRadical);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxRadical);
         String smi = smigen.create(mol);
         assertThat(smi, is("[C]1C[CH][CH]OC1 |^1:2,3,^2:0|"));
     }
@@ -342,8 +342,8 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IAtomContainer mol = smipar.parseSmiles("[C]1C[CH][CH]OC1 |^1:2,3,^2:0|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxRadical |
-                                                     SmiOpt.Canonical);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxRadical |
+                                                     SmiFlavour.Canonical);
         String smi = smigen.create(mol);
         assertThat(smi, is("[C]1CO[CH][CH]C1 |^1:3,4,^2:0|"));
     }
@@ -352,8 +352,8 @@ public void atomValues() throws InvalidSmilesException {
         IChemObjectBuilder bldr = SilentChemObjectBuilder.getInstance();
         SmilesParser smipar = new SmilesParser(bldr);
         IReaction rxn = smipar.parseReactionSmiles("CC(C)c1ccccc1.ClC([*])=O>ClCCl.[Al+3].[Cl-].[Cl-].[Cl-]>CC(C)c1ccc(cc1)C([*])=O |$;;;;;;;;;;;R1;;;;;;;;;;;;;;;;;;;R1;$,f:3.4.5.6|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxAtomLabel |
-                                                     SmiOpt.CxFragmentGroup);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxAtomLabel |
+                                                     SmiFlavour.CxFragmentGroup);
         assertThat(smigen.create(rxn),
                    is("CC(C)C1=CC=CC=C1.ClC(*)=O>ClCCl.[Al+3].[Cl-].[Cl-].[Cl-]>CC(C)C1=CC=C(C=C1)C(*)=O |f:3.4.5.6,$;;;;;;;;;;;R1;;;;;;;;;;;;;;;;;;;R1$|"));
     }
@@ -363,9 +363,9 @@ public void atomValues() throws InvalidSmilesException {
         SmilesParser smipar = new SmilesParser(bldr);
         IReaction rxn1 = smipar.parseReactionSmiles("CC(C)c1ccccc1.ClC([*])=O>[Al+3].[Cl-].[Cl-].[Cl-].ClCCl>CC(C)c1ccc(cc1)C([*])=O |$;;;;;;;;;;;R1;;;;;;;;;;;;;;;;;;;R1;$,f:2.3.4.5|");
         IReaction rxn2 = smipar.parseReactionSmiles("ClC([*])=O.CC(C)c1ccccc1>[Al+3].[Cl-].[Cl-].[Cl-].ClCCl>CC(C)c1ccc(cc1)C([*])=O |$;;R1;;;;;;;;;;;;;;;;;;;;;;;;;;;;R1;$,f:2.3.5.4|");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.CxAtomLabel |
-                                                     SmiOpt.CxFragmentGroup |
-                                                     SmiOpt.Canonical);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.CxAtomLabel |
+                                                     SmiFlavour.CxFragmentGroup |
+                                                     SmiFlavour.Canonical);
         assertThat(smigen.create(rxn1),
                    is(smigen.create(rxn2)));
     }
diff --git a/storage/smiles/src/test/java/org/openscience/cdk/smiles/SmilesGeneratorTest.java b/storage/smiles/src/test/java/org/openscience/cdk/smiles/SmilesGeneratorTest.java
index 9ad810d521b..340686d8ec1 100644
--- a/storage/smiles/src/test/java/org/openscience/cdk/smiles/SmilesGeneratorTest.java
+++ b/storage/smiles/src/test/java/org/openscience/cdk/smiles/SmilesGeneratorTest.java
@@ -49,7 +49,6 @@
 import org.openscience.cdk.config.Elements;
 import org.openscience.cdk.config.IsotopeFactory;
 import org.openscience.cdk.exception.CDKException;
-import org.openscience.cdk.exception.InvalidSmilesException;
 import org.openscience.cdk.graph.AtomContainerAtomPermutor;
 import org.openscience.cdk.graph.AtomContainerBondPermutor;
 import org.openscience.cdk.graph.Cycles;
@@ -1224,7 +1223,7 @@ public void assignDbStereo() throws Exception {
         IReaction r1 = smipar.parseReactionSmiles("CC(C)C1=CC=CC=C1.C(CC(=O)Cl)CCl>[Al+3].[Cl-].[Cl-].[Cl-].C(Cl)Cl>CC(C)C1=CC=C(C=C1)C(=O)CCCCl");
         IReaction r2 = smipar.parseReactionSmiles("C(CC(=O)Cl)CCl.CC(C)C1=CC=CC=C1>[Al+3].[Cl-].[Cl-].[Cl-].C(Cl)Cl>CC(C)C1=CC=C(C=C1)C(=O)CCCCl");
         IReaction r3 = smipar.parseReactionSmiles("CC(C)C1=CC=CC=C1.C(CC(=O)Cl)CCl>C(Cl)Cl.[Al+3].[Cl-].[Cl-].[Cl-]>CC(C)C1=CC=C(C=C1)C(=O)CCCCl");
-        SmilesGenerator smigen = new SmilesGenerator(SmiOpt.Canonical);
+        SmilesGenerator smigen = new SmilesGenerator(SmiFlavour.Canonical);
         assertThat(smigen.create(r1), is(smigen.create(r2)));
         assertThat(smigen.create(r2), is(smigen.create(r3)));
     }