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 }