1 package uk.ac.cam.ch.wwmm.opsin;
2 
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.Iterator;
8 import java.util.LinkedHashMap;
9 import java.util.LinkedHashSet;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.regex.Matcher;
14 
15 import static uk.ac.cam.ch.wwmm.opsin.OpsinTools.*;
16 import static uk.ac.cam.ch.wwmm.opsin.XmlDeclarations.*;
17 
18 /**A fragment of a molecule, holds bonds and atoms.
19  *
20  * @author ptc24
21  * @author dl387
22  *
23  */
24 class Fragment implements Iterable<Atom> {
25 
26 	/**A mapping between IDs and the atoms in this fragment, by default is ordered by the order atoms are added to the fragment*/
27 	private final Map<Integer, Atom> atomMapFromId = new LinkedHashMap<Integer, Atom>();
28 
29 	/**Equivalent to and synced to atomMapFromId.values() */
30 	private final Collection<Atom> atomCollection = atomMapFromId.values();
31 
32 	/**A mapping between locants and the atoms in this fragment*/
33 	private final Map<String, Atom> atomMapFromLocant = new HashMap<String, Atom>();
34 
35 	/**The bonds in the fragment*/
36 	private final Set<Bond> bondSet = new LinkedHashSet<Bond>();
37 
38 	/**The associated token element*/
39 	private Element tokenEl;
40 
41 	/**The atoms that are used when this fragment is connected to another fragment. Unused outAtoms means that the fragment is a radical or an error has occurred
42 	 * Initially empty */
43 	private final List<OutAtom> outAtoms = new ArrayList<OutAtom>();
44 
45 	/**The atoms that are used on this fragment to form things like esters
46 	 * Initially empty */
47 	private final List<FunctionalAtom> functionalAtoms = new ArrayList<FunctionalAtom>();
48 
49 	/**The atom that fragments connecting to this fragment should connect to in preference
50 	 * e.g. for amino acids the alpha amino group
51 	 * Null by default*/
52 	private Atom defaultInAtom = null;
53 
54 	/**The atoms in the fragment that have been indicated to have hydrogen at the SMILES level.*/
55 	private final List<Atom> indicatedHydrogen = new ArrayList<Atom>();
56 
57 	/**Pseudo atoms indicating start and end of polymer structure repeat unit*/
58 	private List<Atom> polymerAttachmentPoints =  null;
59 
60 	/**
61 	 * DO NOT CALL DIRECTLY EXCEPT FOR TESTING
62 	 * Makes an empty Fragment associated with the given tokenEl
63 	 * @param tokenEl
64 	 */
Fragment(Element tokenEl)65 	Fragment(Element tokenEl) {
66 		this.tokenEl = tokenEl;
67 	}
68 
69 	/**
70 	 * DO NOT CALL DIRECTLY EXCEPT FOR TESTING
71 	 * Makes an empty Fragment with the given type
72 	 *
73 	 * @param type
74 	 */
Fragment(String type)75 	Fragment(String type) {
76 		this.tokenEl = new TokenEl("");
77 		this.tokenEl.addAttribute(TYPE_ATR, type);
78 	}
79 
80 	/**Adds an atom to the fragment and associates it with this fragment*/
addAtom(Atom atom)81 	void addAtom(Atom atom) {
82 		List<String> locants =atom.getLocants();
83 		for (String locant: locants) {
84 			atomMapFromLocant.put(locant, atom);
85 		}
86 		atomMapFromId.put(atom.getID(), atom);
87 		atom.setFrag(this);
88 	}
89 
90 	/**
91 	 * Return the number of atoms in the fragment
92 	 * @return
93 	 */
getAtomCount()94 	int getAtomCount() {
95 		return atomCollection.size();
96 	}
97 
98 	/**
99 	 * Returns a copy of the fragment's atoms
100 	 * @return
101 	 */
getAtomList()102 	List<Atom> getAtomList() {
103 		return new ArrayList<Atom>(atomCollection);
104 	}
105 
106 	/**
107 	 * Adds a bond to the fragment.
108 	 * @param bond
109 	 */
addBond(Bond bond)110 	void addBond(Bond bond) {
111 		bondSet.add(bond);
112 	}
113 
114 	/**Removes a bond to the fragment if it is present.
115     * @param bond
116     * @return*/
removeBond(Bond bond)117 	boolean removeBond(Bond bond) {
118 		return bondSet.remove(bond);
119 	}
120 
121 	/**Gets bondSet.*/
getBondSet()122 	Set<Bond> getBondSet() {
123 		return Collections.unmodifiableSet(bondSet);
124 	}
125 
126 	/**Gets the id of the atom in the fragment with the specified locant.
127 	 *
128 	 * @param locant The locant to look for
129 	 * @return The id of the found atom, or 0 if it is not found
130 	 */
getIDFromLocant(String locant)131 	int getIDFromLocant(String locant)  {
132 		Atom a = getAtomByLocant(locant);
133 		if (a != null){
134 			return a.getID();
135 		}
136 		return 0;
137 	}
138 
139 	/**Gets the id of the atom in the fragment with the specified locant, throwing if this fails.
140 	 *
141 	 * @param locant The locant to look for
142 	 * @return The id of the found atom
143      * @throws StructureBuildingException
144 	 */
getIDFromLocantOrThrow(String locant)145 	int getIDFromLocantOrThrow(String locant) throws StructureBuildingException {
146 		int id = getIDFromLocant(locant);
147 		if(id == 0) {
148 			throw new StructureBuildingException("Couldn't find id from locant " + locant + ".");
149 		}
150 		return id;
151 	}
152 
153 	/**Gets the atom in the fragment with the specified locant.
154 	 *
155 	 * @param locant The locant to look for
156 	 * @return The found atom, or null if it is not found
157 	 */
getAtomByLocant(String locant)158 	Atom getAtomByLocant(String locant) {
159 		Atom a =atomMapFromLocant.get(locant);
160 		if (a != null){
161 			return a;
162 		}
163 		Matcher m =MATCH_AMINOACID_STYLE_LOCANT.matcher(locant);
164 		if (m.matches()){//e.g. N5
165 			Atom backboneAtom =atomMapFromLocant.get(m.group(3));//the atom corresponding to the numeric or greek component
166 			if (backboneAtom==null){
167 				return null;
168 			}
169 			a = FragmentTools.getAtomByAminoAcidStyleLocant(backboneAtom,  m.group(1), m.group(2));
170 			if (a != null){
171 				return a;
172 			}
173 		}
174 		return null;
175 	}
176 
177 	/**Gets the atom in the fragment with the specified locant, throwing if this fails.
178 	 *
179 	 * @param locant The locant to look for
180 	 * @return The found atom
181 	 * @throws StructureBuildingException
182 	 */
getAtomByLocantOrThrow(String locant)183 	Atom getAtomByLocantOrThrow(String locant) throws StructureBuildingException {
184 		Atom a = getAtomByLocant(locant);
185 		if(a == null) {
186 			throw new StructureBuildingException("Could not find the atom with locant " + locant + ".");
187 		}
188 		return a;
189 	}
190 
191 	/**Gets the atom in the fragment with the specified ID.
192 	 *
193 	 * @param id The id of the atom.
194 	 * @return The found atom, or null.
195 	 */
getAtomByID(int id)196 	Atom getAtomByID(int id) {
197 		return atomMapFromId.get(id);
198 	}
199 
200 	/**Gets the atom in the fragment with the specified ID, throwing if this fails.
201 	 *
202 	 * @param id The id of the atom.
203 	 * @return The found atom
204      * @throws StructureBuildingException
205 	 */
getAtomByIDOrThrow(int id)206 	Atom getAtomByIDOrThrow(int id) throws StructureBuildingException {
207 		Atom a = getAtomByID(id);
208 		if(a == null) {
209 			throw new StructureBuildingException("Couldn't find atom with id " + id + ".");
210 		}
211 		return a;
212 	}
213 
214 	/**Finds a bond between two specified atoms the first of which must be within the fragment
215 	 *
216 	 * @param ID1 The id of one atom
217 	 * @param ID2 The id of the other atom
218 	 * @return The bond found, or null
219 	 */
findBond(int ID1, int ID2)220 	Bond findBond(int ID1, int ID2) {
221 		Atom a = atomMapFromId.get(ID1);
222 		if (a != null){
223 			for (Bond b : a.getBonds()) {
224 				if((b.getFrom() == ID1 && b.getTo() == ID2) ||
225 						(b.getTo() == ID1 && b.getFrom() == ID2)) {
226 					return b;
227 				}
228 			}
229 		}
230 		return null;
231 	}
232 
233 	/**Finds a bond between two specified atoms the first of which must be within the fragment, throwing if it fails.
234 	 *
235 	 * @param ID1 The id of one atom
236 	 * @param ID2 The id of the other atom
237 	 * @return The bond found
238      * @throws StructureBuildingException
239 	 */
findBondOrThrow(int ID1, int ID2)240 	Bond findBondOrThrow(int ID1, int ID2) throws StructureBuildingException {
241 		Bond b = findBond(ID1, ID2);
242 		if(b == null) {
243 			throw new StructureBuildingException("Couldn't find specified bond");
244 		}
245 		return b;
246 	}
247 
248 	/**Works out how many atoms there are in the fragment there are
249 	 * with consecutive locants, starting from 1 that are in a chain
250 	 *
251 	 * @return The number of atoms in the locant chain
252 	 */
getChainLength()253 	int getChainLength() {
254 		int length = 0;
255 		Atom next = getAtomByLocant(Integer.toString(length + 1));
256 		Atom previous = null;
257 		while (next != null){
258 			if (previous != null && previous.getBondToAtom(next) == null){
259 				break;
260 			}
261 			length++;
262 			previous = next;
263 			next = getAtomByLocant(Integer.toString(length + 1));
264 		}
265 		return length;
266 	}
267 
268 	/**
269 	 * Gets the type of the corresponding tokenEl
270 	 * Returns "" if undefined
271 	 * @return
272 	 */
getType()273 	String getType() {
274 		String type = tokenEl.getAttributeValue(TYPE_ATR);
275 		return type != null ? type : "";
276 	}
277 
278 	/**
279 	 * Gets the subType of the corresponding tokenEl
280 	 * Returns "" if undefined
281 	 * @return
282 	 */
getSubType()283 	String getSubType() {
284 		String subType = tokenEl.getAttributeValue(SUBTYPE_ATR);
285 		return subType != null ? subType : "";
286 	}
287 
288 	/**
289 	 * Gets the associate tokenEl
290 	 * Whether or not this is a real token can be tested by whether it has a parent
291 	 * @return
292 	 */
getTokenEl()293 	Element getTokenEl() {
294 		return tokenEl;
295 	}
296 
297 	/**
298 	 * Sets the associated tokenEl
299 	 * Type/subType are inherited from the tokenEl
300 	 * @param tokenEl
301 	 */
setTokenEl(Element tokenEl)302 	void setTokenEl(Element tokenEl) {
303 		this.tokenEl = tokenEl;
304 	}
305 
306 	/**
307 	 * How many OutAtoms (i.e. radicals) are associated with this fragment
308 	 * @return
309 	 */
getOutAtomCount()310 	int getOutAtomCount() {
311 		return outAtoms.size();
312 	}
313 
314 	/**
315 	 * Gets the outAtom at a specific index of the outAtoms linkedList
316 	 * @param i
317 	 * @return
318 	 */
getOutAtom(int i)319 	OutAtom getOutAtom(int i) {
320 		return outAtoms.get(i);
321 	}
322 
323 	/**
324 	 * Adds an outAtom
325 	 * @param id
326 	 * @param valency
327 	 * @param setExplicitly
328 	 * @throws StructureBuildingException
329 	 */
addOutAtom(int id, int valency, Boolean setExplicitly)330 	void addOutAtom(int id, int valency, Boolean setExplicitly) throws StructureBuildingException {
331 		addOutAtom(getAtomByIDOrThrow(id), valency, setExplicitly);
332 	}
333 
334 	/**
335 	 * Adds an outAtom
336 	 * @param atom
337 	 * @param valency
338 	 * @param setExplicitly
339 	 */
addOutAtom(Atom atom, int valency, Boolean setExplicitly)340 	void addOutAtom(Atom atom, int valency, Boolean setExplicitly) {
341 		outAtoms.add(new OutAtom(atom, valency, setExplicitly));
342 	}
343 
344 	/**
345 	 * Includes the OutAtoms of a given fragment into this fragment
346 	 * Note that no OutAtoms are created in doing this
347 	 * @param frag
348 	 */
incorporateOutAtoms(Fragment frag)349 	void incorporateOutAtoms(Fragment frag) {
350 		outAtoms.addAll(frag.outAtoms);
351 	}
352 
353 	/**
354 	 * Removes the outAtom at a specific index of the outAtom linkedList
355 	 * @param i
356 	 */
removeOutAtom(int i)357 	void removeOutAtom(int i) {
358 		OutAtom removedOutAtom = outAtoms.remove(i);
359 		if (removedOutAtom.isSetExplicitly()){
360 			removedOutAtom.getAtom().addOutValency(-removedOutAtom.getValency());
361 		}
362 	}
363 
364 	/**
365 	 * Removes the specified outAtom from the outAtoms linkedList
366 	 * @param outAtom
367 	 */
removeOutAtom(OutAtom outAtom)368 	void removeOutAtom(OutAtom outAtom) {
369 		if (outAtoms.remove(outAtom) && outAtom.isSetExplicitly()){
370 			outAtom.getAtom().addOutValency(-outAtom.getValency());
371 		}
372 	}
373 
374 	/**
375 	 * How many functionalAtoms (i.e. locations that can form esters) are associated with this fragment
376 	 * @return
377 	 */
getFunctionalAtomCount()378 	int getFunctionalAtomCount() {
379 		return functionalAtoms.size();
380 	}
381 
382 	/**
383 	 * Gets the functionalAtom at a specific index of the functionalAtoms linkedList
384 	 * @param i
385 	 * @return
386 	 */
getFunctionalAtom(int i)387 	FunctionalAtom getFunctionalAtom(int i) {
388 		return functionalAtoms.get(i);
389 	}
390 
391 	/**Adds a functionalAtom
392     * @param atom*/
addFunctionalAtom(Atom atom)393 	void addFunctionalAtom(Atom atom) {
394 		functionalAtoms.add(new FunctionalAtom(atom));
395 	}
396 
397 	/**
398 	 * Includes the FunctionalAtoms of a given fragment into this fragment
399 	 * Note that no FunctionalAtoms are created in doing this
400 	 * @param frag
401 	 */
incorporateFunctionalAtoms(Fragment frag)402 	void incorporateFunctionalAtoms(Fragment frag) {
403 		functionalAtoms.addAll(frag.functionalAtoms);
404 	}
405 
406 	/**
407 	 * Removes the functionalAtom at a specific index of the functionalAtoms linkedList
408 	 * @param i
409 	 * @return
410 	 */
removeFunctionalAtom(int i)411 	FunctionalAtom removeFunctionalAtom(int i) {
412 		return functionalAtoms.remove(i);
413 	}
414 
415 	/**
416 	 * Removes the specified functionalAtom from the functionalAtoms linkedList
417 	 * @param functionalAtom
418 	 */
removeFunctionalAtom(FunctionalAtom functionalAtom)419 	void removeFunctionalAtom(FunctionalAtom functionalAtom) {
420 		functionalAtoms.remove(functionalAtom);
421 	}
422 
getPolymerAttachmentPoints()423 	List<Atom> getPolymerAttachmentPoints() {
424 		return polymerAttachmentPoints;
425 	}
426 
setPolymerAttachmentPoints(List<Atom> polymerAttachmentPoints)427 	void setPolymerAttachmentPoints(List<Atom> polymerAttachmentPoints) {
428 		this.polymerAttachmentPoints = polymerAttachmentPoints;
429 	}
430 
431 	/**Gets a list of atoms in the fragment that connect to a specified atom
432 	 *
433 	 * @param atom The reference atom
434 	 * @return The list of atoms connected to the atom
435 	 */
getIntraFragmentAtomNeighbours(Atom atom)436 	List<Atom> getIntraFragmentAtomNeighbours(Atom atom) {
437 		List<Atom> results = new ArrayList<Atom>(atom.getBondCount());
438 		for(Bond b : atom.getBonds()) {
439 			Atom otherAtom = b.getOtherAtom(atom);
440 			if (otherAtom == null) {
441 				throw new RuntimeException("OPSIN Bug: A bond associated with an atom does not involve it");
442 			}
443 			//If the other atom is in atomMapFromId then it is in this fragment
444 			if (atomMapFromId.get(otherAtom.getID()) != null) {
445 				results.add(otherAtom);
446 			}
447 		}
448 		return results;
449 	}
450 
451 	/**Calculates the number of bonds connecting to the atom, excluding bonds to implicit
452 	 * hydrogens. Double bonds count as
453 	 * two bonds, etc. Eg ethene - both C's have an incoming valency of 2.
454 	 *
455 	 * Only bonds to atoms within the fragment are counted. Suffix atoms are excluded
456 	 *
457 	 * @param atom
458      * @return Incoming Valency
459 	 * @throws StructureBuildingException
460 	 */
getIntraFragmentIncomingValency(Atom atom)461 	int getIntraFragmentIncomingValency(Atom atom) throws StructureBuildingException {
462 		int v = 0;
463 		for(Bond b :  atom.getBonds()) {
464 			//recalled atoms will be null if they are not part of this fragment
465 			if(b.getFromAtom() == atom) {
466 				Atom a =getAtomByID(b.getTo());
467 				if (a != null && !a.getType().equals(SUFFIX_TYPE_VAL)){
468 					v += b.getOrder();
469 				}
470 			} else if(b.getToAtom() == atom) {
471 				Atom a =getAtomByID(b.getFrom());
472 				if (a != null && !a.getType().equals(SUFFIX_TYPE_VAL)){
473 					v += b.getOrder();
474 				}
475 			}
476 			else{
477 				throw new StructureBuildingException("A bond associated with an atom does not involve it");
478 			}
479 		}
480 		return v;
481 	}
482 
483 	/**
484 	 * Checks valencies are sensible
485 	 * @throws StructureBuildingException
486 	 */
checkValencies()487 	void checkValencies() throws StructureBuildingException {
488 		for(Atom a : atomCollection) {
489 			if(!ValencyChecker.checkValency(a)) {
490 				throw new StructureBuildingException("Atom is in unphysical valency state! Element: " + a.getElement() + " valency: " + a.getIncomingValency());
491 			}
492 		}
493 	}
494 
495 	/**
496 	 * Removes an atom from this fragment
497 	 * @param atom
498 	 */
removeAtom(Atom atom)499 	void removeAtom(Atom atom) {
500 		int atomID =atom.getID();
501 		atomMapFromId.remove(atomID);
502 		for (String l : atom.getLocants()) {
503 			atomMapFromLocant.remove(l);
504 		}
505 		if (defaultInAtom == atom){
506 			defaultInAtom = null;
507 		}
508 	}
509 	/**
510 	 * Retrieves the overall charge of the fragment by querying all its atoms
511 	 * @return
512 	 */
getCharge()513 	int getCharge() {
514 		int charge=0;
515 		for (Atom a : atomCollection) {
516 			charge+=a.getCharge();
517 		}
518 		return charge;
519 	}
520 
getDefaultInAtom()521 	Atom getDefaultInAtom() {
522 		return defaultInAtom;
523 	}
524 
setDefaultInAtom(Atom inAtom)525 	void setDefaultInAtom(Atom inAtom) {
526 		this.defaultInAtom = inAtom;
527 	}
528 
getDefaultInAtomOrFirstAtom()529 	Atom getDefaultInAtomOrFirstAtom() {
530 		return defaultInAtom != null ? defaultInAtom : getFirstAtom();
531 	}
532 
533 	/**
534 	 * Adds a mapping between the locant and atom object
535 	 * @param locant A locant as a string
536 	 * @param a An atom
537 	 */
addMappingToAtomLocantMap(String locant, Atom a)538 	void addMappingToAtomLocantMap(String locant, Atom a){
539 		atomMapFromLocant.put(locant, a);
540 	}
541 
542 	/**
543 	 * Removes a mapping between a locant
544 	 * @param locant A locant as a string
545 	 */
removeMappingFromAtomLocantMap(String locant)546 	void removeMappingFromAtomLocantMap(String locant){
547 		atomMapFromLocant.remove(locant);
548 	}
549 
550 	/**
551 	 * Checks to see whether a locant is present on this fragment
552 	 * @param locant
553 	 * @return
554 	 */
hasLocant(String locant)555 	boolean hasLocant(String locant) {
556 		return getAtomByLocant(locant) != null;
557 	}
558 
559 
560 	/**
561 	 * Returns an unmodifiable list of the locants associated with this fragment
562 	 * @return
563 	 */
getLocants()564 	Set<String> getLocants() {
565 		return Collections.unmodifiableSet(atomMapFromLocant.keySet());
566 	}
567 
getIndicatedHydrogen()568 	List<Atom> getIndicatedHydrogen() {
569 		return indicatedHydrogen;
570 	}
571 
addIndicatedHydrogen(Atom atom)572 	void addIndicatedHydrogen(Atom atom) {
573 		indicatedHydrogen.add(atom);
574 	}
575 
576 	/**
577 	 * Returns the id of the first atom in the fragment
578 	 * @return
579 	 * @throws StructureBuildingException
580 	 */
getIdOfFirstAtom()581 	int getIdOfFirstAtom() {
582 		return getFirstAtom().getID();
583 	}
584 
585 	/**
586 	 * Returns the the first atom in the fragment or null if it has no atoms
587 	 * Typically the first atom will be the first atom that was added to the fragment
588 	 * @return firstAtom
589 	 */
getFirstAtom()590 	Atom getFirstAtom(){
591 		Iterator<Atom> atomIterator =atomCollection.iterator();
592 		if (atomIterator.hasNext()){
593 			return atomIterator.next();
594 		}
595 		return null;
596 	}
597 
598 	/**
599 	 * Clears and recreates atomMapFromId (and hence AtomCollection) using the order of the atoms in atomList
600 	 * @param atomList
601 	 * @throws StructureBuildingException
602 	 */
reorderAtomCollection(List<Atom> atomList)603 	void reorderAtomCollection(List<Atom> atomList) throws StructureBuildingException {
604 		if (atomMapFromId.size() != atomList.size()){
605 			throw new StructureBuildingException("atom list is not the same size as the number of atoms in the fragment");
606 		}
607 		atomMapFromId.clear();
608 		for (Atom atom : atomList) {
609 			atomMapFromId.put(atom.getID(), atom);
610 		}
611 	}
612 
613 	/**
614 	 * Reorders the fragment's internal atomList by the value of the first locant of the atoms
615 	 * e.g. 1,2,3,3a,3b,4
616 	 * Used for assuring the correct order of atom iteration when performing ring fusion
617 	 * @throws StructureBuildingException
618 	 */
sortAtomListByLocant()619 	void sortAtomListByLocant() throws StructureBuildingException {
620 		List<Atom> atomList =getAtomList();
621 		Collections.sort(atomList, new FragmentTools.SortByLocants());
622 		reorderAtomCollection(atomList);
623 	}
624 
625 	@Override
iterator()626 	public Iterator<Atom> iterator() {
627 		return atomCollection.iterator();
628 	}
629 }
630 
631 
632 
633