1 package uk.ac.cam.ch.wwmm.opsin;
2 
3 import static uk.ac.cam.ch.wwmm.opsin.XmlDeclarations.*;
4 
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.LinkedHashMap;
9 import java.util.LinkedHashSet;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Map.Entry;
13 import java.util.Set;
14 
15 /** Holds the Fragments during the construction of the molecule,
16  * handles the building of new fragments and handles the creation/deletion of atoms/bonds
17  *
18  * @author ptc24
19  * @author dl387
20  *
21  */
22 class FragmentManager {
23 
24 	/** A mapping between fragments and inter fragment bonds */
25 	private final Map<Fragment,Set<Bond>> fragToInterFragmentBond = new LinkedHashMap<Fragment, Set<Bond>>();
26 
27 	/** All of the atom-containing fragments in the molecule */
28 	private final Set<Fragment> fragments = fragToInterFragmentBond.keySet();
29 
30 	/** A builder for fragments specified as SMILES */
31 	private final SMILESFragmentBuilder sBuilder;
32 
33 	/** A source of unique integers */
34 	private final IDManager idManager;
35 
36 	/** Sets up a new Fragment manager, containing no fragments.
37 	 *
38 	 * @param sBuilder A SMILESFragmentBuilder - dependency injection.
39 	 * @param idManager An IDManager.
40 	 */
FragmentManager(SMILESFragmentBuilder sBuilder, IDManager idManager)41 	FragmentManager(SMILESFragmentBuilder sBuilder, IDManager idManager) {
42 		if (sBuilder == null || idManager == null ){
43 			throw new IllegalArgumentException("FragmentManager was parsed a null object in its constructor!");
44 		}
45 		this.sBuilder = sBuilder;
46 		this.idManager = idManager;
47 	}
48 
49 	/** Builds a fragment, based on an SMILES string
50 	 * The fragment will not correspond to a token
51 	 *
52 	 * @param smiles The fragment to build
53 	 * @return The built fragment
54 	 * @throws StructureBuildingException
55 	 */
buildSMILES(String smiles)56 	Fragment buildSMILES(String smiles) throws StructureBuildingException {
57 		return buildSMILES(smiles, "", NONE_LABELS_VAL);
58 	}
59 
60 	/** Builds a fragment, based on an SMILES string
61 	 * The fragment will not correspond to a token
62 	 *
63 	 * @param smiles
64 	 * @param type
65 	 * @param labelMapping
66 	 * @return
67 	 * @throws StructureBuildingException
68 	 */
buildSMILES(String smiles, String type, String labelMapping)69 	Fragment buildSMILES(String smiles, String type, String labelMapping) throws StructureBuildingException {
70 		Fragment newFrag = sBuilder.build(smiles, type, labelMapping);
71 		addFragment(newFrag);
72 		return newFrag;
73 	}
74 
75 	/** Builds a fragment, based on an SMILES string
76 	 * The fragment will correspond to the given tokenEl
77 	 *
78 	 * @param smiles The fragment to build
79 	 * @param tokenEl The corresponding tokenEl
80 	 * @param labelMapping How to label the fragment
81 	 * @return The built fragment
82 	 * @throws StructureBuildingException
83 	 */
buildSMILES(String smiles, Element tokenEl, String labelMapping)84 	Fragment buildSMILES(String smiles, Element tokenEl, String labelMapping) throws StructureBuildingException {
85 		Fragment newFrag = sBuilder.build(smiles, tokenEl, labelMapping);
86 		addFragment(newFrag);
87 		return newFrag;
88 	}
89 
90 	/**Creates a new fragment, containing all of the atoms and bonds
91 	 * of all of the other fragments - i.e. the whole molecule. This updates
92 	 * which fragments the atoms think they are in to the new super fragment
93 	 * but does not change the original fragments.
94 	 * Hence the original fragments remain associated with their atoms
95 	 * Atoms and Bonds are not copied.
96 	 *
97 	 * @return The unified fragment
98 	 */
getUnifiedFragment()99 	Fragment getUnifiedFragment() {
100 		Fragment uniFrag = new Fragment("");
101 		for (Entry<Fragment, Set<Bond>> entry : fragToInterFragmentBond.entrySet()) {
102 			Fragment f = entry.getKey();
103 			Set<Bond> interFragmentBonds = entry.getValue();
104 			for(Atom atom : f.getAtomList()) {
105 				uniFrag.addAtom(atom);
106 			}
107 			for(Bond bond : f.getBondSet()) {
108 				uniFrag.addBond(bond);
109 			}
110 			uniFrag.incorporateOutAtoms(f);
111 			uniFrag.incorporateFunctionalAtoms(f);
112 
113 			for (Bond interFragmentBond : interFragmentBonds) {
114 				uniFrag.addBond(interFragmentBond);
115 			}
116 		}
117 		addFragment(uniFrag);
118 		return uniFrag;
119 	}
120 
121 	/** Incorporates a fragment, usually a suffix, into a parent fragment
122 	 * This does:
123 	 * Imports all of the atoms and bonds from another fragment into this one.
124 	 * Also imports outAtoms and functionalAtoms
125 	 * Reassigns inter-fragment bonds of the child fragment as either intra-fragment bonds
126 	 * of the parent fragment or as inter-fragment bonds of the parent fragment
127 	 *
128 	 * The original fragment still maintains its original atomList/bondList
129 	 *
130 	 * @param childFrag The fragment to be incorporated
131 	 * @param parentFrag The parent fragment
132      * @throws StructureBuildingException
133 	 */
incorporateFragment(Fragment childFrag, Fragment parentFrag)134 	void incorporateFragment(Fragment childFrag, Fragment parentFrag) throws StructureBuildingException {
135 		for(Atom atom : childFrag.getAtomList()) {
136 			parentFrag.addAtom(atom);
137 		}
138 		for(Bond bond : childFrag.getBondSet()) {
139 			parentFrag.addBond(bond);
140 		}
141 		parentFrag.incorporateOutAtoms(childFrag);
142 		parentFrag.incorporateFunctionalAtoms(childFrag);
143 
144 		Set<Bond> interFragmentBonds = fragToInterFragmentBond.get(childFrag);
145 		if (interFragmentBonds == null){
146 			throw new StructureBuildingException("Fragment not registered with this FragmentManager!");
147 		}
148 		for (Bond bond : interFragmentBonds) {//reassign inter-fragment bonds of child
149 			if (bond.getFromAtom().getFrag() == parentFrag && bond.getToAtom().getFrag() == parentFrag){
150 				//bond is now enclosed within parentFrag so make it an intra-fragment bond
151 				//and remove it from the inter-fragment set of the parentFrag
152 				parentFrag.addBond(bond);
153 				fragToInterFragmentBond.get(parentFrag).remove(bond);
154 			}
155 			else{
156 				//bond was an inter-fragment bond between the childFrag and another frag
157 				//It is now between the parentFrag and another frag
158 				addInterFragmentBond(bond);
159 			}
160 		}
161 		fragToInterFragmentBond.remove(childFrag);
162 	}
163 
164 	/** Incorporates a fragment, usually a suffix, into a parent fragment, creating a bond between them.
165 	 *
166 	 * @param childFrag The fragment to be incorporated
167 	 * @param fromAtom An atom on that fragment
168 	 * @param parentFrag The parent fragment
169 	 * @param toAtom An atom on that fragment
170 	 * @param bondOrder The order of the joining bond
171      * @throws StructureBuildingException
172 	 */
incorporateFragment(Fragment childFrag, Atom fromAtom, Fragment parentFrag, Atom toAtom, int bondOrder)173 	void incorporateFragment(Fragment childFrag, Atom fromAtom, Fragment parentFrag, Atom toAtom, int bondOrder) throws StructureBuildingException {
174 		if (!fromAtom.getFrag().equals(childFrag)){
175 			throw new StructureBuildingException("OPSIN Bug: fromAtom was not associated with childFrag!");
176 		}
177 		if (!toAtom.getFrag().equals(parentFrag)){
178 			throw new StructureBuildingException("OPSIN Bug: toAtom was not associated with parentFrag!");
179 		}
180 		incorporateFragment(childFrag, parentFrag);
181 		createBond(fromAtom, toAtom, bondOrder);
182 	}
183 
184 	/** Converts an atom in a fragment to a different atomic symbol described by a SMILES string
185 	 * Charged atoms can also be specified eg. [NH4+]
186 	 *
187 	 * @param a The atom to change to a heteroatom
188 	 * @param smiles The SMILES for one atom
189 	 * @throws StructureBuildingException
190 	 */
replaceAtomWithSmiles(Atom a, String smiles)191 	void replaceAtomWithSmiles(Atom a, String smiles) throws StructureBuildingException {
192 		replaceAtomWithAtom(a, getHeteroatom(smiles), false);
193 	}
194 
195 	/**
196 	 * Converts the smiles for a heteroatom to an atom
197 	 * @param smiles
198 	 * @return
199 	 * @throws StructureBuildingException
200 	 */
getHeteroatom(String smiles)201 	Atom getHeteroatom(String smiles) throws StructureBuildingException {
202 		Fragment heteroAtomFrag = sBuilder.build(smiles);
203 		if (heteroAtomFrag.getAtomCount() != 1){
204 			throw new StructureBuildingException("Heteroatom smiles described a fragment with multiple SMILES!");
205 		}
206 		return heteroAtomFrag.getFirstAtom();
207 	}
208 
209 	/** Uses the information given in the given heteroatom to change the atomic symbol
210 	 * and charge of the given atom
211 	 *
212 	 * @param a The atom to change to a heteroatom
213 	 * @param heteroAtom The atom to copy element/charge properties from
214 	 * @param assignLocant Whether a locant should be assigned to the heteroatom if the locant is not used elsewhere
215 	 * @throws StructureBuildingException if a charge disagreement occurs
216 	 */
replaceAtomWithAtom(Atom a, Atom heteroAtom, boolean assignLocant)217 	void replaceAtomWithAtom(Atom a, Atom heteroAtom, boolean assignLocant) throws StructureBuildingException {
218 		ChemEl chemEl =heteroAtom.getElement();
219 		int replacementCharge =heteroAtom.getCharge();
220 		if (replacementCharge!=0){
221 			if (a.getCharge()==0){
222 				a.addChargeAndProtons(replacementCharge, heteroAtom.getProtonsExplicitlyAddedOrRemoved());
223 			}
224 			else if (a.getCharge()==replacementCharge){
225 				a.setProtonsExplicitlyAddedOrRemoved(heteroAtom.getProtonsExplicitlyAddedOrRemoved());
226 			}
227 			else{
228 				throw new StructureBuildingException("Charge conflict between replacement term and atom to be replaced");
229 			}
230 		}
231 		a.setElement(chemEl);
232 		a.removeElementSymbolLocants();
233 		if (assignLocant){
234 			String primes = "";
235 			while (a.getFrag().getAtomByLocant(chemEl.toString() + primes) != null){//if element symbol already assigned, add a prime and try again
236 				primes += "'";
237 			}
238 			a.addLocant(chemEl.toString() + primes);
239 		}
240 	}
241 
242 	/** Gets an atom, given an id number
243 	 * Use this if you don't know what fragment the atom is in
244 	 * @param id The id of the atom
245 	 * @return The atom, or null if no such atom exists.
246 	 */
getAtomByID(int id)247 	Atom getAtomByID(int id) {
248 		for(Fragment f : fragments) {
249 			Atom a = f.getAtomByID(id);
250 			if(a != null) {
251 				return a;
252 			}
253 		}
254 		return null;
255 	}
256 
257 	/** Gets an atom, given an id number, throwing if fails.
258 	 * Use this if you don't know what fragment the atom is in
259 	 * @param id The id of the atom
260 	 * @return The atom
261      * @throws StructureBuildingException
262 	 */
getAtomByIDOrThrow(int id)263 	Atom getAtomByIDOrThrow(int id) throws StructureBuildingException {
264 		Atom a = getAtomByID(id);
265 		if(a == null) {
266 			throw new StructureBuildingException("Couldn't get atom by id");
267 		}
268 		return a;
269 	}
270 
271 	/**Turns all of the spare valencies in the fragments into double bonds.
272 	 *
273 	 * @throws StructureBuildingException
274 	 */
convertSpareValenciesToDoubleBonds()275 	void convertSpareValenciesToDoubleBonds() throws StructureBuildingException {
276 		for(Fragment f : fragments) {
277 			FragmentTools.convertSpareValenciesToDoubleBonds(f);
278 		}
279 	}
280 
281 	/**
282 	 * Checks valencies are all chemically reasonable. An exception is thrown if any are not
283 	 * @throws StructureBuildingException
284 	 */
checkValencies()285 	void checkValencies() throws StructureBuildingException {
286 		for(Fragment f : fragments) {
287 			f.checkValencies();
288 		}
289 	}
290 
getFragments()291 	Set<Fragment> getFragments() {
292 		return Collections.unmodifiableSet(fragments);
293 	}
294 
295 	/**
296 	 * Registers a fragment
297 	 * @param frag
298 	 */
addFragment(Fragment frag)299 	private void addFragment(Fragment frag)  {
300 		fragToInterFragmentBond.put(frag, new LinkedHashSet<Bond>());
301 	}
302 
303 	/**
304 	 * Removes a fragment
305 	 * Any inter-fragment bonds of this fragment are removed from the fragments it was connected to
306 	 * Throws an exception if fragment wasn't present
307 	 * @param frag
308 	 * @throws StructureBuildingException
309 	 */
removeFragment(Fragment frag)310 	void removeFragment(Fragment frag) throws StructureBuildingException {
311 		Set<Bond> interFragmentBondsInvolvingFragmentSet = fragToInterFragmentBond.get(frag);
312 		if (interFragmentBondsInvolvingFragmentSet == null) {
313 			throw new StructureBuildingException("Fragment not registered with this FragmentManager!");
314 		}
315 		List<Bond> interFragmentBondsInvolvingFragment = new ArrayList<Bond>(interFragmentBondsInvolvingFragmentSet);
316 		for (Bond bond : interFragmentBondsInvolvingFragment) {
317 			if (bond.getFromAtom().getFrag() == frag){
318 				fragToInterFragmentBond.get(bond.getToAtom().getFrag()).remove(bond);
319 			}
320 			else{
321 				fragToInterFragmentBond.get(bond.getFromAtom().getFrag()).remove(bond);
322 			}
323 		}
324 		fragToInterFragmentBond.remove(frag);
325 	}
326 
getOverallCharge()327 	int getOverallCharge() {
328 		int totalCharge = 0;
329 		for (Fragment frag : fragments) {
330 			totalCharge += frag.getCharge();
331 		}
332 		return totalCharge;
333 	}
334 
335 	/**
336 	 * Creates a copy of a fragment by copying data
337 	 * labels the atoms using new ids from the idManager
338 	 * @param originalFragment
339 	 * @return the clone of the fragment
340 	 * @throws StructureBuildingException
341 	 */
copyFragment(Fragment originalFragment)342 	Fragment copyFragment(Fragment originalFragment) throws StructureBuildingException {
343 		return copyAndRelabelFragment(originalFragment, 0);
344 	}
345 
346 	/**
347 	 * Creates a copy of a fragment by copying data
348 	 * labels the atoms using new ids from the idManager
349 	 * @param originalFragment
350 	 * @param primesToAdd: The minimum number of primes to add to the cloned atoms. More primes will be added if necessary to keep the locants unique e.g. N in the presence of N' becomes N'' when this is 1
351 	 * @return the clone of the fragment
352 	 */
copyAndRelabelFragment(Fragment originalFragment, int primesToAdd)353 	Fragment copyAndRelabelFragment(Fragment originalFragment, int primesToAdd) {
354 		Element tokenEl = new TokenEl("");
355 		tokenEl.addAttribute(TYPE_ATR, originalFragment.getType());
356 		tokenEl.addAttribute(SUBTYPE_ATR, originalFragment.getSubType());
357 		Fragment newFragment = new Fragment(tokenEl);
358 		HashMap<Atom, Atom> oldToNewAtomMap = new HashMap<Atom, Atom>();//maps old Atom to new Atom
359 		List<Atom> atomList =originalFragment.getAtomList();
360 		for (Atom atom : atomList) {
361 			int id = idManager.getNextID();
362 			ArrayList<String> newLocants = new ArrayList<String>(atom.getLocants());
363 			if (primesToAdd !=0){
364 				for (int i = 0; i < newLocants.size(); i++) {
365 					String currentLocant = newLocants.get(i);
366 					int currentPrimes = StringTools.countTerminalPrimes(currentLocant);
367 					String locantSansPrimes = currentLocant.substring(0, currentLocant.length()-currentPrimes);
368 					int highestNumberOfPrimesWithThisLocant = currentPrimes;
369 					while (originalFragment.getAtomByLocant(locantSansPrimes + StringTools.multiplyString("'", highestNumberOfPrimesWithThisLocant +1 ))!=null){
370 						highestNumberOfPrimesWithThisLocant++;
371 					}
372 					newLocants.set(i, locantSansPrimes + StringTools.multiplyString("'", ((highestNumberOfPrimesWithThisLocant +1)*primesToAdd) + currentPrimes));
373 				}
374 			}
375 			Atom newAtom =new Atom(id, atom.getElement(), newFragment);
376 			for (String newLocant : newLocants) {
377 				newAtom.addLocant(newLocant);
378 			}
379 			newAtom.setCharge(atom.getCharge());
380 			newAtom.setIsotope(atom.getIsotope());
381 			newAtom.setSpareValency(atom.hasSpareValency());
382 			newAtom.setProtonsExplicitlyAddedOrRemoved(atom.getProtonsExplicitlyAddedOrRemoved());
383 			newAtom.setLambdaConventionValency(atom.getLambdaConventionValency());
384 			//outValency is derived from the outAtoms so is automatically cloned
385 			newAtom.setAtomIsInACycle(atom.getAtomIsInACycle());
386 			newAtom.setType(atom.getType());//may be different from fragment type if the original atom was formerly in a suffix
387 			newAtom.setMinimumValency(atom.getMinimumValency());
388 			newAtom.setImplicitHydrogenAllowed(atom.getImplicitHydrogenAllowed());
389 			newFragment.addAtom(newAtom);
390 			oldToNewAtomMap.put(atom, newAtom);
391 		}
392 		for (Atom atom : atomList) {
393 			if (atom.getAtomParity() != null){
394 				Atom[] oldAtomRefs4 = atom.getAtomParity().getAtomRefs4();
395 				Atom[] newAtomRefs4 = new Atom[4];
396 				for (int i = 0; i < oldAtomRefs4.length; i++) {
397 					Atom oldAtom = oldAtomRefs4[i];
398 					if (oldAtom.equals(AtomParity.hydrogen)){
399 						newAtomRefs4[i] = AtomParity.hydrogen;
400 					}
401 					else if (oldAtom.equals(AtomParity.deoxyHydrogen)){
402 						newAtomRefs4[i] = AtomParity.deoxyHydrogen;
403 					}
404 					else{
405 						newAtomRefs4[i] = oldToNewAtomMap.get(oldAtom);
406 					}
407 				}
408 				AtomParity newAtomParity =new AtomParity(newAtomRefs4, atom.getAtomParity().getParity());
409 				oldToNewAtomMap.get(atom).setAtomParity(newAtomParity);
410 			}
411 			Set<Atom> oldAmbiguousElementAssignmentAtoms = atom.getProperty(Atom.AMBIGUOUS_ELEMENT_ASSIGNMENT);
412 			if (oldAmbiguousElementAssignmentAtoms!=null){
413 				Set<Atom> newAtoms = new LinkedHashSet<Atom>();
414 				for (Atom oldAtom : oldAmbiguousElementAssignmentAtoms) {
415 					newAtoms.add(oldToNewAtomMap.get(oldAtom));
416 				}
417 				oldToNewAtomMap.get(atom).setProperty(Atom.AMBIGUOUS_ELEMENT_ASSIGNMENT, newAtoms);
418 			}
419 			Integer smilesHydrogenCount = atom.getProperty(Atom.SMILES_HYDROGEN_COUNT);
420 			if (smilesHydrogenCount!=null){
421 				oldToNewAtomMap.get(atom).setProperty(Atom.SMILES_HYDROGEN_COUNT, smilesHydrogenCount);
422 			}
423 			Integer oxidationNumber = atom.getProperty(Atom.OXIDATION_NUMBER);
424 			if (oxidationNumber!=null){
425 				oldToNewAtomMap.get(atom).setProperty(Atom.OXIDATION_NUMBER, oxidationNumber);
426 			}
427 			Boolean isAldehyde = atom.getProperty(Atom.ISALDEHYDE);
428 			if (isAldehyde!=null){
429 				oldToNewAtomMap.get(atom).setProperty(Atom.ISALDEHYDE, isAldehyde);
430 			}
431 			Boolean isAnomeric = atom.getProperty(Atom.ISANOMERIC);
432 			if (isAnomeric!=null){
433 				oldToNewAtomMap.get(atom).setProperty(Atom.ISANOMERIC, isAnomeric);
434 			}
435 			Integer atomClass = atom.getProperty(Atom.ATOM_CLASS);
436 			if (atomClass!=null){
437 				oldToNewAtomMap.get(atom).setProperty(Atom.ATOM_CLASS, atomClass);
438 			}
439 			String homologyGroup = atom.getProperty(Atom.HOMOLOGY_GROUP);
440 			if (homologyGroup != null) {
441 				oldToNewAtomMap.get(atom).setProperty(Atom.HOMOLOGY_GROUP, homologyGroup);
442 			}
443 			List<Atom> oldPositionVariationAtoms = atom.getProperty(Atom.POSITION_VARIATION_BOND);
444 			if (oldPositionVariationAtoms != null) {
445 				List<Atom> newAtoms = new ArrayList<Atom>();
446 				for (Atom oldAtom : oldPositionVariationAtoms) {
447 					newAtoms.add(oldToNewAtomMap.get(oldAtom));
448 				}
449 				oldToNewAtomMap.get(atom).setProperty(Atom.POSITION_VARIATION_BOND, newAtoms);
450 			}
451 		}
452 		for (int i = 0, l = originalFragment.getOutAtomCount(); i < l; i++) {
453 			OutAtom outAtom = originalFragment.getOutAtom(i);
454             newFragment.addOutAtom(oldToNewAtomMap.get(outAtom.getAtom()), outAtom.getValency(), outAtom.isSetExplicitly());
455             if (outAtom.getLocant() !=null){
456             	newFragment.getOutAtom(newFragment.getOutAtomCount() -1).setLocant(outAtom.getLocant() + StringTools.multiplyString("'", primesToAdd) );
457             }
458         }
459 		for (int i = 0, l = originalFragment.getFunctionalAtomCount(); i < l; i++) {
460 			FunctionalAtom functionalAtom = originalFragment.getFunctionalAtom(i);
461 			newFragment.addFunctionalAtom(oldToNewAtomMap.get(functionalAtom.getAtom()));
462 		}
463 		if (originalFragment.getDefaultInAtom() != null) {
464 			newFragment.setDefaultInAtom(oldToNewAtomMap.get(originalFragment.getDefaultInAtom()));
465 		}
466 		Set<Bond> bondSet =originalFragment.getBondSet();
467 		for (Bond bond : bondSet) {
468 			Bond newBond = createBond(oldToNewAtomMap.get(bond.getFromAtom()), oldToNewAtomMap.get(bond.getToAtom()), bond.getOrder());
469 			newBond.setSmilesStereochemistry(bond.getSmilesStereochemistry());
470 			if (bond.getBondStereo() != null){
471 				Atom[] oldAtomRefs4 = bond.getBondStereo().getAtomRefs4();
472 				Atom[] newAtomRefs4 = new Atom[4];
473 				for (int i = 0; i < oldAtomRefs4.length; i++) {
474 					newAtomRefs4[i] = oldToNewAtomMap.get(oldAtomRefs4[i]);
475 				}
476 				newBond.setBondStereoElement(newAtomRefs4, bond.getBondStereo().getBondStereoValue());
477 			}
478 		}
479 		List<Atom> indicatedHydrogenAtoms = originalFragment.getIndicatedHydrogen();
480 		for (Atom atom : indicatedHydrogenAtoms) {
481 			newFragment.addIndicatedHydrogen(oldToNewAtomMap.get(atom));
482 		}
483 		addFragment(newFragment);
484 		return newFragment;
485 	}
486 
487 	/**
488 	 * Takes an element and produces a copy of it. Groups and suffixes are copied so that the new element
489 	 * has its own group and suffix fragments
490 	 * @param elementToBeCloned
491 	 * @param state The current buildstate
492 	 * @return
493 	 * @throws StructureBuildingException
494 	 */
cloneElement(BuildState state, Element elementToBeCloned)495 	Element cloneElement(BuildState state, Element elementToBeCloned) throws StructureBuildingException {
496 		return cloneElement(state, elementToBeCloned, 0);
497 	}
498 
499 	/**
500 	 * Takes an element and produces a copy of it. Groups and suffixes are copied so that the new element
501 	 * has its own group and suffix fragments
502 	 * @param elementToBeCloned
503 	 * @param state The current buildstate
504 	 * @param primesToAdd: The minimum number of primes to add to the cloned atoms. More primes will be added if necessary to keep the locants unique e.g. N in the presence of N' becomes N'' when this is 1
505 	 * @return
506 	 * @throws StructureBuildingException
507 	 */
cloneElement(BuildState state, Element elementToBeCloned, int primesToAdd)508 	Element cloneElement(BuildState state, Element elementToBeCloned, int primesToAdd) throws StructureBuildingException {
509 		Element clone = elementToBeCloned.copy();
510 		List<Element> originalGroups = OpsinTools.getDescendantElementsWithTagName(elementToBeCloned, XmlDeclarations.GROUP_EL);
511 		List<Element> clonedGroups = OpsinTools.getDescendantElementsWithTagName(clone,  XmlDeclarations.GROUP_EL);
512 		HashMap<Fragment,Fragment> oldNewFragmentMapping  =new LinkedHashMap<Fragment, Fragment>();
513 		for (int i = 0; i < originalGroups.size(); i++) {
514 			Fragment originalFragment = originalGroups.get(i).getFrag();
515 			Fragment newFragment = copyAndRelabelFragment(originalFragment, primesToAdd);
516 			oldNewFragmentMapping.put(originalFragment, newFragment);
517 			newFragment.setTokenEl(clonedGroups.get(i));
518 			clonedGroups.get(i).setFrag(newFragment);
519 			List<Fragment> originalSuffixes =state.xmlSuffixMap.get(originalGroups.get(i));
520 			List<Fragment> newSuffixFragments =new ArrayList<Fragment>();
521 			for (Fragment suffix : originalSuffixes) {
522 				newSuffixFragments.add(copyFragment(suffix));
523 			}
524 			state.xmlSuffixMap.put(clonedGroups.get(i), newSuffixFragments);
525 		}
526 		Set<Bond> interFragmentBondsToClone = new LinkedHashSet<Bond>();
527 		for (Fragment originalFragment : oldNewFragmentMapping.keySet()) {//add inter fragment bonds to cloned fragments
528 			for (Bond bond : fragToInterFragmentBond.get(originalFragment)) {
529 				interFragmentBondsToClone.add(bond);
530 			}
531 		}
532 		for (Bond bond : interFragmentBondsToClone) {
533 			Atom originalFromAtom = bond.getFromAtom();
534 			Atom originalToAtom = bond.getToAtom();
535 			Fragment originalFragment1 = originalFromAtom.getFrag();
536 			Fragment originalFragment2 = originalToAtom.getFrag();
537 			if (!oldNewFragmentMapping.containsKey(originalFragment1) || (!oldNewFragmentMapping.containsKey(originalFragment2))){
538 				throw new StructureBuildingException("An element that was a clone contained a bond that went outside the scope of the cloning");
539 			}
540 			Fragment newFragment1 = oldNewFragmentMapping.get(originalFragment1);
541 			Fragment newFragment2 = oldNewFragmentMapping.get(originalFragment2);
542 			Atom fromAtom = newFragment1.getAtomList().get(originalFragment1.getAtomList().indexOf(originalFromAtom));
543 			Atom toAtom = newFragment2.getAtomList().get(originalFragment2.getAtomList().indexOf(originalToAtom));
544 			createBond(fromAtom, toAtom, bond.getOrder());
545 		}
546 		return clone;
547 	}
548 
549 	/**
550 	 * Takes an atom, removes it and bonds everything that was bonded to it to the replacementAtom with the original bond orders.
551 	 * Non element symbol locants are copied to the replacement atom
552 	 * @param atomToBeReplaced
553 	 * @param replacementAtom
554 	 */
replaceAtomWithAnotherAtomPreservingConnectivity(Atom atomToBeReplaced, Atom replacementAtom)555 	void replaceAtomWithAnotherAtomPreservingConnectivity(Atom atomToBeReplaced, Atom replacementAtom) {
556 		atomToBeReplaced.removeElementSymbolLocants();
557 		List<String> locants = new ArrayList<String>(atomToBeReplaced.getLocants());
558 		for (String locant : locants) {
559 			atomToBeReplaced.removeLocant(locant);
560 			replacementAtom.addLocant(locant);
561 		}
562 		List<Bond> bonds = atomToBeReplaced.getBonds();
563 		for (Bond bond : bonds) {
564 			Atom connectedAtom = bond.getOtherAtom(atomToBeReplaced);
565 			if (connectedAtom.getAtomParity() != null){
566 				Atom[] atomRefs4 = connectedAtom.getAtomParity().getAtomRefs4();
567 				for (int i = 0 ; i < 4; i++) {
568 					if (atomRefs4[i] == atomToBeReplaced){
569 						atomRefs4[i] = replacementAtom;
570 						break;
571 					}
572 				}
573 			}
574 			if (bond.getBondStereo() != null){
575 				Atom[] atomRefs4 = bond.getBondStereo().getAtomRefs4();
576 				for (int i = 0 ; i < 4; i++) {
577 					if (atomRefs4[i] == atomToBeReplaced){
578 						atomRefs4[i] = replacementAtom;
579 						break;
580 					}
581 				}
582 			}
583 			createBond(replacementAtom, bond.getOtherAtom(atomToBeReplaced), bond.getOrder());
584 		}
585 		removeAtomAndAssociatedBonds(atomToBeReplaced);
586 	}
587 
588 	/**
589 	 * Removes a bond from the inter-fragment bond mappings if it was present
590 	 * @param bond
591 	 */
removeInterFragmentBondIfPresent(Bond bond)592 	private void removeInterFragmentBondIfPresent(Bond bond) {
593 		fragToInterFragmentBond.get(bond.getFromAtom().getFrag()).remove(bond);
594 		fragToInterFragmentBond.get(bond.getToAtom().getFrag()).remove(bond);
595 	}
596 
597 	/**
598 	 * Adds a bond to the fragment to inter-fragment bond mappings
599 	 * @param bond
600 	 */
addInterFragmentBond(Bond bond)601 	private void addInterFragmentBond(Bond bond) {
602 		fragToInterFragmentBond.get(bond.getFromAtom().getFrag()).add(bond);
603 		fragToInterFragmentBond.get(bond.getToAtom().getFrag()).add(bond);
604 	}
605 
606 	/**
607 	 * Gets an unmodifiable view of the set of the inter-fragment bonds a fragment is involved in
608 	 * @param frag
609 	 * @return set of inter fragment bonds
610 	 */
getInterFragmentBonds(Fragment frag)611 	Set<Bond> getInterFragmentBonds(Fragment frag) {
612 		Set<Bond> interFragmentBonds = fragToInterFragmentBond.get(frag);
613 		if (interFragmentBonds == null) {
614 			throw new IllegalArgumentException("Fragment not registered with this FragmentManager!");
615 		}
616 		return Collections.unmodifiableSet(interFragmentBonds);
617 	}
618 
619 	/**
620 	 * Create a new Atom of the given element belonging to the given fragment
621 	 * @param chemEl
622 	 * @param frag
623 	 * @return Atom
624 	 */
createAtom(ChemEl chemEl, Fragment frag)625 	Atom createAtom(ChemEl chemEl, Fragment frag) {
626 		Atom a = new Atom(idManager.getNextID(), chemEl, frag);
627 		frag.addAtom(a);
628 		return a;
629 	}
630 
631 	/**
632 	 * Create a new bond between two atoms.
633 	 * The bond is associated with these atoms.
634 	 * It is also listed as an inter-fragment bond or associated with a fragment
635 	 * @param fromAtom
636 	 * @param toAtom
637 	 * @param bondOrder
638 	 * @return Bond
639 	 */
createBond(Atom fromAtom, Atom toAtom, int bondOrder)640 	Bond createBond(Atom fromAtom, Atom toAtom, int bondOrder) {
641 		Bond b = new Bond(fromAtom, toAtom, bondOrder);
642 		fromAtom.addBond(b);
643 		toAtom.addBond(b);
644 		if (fromAtom.getFrag() == toAtom.getFrag()){
645 			fromAtom.getFrag().addBond(b);
646 		}
647 		else{
648 			addInterFragmentBond(b);
649 		}
650 		return b;
651 	}
652 
removeAtomAndAssociatedBonds(Atom atom)653 	void removeAtomAndAssociatedBonds(Atom atom){
654 		List<Bond> bondsToBeRemoved = new ArrayList<Bond>(atom.getBonds());
655 		for (Bond bond : bondsToBeRemoved) {
656 			removeBond(bond);
657 		}
658 		atom.getFrag().removeAtom(atom);
659 		Set<Atom> ambiguousElementAssignment = atom.getProperty(Atom.AMBIGUOUS_ELEMENT_ASSIGNMENT);
660 		if (ambiguousElementAssignment != null){
661 			ambiguousElementAssignment.remove(atom);
662 			if (ambiguousElementAssignment.size() == 1){
663 				ambiguousElementAssignment.iterator().next().setProperty(Atom.AMBIGUOUS_ELEMENT_ASSIGNMENT, null);
664 			}
665 		}
666 	}
667 
removeBond(Bond bond)668 	void removeBond(Bond bond){
669 		bond.getFromAtom().getFrag().removeBond(bond);
670 		bond.getFromAtom().removeBond(bond);
671 		bond.getToAtom().removeBond(bond);
672 		removeInterFragmentBondIfPresent(bond);
673 	}
674 
675 	/**
676 	 * Valency is used to determine the expected number of hydrogen
677 	 * Hydrogens are then added to bring the number of connections up to the minimum required to satisfy the atom's valency
678 	 * This allows the valency of the atom to be encoded e.g. phopshane-3 hydrogen, phosphorane-5 hydrogen.
679 	 * It is also necessary when considering stereochemistry as a hydrogen beats nothing in the CIP rules
680 	 * @throws StructureBuildingException
681 	 */
makeHydrogensExplicit()682 	void makeHydrogensExplicit() throws StructureBuildingException {
683 		for (Fragment fragment : fragments) {
684 			List<Atom> atomList = fragment.getAtomList();
685 			for (Atom parentAtom : atomList) {
686 				int explicitHydrogensToAdd = StructureBuildingMethods.calculateSubstitutableHydrogenAtoms(parentAtom);
687 				for (int i = 0; i < explicitHydrogensToAdd; i++) {
688 					Atom hydrogen = createAtom(ChemEl.H, fragment);
689 					createBond(parentAtom, hydrogen, 1);
690 				}
691 				if (parentAtom.getAtomParity() != null){
692 					if (explicitHydrogensToAdd > 1) {
693 						//Cannot have tetrahedral chirality and more than 2 hydrogens
694 						parentAtom.setAtomParity(null);//probably caused by deoxy
695 					}
696 					else {
697 						modifyAtomParityToTakeIntoAccountExplicitHydrogen(parentAtom);
698 					}
699 				}
700 			}
701 		}
702 	}
703 
modifyAtomParityToTakeIntoAccountExplicitHydrogen(Atom atom)704 	private void modifyAtomParityToTakeIntoAccountExplicitHydrogen(Atom atom) throws StructureBuildingException {
705 		AtomParity atomParity = atom.getAtomParity();
706 		if (!StereoAnalyser.isPossiblyStereogenic(atom)){
707 			//no longer a stereoCentre e.g. due to unsaturation
708 			atom.setAtomParity(null);
709 		}
710 		else{
711 			Atom[] atomRefs4 = atomParity.getAtomRefs4();
712 			Integer positionOfImplicitHydrogen = null;
713 			Integer positionOfDeoxyHydrogen = null;
714 			for (int i = 0; i < atomRefs4.length; i++) {
715 				Atom a = atomRefs4[i];
716 				if (a.equals(AtomParity.hydrogen)){
717 					positionOfImplicitHydrogen = i;
718 				}
719 				else if (a.equals(AtomParity.deoxyHydrogen)){
720 					positionOfDeoxyHydrogen = i;
721 				}
722 			}
723 			if (positionOfImplicitHydrogen != null || positionOfDeoxyHydrogen != null) {
724 				//atom parity was set in SMILES, the dummy hydrogen atom has now been substituted
725 				List<Atom> neighbours = atom.getAtomNeighbours();
726 				for (Atom atomRef : atomRefs4) {
727 					neighbours.remove(atomRef);
728 				}
729 				if (neighbours.size() == 0) {
730 					throw new StructureBuildingException("OPSIN Bug: Unable to determine which atom has substituted a hydrogen at stereocentre");
731 				}
732 				else if (neighbours.size() == 1 && positionOfDeoxyHydrogen != null) {
733 					atomRefs4[positionOfDeoxyHydrogen] = neighbours.get(0);
734 					if (positionOfImplicitHydrogen != null){
735 						throw new StructureBuildingException("OPSIN Bug: Unable to determine which atom has substituted a hydrogen at stereocentre");
736 					}
737 				}
738 				else if (neighbours.size() == 1 && positionOfImplicitHydrogen != null) {
739 					atomRefs4[positionOfImplicitHydrogen] = neighbours.get(0);
740 				}
741 				else if (neighbours.size() == 2 && positionOfDeoxyHydrogen != null && positionOfImplicitHydrogen != null) {
742 					try{
743 						List<Atom> cipOrderedAtoms = new CipSequenceRules(atom).getNeighbouringAtomsInCipOrder();
744 						//higher priority group replaces the former hydroxy groups (deoxyHydrogen)
745 						if (cipOrderedAtoms.indexOf(neighbours.get(0)) > cipOrderedAtoms.indexOf(neighbours.get(1))) {
746 							atomRefs4[positionOfDeoxyHydrogen] = neighbours.get(0);
747 							atomRefs4[positionOfImplicitHydrogen] = neighbours.get(1);
748 						}
749 						else{
750 							atomRefs4[positionOfDeoxyHydrogen] = neighbours.get(1);
751 							atomRefs4[positionOfImplicitHydrogen] = neighbours.get(0);
752 						}
753 					}
754 					catch (CipOrderingException e){
755 						//assume ligands equivalent so it makes no difference which is which
756 						atomRefs4[positionOfDeoxyHydrogen] = neighbours.get(0);
757 						atomRefs4[positionOfImplicitHydrogen] = neighbours.get(1);
758 					}
759 				}
760 				else{
761 					throw new StructureBuildingException("OPSIN Bug: Unable to determine which atom has substituted a hydrogen at stereocentre");
762 				}
763 			}
764 		}
765 	}
766 }