1 package uk.ac.cam.ch.wwmm.opsin; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.Collections; 6 import java.util.HashMap; 7 import java.util.HashSet; 8 import java.util.LinkedHashSet; 9 import java.util.List; 10 import java.util.Locale; 11 import java.util.Map; 12 import java.util.Set; 13 14 import static uk.ac.cam.ch.wwmm.opsin.OpsinTools.*; 15 import static uk.ac.cam.ch.wwmm.opsin.XmlDeclarations.*; 16 17 /** 18 * Assembles fused rings named using fusion nomenclature 19 * @author dl387 20 * 21 */ 22 class FusedRingBuilder { 23 private final BuildState state; 24 private final List<Element> groupsInFusedRing; 25 private final Element lastGroup; 26 private final Fragment parentRing; 27 private final Map<Integer,Fragment> fragmentInScopeForEachFusionLevel = new HashMap<Integer,Fragment>(); 28 private final Map<Atom, Atom> atomsToRemoveToReplacementAtom = new HashMap<Atom, Atom>(); 29 FusedRingBuilder(BuildState state, List<Element> groupsInFusedRing)30 private FusedRingBuilder(BuildState state, List<Element> groupsInFusedRing) { 31 this.state = state; 32 this.groupsInFusedRing = groupsInFusedRing; 33 lastGroup = groupsInFusedRing.get(groupsInFusedRing.size()-1); 34 parentRing = lastGroup.getFrag(); 35 fragmentInScopeForEachFusionLevel.put(0, parentRing); 36 } 37 38 /** 39 * Master method for processing fused rings. Fuses groups together 40 * @param state: contains the current id and fragment manager 41 * @param subOrRoot Element (substituent or root) 42 * @throws StructureBuildingException 43 */ processFusedRings(BuildState state, Element subOrRoot)44 static void processFusedRings(BuildState state, Element subOrRoot) throws StructureBuildingException { 45 List<Element> groups = subOrRoot.getChildElements(GROUP_EL); 46 if (groups.size() < 2){ 47 return;//nothing to fuse 48 } 49 List<Element> groupsInFusedRing =new ArrayList<Element>(); 50 for (int i = groups.size()-1; i >=0; i--) {//group groups into fused rings 51 Element group =groups.get(i); 52 groupsInFusedRing.add(0, group); 53 if (i!=0){ 54 Element startingEl = group; 55 if ((group.getValue().equals("benz") || group.getValue().equals("benzo")) && FUSIONRING_SUBTYPE_VAL.equals(group.getAttributeValue(SUBTYPE_ATR))){ 56 Element beforeBenzo = OpsinTools.getPreviousSibling(group); 57 if (beforeBenzo !=null && beforeBenzo.getName().equals(LOCANT_EL)){ 58 startingEl = beforeBenzo; 59 } 60 } 61 Element possibleGroup = OpsinTools.getPreviousSiblingIgnoringCertainElements(startingEl, new String[]{MULTIPLIER_EL, FUSION_EL}); 62 if (!groups.get(i-1).equals(possibleGroup)){//end of fused ring system 63 if (groupsInFusedRing.size()>=2){ 64 //This will be invoked in cases where there are multiple fused ring systems in the same subOrRoot such as some spiro systems 65 new FusedRingBuilder(state, groupsInFusedRing).buildFusedRing(); 66 } 67 groupsInFusedRing.clear(); 68 } 69 } 70 } 71 if (groupsInFusedRing.size()>=2){ 72 new FusedRingBuilder(state, groupsInFusedRing).buildFusedRing(); 73 } 74 } 75 76 /** 77 * Combines the groups given in the {@link FusedRingBuilder} constructor to destructively create the fused ring system 78 * This fused ring is then numbered 79 * @throws StructureBuildingException 80 */ buildFusedRing()81 void buildFusedRing() throws StructureBuildingException{ 82 /* 83 * Apply any nonstandard ring numbering, sorts atomOrder by locant 84 * Aromatises appropriate cycloalkane rings, Rejects groups with acyclic atoms 85 */ 86 processRingNumberingAndIrregularities(); 87 processBenzoFusions();//FR-2.2.8 e.g. in 2H-[1,3]benzodioxino[6',5',4':10,5,6]anthra[2,3-b]azepine benzodioxino is one component 88 List<Element> nameComponents = formNameComponentList(); 89 nameComponents.remove(lastGroup); 90 91 List<Fragment> componentFragments = new ArrayList<Fragment>();//all the ring fragments (other than the parentRing). These will later be merged into the parentRing 92 List<Fragment> parentFragments = new ArrayList<Fragment>(); 93 parentFragments.add(parentRing); 94 95 int numberOfParents = 1; 96 Element possibleMultiplier = OpsinTools.getPreviousSibling(lastGroup); 97 if (nameComponents.size()>0 && possibleMultiplier !=null && possibleMultiplier.getName().equals(MULTIPLIER_EL)){ 98 numberOfParents = Integer.parseInt(possibleMultiplier.getAttributeValue(VALUE_ATR)); 99 possibleMultiplier.detach(); 100 for (int j = 1; j < numberOfParents; j++) { 101 Fragment copyOfParentRing =state.fragManager.copyFragment(parentRing); 102 parentFragments.add(copyOfParentRing); 103 componentFragments.add(copyOfParentRing); 104 } 105 } 106 107 /*The indice from nameComponents to use next. Work from right to left i.e. starts at nameComponents.size()-1*/ 108 int ncIndice = processMultiParentSystem(parentFragments, nameComponents, componentFragments);//handle multiparent systems 109 /* 110 * The number of primes on the component to be connected. 111 * This is initially 0 indicating fusion of unprimed locants with the letter locants of the parentRing 112 * Subsequently it will switch to 1 indicating fusion of a second order component (primed locants) with a 113 * first order component (unprimed locants) 114 * Next would be double primed fusing to single primed locants etc. 115 * 116 */ 117 int fusionLevel = (nameComponents.size()-1 -ncIndice)/2; 118 for (; ncIndice>=0; ncIndice--) { 119 Element fusion = null; 120 if (nameComponents.get(ncIndice).getName().equals(FUSION_EL)){ 121 fusion = nameComponents.get(ncIndice--); 122 } 123 if (ncIndice <0 || !nameComponents.get(ncIndice).getName().equals(GROUP_EL)){ 124 throw new StructureBuildingException("Group not found where group expected. This is probably a bug"); 125 } 126 Fragment nextComponent = nameComponents.get(ncIndice).getFrag(); 127 int multiplier = 1; 128 Element possibleMultiplierEl = OpsinTools.getPreviousSibling(nameComponents.get(ncIndice));//e.g. the di of difuro 129 if (possibleMultiplierEl != null && possibleMultiplierEl.getName().equals(MULTIPLIER_EL)){ 130 multiplier = Integer.parseInt(possibleMultiplierEl.getAttributeValue(VALUE_ATR)); 131 } 132 String[] fusionDescriptors =null; 133 if (fusion !=null){ 134 String fusionDescriptorString = fusion.getValue().toLowerCase(Locale.ROOT).substring(1, fusion.getValue().length()-1); 135 if (multiplier ==1){ 136 fusionDescriptors = new String[]{fusionDescriptorString}; 137 } 138 else{ 139 if (fusionDescriptorString.split(";").length >1){ 140 fusionDescriptors = fusionDescriptorString.split(";"); 141 } 142 else if (fusionDescriptorString.split(":").length >1){ 143 fusionDescriptors = fusionDescriptorString.split(":"); 144 } 145 else if (fusionDescriptorString.split(",").length >1){ 146 fusionDescriptors = fusionDescriptorString.split(","); 147 } 148 else{//multiplier does not appear to mean multiplied component. Could be indicating multiplication of the whole fused ring system 149 if (ncIndice!=0){ 150 throw new StructureBuildingException("Unexpected multiplier: " + possibleMultiplierEl.getValue() +" or incorrect fusion descriptor: " + fusionDescriptorString); 151 } 152 multiplier =1; 153 fusionDescriptors = new String[]{fusionDescriptorString}; 154 } 155 } 156 } 157 if (multiplier >1){ 158 possibleMultiplierEl.detach(); 159 } 160 Fragment[] fusionComponents = new Fragment[multiplier]; 161 for (int j = 0; j < multiplier; j++) { 162 if (j>0){ 163 fusionComponents[j] = state.fragManager.copyAndRelabelFragment(nextComponent, j); 164 } 165 else{ 166 fusionComponents[j] = nextComponent; 167 } 168 } 169 170 for (int j = 0; j < multiplier; j++) { 171 Fragment component = fusionComponents[j]; 172 componentFragments.add(component); 173 if (fusion !=null){ 174 if (fusionDescriptors[j].split(":").length==1){//A fusion bracket without a colon is used when applying to the parent component (except in a special case where locants are ommitted) 175 //check for case of omitted locant from a higher order fusion bracket e.g. cyclopenta[4,5]pyrrolo[2,3-c]pyridine 176 if (fusionDescriptors[j].split("-").length==1 && 177 fusionDescriptors[j].split(",").length >1 && 178 FragmentTools.allAtomsInRingAreIdentical(component) 179 && ((StringTools.countTerminalPrimes(fusionDescriptors[j].split(",")[0])) != fusionLevel) ){//Could be like cyclopenta[3,4]cyclobuta[1,2]benzene where the first fusion to occur has parent locants omitted not child locants 180 int numberOfPrimes = StringTools.countTerminalPrimes(fusionDescriptors[j].split(",")[0]); 181 //note that this is the number of primes on the parent ring. So would expect the child ring and hence the fusionLevel to be 1 higher 182 if (numberOfPrimes + 1 != fusionLevel){ 183 if (numberOfPrimes + 2 == fusionLevel){//ring could be in previous fusion level e.g. the benzo in benzo[10,11]phenanthro[2',3',4',5',6':4,5,6,7]chryseno[1,2,3-bc]coronene 184 fusionLevel--; 185 } 186 else{ 187 throw new StructureBuildingException("Incorrect number of primes in fusion bracket: " +fusionDescriptors[j]); 188 } 189 } 190 relabelAccordingToFusionLevel(component, fusionLevel); 191 List<String> numericalLocantsOfParent = Arrays.asList(fusionDescriptors[j].split(",")); 192 List<String> numericalLocantsOfChild = findPossibleNumericalLocants(component, determineAtomsToFuse(fragmentInScopeForEachFusionLevel.get(fusionLevel), numericalLocantsOfParent, null).size()-1); 193 processHigherOrderFusionDescriptors(component, fragmentInScopeForEachFusionLevel.get(fusionLevel), numericalLocantsOfChild, numericalLocantsOfParent); 194 } 195 else{ 196 fusionLevel = 0; 197 relabelAccordingToFusionLevel(component, fusionLevel); 198 String fusionDescriptor = fusionDescriptors[j]; 199 String[] fusionArray = determineNumericalAndLetterComponents(fusionDescriptor); 200 int numberOfPrimes =0; 201 if (!fusionArray[1].equals("")){ 202 numberOfPrimes =StringTools.countTerminalPrimes(fusionArray[1]); 203 if (fusionArray[0].equals("")){ 204 fusionDescriptor = fusionArray[1].replaceAll("'", ""); 205 } 206 else{ 207 fusionDescriptor = fusionArray[0]+ "-" +fusionArray[1].replaceAll("'", ""); 208 } 209 if (numberOfPrimes >= parentFragments.size()){ 210 throw new StructureBuildingException("Unexpected prime in fusion descriptor"); 211 } 212 } 213 performSimpleFusion(fusionDescriptor, component, parentFragments.get(numberOfPrimes));//e.g. pyrano[3,2-b]imidazo[4,5-e]pyridine where both are level 0 fusions 214 } 215 } 216 else{ 217 //determine number of primes in fusor and hence determine fusion level 218 int numberOfPrimes = -j + StringTools.countTerminalPrimes(fusionDescriptors[j].split(",")[0]); 219 if (numberOfPrimes != fusionLevel){ 220 if (fusionLevel == numberOfPrimes +1){ 221 fusionLevel--; 222 } 223 else{ 224 throw new StructureBuildingException("Incorrect number of primes in fusion bracket: " +fusionDescriptors[j]); 225 } 226 } 227 relabelAccordingToFusionLevel(component, fusionLevel); 228 performHigherOrderFusion(fusionDescriptors[j], component, fragmentInScopeForEachFusionLevel.get(fusionLevel)); 229 } 230 } 231 else{ 232 relabelAccordingToFusionLevel(component, fusionLevel); 233 performSimpleFusion(null, component, fragmentInScopeForEachFusionLevel.get(fusionLevel)); 234 } 235 } 236 fusionLevel++; 237 if (multiplier ==1){//multiplied components may not be substituted onto 238 fragmentInScopeForEachFusionLevel.put(fusionLevel, fusionComponents[0]); 239 } 240 } 241 for (Fragment ring : componentFragments) { 242 state.fragManager.incorporateFragment(ring, parentRing); 243 } 244 removeMergedAtoms(); 245 246 FusedRingNumberer.numberFusedRing(parentRing);//numbers the fused ring; 247 248 StringBuilder fusedRingName = new StringBuilder(); 249 for (Element element : nameComponents) { 250 fusedRingName.append(element.getValue()); 251 } 252 fusedRingName.append(lastGroup.getValue()); 253 254 Element fusedRingEl =lastGroup;//reuse this element to save having to remap suffixes... 255 fusedRingEl.getAttribute(VALUE_ATR).setValue(fusedRingName.toString()); 256 fusedRingEl.getAttribute(TYPE_ATR).setValue(RING_TYPE_VAL); 257 fusedRingEl.setValue(fusedRingName.toString()); 258 259 for (Element element : nameComponents) { 260 element.detach(); 261 } 262 } 263 removeMergedAtoms()264 private void removeMergedAtoms() { 265 for (Atom a : atomsToRemoveToReplacementAtom.keySet()) { 266 state.fragManager.removeAtomAndAssociatedBonds(a); 267 } 268 atomsToRemoveToReplacementAtom.clear(); 269 } 270 271 /** 272 * Forms a list a list of all group and fusion elements between the first and last group in the fused ring 273 * @return 274 */ formNameComponentList()275 private List<Element> formNameComponentList() { 276 List<Element> nameComponents = new ArrayList<Element>(); 277 Element currentEl = groupsInFusedRing.get(0); 278 while(currentEl != lastGroup){ 279 if (currentEl.getName().equals(GROUP_EL) || currentEl.getName().equals(FUSION_EL)){ 280 nameComponents.add(currentEl); 281 } 282 currentEl = OpsinTools.getNextSibling(currentEl); 283 } 284 return nameComponents; 285 } 286 processRingNumberingAndIrregularities()287 private void processRingNumberingAndIrregularities() throws StructureBuildingException { 288 for (Element group : groupsInFusedRing) { 289 Fragment ring = group.getFrag(); 290 if (ALKANESTEM_SUBTYPE_VAL.equals(group.getAttributeValue(SUBTYPE_ATR))){ 291 aromatiseCyclicAlkane(group); 292 } 293 processPartiallyUnsaturatedHWSystems(group, ring); 294 if (group == lastGroup) { 295 //perform a quick check that every atom in this group is infact cyclic. Fusion components are enumerated and hence all guaranteed to be purely cyclic 296 List<Atom> atomList = ring.getAtomList(); 297 for (Atom atom : atomList) { 298 if (!atom.getAtomIsInACycle()) { 299 throw new StructureBuildingException("Inappropriate group used in fusion nomenclature. Only groups composed entirely of atoms in cycles may be used. i.e. not: " + group.getValue()); 300 } 301 } 302 if (group.getAttribute(FUSEDRINGNUMBERING_ATR) != null) { 303 String[] standardNumbering = group.getAttributeValue(FUSEDRINGNUMBERING_ATR).split("/", -1); 304 for (int j = 0; j < standardNumbering.length; j++) { 305 atomList.get(j).replaceLocants(standardNumbering[j]); 306 } 307 } else { 308 ring.sortAtomListByLocant();//for those where the order the locants are in is sensible } 309 } 310 for (Atom atom : atomList) { 311 atom.clearLocants();//the parentRing does not have locants, letters are used to indicate the edges 312 } 313 } else if (group.getAttribute(FUSEDRINGNUMBERING_ATR) == null) { 314 ring.sortAtomListByLocant();//for those where the order the locants are in is sensible 315 } 316 } 317 } 318 319 /** 320 * Interprets the unlocanted unsaturator after a partially unsaturated HW Rings as indication of spare valency and detaches it 321 * This is necessary as this unsaturator can only refer to the HW ring and for names like 2-Benzoxazolinone to avoid confusion as to what the 2 refers to. 322 * @param group 323 * @param ring 324 */ processPartiallyUnsaturatedHWSystems(Element group, Fragment ring)325 private void processPartiallyUnsaturatedHWSystems(Element group, Fragment ring) { 326 if (HANTZSCHWIDMAN_SUBTYPE_VAL.equals(group.getAttributeValue(SUBTYPE_ATR)) && group.getAttribute(ADDBOND_ATR)!=null){ 327 List<Element> unsaturators = OpsinTools.getNextAdjacentSiblingsOfType(group, UNSATURATOR_EL); 328 if (unsaturators.size()>0){ 329 Element unsaturator = unsaturators.get(0); 330 if (unsaturator.getAttribute(LOCANT_ATR)==null && unsaturator.getAttributeValue(VALUE_ATR).equals("2")){ 331 unsaturator.detach(); 332 List<Bond> bondsToUnsaturate = StructureBuildingMethods.findBondsToUnSaturate(ring, 2, true); 333 if (bondsToUnsaturate.size() == 0) { 334 throw new RuntimeException("Failed to find bond to unsaturate on partially saturated HW ring"); 335 } 336 Bond b = bondsToUnsaturate.get(0); 337 b.getFromAtom().setSpareValency(true); 338 b.getToAtom().setSpareValency(true); 339 } 340 } 341 } 342 } 343 344 /** 345 * Given a cyclicAlkaneGroup determines whether or not it should be aromatised. Unlocanted ene will be detached if it is an aromatisation hint 346 * No unsaturators -->aromatise 347 * Just ane -->don't 348 * More than 1 ene or locants on ene -->don't 349 * yne --> don't 350 * @param cyclicAlkaneGroup 351 */ aromatiseCyclicAlkane(Element cyclicAlkaneGroup)352 private void aromatiseCyclicAlkane(Element cyclicAlkaneGroup) { 353 Element next = OpsinTools.getNextSibling(cyclicAlkaneGroup); 354 List<Element> unsaturators = new ArrayList<Element>(); 355 while (next!=null && next.getName().equals(UNSATURATOR_EL)){ 356 unsaturators.add(next); 357 next = OpsinTools.getNextSibling(next); 358 } 359 boolean conjugate =true; 360 if (unsaturators.size()==1){ 361 int value = Integer.parseInt(unsaturators.get(0).getAttributeValue(VALUE_ATR)); 362 if (value !=2){ 363 conjugate =false; 364 } 365 else if (unsaturators.get(0).getAttribute(LOCANT_ATR)!=null){ 366 conjugate =false; 367 } 368 } 369 else if (unsaturators.size()==2){ 370 int value1 = Integer.parseInt(unsaturators.get(0).getAttributeValue(VALUE_ATR)); 371 if (value1 !=1){ 372 conjugate =false; 373 } 374 else{ 375 int value2 = Integer.parseInt(unsaturators.get(1).getAttributeValue(VALUE_ATR)); 376 if (value2 !=2 || unsaturators.get(1).getAttribute(LOCANT_ATR)!=null){ 377 conjugate =false; 378 } 379 } 380 } 381 else if (unsaturators.size() >2){ 382 conjugate =false; 383 } 384 if (conjugate){ 385 for (Element unsaturator : unsaturators) { 386 unsaturator.detach(); 387 } 388 List<Atom> atomList = cyclicAlkaneGroup.getFrag().getAtomList(); 389 for (Atom atom : atomList) { 390 atom.setSpareValency(true); 391 } 392 } 393 } 394 processMultiParentSystem(List<Fragment> parentFragments, List<Element> nameComponents, List<Fragment> componentFragments)395 private int processMultiParentSystem(List<Fragment> parentFragments, List<Element> nameComponents, List<Fragment> componentFragments) throws StructureBuildingException { 396 int i = nameComponents.size()-1; 397 int fusionLevel =0; 398 if (i>=0 && parentFragments.size()>1){ 399 List<Fragment> previousFusionLevelFragments = parentFragments; 400 for (; i>=0; i--) { 401 if (previousFusionLevelFragments.size()==1){//completed multi parent system 402 fragmentInScopeForEachFusionLevel.put(fusionLevel, previousFusionLevelFragments.get(0)); 403 break; 404 } 405 Element fusion = null; 406 if (nameComponents.get(i).getName().equals(FUSION_EL)){ 407 fusion = nameComponents.get(i--); 408 } 409 else{ 410 throw new StructureBuildingException("Fusion bracket not found where fusion bracket expected"); 411 } 412 if (i <0 || !nameComponents.get(i).getName().equals(GROUP_EL)){ 413 throw new StructureBuildingException("Group not found where group expected. This is probably a bug"); 414 } 415 Fragment nextComponent = nameComponents.get(i).getFrag(); 416 relabelAccordingToFusionLevel(nextComponent, fusionLevel); 417 int multiplier = 1; 418 Element possibleMultiplierEl = OpsinTools.getPreviousSibling(nameComponents.get(i)); 419 if (possibleMultiplierEl != null && possibleMultiplierEl.getName().equals(MULTIPLIER_EL)){ 420 multiplier = Integer.parseInt(possibleMultiplierEl.getAttributeValue(VALUE_ATR)); 421 possibleMultiplierEl.detach(); 422 } 423 List<Fragment> fusionComponents = new ArrayList<Fragment>(); 424 for (int j = 0; j < multiplier; j++) { 425 if (j>0){ 426 Fragment clonedFrag = state.fragManager.copyFragment(nextComponent); 427 relabelAccordingToFusionLevel(clonedFrag, j);//fusionLevels worth of primes already added 428 fusionComponents.add(clonedFrag); 429 } 430 else{ 431 fusionComponents.add(nextComponent); 432 } 433 } 434 fusionLevel+=multiplier; 435 if (multiplier>1 && multiplier != previousFusionLevelFragments.size()){ 436 throw new StructureBuildingException("Mismatch between number of components and number of parents in fused ring system"); 437 } 438 String fusionDescriptorString = fusion.getValue().toLowerCase(Locale.ROOT).substring(1, fusion.getValue().length()-1); 439 String[] fusionDescriptors =null; 440 if (fusionDescriptorString.split(";").length >1){ 441 fusionDescriptors = fusionDescriptorString.split(";"); 442 } 443 else if (fusionDescriptorString.split(":").length >1){ 444 fusionDescriptors = fusionDescriptorString.split(":"); 445 } 446 else if (fusionDescriptorString.split(",").length >1){ 447 fusionDescriptors = fusionDescriptorString.split(","); 448 } 449 else{ 450 throw new StructureBuildingException("Invalid fusion descriptor: " + fusionDescriptorString); 451 } 452 if (fusionDescriptors.length != previousFusionLevelFragments.size()){ 453 throw new StructureBuildingException("Invalid fusion descriptor: "+fusionDescriptorString +"(Number of locants disagrees with number of parents)"); 454 } 455 for (int j = 0; j < fusionDescriptors.length; j++) { 456 String fusionDescriptor = fusionDescriptors[j]; 457 Fragment component = multiplier>1 ? fusionComponents.get(j) : nextComponent; 458 Fragment parentToUse = previousFusionLevelFragments.get(j); 459 boolean simpleFusion = fusionDescriptor.split(":").length <= 1; 460 if (simpleFusion){ 461 String[] fusionArray = determineNumericalAndLetterComponents(fusionDescriptor); 462 if (fusionArray[1].length() != 0){ 463 int numberOfPrimes =StringTools.countTerminalPrimes(fusionArray[1]); 464 if (fusionArray[0].length() == 0){ 465 fusionDescriptor = fusionArray[1].replaceAll("'", ""); 466 } 467 else{ 468 fusionDescriptor = fusionArray[0]+ "-" +fusionArray[1].replaceAll("'", ""); 469 } 470 if (numberOfPrimes !=j){//check the number of primes on the letter part agree with the parent to use e.g.[4,5-bcd:1,2-c']difuran 471 throw new StructureBuildingException("Incorrect number of primes in fusion descriptor: " + fusionDescriptor); 472 } 473 } 474 performSimpleFusion(fusionDescriptor, component, parentToUse); 475 } 476 else{ 477 performHigherOrderFusion(fusionDescriptor, component, parentToUse); 478 } 479 } 480 previousFusionLevelFragments = fusionComponents; 481 componentFragments.addAll(fusionComponents); 482 } 483 if (previousFusionLevelFragments.size()!=1){ 484 throw new StructureBuildingException("Invalid fused ring system. Incomplete multiparent system"); 485 } 486 } 487 return i; 488 } 489 490 /** 491 * Splits a first order fusion component into it's numerical and letter parts 492 * Either one of these can be the blank string as they may have been omitted 493 * The first entry in the array is the numbers and the second the letters 494 * @param fusionDescriptor 495 * @return 496 */ determineNumericalAndLetterComponents(String fusionDescriptor)497 private String[] determineNumericalAndLetterComponents(String fusionDescriptor) { 498 String[] fusionArray = fusionDescriptor.split("-"); 499 if (fusionArray.length ==2){ 500 return fusionArray; 501 } 502 else{ 503 String[] components = new String[2]; 504 if (fusionArray[0].contains(",")){//the digit section 505 components[0]=fusionArray[0]; 506 components[1]=""; 507 } 508 else{ 509 components[0]=""; 510 components[1]=fusionArray[0]; 511 } 512 return components; 513 } 514 } 515 516 /** 517 * Searches groups for benz(o) components and fuses them in accordance with 518 * FR-2.2.8 Heterobicyclic components with a benzene ring 519 * @throws StructureBuildingException 520 */ processBenzoFusions()521 private void processBenzoFusions() throws StructureBuildingException { 522 for(int i = groupsInFusedRing.size() - 2; i >= 0; i--) { 523 Element group = groupsInFusedRing.get(i); 524 if (group.getValue().equals("benz") || group.getValue().equals("benzo")) { 525 Element possibleFusionbracket = OpsinTools.getNextSibling(group); 526 if (!possibleFusionbracket.getName().equals(FUSION_EL)) { 527 Element possibleMultiplier = OpsinTools.getPreviousSibling(group); 528 if (possibleMultiplier == null || !possibleMultiplier.getName().equals(MULTIPLIER_EL) || possibleMultiplier.getAttributeValue(TYPE_ATR).equals(GROUP_TYPE_VAL)) { 529 //e.g. 2-benzofuran. Fused rings of this type are a special case treated as being a single component 530 //and have a special convention for indicating the position of heteroatoms 531 benzoSpecificFusion(group, groupsInFusedRing.get(i + 1)); 532 group.detach(); 533 groupsInFusedRing.remove(i); 534 } 535 } 536 } 537 } 538 } 539 540 /** 541 * Modifies nextComponent's locants according to the fusionLevel. 542 * @param component 543 * @param fusionLevel 544 */ relabelAccordingToFusionLevel(Fragment component, int fusionLevel)545 private void relabelAccordingToFusionLevel(Fragment component, int fusionLevel) { 546 if (fusionLevel > 0){ 547 FragmentTools.relabelNumericLocants(component.getAtomList(), StringTools.multiplyString("'", fusionLevel)); 548 } 549 } 550 551 /** 552 * Handles fusion between components where the fusion descriptor is of the form: 553 * comma separated locants dash letters 554 * e.g imidazo[4,5-d]pyridine 555 * The fusionDescriptor may be given as null or the letter/numerical part omitted. 556 * Sensible defaults will be found instead 557 * @param fusionDescriptor 558 * @param childRing 559 * @param parentRing 560 * @throws StructureBuildingException 561 */ performSimpleFusion(String fusionDescriptor, Fragment childRing, Fragment parentRing)562 private void performSimpleFusion(String fusionDescriptor, Fragment childRing, Fragment parentRing) throws StructureBuildingException { 563 List<String> numericalLocantsOfChild = null; 564 List<String> letterLocantsOfParent = null; 565 if (fusionDescriptor != null){ 566 String[] fusionArray = fusionDescriptor.split("-"); 567 if (fusionArray.length ==2){ 568 numericalLocantsOfChild = Arrays.asList(fusionArray[0].split(",")); 569 char[] tempLetterLocantsOfParent = fusionArray[1].toCharArray(); 570 letterLocantsOfParent = new ArrayList<String>(); 571 for (char letterLocantOfParent : tempLetterLocantsOfParent) { 572 letterLocantsOfParent.add(String.valueOf(letterLocantOfParent)); 573 } 574 } 575 else{ 576 if (fusionArray[0].contains(",")){//only has digits 577 String[] numericalLocantsOfChildTemp = fusionArray[0].split(","); 578 numericalLocantsOfChild = Arrays.asList(numericalLocantsOfChildTemp); 579 } 580 else{//only has letters 581 char[] tempLetterLocantsOfParentCharArray = fusionArray[0].toCharArray(); 582 letterLocantsOfParent = new ArrayList<String>(); 583 for (char letterLocantOfParentCharArray : tempLetterLocantsOfParentCharArray) { 584 letterLocantsOfParent.add(String.valueOf(letterLocantOfParentCharArray)); 585 } 586 } 587 } 588 } 589 590 int edgeLength =1; 591 if (numericalLocantsOfChild != null){ 592 if (numericalLocantsOfChild.size() <=1){ 593 throw new StructureBuildingException("At least two numerical locants must be provided to perform fusion!"); 594 } 595 edgeLength = numericalLocantsOfChild.size()-1; 596 } 597 else if (letterLocantsOfParent != null){ 598 edgeLength = letterLocantsOfParent.size(); 599 } 600 601 if (numericalLocantsOfChild == null){ 602 numericalLocantsOfChild = findPossibleNumericalLocants(childRing, edgeLength); 603 } 604 605 if (letterLocantsOfParent == null){ 606 letterLocantsOfParent = findPossibleLetterLocants(parentRing, edgeLength); 607 } 608 if (numericalLocantsOfChild == null || letterLocantsOfParent ==null){ 609 throw new StructureBuildingException("Unable to find bond to form fused ring system. Some information for forming fused ring system was only supplyed implicitly"); 610 } 611 612 processFirstOrderFusionDescriptors(childRing, parentRing, numericalLocantsOfChild, letterLocantsOfParent);//fuse the rings 613 } 614 615 /** 616 * Takes a ring an returns and array with one letter corresponding to a side/s 617 * that contains two adjacent non bridgehead carbons 618 * The number of sides is specified by edgeLength 619 * @param ring 620 * @param edgeLength The number of bonds to be fused along 621 * @return 622 */ findPossibleLetterLocants(Fragment ring, int edgeLength)623 private List<String> findPossibleLetterLocants(Fragment ring, int edgeLength) { 624 List<Integer> carbonAtomIndexes = new ArrayList<Integer>(); 625 int numberOfAtoms = ring.getAtomCount(); 626 CyclicAtomList cyclicAtomList = new CyclicAtomList(ring.getAtomList()); 627 for (int i = 0; i <= numberOfAtoms; i++) { 628 //iterate backwards in list to use highest locanted edge in preference. 629 //this retains what is currently locant 1 on the parent ring as locant 1 if the first two atoms found match 630 //the last atom in the list is potentially tested twice e.g. on a 6 membered ring, 6-5 and 1-6 are both possible 631 Atom atom = cyclicAtomList.previous(); 632 //want non-bridgehead carbon atoms. Double-check that these carbon atoms are actually bonded (e.g. von baeyer systems have non-consecutive atom numbering!) 633 if (atom.getElement() == ChemEl.C && atom.getBondCount() == 2 634 && (carbonAtomIndexes.size() == 0 || atom.getAtomNeighbours().contains(cyclicAtomList.peekNext()))){ 635 carbonAtomIndexes.add(cyclicAtomList.getIndex()); 636 if (carbonAtomIndexes.size() == edgeLength + 1){//as many carbons in a row as to give that edgelength ->use these side/s 637 Collections.reverse(carbonAtomIndexes); 638 List<String> letterLocantsOfParent = new ArrayList<String>(); 639 for (int j = 0; j < edgeLength; j++) { 640 letterLocantsOfParent.add(String.valueOf((char)(97 + carbonAtomIndexes.get(j))));//97 is ascii for a 641 } 642 return letterLocantsOfParent; 643 } 644 } 645 else{ 646 carbonAtomIndexes.clear(); 647 } 648 } 649 return null; 650 } 651 652 /** 653 * Takes a ring and returns an array of numbers corresponding to a side/s 654 * that contains two adjacent non bridgehead carbons 655 * The number of sides is specified by edgeLength 656 * @param ring 657 * @param edgeLength The number of bonds to be fused along 658 * @return 659 */ findPossibleNumericalLocants(Fragment ring, int edgeLength)660 private List<String> findPossibleNumericalLocants(Fragment ring, int edgeLength) { 661 List<String> carbonLocants = new ArrayList<String>(); 662 int numberOfAtoms = ring.getAtomCount(); 663 CyclicAtomList cyclicAtomList = new CyclicAtomList(ring.getAtomList()); 664 for (int i = 0; i <= numberOfAtoms; i++) { 665 //the last atom in the list is potentially tested twice e.g. on a 6 membered ring, 1-2 and 6-1 are both possible 666 Atom atom = cyclicAtomList.next(); 667 //want non-bridgehead carbon atoms. Double-check that these carbon atoms are actually bonded (e.g. von baeyer systems have non-consecutive atom numbering!) 668 if (atom.getElement() == ChemEl.C && atom.getBondCount() == 2 669 && (carbonLocants.size() == 0 || atom.getAtomNeighbours().contains(cyclicAtomList.peekPrevious()))){ 670 carbonLocants.add(atom.getFirstLocant()); 671 if (carbonLocants.size() == edgeLength + 1){//as many carbons in a row as to give that edgelength ->use these side/s 672 List<String> numericalLocantsOfChild = new ArrayList<String>(); 673 for (String locant : carbonLocants) { 674 numericalLocantsOfChild.add(locant); 675 } 676 return numericalLocantsOfChild; 677 } 678 } 679 else{ 680 carbonLocants.clear(); 681 } 682 } 683 return null; 684 } 685 686 /** 687 * Performs a single ring fusion using the values in numericalLocantsOfChild/letterLocantsOfParent 688 * @param childRing 689 * @param parentRing 690 * @param numericalLocantsOfChild 691 * @param letterLocantsOfParent 692 * @throws StructureBuildingException 693 */ processFirstOrderFusionDescriptors(Fragment childRing, Fragment parentRing, List<String> numericalLocantsOfChild, List<String> letterLocantsOfParent)694 private void processFirstOrderFusionDescriptors(Fragment childRing, Fragment parentRing, List<String> numericalLocantsOfChild, List<String> letterLocantsOfParent) throws StructureBuildingException { 695 List<Atom> childAtoms = determineAtomsToFuse(childRing, numericalLocantsOfChild, letterLocantsOfParent.size() +1); 696 if (childAtoms ==null){ 697 throw new StructureBuildingException("Malformed fusion bracket!"); 698 } 699 700 List<Atom> parentAtoms = new ArrayList<Atom>(); 701 List<Atom> parentPeripheralAtomList = getPeripheralAtoms(parentRing.getAtomList()); 702 CyclicAtomList cyclicListAtomsOnSurfaceOfParent = new CyclicAtomList(parentPeripheralAtomList, (int)letterLocantsOfParent.get(0).charAt(0) -97);//convert from lower case character through ascii to 0-23 703 parentAtoms.add(cyclicListAtomsOnSurfaceOfParent.getCurrent()); 704 for (int i = 0; i < letterLocantsOfParent.size(); i++) { 705 parentAtoms.add(cyclicListAtomsOnSurfaceOfParent.next()); 706 } 707 fuseRings(childAtoms, parentAtoms); 708 } 709 710 /** 711 * Returns the sublist of the given atoms that are peripheral atoms given that the list is ordered such that the interior atoms are at the end of the list 712 * @param atomList 713 * @return 714 */ getPeripheralAtoms(List<Atom> atomList)715 private List<Atom> getPeripheralAtoms(List<Atom> atomList) { 716 //find the indice of the last atom on the surface of the ring. This obviously connects to the first atom. The objective is to exclude any interior atoms. 717 List<Atom> neighbours = atomList.get(0).getAtomNeighbours(); 718 int indice = Integer.MAX_VALUE; 719 for (Atom atom : neighbours) { 720 int indexOfAtom =atomList.indexOf(atom); 721 if (indexOfAtom ==1){//not the next atom 722 continue; 723 } 724 else if (indexOfAtom ==-1){//not in parentRing 725 continue; 726 } 727 if (atomList.indexOf(atom)< indice){ 728 indice = indexOfAtom; 729 } 730 } 731 return atomList.subList(0, indice +1); 732 } 733 734 /** 735 * Handles fusion between components where the fusion descriptor is of the form: 736 * comma separated locants colon comma separated locants 737 * e.g pyrido[1'',2'':1',2']imidazo 738 * @param fusionDescriptor 739 * @param nextComponent 740 * @param fusedRing 741 * @throws StructureBuildingException 742 */ performHigherOrderFusion(String fusionDescriptor, Fragment nextComponent, Fragment fusedRing)743 private void performHigherOrderFusion(String fusionDescriptor, Fragment nextComponent, Fragment fusedRing) throws StructureBuildingException { 744 List<String> numericalLocantsOfChild = null; 745 List<String> numericalLocantsOfParent = null; 746 String[] fusionArray = fusionDescriptor.split(":"); 747 if (fusionArray.length ==2){ 748 numericalLocantsOfChild = Arrays.asList(fusionArray[0].split(",")); 749 numericalLocantsOfParent = Arrays.asList(fusionArray[1].split(",")); 750 } 751 else{ 752 throw new StructureBuildingException("Malformed fusion bracket: This is an OPSIN bug, check regexTokens.xml"); 753 } 754 processHigherOrderFusionDescriptors(nextComponent, fusedRing, numericalLocantsOfChild, numericalLocantsOfParent);//fuse the rings 755 } 756 757 /** 758 * Performs a single ring fusion using the values in numericalLocantsOfChild/numericalLocantsOfParent 759 * @param childRing 760 * @param parentRing 761 * @param numericalLocantsOfChild 762 * @param numericalLocantsOfParent 763 * @throws StructureBuildingException 764 */ processHigherOrderFusionDescriptors(Fragment childRing, Fragment parentRing, List<String> numericalLocantsOfChild, List<String> numericalLocantsOfParent)765 private void processHigherOrderFusionDescriptors(Fragment childRing, Fragment parentRing, List<String> numericalLocantsOfChild, List<String> numericalLocantsOfParent) throws StructureBuildingException { 766 List<Atom> childAtoms =determineAtomsToFuse(childRing, numericalLocantsOfChild, null); 767 if (childAtoms ==null){ 768 throw new StructureBuildingException("Malformed fusion bracket!"); 769 } 770 771 List<Atom> parentAtoms = determineAtomsToFuse(parentRing, numericalLocantsOfParent, childAtoms.size()); 772 if (parentAtoms ==null){ 773 throw new StructureBuildingException("Malformed fusion bracket!"); 774 } 775 fuseRings(childAtoms, parentAtoms); 776 } 777 778 /** 779 * Determines which atoms on a ring should be used for fusion given a set of numerical locants. 780 * If from the other ring involved in the fusion it is known how many atoms are expected to be found this should be provided 781 * If this is not known it should be set to null and the smallest number of fusion atoms will be returned. 782 * @param ring 783 * @param numericalLocantsOnRing 784 * @param expectedNumberOfAtomsToBeUsedForFusion 785 * @return 786 * @throws StructureBuildingException 787 */ determineAtomsToFuse(Fragment ring, List<String> numericalLocantsOnRing, Integer expectedNumberOfAtomsToBeUsedForFusion)788 private List<Atom> determineAtomsToFuse(Fragment ring, List<String> numericalLocantsOnRing, Integer expectedNumberOfAtomsToBeUsedForFusion) throws StructureBuildingException { 789 List<Atom> parentPeripheralAtomList = getPeripheralAtoms(ring.getAtomList()); 790 int indexfirst = parentPeripheralAtomList.indexOf(ring.getAtomByLocantOrThrow(numericalLocantsOnRing.get(0))); 791 int indexfinal = parentPeripheralAtomList.indexOf(ring.getAtomByLocantOrThrow(numericalLocantsOnRing.get(numericalLocantsOnRing.size()-1))); 792 CyclicAtomList cyclicRingAtomList = new CyclicAtomList(parentPeripheralAtomList, indexfirst); 793 List<Atom> fusionAtoms = null; 794 795 List<Atom> potentialFusionAtomsAscending = new ArrayList<Atom>(); 796 potentialFusionAtomsAscending.add(cyclicRingAtomList.getCurrent()); 797 while (cyclicRingAtomList.getIndex() != indexfinal){//assume numbers are ascending 798 potentialFusionAtomsAscending.add(cyclicRingAtomList.next()); 799 } 800 if (expectedNumberOfAtomsToBeUsedForFusion ==null ||expectedNumberOfAtomsToBeUsedForFusion == potentialFusionAtomsAscending.size()){ 801 boolean notInPotentialParentAtoms =false; 802 for (int i =1; i < numericalLocantsOnRing.size()-1 ; i ++){ 803 if (!potentialFusionAtomsAscending.contains(ring.getAtomByLocantOrThrow(numericalLocantsOnRing.get(i)))){ 804 notInPotentialParentAtoms =true; 805 } 806 } 807 if (!notInPotentialParentAtoms){ 808 fusionAtoms = potentialFusionAtomsAscending; 809 } 810 } 811 812 if (fusionAtoms ==null || expectedNumberOfAtomsToBeUsedForFusion ==null){//that didn't work, so try assuming the numbers are descending 813 cyclicRingAtomList.setIndex(indexfirst); 814 List<Atom> potentialFusionAtomsDescending = new ArrayList<Atom>(); 815 potentialFusionAtomsDescending.add(cyclicRingAtomList.getCurrent()); 816 while (cyclicRingAtomList.getIndex() != indexfinal){//assume numbers are descending 817 potentialFusionAtomsDescending.add(cyclicRingAtomList.previous()); 818 } 819 if (expectedNumberOfAtomsToBeUsedForFusion ==null || expectedNumberOfAtomsToBeUsedForFusion == potentialFusionAtomsDescending.size()){ 820 boolean notInPotentialParentAtoms =false; 821 for (int i =1; i < numericalLocantsOnRing.size()-1 ; i ++){ 822 if (!potentialFusionAtomsDescending.contains(ring.getAtomByLocantOrThrow(numericalLocantsOnRing.get(i)))){ 823 notInPotentialParentAtoms =true; 824 } 825 } 826 if (!notInPotentialParentAtoms){ 827 if (fusionAtoms!=null && expectedNumberOfAtomsToBeUsedForFusion ==null){ 828 //prefer less fusion atoms 829 if (potentialFusionAtomsDescending.size()< fusionAtoms.size()){ 830 fusionAtoms = potentialFusionAtomsDescending; 831 } 832 } 833 else{ 834 fusionAtoms = potentialFusionAtomsDescending; 835 } 836 } 837 } 838 } 839 return fusionAtoms; 840 } 841 842 /** 843 * Creates the bonds required to fuse two rings together. 844 * The child atoms are recorded as atoms that should be removed later 845 * @param childAtoms 846 * @param parentAtoms 847 * @throws StructureBuildingException 848 */ fuseRings(List<Atom> childAtoms, List<Atom> parentAtoms)849 private void fuseRings(List<Atom> childAtoms, List<Atom> parentAtoms) throws StructureBuildingException { 850 if (parentAtoms.size()!=childAtoms.size()){ 851 throw new StructureBuildingException("Problem with fusion descriptors: Parent atoms specified: " + parentAtoms.size() +" Child atoms specified: " + childAtoms.size() + " These should have been identical!"); 852 } 853 //replace parent atoms if the atom has already been used in fusion with the original atom 854 //This will occur if fusion has resulted in something resembling a spiro centre e.g. cyclopenta[1,2-b:5,1-b']bis[1,4]oxathiine 855 for (int i = parentAtoms.size() -1; i >=0; i--) { 856 if (atomsToRemoveToReplacementAtom.get(parentAtoms.get(i))!=null){ 857 parentAtoms.set(i, atomsToRemoveToReplacementAtom.get(parentAtoms.get(i))); 858 } 859 if (atomsToRemoveToReplacementAtom.get(childAtoms.get(i))!=null){ 860 childAtoms.set(i, atomsToRemoveToReplacementAtom.get(childAtoms.get(i))); 861 } 862 } 863 864 //sync spareValency and check that element type matches 865 for (int i = 0; i < childAtoms.size(); i++) { 866 Atom parentAtom = parentAtoms.get(i); 867 Atom childAtom = childAtoms.get(i); 868 if (childAtom.hasSpareValency()){ 869 parentAtom.setSpareValency(true); 870 } 871 if (parentAtom.getElement() != childAtom.getElement()){ 872 throw new StructureBuildingException("Invalid fusion descriptor: Heteroatom placement is ambiguous as it is not present in both components of the fusion"); 873 } 874 atomsToRemoveToReplacementAtom.put(childAtom, parentAtom); 875 } 876 877 Set<Bond> fusionEdgeBonds = new HashSet<Bond>();//these bonds already exist in both the child and parent atoms 878 for (int i = 0; i < childAtoms.size() -1; i++) { 879 fusionEdgeBonds.add(childAtoms.get(i).getBondToAtomOrThrow(childAtoms.get(i+1))); 880 fusionEdgeBonds.add(parentAtoms.get(i).getBondToAtomOrThrow(parentAtoms.get(i+1))); 881 } 882 883 Set<Bond> bondsToAddToParentAtoms = new LinkedHashSet<Bond>(); 884 for (Atom childAtom : childAtoms) { 885 for (Bond b : childAtom.getBonds()) { 886 if (!fusionEdgeBonds.contains(b)){ 887 bondsToAddToParentAtoms.add(b); 888 } 889 } 890 } 891 892 Set<Bond> bondsToAddToChildAtoms = new LinkedHashSet<Bond>(); 893 for (Atom parentAtom : parentAtoms) { 894 for (Bond b : parentAtom.getBonds()) { 895 if (!fusionEdgeBonds.contains(b)){ 896 bondsToAddToChildAtoms.add(b); 897 } 898 } 899 } 900 901 for (Bond bond : bondsToAddToParentAtoms) { 902 Atom from = bond.getFromAtom(); 903 int indiceInChildAtoms = childAtoms.indexOf(from); 904 if (indiceInChildAtoms !=-1){ 905 from = parentAtoms.get(indiceInChildAtoms); 906 } 907 Atom to = bond.getToAtom(); 908 indiceInChildAtoms = childAtoms.indexOf(to); 909 if (indiceInChildAtoms !=-1){ 910 to = parentAtoms.get(indiceInChildAtoms); 911 } 912 state.fragManager.createBond(from, to, 1); 913 } 914 915 for (Bond bond : bondsToAddToChildAtoms) { 916 Atom from = bond.getFromAtom(); 917 int indiceInParentAtoms = parentAtoms.indexOf(from); 918 if (indiceInParentAtoms !=-1){ 919 from = childAtoms.get(indiceInParentAtoms); 920 } 921 Atom to = bond.getToAtom(); 922 indiceInParentAtoms = parentAtoms.indexOf(to); 923 if (indiceInParentAtoms !=-1){ 924 to = childAtoms.get(indiceInParentAtoms); 925 } 926 Bond newBond = new Bond(from, to, 1); 927 if (childAtoms.contains(from)){ 928 from.addBond(newBond); 929 } 930 else{ 931 to.addBond(newBond); 932 } 933 } 934 } 935 936 /** 937 * Fuse the benzo with the subsequent ring 938 * Uses locants in front of the benz/benzo group to assign heteroatoms on the now numbered fused ring system 939 * @param benzoEl 940 * @param parentEl 941 * @throws StructureBuildingException 942 */ benzoSpecificFusion(Element benzoEl, Element parentEl)943 private void benzoSpecificFusion(Element benzoEl, Element parentEl) throws StructureBuildingException { 944 /* 945 * Perform the fusion, number it and associate it with the parentEl 946 */ 947 Fragment benzoRing = benzoEl.getFrag(); 948 Fragment parentRing = parentEl.getFrag(); 949 performSimpleFusion(null, benzoRing , parentRing); 950 state.fragManager.incorporateFragment(benzoRing, parentRing); 951 removeMergedAtoms(); 952 FusedRingNumberer.numberFusedRing(parentRing);//numbers the fused ring; 953 Fragment fusedRing =parentRing; 954 setBenzoHeteroatomPositioning(benzoEl, fusedRing); 955 } 956 957 /** 958 * Checks for locant(s) before benzo and uses these to set 959 * @param benzoEl 960 * @param fusedRing 961 * @throws StructureBuildingException 962 */ setBenzoHeteroatomPositioning(Element benzoEl, Fragment fusedRing)963 private void setBenzoHeteroatomPositioning(Element benzoEl, Fragment fusedRing) throws StructureBuildingException { 964 Element locantEl = OpsinTools.getPreviousSibling(benzoEl); 965 if (locantEl != null && locantEl.getName().equals(LOCANT_EL)) { 966 String[] locants = locantEl.getValue().split(","); 967 if (locantsCouldApplyToHeteroatomPositions(locants, benzoEl)) { 968 List<Atom> atomList =fusedRing.getAtomList(); 969 List<Atom> heteroatoms = new ArrayList<Atom>(); 970 List<ChemEl> elementOfHeteroAtom = new ArrayList<ChemEl>(); 971 for (Atom atom : atomList) {//this iterates in the same order as the numbering system 972 if (atom.getElement() != ChemEl.C){ 973 heteroatoms.add(atom); 974 elementOfHeteroAtom.add(atom.getElement()); 975 } 976 } 977 if (locants.length == heteroatoms.size()){//as many locants as there are heteroatoms to assign 978 //check for special case of a single locant indicating where the group substitutes e.g. 4-benzofuran-2-yl 979 if (!(locants.length == 1 && OpsinTools.getPreviousSibling(locantEl) == null 980 && ComponentProcessor.checkLocantPresentOnPotentialRoot(state, benzoEl.getParent(), locants[0]))) { 981 for (Atom atom : heteroatoms) { 982 atom.setElement(ChemEl.C); 983 } 984 for (int i=0; i< heteroatoms.size(); i++) { 985 fusedRing.getAtomByLocantOrThrow(locants[i]).setElement(elementOfHeteroAtom.get(i)); 986 } 987 locantEl.detach(); 988 } 989 } 990 else if (locants.length > 1){ 991 throw new StructureBuildingException("Unable to assign all locants to benzo-fused ring or multiplier was mising"); 992 } 993 } 994 } 995 } 996 locantsCouldApplyToHeteroatomPositions(String[] locants, Element benzoEl)997 private boolean locantsCouldApplyToHeteroatomPositions(String[] locants, Element benzoEl) { 998 if (!locantsAreAllNumeric(locants)) { 999 return false; 1000 } 1001 List<Element> suffixes = benzoEl.getParent().getChildElements(SUFFIX_EL); 1002 int suffixesWithoutLocants = 0; 1003 for (Element suffix : suffixes) { 1004 if (suffix.getAttribute(LOCANT_ATR)==null){ 1005 suffixesWithoutLocants++; 1006 } 1007 } 1008 if (locants.length == suffixesWithoutLocants){//In preference locants will be assigned to suffixes rather than to this nomenclature 1009 return false; 1010 } 1011 return true; 1012 } 1013 locantsAreAllNumeric(String[] locants)1014 private boolean locantsAreAllNumeric(String[] locants) { 1015 for (String locant : locants) { 1016 if (!MATCH_NUMERIC_LOCANT.matcher(locant).matches()){ 1017 return false; 1018 } 1019 } 1020 return true; 1021 } 1022 } 1023