[Git][java-team/openchemlib][upstream] New upstream version 2021.10.0+dfsg

Andrius Merkys (@merkys) gitlab at salsa.debian.org
Mon Oct 4 07:50:43 BST 2021



Andrius Merkys pushed to branch upstream at Debian Java Maintainers / openchemlib


Commits:
f63e99bf by Andrius Merkys at 2021-10-04T02:16:25-04:00
New upstream version 2021.10.0+dfsg
- - - - -


29 changed files:

- pom.xml
- src/main/java/com/actelion/research/chem/Canonizer.java
- src/main/java/com/actelion/research/chem/ExtendedMolecule.java
- src/main/java/com/actelion/research/chem/IsomericSmilesCreator.java
- src/main/java/com/actelion/research/chem/SmilesParser.java
- src/main/java/com/actelion/research/chem/StereoIsomerEnumerator.java
- src/main/java/com/actelion/research/chem/TautomerHelper.java
- src/main/java/com/actelion/research/chem/coords/CoordinateInventor.java
- src/main/java/com/actelion/research/chem/descriptor/flexophore/generator/CreatorMolDistHistViz.java
- src/main/java/com/actelion/research/chem/docking/DockingEngine.java
- src/main/java/com/actelion/research/chem/docking/LigandPose.java
- src/main/java/com/actelion/research/chem/docking/scoring/AbstractScoringEngine.java
- src/main/java/com/actelion/research/chem/docking/scoring/ChemPLP.java
- src/main/java/com/actelion/research/chem/docking/scoring/IdoScore.java
- src/main/java/com/actelion/research/chem/io/Mol2FileParser.java
- src/main/java/com/actelion/research/chem/properties/complexity/ContainerFragBondsSolutions.java
- src/main/java/com/actelion/research/chem/properties/complexity/ExhaustiveFragmentsStatistics.java
- src/main/java/com/actelion/research/chem/reaction/mapping/MappingScorer.java
- src/main/java/com/actelion/research/chem/reaction/mapping/RootAtomPairSource.java
- src/main/java/com/actelion/research/chem/reaction/mapping/SimilarityGraphBasedReactionMapper.java
- src/main/java/com/actelion/research/gui/JDrawDialog.java
- src/main/java/com/actelion/research/gui/JEditableChemistryView.java
- src/main/java/com/actelion/research/gui/clipboard/ClipboardHandler.java
- + src/main/java/com/actelion/research/gui/dock/DividerChangeListener.java
- src/main/java/com/actelion/research/gui/dock/JDockingPanel.java
- src/main/java/com/actelion/research/gui/dock/TreeFork.java
- src/main/java/com/actelion/research/gui/dock/TreeLeaf.java
- src/main/java/com/actelion/research/gui/dock/TreeRoot.java
- src/main/java/com/actelion/research/util/ConstantsDWAR.java


Changes:

=====================================
pom.xml
=====================================
@@ -8,7 +8,7 @@
     Please follow the naming scheme YEAR.MONTH.RELEASE_NO_OF_MONTH
     (eg. 2016.4.1 for second release in Apr 2016)
     -->
-    <version>2021.9.0</version>
+    <version>2021.10.0</version>
 
     <name>OpenChemLib</name>
     <description>Open Source Chemistry Library</description>
@@ -195,7 +195,7 @@
         <connection>scm:git:git at github.com:Actelion/openchemlib.git</connection>
         <developerConnection>scm:git:git at github.com:Actelion/openchemlib.git</developerConnection>
         <url>https://github.com/Actelion/openchemlib</url>
-      <tag>openchemlib-2021.9.0</tag>
+      <tag>openchemlib-2021.10.0</tag>
   </scm>
 
     <distributionManagement>


=====================================
src/main/java/com/actelion/research/chem/Canonizer.java
=====================================
@@ -245,9 +245,10 @@ public class Canonizer {
 	/**
 	 * Locate those tetrahedral nitrogen atoms with at least 3 neighbors that
 	 * qualify for tetrahedral parity calculation because:<br>
-	 * - they are quarternary nitrogen atoms<br>
-	 * or - their configuration inversion is hindered in a polycyclic structure<br>
-	 * or - flag ASSIGN_PARITIES_TO_TETRAHEDRAL_N is set
+	 * - being a quarternary nitrogen atom<br>
+	 * - being an aziridin nitrogen atom<br>
+	 * - the configuration inversion is hindered in a polycyclic structure<br>
+	 * - flag ASSIGN_PARITIES_TO_TETRAHEDRAL_N is set
 	 */
 	private void canFindNitrogenQualifyingForParity() {
 		mNitrogenQualifiesForParity = new boolean[mMol.getAtoms()];
@@ -258,6 +259,11 @@ public class Canonizer {
 					continue;
 					}
 				if (mMol.getConnAtoms(atom) == 3) {
+					if (mMol.getAtomRingSize(atom) == 3) {
+						mNitrogenQualifiesForParity[atom] = true;
+						continue;
+						}
+
 					if (mMol.getAtomCharge(atom) == 1) {
 						mNitrogenQualifiesForParity[atom] = true;
 						continue;


=====================================
src/main/java/com/actelion/research/chem/ExtendedMolecule.java
=====================================
@@ -232,7 +232,7 @@ public class ExtendedMolecule extends Molecule implements Serializable {
 	 * The neighbours (connected atoms) of any atom are sorted by their relevance:<br>
 	 * 1. non-hydrogen atoms (bond order 1 and above) and unusual hydrogen atoms (non natural abundance isotops, custom labelled hydrogen, etc.)<br>
 	 * 2. plain-hydrogen atoms (natural abundance, bond order 1)<br>
-	 * 3. loosely connected atoms (bond order 0, i.e. metall ligand bond)<br>
+	 * 3. loosely connected atoms (bond order 0, i.e. metal ligand bond)<br>
 	 * Only valid after calling ensureHelperArrays(cHelperNeighbours or higher);
 	 * @param atom
 	 * @return count of category 1 & 2 neighbour atoms (excludes neighbours connected with zero bond order)
@@ -3141,13 +3141,13 @@ public class ExtendedMolecule extends Molecule implements Serializable {
 		return getHandleHydrogenAtomMap(findSimpleHydrogens());
 		}
 
-		/**
-		 * If ensureHelperArrays() (and with it handleHydrogens()) was not called yet
-		 * on a fresh molecule and if the molecule contains simple hydrogen atoms within
-		 * non-hydrogens atoms, then this function returns a map from current atom indexes
-		 * to those new atom indexes that would result from a call to handleHydrogens.
-		 * @return
-		 */
+	/**
+	 * If ensureHelperArrays() (and with it handleHydrogens()) was not called yet
+	 * on a fresh molecule and if the molecule contains simple hydrogen atoms within
+	 * non-hydrogens atoms, then this function returns a map from current atom indexes
+	 * to those new atom indexes that would result from a call to handleHydrogens.
+	 * @return
+	 */
 	public int[] getHandleHydrogenAtomMap(boolean[] isSimpleHydrogen) {
 		int[] map = new int[mAllAtoms];
 		for (int i=0; i<mAllAtoms; i++)
@@ -3163,6 +3163,11 @@ public class ExtendedMolecule extends Molecule implements Serializable {
 				map[i] = map[lastNonHAtom];
 				map[lastNonHAtom] = tempIndex;
 
+				// swap simple H flags also
+				boolean temp = isSimpleHydrogen[i];
+				isSimpleHydrogen[i] = isSimpleHydrogen[lastNonHAtom];
+				isSimpleHydrogen[lastNonHAtom] = temp;
+
 				do lastNonHAtom--;
 				while (isSimpleHydrogen[lastNonHAtom]);
 				}


=====================================
src/main/java/com/actelion/research/chem/IsomericSmilesCreator.java
=====================================
@@ -50,12 +50,10 @@ public class IsomericSmilesCreator {
 	private int[] mAtomRank;
 	private int[] mClosureNumber;
 	private int[] mSmilesIndex;
-	private int[] mClosureBuffer;
 	private int[][] mKnownTHCountInESRGroup;
 	private List<SmilesAtom> mGraphAtomList;
 	private boolean[] mAtomUsed;
 	private boolean[] mBondUsed;
-	private boolean[] mClosureOpened;
 	private boolean[] mPseudoStereoGroupInversion;
 	private boolean[] mPseudoStereoGroupInitialized;
 
@@ -205,33 +203,56 @@ public class IsomericSmilesCreator {
 	}
 
 	private void findRingClosures() {
-		boolean[] closureNumberUsed = new boolean[mMol.getBonds()];
-		mClosureNumber = new int[mMol.getBonds()];
-
+		// find closure neighbours of every atom and put them in canonical order. i.e. order of appearance in SMILES
 		for (SmilesAtom smilesAtom:mGraphAtomList) {
-			for (int i=0; i<mMol.getConnAtoms(smilesAtom.atom); i++) {
-				int bond = mMol.getConnBond(smilesAtom.atom, i);
-				closureNumberUsed[mClosureNumber[bond]] = false;
-			}
-
-			int index = getUnusedConnBondIndex(smilesAtom.atom);
-			while (index != -1) {
-				int closureBond = mMol.getConnBond(smilesAtom.atom, index);
-				mBondUsed[closureBond] = true;
-
-				int closureNumber = 1;
-				while (closureNumberUsed[closureNumber])
-					closureNumber++;
-
-				mClosureNumber[closureBond] = closureNumber;
-				closureNumberUsed[closureNumber] = true;
+			int closureCount = 0;
+			for (int i=0; i<mMol.getConnAtoms(smilesAtom.atom); i++)
+				if (!mBondUsed[mMol.getConnBond(smilesAtom.atom, i)])
+					closureCount++;
+
+			if (closureCount != 0) {
+				smilesAtom.closureNeighbour = new int[closureCount];
+
+				closureCount = 0;
+				for (int i=0; i<mMol.getConnAtoms(smilesAtom.atom); i++) {
+					if (!mBondUsed[mMol.getConnBond(smilesAtom.atom, i)]) {
+						int neighbour = mMol.getConnAtom(smilesAtom.atom, i);
+						smilesAtom.closureNeighbour[closureCount++] = (mSmilesIndex[neighbour] << 16) | neighbour;
+					}
+				}
 
-				index = getUnusedConnBondIndex(smilesAtom.atom);
+				Arrays.sort(smilesAtom.closureNeighbour);
+				for (int i=0; i<smilesAtom.closureNeighbour.length; i++)
+					smilesAtom.closureNeighbour[i] = 0x0000FFFF & smilesAtom.closureNeighbour[i];
 			}
 		}
 
-		mClosureOpened = new boolean[mMol.getBonds()];
-		mClosureBuffer = new int[8];
+		// assign closure digits to closure bonds
+		boolean[] closureNumberUsed = new boolean[mMol.getBonds()];
+		mClosureNumber = new int[mMol.getBonds()];
+		for (SmilesAtom smilesAtom:mGraphAtomList) {
+			if (smilesAtom.closureNeighbour != null) {
+				for (int neighbour:smilesAtom.closureNeighbour) {
+					for (int i=0; i<mMol.getConnAtoms(smilesAtom.atom); i++) {
+						if (neighbour == mMol.getConnAtom(smilesAtom.atom, i)) {
+							int bond = mMol.getConnBond(smilesAtom.atom, i);
+							if (!mBondUsed[bond]) { // opening closure bond
+								mBondUsed[bond] = true;
+
+								// assign and allocation closure digit
+								mClosureNumber[bond] = 1;
+								while (closureNumberUsed[mClosureNumber[bond]])
+									mClosureNumber[bond]++;
+								closureNumberUsed[mClosureNumber[bond]] = true;
+							}
+							else {  // closing closure bond: release closure digit
+								closureNumberUsed[mClosureNumber[bond]] = false;
+							}
+						}
+					}
+				}
+			}
+		}
 	}
 
 	private void calculateEZBonds() {
@@ -391,16 +412,6 @@ public class IsomericSmilesCreator {
 		return bestIndex;
 	}
 
-	private int getUnusedConnBondIndex(int atom) {
-		int bestIndex = -1;
-		for (int i=0; i<mMol.getConnAtoms(atom); i++)
-			if (!mBondUsed[mMol.getConnBond(atom, i)]
-			 && (bestIndex == -1 || mAtomRank[mMol.getConnAtom(atom, bestIndex)] < mAtomRank[mMol.getConnAtom(atom, i)]))
-				bestIndex = i;
-
-		return bestIndex;
-	}
-
 	private void addToGraph(SmilesAtom smilesAtom, int listIndex) {
 		mGraphAtomList.add(listIndex, smilesAtom);
 		mAtomUsed[smilesAtom.atom] = true;
@@ -444,7 +455,7 @@ public class IsomericSmilesCreator {
 		int isotop = mMol.getAtomMass(atom);
 		int mapNo = (mMode & MODE_INCLUDE_MAPPING) != 0 ? mMol.getAtomMapNo(atom) : 0;
 
-		String smartsFeatures = (mMode & MODE_CREATE_SMARTS) != 0 ? getAtomSMARTSFeatures(atom, buffer) : "";
+		String smartsFeatures = (mMode & MODE_CREATE_SMARTS) != 0 ? getAtomSMARTSFeatures(atom, buffer) : null;
 
 		boolean useBrackets =
 				(!isAnyAtom && !isOrganic(mMol.getAtomicNo(atom)))
@@ -456,7 +467,7 @@ public class IsomericSmilesCreator {
 				|| mapNo != 0
 				|| mMol.getAtomAbnormalValence(atom) != -1
 				|| mMol.getAtomRadical(atom) != Molecule.cAtomRadicalStateNone
-				|| smartsFeatures.length() != 0;
+				|| smartsFeatures != null;
 
 		if (useBrackets)
 			builder.append('[');
@@ -483,6 +494,9 @@ public class IsomericSmilesCreator {
 				builder.append(Math.abs(charge));
 			}
 
+		if (smartsFeatures != null)
+			builder.append(smartsFeatures);
+
 		if (mapNo != 0) {
 			builder.append(':');
 			builder.append(mapNo);
@@ -589,7 +603,8 @@ public class IsomericSmilesCreator {
 		}
 
 		int ringSize = (queryFeatures & Molecule.cAtomQFRingSize) >> Molecule.cAtomQFRingSizeShift;
-		buffer.append(";r"+(ringSize == 0 ? 0 : ringSize-2));
+		if (ringSize != 0)
+			buffer.append(";r"+ringSize);
 
 		int neighbourFeatures = queryFeatures & Molecule.cAtomQFNeighbours;
 		switch (neighbourFeatures) {
@@ -625,7 +640,7 @@ public class IsomericSmilesCreator {
 		if ((queryFeatures & Molecule.cAtomQFMoreNeighbours) != 0)
 			buffer.append(";!D"+mMol.getConnAtoms(atom));  // Convert into exact explicit neighbour count 'D'
 
-		return buffer.toString();
+		return buffer.length() == 0 ? null : buffer.toString();
 		}
 
 	/**
@@ -647,31 +662,47 @@ public class IsomericSmilesCreator {
 	}
 
 	private void appendClosureBonds(SmilesAtom smilesAtom, StringBuilder builder) {
-		int closureCount = 0;
-		for (int i=0; i<mMol.getConnAtoms(smilesAtom.atom); i++) {
-			int bond = mMol.getConnBond(smilesAtom.atom, i);
-			if (mClosureNumber[bond] != 0) {
-				int isOpenFlag = mClosureOpened[bond] ? 0 : 0x40000000;
-				mClosureBuffer[closureCount++] = isOpenFlag | (mClosureNumber[bond] << 20) | bond;
-			}
-		}
-		if (closureCount != 0) {
-			// when sorting, then put and handle open closures first
-			Arrays.sort(mClosureBuffer, 0, closureCount); // we must sort to be canonical
-			for (int i=0; i<closureCount; i++) {
-				int bond = mClosureBuffer[i] & 0x0003FFFF;
-				int closureNumber = ((mClosureBuffer[i] & 0x3FFC0000) >> 20);
-				if (!mClosureOpened[bond]) {
-					mClosureOpened[bond] = true;
-					appendBondOrderSymbol(bond, smilesAtom.atom, builder);
+		if (smilesAtom.closureNeighbour != null) {
+			for (int neighbour:smilesAtom.closureNeighbour) {
+				for (int i=0; i<mMol.getConnAtoms(smilesAtom.atom); i++) {
+					if (neighbour == mMol.getConnAtom(smilesAtom.atom, i)) {
+						int bond = mMol.getConnBond(smilesAtom.atom, i);
+						appendBondOrderSymbol(bond, smilesAtom.atom, builder);
+						if (mClosureNumber[bond] > 9)
+							builder.append('%');
+						builder.append(mClosureNumber[bond]);
+					}
 				}
-				if (closureNumber > 9)
-					builder.append('%');
-				builder.append(closureNumber);
 			}
 		}
 	}
 
+//	private void appendClosureBonds(SmilesAtom smilesAtom, StringBuilder builder) {
+//		int closureCount = 0;
+//		for (int i=0; i<mMol.getConnAtoms(smilesAtom.atom); i++) {
+//			int bond = mMol.getConnBond(smilesAtom.atom, i);
+//			if (mClosureNumber[bond] != 0) {
+//				int isOpenFlag = mClosureOpened[bond] ? 0 : 0x40000000;
+//				mClosureBuffer[closureCount++] = isOpenFlag | (mClosureNumber[bond] << 20) | bond;
+//			}
+//		}
+//		if (closureCount != 0) {
+//			// when sorting, then put and handle open closures first
+//			Arrays.sort(mClosureBuffer, 0, closureCount); // we must sort to be canonical
+//			for (int i=0; i<closureCount; i++) {
+//				int bond = mClosureBuffer[i] & 0x0003FFFF;
+//				int closureNumber = ((mClosureBuffer[i] & 0x3FFC0000) >> 20);
+//				if (!mClosureOpened[bond]) {
+//					mClosureOpened[bond] = true;
+//					appendBondOrderSymbol(bond, smilesAtom.atom, builder);
+//				}
+//				if (closureNumber > 9)
+//					builder.append('%');
+//				builder.append(closureNumber);
+//			}
+//		}
+//	}
+
 	private void appendBondOrderSymbol(SmilesAtom smilesAtom, StringBuilder builder) {
 		if (smilesAtom.ezHalfParity != 0) {
 			builder.append(smilesAtom.ezHalfParity == 1 ? '/' : '\\');
@@ -814,23 +845,24 @@ public class IsomericSmilesCreator {
 
 	/**
 	 * @param atom for which to return a neighbor's smiles rank
-	 * @param neighborIndex index for getConnAtoms() to get neighbor atom and bond
+	 * @param neighbourIndex index for getConnAtoms() to get neighbor atom and bond
 	 * @return neighbor's position rank in smiles from a perspective of atom using closure digit positions for closure neighbors
 	 */
-	private int getSmilesRank(int atom, int neighborIndex) {
-		int bond = mMol.getConnBond(atom, neighborIndex);
+	private int getSmilesRank(int atom, int neighbourIndex) {
+		int bond = mMol.getConnBond(atom, neighbourIndex);
+		int neighbour = mMol.getConnAtom(atom, neighbourIndex);
 		if (mClosureNumber[bond] != 0) {
-			// if neighbor is attached via a closure digit, then the rank is based primarily on atom's position
+			// if neighbour is attached via a closure digit, then the rank is based primarily on atom's position
 			// in the smiles and secondary on the count of other closures at atom that precede this closure
 			int rank = 8 * mSmilesIndex[atom] + 1;
-			for (int i=0; i<neighborIndex; i++)
-				if (mClosureNumber[mMol.getConnAtom(atom, i)] != 0)
-					rank++;
+			int[] closureNeighbour = mGraphAtomList.get(mSmilesIndex[atom]).closureNeighbour;
+			for (int i=0; i<closureNeighbour.length && neighbour != closureNeighbour[i]; i++)
+				rank++;
 			return rank;
 			}
 
-		// if the neighbor is not a closure return a rank based on its atom index in the smiles
-		return 8 * mSmilesIndex[mMol.getConnAtom(atom, neighborIndex)];
+		// if the neighbour is not a closure return a rank based on its atom index in the smiles
+		return 8 * mSmilesIndex[neighbour];
 	}
 
 	private boolean isOrganic (int atomicNo) {
@@ -844,6 +876,7 @@ public class IsomericSmilesCreator {
 class SmilesAtom {
 	public int atom,parent,ezHalfParity;
 	public boolean isSideChainStart,isSideChainEnd;
+	public int[] closureNeighbour;
 
 	public SmilesAtom(int atom, int parent, boolean isSideChainStart, boolean isSideChainEnd) {
 		this.atom = atom;


=====================================
src/main/java/com/actelion/research/chem/SmilesParser.java
=====================================
@@ -38,16 +38,22 @@ import com.actelion.research.chem.reaction.Reaction;
 import com.actelion.research.util.ArrayUtils;
 import com.actelion.research.util.SortedList;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.TreeMap;
 
 
 public class SmilesParser {
+	private static final int SMARTS_MODE_MASK = 3;
 	public static final int SMARTS_MODE_IS_SMILES = 0;
 	public static final int SMARTS_MODE_GUESS = 1;
 	public static final int SMARTS_MODE_IS_SMARTS = 2;
 
+	public static final int MODE_SKIP_COORDINATE_TEMPLATES = 4;
+
+	private static final int INITIAL_CONNECTIONS = 16;
+	private static final int MAX_CONNECTIONS = 100; // largest allowed one in SMILES is 99
 	private static final int MAX_BRACKET_LEVELS = 64;
-	private static final int MAX_RE_CONNECTIONS = 64;
 	private static final int MAX_AROMATIC_RING_SIZE = 15;
 
 	private static final int HYDROGEN_ANY = -1;
@@ -58,8 +64,8 @@ public class SmilesParser {
 
 	private StereoMolecule mMol;
 	private boolean[] mIsAromaticBond;
-	private int mAromaticAtoms,mAromaticBonds,mSmartsMode;
-	private boolean mCreateSmartsWarnings;
+	private int mAromaticAtoms,mAromaticBonds,mSmartsMode,mCoordinateMode;
+	private boolean mCreateSmartsWarnings,mSkipTemplates;
 	private StringBuilder mSmartsWarningBuffer;
 
 	/**
@@ -79,12 +85,39 @@ public class SmilesParser {
 	 * single bond and without any implicit hydrogen atoms. If smartsMode is SMARTS_MODE_IS_GUESS,
 	 * then
 	 * molecules is never set.
-	 * @param smartsMode one of SMARTS_MODE...
+	 * @param mode one of SMARTS_MODE... and optionally other mode flags
 	 * @param createSmartsWarnings if true, then getSmartsWarning() may be used after parsing a SMILES or SMARTS
 	 */
-	public SmilesParser(int smartsMode, boolean createSmartsWarnings) {
-		mSmartsMode = smartsMode;
+	public SmilesParser(int mode, boolean createSmartsWarnings) {
+		mSmartsMode = mode & SMARTS_MODE_MASK;
 		mCreateSmartsWarnings = createSmartsWarnings;
+		mCoordinateMode = CoordinateInventor.MODE_DEFAULT;
+		if ((mode & MODE_SKIP_COORDINATE_TEMPLATES) != 0)
+			mCoordinateMode |= CoordinateInventor.MODE_SKIP_DEFAULT_TEMPLATES;
+
+		mSkipTemplates = ((mode & MODE_SKIP_COORDINATE_TEMPLATES) != 0);
+		}
+
+	public StereoMolecule parseMolecule(String smiles) {
+		return smiles == null ? null : parseMolecule(smiles.getBytes());
+		}
+
+	/**
+	 * Convenience method to quickly obtain a StereoMolecule from a SMILES string.
+	 * If you process many SMILES, then the parse() methods are preferred, because
+	 * they avoid the steady instantiation new StereoMolecules.
+	 * @param smiles
+	 * @return
+	 */
+	public StereoMolecule parseMolecule(byte[] smiles) {
+		StereoMolecule mol = new StereoMolecule();
+		try {
+			parse(mol, smiles);
+			}
+		catch (Exception e) {
+			return null;
+			}
+		return mol;
 		}
 
 	public Reaction parseReaction(String smiles) throws Exception {
@@ -207,17 +240,17 @@ public class SmilesParser {
 		int[] baseAtom = new int[MAX_BRACKET_LEVELS];
 		baseAtom[0] = -1;
 
-		int[] ringClosureAtom = new int[MAX_RE_CONNECTIONS];
-		int[] ringClosurePosition = new int[MAX_RE_CONNECTIONS];
-		int[] ringClosureBondType = new int[MAX_RE_CONNECTIONS];
-		int[] ringClosureBondQueryFeatures = new int[MAX_RE_CONNECTIONS];
-		for (int i=0; i<MAX_RE_CONNECTIONS; i++)
+		int[] ringClosureAtom = new int[INITIAL_CONNECTIONS];
+		int[] ringClosurePosition = new int[INITIAL_CONNECTIONS];
+		int[] ringClosureBondType = new int[INITIAL_CONNECTIONS];
+		int[] ringClosureBondQueryFeatures = new int[INITIAL_CONNECTIONS];
+		for (int i = 0; i<INITIAL_CONNECTIONS; i++)
 			ringClosureAtom[i] = -1;
 
 		int atomMass = 0;
 		int fromAtom = -1;
 		boolean squareBracketOpen = false;
-		boolean percentFound = false;
+		boolean isDoubleDigit = false;
 		boolean smartsFeatureFound = false;
 		int bracketLevel = 0;
 		int bondType = Molecule.cBondTypeSingle;
@@ -409,8 +442,11 @@ public class SmilesParser {
 
 						if (smiles[position] == 'D') {   // non-H-neighbours
 							position++;
-							int neighbours = smiles[position] - '0';
-							position++;
+							int neighbours = 1;
+							if (Character.isDigit(smiles[position])) {
+								neighbours = smiles[position] - '0';
+								position++;
+								}
 							int qf = (neighbours == 0) ? Molecule.cAtomQFNot0Neighbours
 								   : (neighbours == 1) ? Molecule.cAtomQFNot1Neighbour
 								   : (neighbours == 2) ? Molecule.cAtomQFNot2Neighbours
@@ -432,15 +468,17 @@ public class SmilesParser {
 
 						if (smiles[position] == 'R') {
 							position++;
-							int ringCount = -1;
-							if (Character.isDigit(smiles[position])) {
-								ringCount = smiles[position] - '0';
-								position++;
+							if (!Character.isDigit(smiles[position])) {
+								if (isNot)
+									atomQueryFeatures |= Molecule.cBondQFRingState & ~Molecule.cAtomQFNotChain;
+								else
+									atomQueryFeatures |= Molecule.cAtomQFNotChain;
+								continue;
 								}
+							int ringCount = smiles[position] - '0';
+							position++;
 							if (isNot) {
-								if (ringCount == -1)
-									atomQueryFeatures |= Molecule.cAtomQFRingState & ~Molecule.cAtomQFNotChain;
-								else if (ringCount == 0)
+								if (ringCount == 0)
 									atomQueryFeatures |= Molecule.cAtomQFNotChain;
 								else if (ringCount == 1)
 									atomQueryFeatures |= Molecule.cAtomQFNot2RingBonds;
@@ -454,9 +492,7 @@ public class SmilesParser {
 							else {
 								if (ringCount >= 3)
 									ringCount = 3;
-								if (ringCount == -1)
-									atomQueryFeatures |= Molecule.cAtomQFNotChain;
-								else if (ringCount == 0)
+								if (ringCount == 0)
 									atomQueryFeatures |= Molecule.cAtomQFRingState & ~Molecule.cAtomQFNotChain;
 								else if (ringCount == 1)
 									atomQueryFeatures |= Molecule.cAtomQFRingState & ~Molecule.cAtomQFNot2RingBonds;
@@ -472,11 +508,17 @@ public class SmilesParser {
 
 						if (smiles[position] == 'r') {
 							position++;
+							if (!Character.isDigit(smiles[position])) {
+								if (isNot)
+									atomQueryFeatures |= Molecule.cBondQFRingState & ~Molecule.cAtomQFNotChain;
+								else
+									atomQueryFeatures |= Molecule.cAtomQFNotChain;
+								continue;
+								}
 							int ringSize = smiles[position] - '0';
-							int ringCode = (ringSize >= 3) ? ringSize-2 : 0;
 							position++;
-							if (!isNot && ringCode <= 7)
-								atomQueryFeatures |= (ringCode << Molecule.cAtomQFRingSizeShift);
+							if (!isNot && ringSize >= 3 && ringSize <= 7)
+								atomQueryFeatures |= (ringSize << Molecule.cAtomQFRingSizeShift);
 							else
 								smartsWarning((isNot ? "!r" : "r") + ringSize);
 							continue;
@@ -582,6 +624,9 @@ public class SmilesParser {
 
 				// mark aromatic atoms
 				if (Character.isLowerCase(theChar)) {
+					if (atomicNo != 5 && atomicNo != 6 && atomicNo != 7 && atomicNo != 8 && atomicNo != 15 &&atomicNo != 16)
+						throw new Exception("SmilesParser: atomicNo "+atomicNo+" must not be aromatic");
+
 					mMol.setAtomMarker(atom, true);
 					mAromaticAtoms++;
 					}
@@ -626,7 +671,7 @@ public class SmilesParser {
 	
 						// using position as hydrogenPosition is close enough
 						int hydrogenCount = (explicitHydrogens == HYDROGEN_IMPLICIT_ZERO) ? 0 : explicitHydrogens;
-						parityMap.put(atom, new THParity(atom, fromAtom, hydrogenCount, position, isClockwise));
+						parityMap.put(atom, new THParity(atom, fromAtom, hydrogenCount, position-1, isClockwise));
 						}
 					}
 
@@ -739,15 +784,29 @@ public class SmilesParser {
 										|| smiles[position-2] == '#'
 										|| smiles[position-2] == ':'
 										|| smiles[position-2] == '>');
-					if (percentFound
+					if (isDoubleDigit
 					 && position < endIndex
 					 && Character.isDigit(smiles[position])) {
 						number = 10 * number + smiles[position] - '0';
+						isDoubleDigit = false;
 						position++;
 						}
-					percentFound = false;
-					if (number >= MAX_RE_CONNECTIONS)
-						throw new Exception("SmilesParser: ringClosureAtom number out of range");
+					if (number >= ringClosureAtom.length) {
+						if (number >=MAX_CONNECTIONS)
+							throw new Exception("SmilesParser: ringClosureAtom number out of range");
+
+						int oldSize = ringClosureAtom.length;
+						int newSize = ringClosureAtom.length;
+						while (newSize <= number)
+							newSize = Math.min(MAX_CONNECTIONS, newSize + INITIAL_CONNECTIONS);
+
+						ringClosureAtom = Arrays.copyOf(ringClosureAtom, newSize);
+						ringClosurePosition = Arrays.copyOf(ringClosurePosition, newSize);
+						ringClosureBondType = Arrays.copyOf(ringClosureBondType, newSize);
+						ringClosureBondQueryFeatures = Arrays.copyOf(ringClosureBondQueryFeatures, newSize);
+						for (int i=oldSize; i<newSize; i++)
+							ringClosureAtom[i] = -1;
+						}
 					if (ringClosureAtom[number] == -1) {
 						ringClosureAtom[number] = baseAtom[bracketLevel];
 						ringClosurePosition[number] = position-1;
@@ -816,7 +875,7 @@ public class SmilesParser {
 				}
 
 			if (theChar == '%') {
-				percentFound = true;
+				isDoubleDigit = true;
 				continue;
 				}
 
@@ -835,8 +894,8 @@ public class SmilesParser {
 		// Check for unsatisfied open bonds
 		if (bondType != Molecule.cBondTypeSingle)
 			throw new Exception("SmilesParser: dangling open bond");
-		for (int i=0; i<MAX_RE_CONNECTIONS; i++)
-			if (ringClosureAtom[i] != -1)
+		for (int rca:ringClosureAtom)
+			if (rca != -1)
 				throw new Exception("SmilesParser: dangling ring closure");
 
 		int[] handleHydrogenAtomMap = mMol.getHandleHydrogenMap();
@@ -921,7 +980,7 @@ public class SmilesParser {
 		mMol.setParitiesValid(0);
 
 		if (createCoordinates) {
-			new CoordinateInventor().invent(mMol);
+			new CoordinateInventor(mCoordinateMode).invent(mMol);
 
 			if (readStereoFeatures)
 				mMol.setUnknownParitiesToExplicitlyUnknown();
@@ -1456,11 +1515,21 @@ public class SmilesParser {
 		return paritiesFound;
 		}
 
+	private class ParityNeighbour {
+		int mAtom,mPosition;
+		boolean mIsHydrogen;
+
+		public ParityNeighbour(int atom, int position, boolean isHydrogen) {
+			mAtom = atom;
+			mPosition = position;
+			mIsHydrogen = isHydrogen;
+			}
+		}
+
 	private class THParity {
-		int mCentralAtom,mImplicitHydrogen,mFromAtom,mNeighborCount;
-		int[] mNeighborAtom,mNeighborPosition;
-		boolean[] mNeighborIsHydrogen;
+		int mCentralAtom,mImplicitHydrogen,mFromAtom;
 		boolean mIsClockwise,mError;
+		ArrayList<ParityNeighbour> mNeighbourList;
 
 		/**
 		 * Instantiates a new parity object during smiles traversal.
@@ -1472,26 +1541,23 @@ public class SmilesParser {
 		public THParity(int centralAtom, int fromAtom, int implicitHydrogen, int hydrogenPosition, boolean isClockwise) {
 			if (implicitHydrogen != 0 && implicitHydrogen != 1) {
 				mError = true;
-				}
+			}
 			else {
 				mCentralAtom = centralAtom;
 				mFromAtom = fromAtom;
 				mImplicitHydrogen = implicitHydrogen;
 				mIsClockwise = isClockwise;
-				mNeighborCount = 0;
-				mNeighborIsHydrogen = new boolean[4];
-				mNeighborAtom = new int[4];
-				mNeighborPosition = new int[4];
+				mNeighbourList = new ArrayList<>();
 
 				// If we have a fromAtom and we have an implicit hydrogen,
-				// then make the implicit hydrogen a normal neighbor.
+				// then make the implicit hydrogen a normal neighbour.
 				if (fromAtom != -1 && implicitHydrogen == 1) {
 					// We put it at the end of the atom list with MAX_VALUE
 					addNeighbor(Integer.MAX_VALUE, hydrogenPosition, true);
 					mImplicitHydrogen = 0;
-					}
 				}
 			}
+		}
 
 		/**
 		 * Adds a currently traversed neighbor or ring closure to parity object,
@@ -1505,19 +1571,14 @@ public class SmilesParser {
 		 * @param isHydrogen
 		 */
 		public void addNeighbor(int atom, int position, boolean isHydrogen) {
-			if (mError)
-				return;
+			if (!mError) {
+				if (mNeighbourList.size() == 4 || (mNeighbourList.size() == 3 && mFromAtom != -1)) {
+					mError = true;
+					return;
+					}
 
-			if (mNeighborCount == 4
-			 || (mNeighborCount == 3 && mFromAtom != -1)) {
-				mError = true;
-				return;
+				mNeighbourList.add(new ParityNeighbour(atom, position, isHydrogen));
 				}
-
-			mNeighborIsHydrogen[mNeighborCount] = isHydrogen;
-			mNeighborAtom[mNeighborCount] = atom;
-			mNeighborPosition[mNeighborCount] = position;
-			mNeighborCount++;
 			}
 
 		public int calculateParity(int[] handleHydrogenAtomMap) {
@@ -1528,71 +1589,66 @@ public class SmilesParser {
 			// uses after calling handleHydrogens, which is called from ensureHelperArrays().
 			if (mFromAtom != -1)
 				mFromAtom = handleHydrogenAtomMap[mFromAtom];
-			for (int i=0; i<mNeighborCount; i++)
-				if (mNeighborAtom[i] != Integer.MAX_VALUE)
-					mNeighborAtom[i] = handleHydrogenAtomMap[mNeighborAtom[i]];
+			for (ParityNeighbour neighbour:mNeighbourList)
+				if (neighbour.mAtom != Integer.MAX_VALUE)
+					neighbour.mAtom = handleHydrogenAtomMap[neighbour.mAtom];
 
 			if (mFromAtom == -1 && mImplicitHydrogen == 0) {
 				// If we have no implicit hydrogen and the central atom is the first atom in the smiles,
 				// then we assume that we have to take the first neighbor as from-atom (not described in Daylight theory manual).
 				// Assumption: take the first neighbor as front atom, i.e. skip it when comparing positions
 				int minPosition = Integer.MAX_VALUE;
-				int minIndex = -1;
-				for (int i=0; i<mNeighborCount; i++) {
-					if (minPosition > mNeighborPosition[i]) {
-						minPosition = mNeighborPosition[i];
-						minIndex = i;
+				ParityNeighbour minNeighbour = null;
+				for (ParityNeighbour neighbour:mNeighbourList) {
+					if (minPosition > neighbour.mPosition) {
+						minPosition = neighbour.mPosition;
+						minNeighbour = neighbour;
 						}
 					}
-				mFromAtom = mNeighborAtom[minIndex];
-				for (int i=minIndex+1; i<mNeighborCount; i++) {
-					mNeighborAtom[i-1] = mNeighborAtom[i];
-					mNeighborPosition[i-1] = mNeighborPosition[i];
-					mNeighborIsHydrogen[i-1] = mNeighborIsHydrogen[i];
-					}
-				mNeighborCount--;
+				mFromAtom = minNeighbour.mAtom;
+				mNeighbourList.remove(minNeighbour);
 				}
 
-			int totalNeighborCount = (mFromAtom == -1? 0 : 1) + mImplicitHydrogen + mNeighborCount;
+			int totalNeighborCount = (mFromAtom == -1? 0 : 1) + mImplicitHydrogen + mNeighbourList.size();
 			if (totalNeighborCount > 4 || totalNeighborCount < 3)
 				return Molecule.cAtomParityUnknown;
 
 			// We look from the hydrogen towards the central carbon if the fromAtom is a hydrogen or
 			// if there is no fromAtom but the central atom has an implicit hydrogen.
 			boolean fromAtomIsHydrogen = (mFromAtom == -1 && mImplicitHydrogen == 1)
-									  || (mFromAtom != -1 && mMol.isSimpleHydrogen(mFromAtom));
+					|| (mFromAtom != -1 && mMol.isSimpleHydrogen(mFromAtom));
 
-			int hydrogenNeighborIndex = -1;
-			for (int i=0; i<mNeighborCount; i++) {
-				if (mNeighborIsHydrogen[i]) {
-					if (hydrogenNeighborIndex != -1 || fromAtomIsHydrogen)
+			ParityNeighbour hydrogenNeighbour = null;
+			for (ParityNeighbour neighbour:mNeighbourList) {
+				if (neighbour.mIsHydrogen) {
+					if (hydrogenNeighbour != null || fromAtomIsHydrogen)
 						return Molecule.cAtomParityUnknown;
-					hydrogenNeighborIndex = i;
-					}
+					hydrogenNeighbour = neighbour;
 				}
+			}
 
 			// hydrogens are moved to the end of the atom list. If the hydrogen passes an odd number of
 			// neighbor atoms on its way to the list end, we are effectively inverting the atom order.
 			boolean isHydrogenTraversalInversion = false;
-			if (hydrogenNeighborIndex != -1)
-				for (int i=0; i<mNeighborCount; i++)
-					if (!mNeighborIsHydrogen[i]
-					 && mNeighborAtom[hydrogenNeighborIndex] < mNeighborAtom[i])
+			if (hydrogenNeighbour != null)
+				for (ParityNeighbour neighbour:mNeighbourList)
+					if (neighbour != hydrogenNeighbour
+					 && hydrogenNeighbour.mAtom < neighbour.mAtom)
 						isHydrogenTraversalInversion = !isHydrogenTraversalInversion;
 
 			// If fromAtom is not a hydrogen, we consider it moved to highest atom index,
 			// because
 			boolean fromAtomTraversalInversion = false;
 			if (mFromAtom != -1 && !fromAtomIsHydrogen)
-				for (int i=0; i<mNeighborCount; i++)
-					if (mFromAtom < mNeighborAtom[i])
+				for (ParityNeighbour neighbour:mNeighbourList)
+					if (mFromAtom < neighbour.mAtom)
 						fromAtomTraversalInversion = !fromAtomTraversalInversion;
 
 			int parity = (mIsClockwise
-						^ isInverseOrder(mNeighborAtom, mNeighborPosition, mNeighborCount)
-						^ fromAtomTraversalInversion
-						^ isHydrogenTraversalInversion) ?
-								Molecule.cAtomParity2 : Molecule.cAtomParity1;
+					^ isInverseOrder()
+					^ fromAtomTraversalInversion
+					^ isHydrogenTraversalInversion) ?
+					Molecule.cAtomParity2 : Molecule.cAtomParity1;
 /*
 System.out.println();
 System.out.println("central:"+mCentralAtom+(mIsClockwise?" @@":" @")+" from:"
@@ -1606,19 +1662,19 @@ System.out.println("parity:"+parity);
 			return parity;
 			}
 
-		private boolean isInverseOrder(int[] atom, int[] position, int count) {
+		private boolean isInverseOrder() {
 			boolean inversion = false;
-			for (int i=1; i<count; i++) {
+			for (int i=1; i<mNeighbourList.size(); i++) {
 				for (int j=0; j<i; j++) {
-					if (atom[j] > atom[i])
+					if (mNeighbourList.get(j).mAtom > mNeighbourList.get(i).mAtom)
 						inversion = !inversion;
-					if (position[j] > position[i])
+					if (mNeighbourList.get(j).mPosition > mNeighbourList.get(i).mPosition)
 						inversion = !inversion;
-					}
 				}
-			return inversion;
 			}
+			return inversion;
 		}
+	}
 
 	private static void testStereo() {
 		final String[][] data = { { "F/C=C/I", "F/C=C/I" },


=====================================
src/main/java/com/actelion/research/chem/StereoIsomerEnumerator.java
=====================================
@@ -28,11 +28,11 @@ public class StereoIsomerEnumerator {
 	private boolean[][] mAtomIsParity1,mBondIsParity1;
 
 	/**
-	 * If the passed molecules has stereo-chemically undefined configurations
+	 * If the passed molecule has stereo-chemically undefined configurations
 	 * (double bonds, stereo centers) or/and one or more AND/OR groups of
 	 * defined relative stereo configurations, then it represents multiple
 	 * stereo isomers. The StereoIsomerEnumerator generates all individual
-	 * stereo isomers of the passes molecule. If the passed molecule does
+	 * stereo isomers of the passed molecule. If the passed molecule does
 	 * not include absolute stereo centers (or atrop isomeric configuration),
 	 * but unknown stereo centers or groups with defined relative configuration,
 	 * then we have pairs of enantiomers. In this case the StereoIsomerEnumerator


=====================================
src/main/java/com/actelion/research/chem/TautomerHelper.java
=====================================
@@ -42,6 +42,8 @@ import java.util.TreeSet;
 
 public class TautomerHelper {
 	private static final int MAX_TAUTOMERS = 100000;
+	private static int sMaxTautomers = MAX_TAUTOMERS;
+	private static boolean sSuppressWarning = false;
 
 	private StereoMolecule mOriginalMol;
 	private boolean[] mIsTautomerBond;
@@ -54,6 +56,14 @@ public class TautomerHelper {
 	private TreeSet<BondOrders> mBondOrderSet;
 	private ArrayDeque<BondOrders> mBondOrderDeque;
 
+	public static void setMaxTautomers(int maxTautomers) {
+		sMaxTautomers = maxTautomers;
+		}
+
+	public static void setSuppressWarning(boolean suppressWarning) {
+		sSuppressWarning = suppressWarning;
+		}
+
 	/**
 	 * @param mol
 	 */
@@ -284,8 +294,9 @@ public class TautomerHelper {
 			mBondOrderDeque.poll().copyToTautomer(tautomer);
 			addAllTautomers(tautomer);
 
-			if (mBondOrderSet.size() >= MAX_TAUTOMERS) {
-				System.out.println("Tautomer count exceeds maximum: "+new Canonizer(mOriginalMol).getIDCode());
+			if (mBondOrderSet.size() >= sMaxTautomers) {
+				if (!sSuppressWarning)
+					System.out.println("Tautomer count exceeds maximum: "+new Canonizer(mOriginalMol).getIDCode());
 				break;
 				}
 			}


=====================================
src/main/java/com/actelion/research/chem/coords/CoordinateInventor.java
=====================================
@@ -43,6 +43,7 @@ public class CoordinateInventor {
 	public static final int MODE_KEEP_MARKED_ATOM_COORDS = 4;
 	public static final int MODE_PREFER_MARKED_ATOM_COORDS = 8;
 	protected static final int MODE_CONSIDER_MARKED_ATOMS = MODE_KEEP_MARKED_ATOM_COORDS | MODE_PREFER_MARKED_ATOM_COORDS;
+	public static final int MODE_DEFAULT = MODE_REMOVE_HYDROGEN;
 
 	private static final byte FLIP_AS_LAST_RESORT = 1;
 	private static final byte FLIP_POSSIBLE = 2;
@@ -79,7 +80,7 @@ public class CoordinateInventor {
 	 * polycyclic structures from these templates (adamantanes, cubane, etc.).
 	 */
 	public CoordinateInventor () {
-		this(MODE_REMOVE_HYDROGEN);
+		this(MODE_DEFAULT);
 		}
 
 
@@ -372,7 +373,7 @@ public class CoordinateInventor {
 
 	private void locateInitialFragments() {
 		// take every atom with more than 4 neighbours including first neighbour shell
-		for (int atom=0; atom<mMol.getAllAtoms(); atom++) {
+		for (int atom=0; atom<mMol.getAtoms(); atom++) {
 			if (mMol.getAllConnAtoms(atom) > 4) {
 				InventorFragment f = new InventorFragment(mMol, 1+mMol.getAllConnAtoms(atom), mMode);
 
@@ -437,7 +438,7 @@ public class CoordinateInventor {
 			}
 
 			// take every large ring that has ring bonds that are not member of a fragment added already
-		for (int bond=0; bond<mMol.getAllBonds(); bond++) {
+		for (int bond=0; bond<mMol.getBonds(); bond++) {
 			if (mMol.isRingBond(bond) && !mBondHandled[bond]) {
 				InventorChain theRing = getSmallestRingFromBond(bond);
 				int[] ringAtom = theRing.getRingAtoms();
@@ -1183,9 +1184,7 @@ public class CoordinateInventor {
 		int current = 1;
 		int highest = 1;
 		while (current <= highest) {
-//			if (graphLevel[graphAtom[current]] > RingCollection.MAX_LARGE_RING_SIZE)
-//				return null;		// disabled ring size limit;  TLS 20130613
-			for (int i=0; i<mMol.getAllConnAtoms(graphAtom[current]); i++) {
+			for (int i=0; i<mMol.getConnAtoms(graphAtom[current]); i++) {
 				int candidate = mMol.getConnAtom(graphAtom[current], i);
 				if ((current > 1) && candidate == atom1) {
 					InventorChain theRing = new InventorChain(graphLevel[graphAtom[current]]);
@@ -1222,9 +1221,7 @@ public class CoordinateInventor {
 		int current = 1;
 		int highest = 1;
 		while (current <= highest) {
-//			if (graphLevel[graphAtom[current]] > RingCollection.MAX_LARGE_RING_SIZE)
-//				return 0;		// disabled ring size limit;  TLS 20130613
-			for (int i=0; i<mMol.getAllConnAtoms(graphAtom[current]); i++) {
+			for (int i=0; i<mMol.getConnAtoms(graphAtom[current]); i++) {
 				int candidate = mMol.getConnAtom(graphAtom[current], i);
 				if (candidate == atom3)
 					return 1 + graphLevel[graphAtom[current]];


=====================================
src/main/java/com/actelion/research/chem/descriptor/flexophore/generator/CreatorMolDistHistViz.java
=====================================
@@ -9,6 +9,7 @@ import com.actelion.research.chem.descriptor.flexophore.redgraph.SubGraphExtract
 import com.actelion.research.chem.descriptor.flexophore.redgraph.SubGraphIndices;
 import com.actelion.research.chem.interactionstatistics.InteractionAtomTypeCalculator;
 import org.openmolecules.chem.conf.gen.ConformerGenerator;
+import org.openmolecules.chem.conf.gen.RigidFragmentCache;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -60,6 +61,7 @@ public class CreatorMolDistHistViz {
         subGraphExtractor = new SubGraphExtractor();
 
         conformerGenerator = new ConformerGenerator(seed, false);
+        RigidFragmentCache.getDefaultInstance().loadDefaultCache();
 
         conformationMode = CONF_GEN_TS;
 


=====================================
src/main/java/com/actelion/research/chem/docking/DockingEngine.java
=====================================
@@ -17,6 +17,7 @@ import org.openmolecules.chem.conf.gen.ConformerGenerator;
 import com.actelion.research.calc.ThreadMaster;
 import com.actelion.research.chem.Canonizer;
 import com.actelion.research.chem.Coordinates;
+import com.actelion.research.chem.IDCodeParser;
 import com.actelion.research.chem.IDCodeParserWithoutCoordinateInvention;
 import com.actelion.research.chem.Molecule;
 import com.actelion.research.chem.Molecule3D;
@@ -121,7 +122,7 @@ public class DockingEngine {
 		threadMaster = tm;
 	}
 	
-	private double getStartingPositions(StereoMolecule mol, List<Conformer> initialPos) {
+	private double getStartingPositions(StereoMolecule mol, List<Conformer> initialPos) throws DockingFailedException {
 
 		double eMin = Double.MAX_VALUE;
 		Map<String, Object> ffOptions = new HashMap<String, Object>();
@@ -140,7 +141,14 @@ public class DockingEngine {
 			if(c!=null) {
 				StereoMolecule conf = c.toMolecule();
 				conf.ensureHelperArrays(Molecule.cHelperParities);
-				ForceFieldMMFF94 mmff = new ForceFieldMMFF94(conf, ForceFieldMMFF94.MMFF94SPLUS, ffOptions);
+				
+				ForceFieldMMFF94 mmff;
+				try {
+					mmff = new ForceFieldMMFF94(conf, ForceFieldMMFF94.MMFF94SPLUS, ffOptions);
+				}
+				catch(Exception e) {
+					throw new DockingFailedException("could not assess atom types");
+				}
 				PositionConstraint constraint = new PositionConstraint(conf,50,0.2);
 				mmff.addEnergyTerm(constraint);
 				mmff.minimise();
@@ -170,6 +178,7 @@ public class DockingEngine {
 			ForceFieldMMFF94.initialize(ForceFieldMMFF94.MMFF94SPLUS);
 		List<Conformer> startPoints = new ArrayList<>();
 		double eMin = getStartingPositions(mol, startPoints);
+		Map<String,Double> contributions = null;
 		for(Conformer ligConf : startPoints) {
 			for(double[] transform : PheSAAlignment.initialTransform(0)) {
 				Conformer newLigConf = new Conformer(ligConf);
@@ -191,6 +200,7 @@ public class DockingEngine {
 				if(energy<bestEnergy) {
 					bestEnergy = energy;
 					bestPose = pose.getLigConf();
+					contributions = pose.getContributions();
 				}
 				if(threadMaster!=null && threadMaster.threadMustDie())
 					break;
@@ -202,8 +212,7 @@ public class DockingEngine {
 			Translation translate = new Translation(new double[] {origCOM.x, origCOM.y, origCOM.z});
 			rot.apply(best);
 			translate.apply(best);
-
-			return new DockingResult(best,bestEnergy);
+			return new DockingResult(best,bestEnergy,contributions);
 		}
 		else {
 			throw new DockingFailedException("docking failed");
@@ -265,7 +274,6 @@ public class DockingEngine {
 		}
 
 		pose.setState(bestState);
-		engine.getScore();
 		return bestEnergy;
 		
 	}
@@ -391,11 +399,16 @@ public class DockingEngine {
 	public static class DockingResult implements Comparable<DockingResult>  {
 		private double score;
 		private StereoMolecule pose;
+		private Map<String,Double> contributions;
 		private static final String DELIMITER = ";";
+		private static final String DELIMITER2 = ":";
+		private static final String DELIMITER3 = "%";
+		private static final String NULL_CONTRIBUTION = "#";
 		
-		public DockingResult(StereoMolecule pose, double score) {
+		public DockingResult(StereoMolecule pose, double score, Map<String,Double> contributions) {
 			this.score = score;
 			this.pose = pose;
+			this.contributions = contributions;
 		}
 		
 		public double getScore() {
@@ -406,6 +419,11 @@ public class DockingEngine {
 			return pose;
 		}
 		
+		public Map<String,Double> getContributions() {
+			return contributions;
+		}
+		
+		
 		public String encode() {
 			Encoder encoder = Base64.getEncoder();
 			StringBuilder sb = new StringBuilder();
@@ -417,6 +435,19 @@ public class DockingEngine {
 			sb.append(idcoords);
 			sb.append(DELIMITER);
 			sb.append(encoder.encodeToString(EncodeFunctions.doubleToByteArray(score)));
+			sb.append(DELIMITER);
+			if(contributions==null || contributions.keySet().size()==0)
+				sb.append(NULL_CONTRIBUTION);
+			else {
+				for(String name : contributions.keySet()) {
+					sb.append(name);
+					sb.append(DELIMITER3);
+					sb.append(encoder.encodeToString(EncodeFunctions.doubleToByteArray(contributions.get(name))));
+					sb.append(DELIMITER2);
+				}
+				sb.setLength(sb.length() - 1);
+			}
+			
 			return sb.toString();
 		}
 		
@@ -430,7 +461,19 @@ public class DockingEngine {
 			parser.parse(pose, idcode, idcoords);
 			pose.ensureHelperArrays(Molecule.cHelperCIP);
 			double score = EncodeFunctions.byteArrayToDouble(decoder.decode(s[2].getBytes()));
-			DockingResult dockingResult = new DockingResult(pose,score);
+			Map<String,Double> contributions = null;
+			if(!s[3].equals(NULL_CONTRIBUTION)) {
+				contributions = new HashMap<String,Double>();
+				String[] splitted = s[3].split(DELIMITER2);
+				for(String contr : splitted) {
+					String[] splitted2 = contr.split(DELIMITER3);
+					String name = splitted2[0];
+					double value = EncodeFunctions.byteArrayToDouble(decoder.decode(splitted2[1].getBytes()));
+					contributions.put(name, value);
+				}
+			}
+				
+			DockingResult dockingResult = new DockingResult(pose,score,contributions);
 			return dockingResult;
 		}
 
@@ -444,6 +487,8 @@ public class DockingEngine {
            
 		}
 	}
+	
+
 
 	
 	


=====================================
src/main/java/com/actelion/research/chem/docking/LigandPose.java
=====================================
@@ -136,6 +136,10 @@ public class LigandPose implements Evaluable{
 			
 	}
 	
+	public Map<String,Double> getContributions() {
+		return engine.getContributions();
+	}
+	
 	public void setInitialState() {
 		int elements = 3+3+torsionHelper.getRotatableBonds().length; //3 translational, 3 rotational, 3 torsion
 		state = new double[elements];


=====================================
src/main/java/com/actelion/research/chem/docking/scoring/AbstractScoringEngine.java
=====================================
@@ -3,6 +3,8 @@ package com.actelion.research.chem.docking.scoring;
 import java.util.Set;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+
 import com.actelion.research.chem.Coordinates;
 import com.actelion.research.chem.Molecule3D;
 import com.actelion.research.chem.StereoMolecule;
@@ -78,6 +80,8 @@ public abstract class AbstractScoringEngine  {
 	public abstract double getFGValue(double[] grad);
 	
 	public abstract double getScore();
+	
+	public abstract Map<String,Double> getContributions();
 
 	public Conformer getReceptorConf() {
 		return receptorConf;


=====================================
src/main/java/com/actelion/research/chem/docking/scoring/ChemPLP.java
=====================================
@@ -98,7 +98,7 @@ public class ChemPLP extends AbstractScoringEngine {
 		ff.setState(candidatePose.getCartState());
 		double ffEnergy = ff.getTotalEnergy();
 		if((ffEnergy-e0)>STRAIN_CUTOFF) {
-			energy+=ffEnergy;
+			energy+=ffEnergy-e0;
 			ff.addGradient(grad);
 		}
 		for(PotentialEnergyTerm term : constraints)
@@ -498,6 +498,34 @@ public class ChemPLP extends AbstractScoringEngine {
 	}
 
 
+	@Override
+	public Map<String, Double> getContributions() {
+		Map<String,Double> contributions = new HashMap<String,Double>();
+		double[] grad = new double[3*candidatePose.getLigConf().getMolecule().getAllAtoms()];
+		double hbond = 0.0;
+		for(PotentialEnergyTerm term : chemscoreHbond)
+			hbond+=term.getFGValue(grad);
+		contributions.put("HBOND", hbond);
+		double metal = 0.0;
+		for(PotentialEnergyTerm term : chemscoreMetal) 
+			metal+=term.getFGValue(grad);
+		contributions.put("METAL", metal);
+		double plpContr = 0.0;
+		for(PotentialEnergyTerm term : plp) 
+			plpContr+=term.getFGValue(grad);
+		contributions.put("PLP", plpContr);
+		double strain = 0.0;
+		ff.setState(candidatePose.getCartState());
+		double ffEnergy = ff.getTotalEnergy();
+		if((ffEnergy-e0)>STRAIN_CUTOFF) {
+			strain+=ffEnergy;
+			ff.addGradient(grad);
+		}
+		contributions.put("STRAIN", strain);
+		return contributions;
+	}
+
+
 
 
 


=====================================
src/main/java/com/actelion/research/chem/docking/scoring/IdoScore.java
=====================================
@@ -140,6 +140,12 @@ public class IdoScore extends AbstractScoringEngine {
 			
 	}
 
+	@Override
+	public Map<String, Double> getContributions() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
 
 
 


=====================================
src/main/java/com/actelion/research/chem/io/Mol2FileParser.java
=====================================
@@ -243,8 +243,9 @@ public class Mol2FileParser extends AbstractParser {
 	
 						int a = m.addAtom(atomicNo);
 						m.setAtomX(a, x);
-						m.setAtomY(a, y);
-						m.setAtomZ(a, z);
+						//invert y and z coordinates for compatibility with Java coordinate system (analogously to Molfileparser)
+						m.setAtomY(a, -y);
+						m.setAtomZ(a, -z);
 						m.setAtomName(a, atomName);
 						m.setAtomChainId(a, chainId);
 						m.setAtomAmino(a, amino);


=====================================
src/main/java/com/actelion/research/chem/properties/complexity/ContainerFragBondsSolutions.java
=====================================
@@ -197,7 +197,7 @@ public class ContainerFragBondsSolutions {
         }
 
 		if(ELUSIVE)
-			System.out.println("ContainerFragBondsSolutions maximumNumberBondsInFragment " + maximumNumberBondsInFragment);
+			System.out.println("ContainerFragBondsSolutions maximum number of bonds in a single fragment " + maximumNumberBondsInFragment + ".");
 
         return arrCapacity;
 	}


=====================================
src/main/java/com/actelion/research/chem/properties/complexity/ExhaustiveFragmentsStatistics.java
=====================================
@@ -115,7 +115,7 @@ public class ExhaustiveFragmentsStatistics {
 
 		efg  = new ExhaustiveFragmentGeneratorBonds(bits, totalCapacity);
 
-		System.out.println("ExhaustiveFragmentsStatistics init(...) max capacity bonds in fragment " + efg.getMaximumCapacityBondsInFragment());
+		System.out.println("ExhaustiveFragmentsStatistics init(...) max number of bonds in a fragment " + efg.getMaximumCapacityBondsInFragment());
 
 		minNumBondsFragment = MINLEN_FRAG;
 				


=====================================
src/main/java/com/actelion/research/chem/reaction/mapping/MappingScorer.java
=====================================
@@ -52,7 +52,8 @@ public class MappingScorer {
 		// we add/remove fractional bond orders for new/broken bonds and
 		// we add fractional bond order changes of changed bonds
 		// to reflect the corresponding change in implicit hydrogen bond counts.
-		float[] hydrogenBondPenalty = new float[mProduct.getAtoms()];
+		float[] hydrogenBondPenalty = SCORE_HYDROGEN ? new float[mProduct.getAtoms()] : null;
+
 		boolean[] isAssignedProductAtom = new boolean[mProduct.getAtoms()];
 		for (int atom:reactantToProductAtom)
 			if (atom != -1)
@@ -71,24 +72,32 @@ public class MappingScorer {
 				if (pAtom1 != -1 || pAtom2 != -1)
 					penalty += getBondCreateOrBreakPenalty(mReactant, rBond);
 
-				if (pAtom1 != -1)
-					hydrogenBondPenalty[pAtom1] += rBondOrder;
-				if (pAtom2 != -1)
-					hydrogenBondPenalty[pAtom2] += rBondOrder;
+				if (SCORE_HYDROGEN) {
+					if (pAtom1 != -1)
+						hydrogenBondPenalty[pAtom1] += rBondOrder;
+					if (pAtom2 != -1)
+						hydrogenBondPenalty[pAtom2] += rBondOrder;
+					}
 				continue;
 				}
 
 			int pBond = mProduct.getBond(pAtom1, pAtom2);
 			if (pBond == -1) {
 				penalty += getBondCreateOrBreakPenalty(mReactant, rBond);
-				hydrogenBondPenalty[pAtom1] += rBondOrder;
-				hydrogenBondPenalty[pAtom2] += rBondOrder;
+
+				if (SCORE_HYDROGEN) {
+					hydrogenBondPenalty[pAtom1] += rBondOrder;
+					hydrogenBondPenalty[pAtom2] += rBondOrder;
+					}
+
 				continue;
 				}
 
-			float bondOrderChange = rBondOrder - getFractionalBondOrder(mProduct, pBond);
-			hydrogenBondPenalty[pAtom1] += bondOrderChange;
-			hydrogenBondPenalty[pAtom2] += bondOrderChange;
+			if (SCORE_HYDROGEN) {
+				float bondOrderChange = rBondOrder - getFractionalBondOrder(mProduct, pBond);
+				hydrogenBondPenalty[pAtom1] += bondOrderChange;
+				hydrogenBondPenalty[pAtom2] += bondOrderChange;
+				}
 
 			productBondHandled[pBond] = true;
 			penalty += getBondChangePenalty(rBond, pBond);
@@ -97,13 +106,16 @@ public class MappingScorer {
 		for (int pBond=0; pBond<mProduct.getBonds(); pBond++) {
 			if (!productBondHandled[pBond]) {
 				penalty += getBondCreateOrBreakPenalty(mProduct, pBond);
-				float pBondOrder = getFractionalBondOrder(mProduct, pBond);
-				int pAtom1 = mProduct.getBondAtom(0, pBond);
-				int pAtom2 = mProduct.getBondAtom(1, pBond);
-				if (isAssignedProductAtom[pAtom1])
-					hydrogenBondPenalty[pAtom1] -= pBondOrder;
-				if (isAssignedProductAtom[pAtom2])
-					hydrogenBondPenalty[pAtom2] -= pBondOrder;
+
+				if (SCORE_HYDROGEN) {
+					float pBondOrder = getFractionalBondOrder(mProduct, pBond);
+					int pAtom1 = mProduct.getBondAtom(0, pBond);
+					int pAtom2 = mProduct.getBondAtom(1, pBond);
+					if (isAssignedProductAtom[pAtom1])
+						hydrogenBondPenalty[pAtom1] -= pBondOrder;
+					if (isAssignedProductAtom[pAtom2])
+						hydrogenBondPenalty[pAtom2] -= pBondOrder;
+					}
 				}
 			}
 
@@ -135,7 +147,7 @@ public class MappingScorer {
 		boolean isHetero2 = mol.isElectronegative(atom2);
 
 		if (!isHetero1 && !isHetero2)
-			return mol.isAromaticBond(bond) ? 3f : 1.9f + (float)mol.getBondOrder(bond) / 10f;
+			return mol.isAromaticBond(bond) ? 2.1f : 1.9f + (float)mol.getBondOrder(bond) / 10f;
 
 		if (isHetero1 && isHetero2)    // e.g. m-CPBA
 			return 1.7f;


=====================================
src/main/java/com/actelion/research/chem/reaction/mapping/RootAtomPairSource.java
=====================================
@@ -139,10 +139,6 @@ public class RootAtomPairSource {
 		return true;
 		}
 
-	public int getPairSequenceCount() {
-		return mSequenceCount;
-		}
-
 	/**
 	 * RootAtomPairs are returned in similarity order. The first returned pair is that
 	 * pair of atoms that is more similar than any other mutual combination of reactant


=====================================
src/main/java/com/actelion/research/chem/reaction/mapping/SimilarityGraphBasedReactionMapper.java
=====================================
@@ -75,7 +75,10 @@ public class SimilarityGraphBasedReactionMapper {
 
 		RootAtomPairSource rootAtomPairSource = new RootAtomPairSource(reactant, product, mReactantMapNo, mProductMapNo);
 
+		mAtomPairSequenceCount = 0;
+
 		while (rootAtomPairSource.hasNextPairSequence()) {
+			mAtomPairSequenceCount++;
 			mMapNo = rootAtomPairSource.getManualMapCount();
 			mMappableAtomCount = rootAtomPairSource.getMappableAtomCount();
 
@@ -117,8 +120,6 @@ if (DEBUG) {
 					productMapNo[i] = mProductMapNo[i];
 				}
 			}
-
-		mAtomPairSequenceCount = rootAtomPairSource.getPairSequenceCount();
 		}
 
 	/**
@@ -299,16 +300,16 @@ if (DEBUG) {
 			 && mSimilarityComparator.compare(mReactantConnAtomEnv[reactantAtom][reactantConnIndex][radius],
 				mProductConnAtomEnv[productAtom][productConnIndex][radius]) == 0)
 			radius++;
-		while (radius+NO_PI_PENALTY < MAX_ENVIRONMENT_RADIUS
-			 && mReactantNoPiAtomEnv[reactantAtom][reactantConnIndex][radius+NO_PI_PENALTY] != null
-			 && mSimilarityComparator.compare(mReactantNoPiAtomEnv[reactantAtom][reactantConnIndex][radius+NO_PI_PENALTY],
-				mProductNoPiAtomEnv[productAtom][productConnIndex][radius+NO_PI_PENALTY]) == 0)
-			radius++;
-		while (radius+SKELETON_PENALTY < MAX_ENVIRONMENT_RADIUS
-			 && mReactantSkelAtomEnv[reactantAtom][reactantConnIndex][radius+SKELETON_PENALTY] != null
-			 && mSimilarityComparator.compare(mReactantSkelAtomEnv[reactantAtom][reactantConnIndex][radius+SKELETON_PENALTY],
-				mProductSkelAtomEnv[productAtom][productConnIndex][radius+SKELETON_PENALTY]) == 0)
-			radius++;
+//		while (radius+NO_PI_PENALTY < MAX_ENVIRONMENT_RADIUS
+//			 && mReactantNoPiAtomEnv[reactantAtom][reactantConnIndex][radius+NO_PI_PENALTY] != null
+//			 && mSimilarityComparator.compare(mReactantNoPiAtomEnv[reactantAtom][reactantConnIndex][radius+NO_PI_PENALTY],
+//				mProductNoPiAtomEnv[productAtom][productConnIndex][radius+NO_PI_PENALTY]) == 0)
+//			radius++;
+//		while (radius+SKELETON_PENALTY < MAX_ENVIRONMENT_RADIUS
+//			 && mReactantSkelAtomEnv[reactantAtom][reactantConnIndex][radius+SKELETON_PENALTY] != null
+//			 && mSimilarityComparator.compare(mReactantSkelAtomEnv[reactantAtom][reactantConnIndex][radius+SKELETON_PENALTY],
+//				mProductSkelAtomEnv[productAtom][productConnIndex][radius+SKELETON_PENALTY]) == 0)
+//			radius++;
 
 		return radius << SIMILARITY_SHIFT;
 		}
@@ -524,10 +525,10 @@ if (DEBUG) {
 						if (bondType == getBondType(mProduct, candidateBond)
 						 || skelSimilarity != 0) {
 							if (passesBasicRules(reactantRoot, reactantCandidate, productRoot, productCandidate)) {
-								int envSimilarity = getCombinedAtomSimilarity(reactantRoot, reactantCandidate, productRoot, productCandidate);
+//								int envSimilarity = getCombinedAtomSimilarity(reactantRoot, reactantCandidate, productRoot, productCandidate);
 
 								// introducing the non-pi similarity does not really seem to improve matters
-//								int envSimilarity = getCombinedAtomSimilarity(reactantRoot, reactantCandidate, productRoot, productCandidate);
+								int envSimilarity = getAtomSimilarity(reactantRoot, reactantCandidate, productRoot, productCandidate);
 //System.out.println("skel:"+skelSimilarity+" conn:"+envSimilarity+" comb:"+envSimilarityCandidate);
 
 								int similarity = Math.max(skelSimilarity, envSimilarity);


=====================================
src/main/java/com/actelion/research/gui/JDrawDialog.java
=====================================
@@ -19,15 +19,16 @@
 package com.actelion.research.gui;
 
 import com.actelion.research.chem.StereoMolecule;
-import com.actelion.research.chem.reaction.IReactionMapper;
-import com.actelion.research.chem.reaction.MCSReactionMapper;
 import com.actelion.research.chem.reaction.Reaction;
 import com.actelion.research.gui.clipboard.ClipboardHandler;
 import com.actelion.research.gui.hidpi.HiDPIHelper;
 
 import javax.swing.*;
 import java.awt.*;
-import java.awt.event.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
 import java.util.ArrayList;
 
 public class JDrawDialog extends JDialog implements ActionListener,KeyListener {
@@ -60,6 +61,22 @@ public class JDrawDialog extends JDialog implements ActionListener,KeyListener {
 		initialize(owner, 0);
 		}
 
+	public JDrawDialog(Dialog owner, StereoMolecule[] mol, String title, ModalityType modalityType) {
+		super(owner, title, modalityType);
+		mMolecule = new StereoMolecule();
+		initialize(owner, JDrawArea.MODE_MULTIPLE_FRAGMENTS);
+		if (mol != null)
+			mArea.setFragments(mol);
+		}
+
+	public JDrawDialog(Dialog owner, Reaction rxn, String title, ModalityType modalityType) {
+		super(owner, title, modalityType);
+		mMolecule = new StereoMolecule();
+		initialize(owner, JDrawArea.MODE_REACTION);
+		if (rxn != null)
+			mArea.setReaction(rxn);
+		}
+
 	public JDrawDialog(Frame owner) {
 		this(owner, false, DEFAULT_TITLE);
 		}
@@ -108,7 +125,7 @@ public class JDrawDialog extends JDialog implements ActionListener,KeyListener {
 	public JDrawDialog(Frame owner, StereoMolecule[] mol, String title, ModalityType modalityType) {
 		super(owner, title, modalityType);
 		mMolecule = new StereoMolecule();
-		initialize(owner, JDrawArea.MODE_REACTION);
+		initialize(owner, JDrawArea.MODE_MULTIPLE_FRAGMENTS);
 		if (mol != null)
 			mArea.setFragments(mol);
 		}


=====================================
src/main/java/com/actelion/research/gui/JEditableChemistryView.java
=====================================
@@ -149,7 +149,10 @@ public class JEditableChemistryView extends JChemistryView {
 		while (!(c instanceof Frame || c instanceof Dialog))
 			c = c.getParent();
 
-		return new JDrawDialog((Frame)c, reaction, "Edit Reaction", Dialog.ModalityType.DOCUMENT_MODAL);
+		if (c instanceof Frame)
+			return new JDrawDialog((Frame)c, reaction, "Edit Reaction", Dialog.ModalityType.DOCUMENT_MODAL);
+		else
+			return new JDrawDialog((Dialog)c, reaction, "Edit Reaction", Dialog.ModalityType.DOCUMENT_MODAL);
 		}
 
 	protected JDrawDialog createDrawDialog(String title, StereoMolecule[] mol) {
@@ -157,6 +160,9 @@ public class JEditableChemistryView extends JChemistryView {
 		while (!(c instanceof Frame || c instanceof Dialog))
 			c = c.getParent();
 
-		return new JDrawDialog((Frame)c, mol, "Edit Molecules", Dialog.ModalityType.DOCUMENT_MODAL);
-		}
+		if (c instanceof Frame)
+			return new JDrawDialog((Frame)c, mol, "Edit Molecules", Dialog.ModalityType.DOCUMENT_MODAL);
+		else
+			return new JDrawDialog((Dialog)c, mol, "Edit Molecules", Dialog.ModalityType.DOCUMENT_MODAL);
+	}
 }


=====================================
src/main/java/com/actelion/research/gui/clipboard/ClipboardHandler.java
=====================================
@@ -35,6 +35,7 @@ package com.actelion.research.gui.clipboard;
 import com.actelion.research.chem.*;
 import com.actelion.research.chem.coords.CoordinateInventor;
 import com.actelion.research.chem.dnd.ChemistryFlavors;
+import com.actelion.research.chem.io.RXNFileCreator;
 import com.actelion.research.chem.io.RXNFileParser;
 import com.actelion.research.chem.name.StructureNameResolver;
 import com.actelion.research.chem.reaction.Reaction;
@@ -497,10 +498,10 @@ public class ClipboardHandler implements IClipboardHandler
 	}
 
 	private boolean copyReactionToClipboard(String ctab, Reaction rxn) throws IOException {
-//		if (ctab == null) {
-//			RXNFileCreator mc = new RXNFileCreator(rxn);
-//			ctab = mc.getRXNfile();
-//		}
+		if (ctab == null) {
+			RXNFileCreator mc = new RXNFileCreator(rxn);
+			ctab = mc.getRXNfile();
+		}
 
 		ChemDrawCDX cdx = new com.actelion.research.gui.clipboard.external.ChemDrawCDX();
 		byte[] cdxBuffer = cdx.getChemDrawBuffer(rxn);
@@ -514,7 +515,7 @@ public class ClipboardHandler implements IClipboardHandler
 		out.close();
 		bos.close();
 
-		return NativeClipboardAccessor.copyMoleculeToClipboard("", cdxBuffer, bos.toByteArray());
+		return NativeClipboardAccessor.copyReactionToClipboard(ctab.getBytes(), cdxBuffer, bos.toByteArray());
 	}
 
 	private boolean writeMol2Metafile(File temp, StereoMolecule m, byte[] sketch) {


=====================================
src/main/java/com/actelion/research/gui/dock/DividerChangeListener.java
=====================================
@@ -0,0 +1,5 @@
+package com.actelion.research.gui.dock;
+
+public interface DividerChangeListener {
+	public void dividerLocationChanged(TreeFork fork);
+}


=====================================
src/main/java/com/actelion/research/gui/dock/JDockingPanel.java
=====================================
@@ -28,6 +28,7 @@ public class JDockingPanel extends JPanel implements ActionListener {
 	private int mTargetPosition,mPreviousTargetPosition;
 	private GhostPreview mPreview;
 	private Dockable mMaximizedView;
+	private Vector<DividerChangeListener> mDividerChangeListeners;
 
 	/**
 	 * Creates a docking panel to which any Dockables may be added by
@@ -85,6 +86,23 @@ public class JDockingPanel extends JPanel implements ActionListener {
 			}, true);
 		}
 
+	public void addDividerChangeLister(DividerChangeListener l) {
+		if (mDividerChangeListeners == null)
+			mDividerChangeListeners = new Vector<>();
+
+		mDividerChangeListeners.add(l);
+		if (mTreeRoot != null)
+			mTreeRoot.setDividerChangeListeners(mDividerChangeListeners);
+		}
+
+	public void removeDividerChangeLister(DividerChangeListener l) {
+		if (mDividerChangeListeners != null) {
+			mDividerChangeListeners.remove(l);
+			if (mTreeRoot != null)
+				mTreeRoot.setDividerChangeListeners(mDividerChangeListeners);
+			}
+		}
+
 	public void actionPerformed(ActionEvent e) {
 		if (e.getActionCommand().startsWith("close_")) {
 			String title = e.getActionCommand().substring(6);
@@ -309,7 +327,7 @@ public class JDockingPanel extends JPanel implements ActionListener {
 		else {
 			TreeLeaf newLeaf = new TreeLeaf(dockable, this, isDragging);
 			TreeContainer parent = treeLeaf.getParent();
-			TreeFork treeFork = new TreeFork(treeLeaf, newLeaf, position, dividerPosition);
+			TreeFork treeFork = new TreeFork(treeLeaf, newLeaf, position, dividerPosition, mDividerChangeListeners);
 			parent.replaceChildElement(treeLeaf, treeFork);
 			mLeafMap.put(dockable.getTitle(), newLeaf);
 			}
@@ -430,6 +448,34 @@ public class JDockingPanel extends JPanel implements ActionListener {
 			}
 		}
 
+	/**
+	 * Checks, whether a dockable with the given title exists, and whether it is visible (selected if in a JTabbedPane)
+	 * and whether it is in the left or right branch (depending on parameter left) of a JSplitPane.
+	 * @param title
+	 * @param left if true, then
+	 * @return true the respective dockable is visible and in the left part of a JSplitPane
+	 */
+	public boolean isVisibleInSplitPane(String title, boolean left) {
+		Component view = mDockableMap.get(title);
+		if (view == null)
+			return false;
+
+		Component pane = view.getParent();
+		if (pane instanceof JTabbedPane) {
+			if (view != ((JTabbedPane)pane).getSelectedComponent())
+				return false;
+
+			view = pane;
+			pane = pane.getParent();
+			}
+
+		if (pane instanceof JSplitPane)
+			return (left && view == ((JSplitPane)pane).getLeftComponent())
+				|| (!left && view == ((JSplitPane)pane).getRightComponent());
+
+		return false;
+		}
+
 	/**
 	 * changes a title of a docked Dockable
 	 * @param oldTitle existing title
@@ -596,7 +642,7 @@ public class JDockingPanel extends JPanel implements ActionListener {
 			Dockable dockable = dockables.get(size2);
 			bottomLeft = new TreeLeaf(dockable, this, false);
 			TreeContainer parent = topLeft.getParent();
-			TreeFork treeFork = new TreeFork(topLeft, bottomLeft, DOCK_BOTTOM, .5);	
+			TreeFork treeFork = new TreeFork(topLeft, bottomLeft, DOCK_BOTTOM, .5, mDividerChangeListeners);
 			parent.replaceChildElement(topLeft, treeFork);
 			mLeafMap.put(dockable.getTitle(), bottomLeft);
 			mDockableMap.put(dockable.getTitle(), dockable);
@@ -606,7 +652,7 @@ public class JDockingPanel extends JPanel implements ActionListener {
 			Dockable dockable = dockables.get(size1);
 			topRight = new TreeLeaf(dockable, this, false);
 			TreeContainer parent = topLeft.getParent();
-			TreeFork treeFork = new TreeFork(topLeft, topRight, DOCK_RIGHT, .5);	
+			TreeFork treeFork = new TreeFork(topLeft, topRight, DOCK_RIGHT, .5, mDividerChangeListeners);
 			parent.replaceChildElement(topLeft, treeFork);
 			mLeafMap.put(dockable.getTitle(), topRight);
 			mDockableMap.put(dockable.getTitle(), dockable);
@@ -616,7 +662,7 @@ public class JDockingPanel extends JPanel implements ActionListener {
 			Dockable dockable = dockables.get(size3);
 			bottomRight = new TreeLeaf(dockable, this, false);
 			TreeContainer parent = bottomLeft.getParent();
-			TreeFork treeFork = new TreeFork(bottomLeft, bottomRight, DOCK_RIGHT, .5);	
+			TreeFork treeFork = new TreeFork(bottomLeft, bottomRight, DOCK_RIGHT, .5, mDividerChangeListeners);
 			parent.replaceChildElement(bottomLeft, treeFork);
 			mLeafMap.put(dockable.getTitle(), bottomRight);
 			mDockableMap.put(dockable.getTitle(), dockable);


=====================================
src/main/java/com/actelion/research/gui/dock/TreeFork.java
=====================================
@@ -1,12 +1,18 @@
 package com.actelion.research.gui.dock;
 
 import javax.swing.*;
+import javax.swing.plaf.SplitPaneUI;
+import javax.swing.plaf.basic.BasicSplitPaneUI;
 import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
 import java.util.ArrayList;
+import java.util.Vector;
 
 public class TreeFork extends TreeContainer {
 	private TreeElement mLeftChildElement;
 	private TreeElement mRightChildElement;
+	private Vector<DividerChangeListener> mDividerChangeListeners;
 
 	/**
 	 * Constructor to create a fork element that is inserted between the specified
@@ -14,42 +20,55 @@ public class TreeFork extends TreeContainer {
 	 * @param oldLeaf
 	 * @param newLeaf
 	 * @param newLeafPosition
-	 * @param proportionalDividerLocation
+	 * @param dividerLocation
 	 */
-	public TreeFork(TreeLeaf oldLeaf, TreeLeaf newLeaf, int newLeafPosition, double proportionalDividerLocation) {
+	public TreeFork(TreeLeaf oldLeaf, TreeLeaf newLeaf, int newLeafPosition, double dividerLocation, Vector<DividerChangeListener> listeners) {
 		JSplitPane splitPane = null;
 		switch (newLeafPosition) {
 		case JDockingPanel.DOCK_TOP:
 			splitPane = new MySplitPane(JSplitPane.VERTICAL_SPLIT,
-					newLeaf.getComponent(), oldLeaf.getComponent(), proportionalDividerLocation);
+					newLeaf.getComponent(), oldLeaf.getComponent(), dividerLocation);
 			mLeftChildElement = newLeaf;
 			mRightChildElement = oldLeaf;
 			break;
 		case JDockingPanel.DOCK_LEFT:
 			splitPane = new MySplitPane(JSplitPane.HORIZONTAL_SPLIT,
-					newLeaf.getComponent(), oldLeaf.getComponent(), proportionalDividerLocation);
+					newLeaf.getComponent(), oldLeaf.getComponent(), dividerLocation);
 			mLeftChildElement = newLeaf;
 			mRightChildElement = oldLeaf;
 			break;
 		case JDockingPanel.DOCK_BOTTOM:
 			splitPane = new MySplitPane(JSplitPane.VERTICAL_SPLIT,
-					oldLeaf.getComponent(), newLeaf.getComponent(), proportionalDividerLocation);
+					oldLeaf.getComponent(), newLeaf.getComponent(), dividerLocation);
 			mLeftChildElement = oldLeaf;
 			mRightChildElement = newLeaf;
 			break;
 		case JDockingPanel.DOCK_RIGHT:
 			splitPane = new MySplitPane(JSplitPane.HORIZONTAL_SPLIT,
-					oldLeaf.getComponent(), newLeaf.getComponent(), proportionalDividerLocation);
+					oldLeaf.getComponent(), newLeaf.getComponent(), dividerLocation);
 			mLeftChildElement = oldLeaf;
 			mRightChildElement = newLeaf;
 			break;
 			}
-//		splitPane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, pce -> {} );
+		splitPane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, e -> {
+			if (mDividerChangeListeners != null && ((MySplitPane)mComponent).isDragged())
+				for (DividerChangeListener l:mDividerChangeListeners)
+					l.dividerLocationChanged(this);
+			} );
+		mDividerChangeListeners = listeners;
 		mComponent = splitPane;
 		oldLeaf.setParent(this);
 		newLeaf.setParent(this);
 		}
 
+	public void updateDividerChangeListeners(Vector<DividerChangeListener> listeners) {
+		mDividerChangeListeners = listeners;
+		if (mLeftChildElement instanceof TreeFork)
+			((TreeFork)mLeftChildElement).updateDividerChangeListeners(listeners);
+		if (mLeftChildElement instanceof TreeFork)
+			((TreeFork)mRightChildElement).updateDividerChangeListeners(listeners);
+		}
+
 	public void removeWithLeaf(TreeLeaf leaf) {
 		assert(leaf == mLeftChildElement || leaf == mRightChildElement);
 		if (leaf == mLeftChildElement)
@@ -156,6 +175,7 @@ class MySplitPane extends JSplitPane {
 	private static final long serialVersionUID = 0x20070726;
 
 	private double mProportionalLocation;
+	private boolean mMouseDown;
 
 	public MySplitPane(int newOrientation, Component newLeftComponent, Component newRightComponent, double proportionalLocation) {
 		super(newOrientation, true, newLeftComponent, newRightComponent);
@@ -163,6 +183,22 @@ class MySplitPane extends JSplitPane {
 		setResizeWeight(proportionalLocation);
 		setBorder(null);
 		mProportionalLocation = proportionalLocation;
+
+		SplitPaneUI spui = getUI();
+		if (spui instanceof BasicSplitPaneUI) {
+			((BasicSplitPaneUI) spui).getDivider().addMouseListener(new MouseAdapter() {
+				public void mousePressed(MouseEvent e) {
+					mMouseDown = true;
+					}
+				public void mouseReleased(MouseEvent e) {
+					mMouseDown = false;
+					}
+				} );
+			}
+		}
+
+	public boolean isDragged() {
+		return mMouseDown;
 		}
 
 	@Override


=====================================
src/main/java/com/actelion/research/gui/dock/TreeLeaf.java
=====================================
@@ -1,5 +1,7 @@
 package com.actelion.research.gui.dock;
 
+import com.actelion.research.gui.hidpi.HiDPIHelper;
+
 import javax.swing.*;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
@@ -80,7 +82,13 @@ public class TreeLeaf extends TreeElement implements ChangeListener {
 	public void addContent(Dockable dockable, boolean isDragging) {
 		if (mComponent instanceof Dockable) {
 			Dockable existingDockable = (Dockable)mComponent;
-			JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
+			JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.BOTTOM) {
+				@Override public Dimension getMinimumSize() {
+					// of not modified, the minimum size == preferred size.
+					// Then width is at least the width of the longest tab name.
+					return new Dimension(HiDPIHelper.scale(100),HiDPIHelper.scale(100));
+					}
+				};
 			tabbedPane.putClientProperty("Quaqua.TabbedPane.contentBorderPainted", Boolean.FALSE);
 			tabbedPane.add(existingDockable, existingDockable.getTitle());
 			tabbedPane.add(dockable, dockable.getTitle());


=====================================
src/main/java/com/actelion/research/gui/dock/TreeRoot.java
=====================================
@@ -1,17 +1,16 @@
 package com.actelion.research.gui.dock;
 
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.util.ArrayList;
-
 import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Vector;
 
 public class TreeRoot extends TreeContainer {
     private TreeElement mChildElement;
 
     /**
      * Constructor to create a root element to which the first leaf should be connected.
-     * @param parent
+     * @param rootComponent
      */
     public TreeRoot(JComponent rootComponent, TreeElement child) {
         mComponent = rootComponent;
@@ -20,6 +19,11 @@ public class TreeRoot extends TreeContainer {
         mChildElement.setParent(this);
         }
 
+    public void setDividerChangeListeners(Vector<DividerChangeListener> listeners) {
+        if (mChildElement instanceof TreeFork)
+            ((TreeFork)mChildElement).updateDividerChangeListeners(listeners);
+    }
+
     public TreeElement getChild() {
         return mChildElement;
         }


=====================================
src/main/java/com/actelion/research/util/ConstantsDWAR.java
=====================================
@@ -115,6 +115,10 @@ public class ConstantsDWAR {
 
 	public static final String TAG_COOR2 = "idcoordinates2D";
 
+	public static final String IDCODE_EMPTY = "dH";
+
+
+
 	/**
 	 * Can be one or multiple sets of 3D coordinates.
 	 */



View it on GitLab: https://salsa.debian.org/java-team/openchemlib/-/commit/f63e99bffb2f96ef45a8e4d1d087acd1dd99a4fb

-- 
View it on GitLab: https://salsa.debian.org/java-team/openchemlib/-/commit/f63e99bffb2f96ef45a8e4d1d087acd1dd99a4fb
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20211004/64aeeaf7/attachment.htm>


More information about the pkg-java-commits mailing list