/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * * Created on Jun 12, 2010 * Author: Jianjiong Gao * */ package org.biojava.nbio.protmod.structure; import org.biojava.nbio.protmod.*; import org.biojava.nbio.structure.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * Identify attachment modification in a 3-D structure. * * @author Jianjiong Gao * @since 3.0 */ public class ProteinModificationIdentifier { private static final Logger logger = LoggerFactory.getLogger(ProteinModificationIdentifier.class); private double bondLengthTolerance ; private boolean recordUnidentifiableModifiedCompounds ; private boolean recordAdditionalAttachments ; private Set<ModifiedCompound> identifiedModifiedCompounds = null; private Set<StructureAtomLinkage> unidentifiableAtomLinkages = null; private Set<StructureGroup> unidentifiableModifiedResidues = null; /** * Temporary save the amino acids for each call of identify(). */ private List<Group> residues; public ProteinModificationIdentifier(){ bondLengthTolerance = 0.4; recordUnidentifiableModifiedCompounds = false; recordAdditionalAttachments = true; reset(); } public void destroy(){ if ( identifiedModifiedCompounds != null) identifiedModifiedCompounds.clear(); if ( unidentifiableAtomLinkages != null) unidentifiableAtomLinkages.clear(); if ( unidentifiableModifiedResidues != null) unidentifiableModifiedResidues.clear(); unidentifiableAtomLinkages = null; unidentifiableAtomLinkages = null; unidentifiableModifiedResidues = null; } /** * * @param bondLengthTolerance tolerance of error (in Angstroms) of the * covalent bond length, when calculating the atom distance threshold. */ public void setbondLengthTolerance(final double bondLengthTolerance) { if (bondLengthTolerance<0) { throw new IllegalArgumentException("bondLengthTolerance " + "must be positive."); } this.bondLengthTolerance = bondLengthTolerance; } /** * * @param recordUnidentifiableModifiedCompounds true if choosing to record unidentifiable * atoms; false, otherwise. * @see #getRecordUnidentifiableCompounds * @see #getUnidentifiableModifiedResidues * @see #getUnidentifiableAtomLinkages */ public void setRecordUnidentifiableCompounds(boolean recordUnidentifiableModifiedCompounds) { this.recordUnidentifiableModifiedCompounds = recordUnidentifiableModifiedCompounds; } /** * * @return true if choosing to record unidentifiable * atoms; false, otherwise. * @see #setRecordUnidentifiableCompounds * @see #getUnidentifiableModifiedResidues * @see #getUnidentifiableAtomLinkages */ public boolean getRecordUnidentifiableCompounds() { return recordUnidentifiableModifiedCompounds; } /** * * @param recordAdditionalAttachments true if choosing to record additional attachments * that are not directly attached to a modified residue. * @see #getRecordAdditionalAttachments */ public void setRecordAdditionalAttachments(boolean recordAdditionalAttachments) { this.recordAdditionalAttachments = recordAdditionalAttachments; } /** * * @return true if choosing to record additional attachments * that are not directly attached to a modified residue. * @see #setRecordAdditionalAttachments */ public boolean getRecordAdditionalAttachments() { return recordAdditionalAttachments; } /** * * @return a set of identified {@link ModifiedCompound}s from * the last parse result. * @see ModifiedCompound */ public Set<ModifiedCompound> getIdentifiedModifiedCompound() { if (identifiedModifiedCompounds==null) { throw new IllegalStateException("No result available. Please call parse() first."); } return identifiedModifiedCompounds; } /** * * @return a set of atom linkages, which represent the * atom bonds that were not covered by the identified * {@link ModifiedCompound}s from the last parse result. * Each element of the list is a array containing two atoms. * @see StructureAtomLinkage * @see #setRecordUnidentifiableCompounds */ public Set<StructureAtomLinkage> getUnidentifiableAtomLinkages() { if (!recordUnidentifiableModifiedCompounds) { throw new UnsupportedOperationException("Recording unidentified atom linkages" + "is not supported. Please setRecordUnidentifiableCompounds(true) first."); } if (identifiedModifiedCompounds==null) { throw new IllegalStateException("No result available. Please call parse() first."); } return unidentifiableAtomLinkages; } /** * * @return a set of modified residues that were not covered by * the identified ModifiedCompounds from the last parse * result. * @see StructureGroup * @see #setRecordUnidentifiableCompounds * @see #getIdentifiedModifiedCompound */ public Set<StructureGroup> getUnidentifiableModifiedResidues() { if (!recordUnidentifiableModifiedCompounds) { throw new UnsupportedOperationException("Recording unidentified atom linkages" + "is not supported. Please setRecordUnidentifiableCompounds(true) first."); } if (identifiedModifiedCompounds==null) { throw new IllegalStateException("No result available. Please call parse() first."); } return unidentifiableModifiedResidues; } /** * Identify all registered modifications in a structure. * @param structure */ public void identify(final Structure structure) { identify(structure, ProteinModificationRegistry.allModifications()); } /** * Identify a set of modifications in a structure. * @param structure query {@link Structure}. * @param potentialModifications query {@link ProteinModification}s. */ public void identify(final Structure structure, final Set<ProteinModification> potentialModifications) { if (structure==null) { throw new IllegalArgumentException("Null structure."); } identify(structure.getChains(), potentialModifications); } /** * Identify all registered modifications in a chain. * @param chain query {@link Chain}. */ public void identify(final Chain chain) { identify(Collections.singletonList(chain)); } /** * Identify all registered modifications in chains. * @param chains query {@link Chain}s. */ public void identify(final List<Chain> chains) { identify(chains, ProteinModificationRegistry.allModifications()); } /** * Identify a set of modifications in a a chains. * @param chain query {@link Chain}. * @param potentialModifications query {@link ProteinModification}s. */ public void identify(final Chain chain, final Set<ProteinModification> potentialModifications) { identify(Collections.singletonList(chain), potentialModifications); } /** * Identify a set of modifications in a a list of chains. * @param chains query {@link Chain}s. * @param potentialModifications query {@link ProteinModification}s. */ public void identify(final List<Chain> chains, final Set<ProteinModification> potentialModifications) { if (chains==null) { throw new IllegalArgumentException("Null structure."); } if (potentialModifications==null) { throw new IllegalArgumentException("Null potentialModifications."); } reset(); if (potentialModifications.isEmpty()) { return; } residues = new ArrayList<Group>(); List<Group> ligands = new ArrayList<Group>(); Map<Component, Set<Group>> mapCompGroups = new HashMap<Component, Set<Group>>(); for (Chain chain : chains) { List<Group> ress = StructureUtil.getAminoAcids(chain); //List<Group> ligs = chain.getAtomLigands(); List<Group> ligs = StructureTools.filterLigands(chain.getAtomGroups()); residues.addAll(ress); residues.removeAll(ligs); ligands.addAll(ligs); addModificationGroups(potentialModifications, ress, ligs, mapCompGroups); } if (residues.isEmpty()) { String pdbId = "?"; if ( chains.size() > 0) { Structure struc = chains.get(0).getStructure(); if ( struc != null) pdbId = struc.getPDBCode(); } logger.warn("No amino acids found for {}. Either you did not parse the PDB file with alignSEQRES records, or this record does not contain any amino acids.", pdbId); } List<ModifiedCompound> modComps = new ArrayList<ModifiedCompound>(); for (ProteinModification mod : potentialModifications) { ModificationCondition condition = mod.getCondition(); List<Component> components = condition.getComponents(); if (!mapCompGroups.keySet().containsAll(components)) { // not all components exist for this mod. continue; } int sizeComps = components.size(); if (sizeComps==1) { processCrosslink1(mapCompGroups, modComps, mod, components); } else { processMultiCrosslink(mapCompGroups, modComps, mod, condition); } } if (recordAdditionalAttachments) { // identify additional groups that are not directly attached to amino acids. for (ModifiedCompound mc : modComps) { identifyAdditionalAttachments(mc, ligands, chains); } } mergeModComps(modComps); identifiedModifiedCompounds.addAll(modComps); // record unidentifiable linkage if (recordUnidentifiableModifiedCompounds) { recordUnidentifiableAtomLinkages(modComps, ligands); recordUnidentifiableModifiedResidues(modComps); } } private void reset() { identifiedModifiedCompounds = new LinkedHashSet<ModifiedCompound>(); if (recordUnidentifiableModifiedCompounds) { unidentifiableAtomLinkages = new LinkedHashSet<StructureAtomLinkage>(); unidentifiableModifiedResidues = new LinkedHashSet<StructureGroup>(); } } private void processMultiCrosslink( Map<Component, Set<Group>> mapCompGroups, List<ModifiedCompound> modComps, ProteinModification mod, ModificationCondition condition) { // for multiple components // find linkages first List<List<Atom[]>> matchedAtomsOfLinkages = getMatchedAtomsOfLinkages(condition, mapCompGroups); if (matchedAtomsOfLinkages.size() != condition.getLinkages().size()) { return; } assembleLinkages(matchedAtomsOfLinkages, mod, modComps); } private void processCrosslink1(Map<Component, Set<Group>> mapCompGroups, List<ModifiedCompound> modComps, ProteinModification mod, List<Component> components) { // modified residue // TODO: is this the correct logic for CROSS_LINK_1? Set<Group> modifiedResidues = mapCompGroups.get(components.get(0)); if (modifiedResidues != null) { for (Group residue : modifiedResidues) { StructureGroup strucGroup = StructureUtil.getStructureGroup(residue, true); ModifiedCompound modRes = new ModifiedCompoundImpl(mod, strucGroup); modComps.add(modRes); } } } /** * identify additional groups that are not directly attached to amino acids. * @param mc {@link ModifiedCompound} * @param ligands {@link Group} * @param chains List of {@link Chain}s * @return a list of added groups */ private void identifyAdditionalAttachments(ModifiedCompound mc, List<Group> ligands, List<Chain> chains) { if (ligands.isEmpty()) { return; } // TODO: should the additional groups only be allowed to the identified // ligands or both amino acids and ligands? Currently only on ligands // ligands to amino acid bonds for same modification of unknown category // will be combined in mergeModComps() // TODO: how about chain-chain links? List<Group> identifiedGroups = new ArrayList<Group>(); for (StructureGroup num : mc.getGroups(false)) { Group group; try { //String numIns = "" + num.getResidueNumber(); //if (num.getInsCode() != null) { // numIns += num.getInsCode(); //} ResidueNumber resNum = new ResidueNumber(); resNum.setChainName(num.getChainId()); resNum.setSeqNum(num.getResidueNumber()); resNum.setInsCode(num.getInsCode()); //group = chain.getGroupByPDB(numIns); group = getGroup(num,chains); //group = mapChainIdChain.get(num.getChainId()).getGroupByPDB(resNum); } catch (StructureException e) { logger.error("Exception: ", e); // should not happen continue; } identifiedGroups.add(group); } int start = 0; int n = identifiedGroups.size(); while (n > start) { for (Group group1 : ligands) { for (int i=start; i<n; i++) { Group group2 = identifiedGroups.get(i); if (!identifiedGroups.contains(group1)) { List<Atom[]> linkedAtoms = StructureUtil.findAtomLinkages( group1, group2, false, bondLengthTolerance); if (!linkedAtoms.isEmpty()) { for (Atom[] atoms : linkedAtoms) { mc.addAtomLinkage(StructureUtil.getStructureAtomLinkage(atoms[0], false, atoms[1], false)); } identifiedGroups.add(group1); break; } } } } start = n; n = identifiedGroups.size(); } } private Group getGroup(StructureGroup num, List<Chain> chains) throws StructureException { for (Chain c : chains){ if ( c.getId().equals(num.getChainId())){ ResidueNumber resNum = new ResidueNumber(); resNum.setSeqNum(num.getResidueNumber()); resNum.setInsCode(num.getInsCode()); return c.getGroupByPDB(resNum); } } throw new StructureException("Could not find residue " + num); } /** * Merge identified modified compounds if linked. */ private void mergeModComps(List<ModifiedCompound> modComps) { TreeSet<Integer> remove = new TreeSet<Integer>(); int n = modComps.size(); for (int icurr=1; icurr<n; icurr++) { ModifiedCompound curr = modComps.get(icurr); String id = curr.getModification().getId(); if (ProteinModificationRegistry.getById(id).getCategory() !=ModificationCategory.UNDEFINED) continue; // find linked compounds that before curr //List<Integer> merging = new ArrayList<Integer>(); int ipre = 0; for (; ipre<icurr; ipre++) { if (remove.contains(ipre)) continue; ModifiedCompound pre = modComps.get(ipre); if (!Collections.disjoint(pre.getGroups(false), curr.getGroups(false))) { break; } } if (ipre<icurr) { ModifiedCompound mcKeep = modComps.get(ipre); // merge modifications of the same type if (mcKeep.getModification().getId().equals(id)) { // merging the current one to the previous one mcKeep.addAtomLinkages(curr.getAtomLinkages()); remove.add(icurr); } } } Iterator<Integer> it = remove.descendingIterator(); while (it.hasNext()) { modComps.remove(it.next().intValue()); } } /** * Record unidentifiable atom linkages in a chain. Only linkages between two * residues or one residue and one ligand will be recorded. */ private void recordUnidentifiableAtomLinkages(List<ModifiedCompound> modComps, List<Group> ligands) { // first put identified linkages in a map for fast query Set<StructureAtomLinkage> identifiedLinkages = new HashSet<StructureAtomLinkage>(); for (ModifiedCompound mc : modComps) { identifiedLinkages.addAll(mc.getAtomLinkages()); } // record // cross link int nRes = residues.size(); for (int i=0; i<nRes-1; i++) { Group group1 = residues.get(i); for (int j=i+1; j<nRes; j++) { Group group2 = residues.get(j); List<Atom[]> linkages = StructureUtil.findAtomLinkages( group1, group2, true, bondLengthTolerance); for (Atom[] atoms : linkages) { StructureAtomLinkage link = StructureUtil.getStructureAtomLinkage(atoms[0], true, atoms[1], true); unidentifiableAtomLinkages.add(link); } } } // attachment int nLig = ligands.size(); for (int i=0; i<nRes; i++) { Group group1 = residues.get(i); for (int j=0; j<nLig; j++) { Group group2 = ligands.get(j); if (group1.equals(group2)) { // overlap between residues and ligands continue; } List<Atom[]> linkages = StructureUtil.findAtomLinkages( group1, group2, false, bondLengthTolerance); for (Atom[] atoms : linkages) { StructureAtomLinkage link = StructureUtil.getStructureAtomLinkage(atoms[0], true, atoms[1], false); unidentifiableAtomLinkages.add(link); } } } } private void recordUnidentifiableModifiedResidues(List<ModifiedCompound> modComps) { Set<StructureGroup> identifiedComps = new HashSet<StructureGroup>(); for (ModifiedCompound mc : modComps) { identifiedComps.addAll(mc.getGroups(true)); } // TODO: use the ModifiedAminoAcid after Andreas add that. for (Group group : residues) { if (group.getType().equals(GroupType.HETATM)) { StructureGroup strucGroup = StructureUtil.getStructureGroup( group, true); strucGroup.setChainId(group.getChainId()); if (!identifiedComps.contains(strucGroup)) { unidentifiableModifiedResidues.add(strucGroup); } } } } /** * * @param modifications a set of {@link ProteinModification}s. * @param residues * @param ligands * @param saveTo save result to * @return map from component to list of corresponding residues * in the chain. */ private void addModificationGroups( final Set<ProteinModification> modifications, final List<Group> residues, final List<Group> ligands, final Map<Component, Set<Group>> saveTo) { if (residues==null || ligands==null || modifications==null) { throw new IllegalArgumentException("Null argument(s)."); } Map<Component,Set<Component>> mapSingleMultiComps = new HashMap<Component,Set<Component>>(); for (ProteinModification mod : modifications) { ModificationCondition condition = mod.getCondition(); for (Component comp : condition.getComponents()) { for (String pdbccId : comp.getPdbccIds()) { Component single = Component.of(Collections.singleton(pdbccId), comp.isNTerminal(), comp.isCTerminal()); Set<Component> mult = mapSingleMultiComps.get(single); if (mult == null) { mult = new HashSet<Component>(); mapSingleMultiComps.put(single, mult); } mult.add(comp); } } } { // ligands Set<Component> ligandsWildCard = mapSingleMultiComps.get( Component.of("*")); for (Group group : ligands) { String pdbccId = group.getPDBName().trim(); Set<Component> comps = mapSingleMultiComps.get( Component.of(pdbccId)); for (Component comp : unionComponentSet(ligandsWildCard, comps)) { Set<Group> gs = saveTo.get(comp); if (gs==null) { gs = new LinkedHashSet<Group>(); saveTo.put(comp, gs); } gs.add(group); } } } { // residues if (residues.isEmpty()) { return; } Set<Component> residuesWildCard = mapSingleMultiComps.get( Component.of("*")); // for all residues for (Group group : residues) { String pdbccId = group.getPDBName().trim(); Set<Component> comps = mapSingleMultiComps.get( Component.of(pdbccId)); for (Component comp : unionComponentSet(residuesWildCard, comps)) { Set<Group> gs = saveTo.get(comp); if (gs==null) { gs = new LinkedHashSet<Group>(); saveTo.put(comp, gs); } gs.add(group); } } // for N-terminal int nRes = residues.size(); int iRes = 0; Group res; do { // for all ligands on N terminal and the first residue res = residues.get(iRes++); Set<Component> nTermWildCard = mapSingleMultiComps.get( Component.of("*", true, false)); Set<Component> comps = mapSingleMultiComps.get( Component.of(res.getPDBName(), true, false)); for (Component comp : unionComponentSet(nTermWildCard, comps)) { Set<Group> gs = saveTo.get(comp); if (gs==null) { gs = new LinkedHashSet<Group>(); saveTo.put(comp, gs); } gs.add(res); } } while (iRes<nRes && ligands.contains(res)); // for C-terminal iRes = residues.size()-1; do { // for all ligands on C terminal and the last residue res = residues.get(iRes--); Set<Component> cTermWildCard = mapSingleMultiComps.get( Component.of("*", false, true)); Set<Component> comps = mapSingleMultiComps.get( Component.of(res.getPDBName(), false, true)); for (Component comp : unionComponentSet(cTermWildCard, comps)) { Set<Group> gs = saveTo.get(comp); if (gs==null) { gs = new LinkedHashSet<Group>(); saveTo.put(comp, gs); } gs.add(res); } } while (iRes>=0 && ligands.contains(res)); } } private Set<Component> unionComponentSet(Set<Component> set1, Set<Component> set2) { if (set1 == null && set2 == null) return Collections.emptySet(); if (set1 == null) return set2; if (set2 == null) return set1; Set<Component> set = new HashSet<Component>(set1.size()+set2.size()); set.addAll(set1); set.addAll(set2); return set; } /** * Get matched atoms for all linkages. */ private List<List<Atom[]>> getMatchedAtomsOfLinkages( ModificationCondition condition, Map<Component, Set<Group>> mapCompGroups) { List<ModificationLinkage> linkages = condition.getLinkages(); int nLink = linkages.size(); List<List<Atom[]>> matchedAtomsOfLinkages = new ArrayList<List<Atom[]>>(nLink); for (int iLink=0; iLink<nLink; iLink++) { ModificationLinkage linkage = linkages.get(iLink); Component comp1 = linkage.getComponent1(); Component comp2 = linkage.getComponent2(); // boolean isAA1 = comp1.; // boolean isAA2 = comp2.getType()==true; Set<Group> groups1 = mapCompGroups.get(comp1); Set<Group> groups2 = mapCompGroups.get(comp2); List<Atom[]> list = new ArrayList<Atom[]>(); List<String> potentialNamesOfAtomOnGroup1 = linkage.getPDBNameOfPotentialAtomsOnComponent1(); for (String name : potentialNamesOfAtomOnGroup1) { if (name.equals("*")) { // wildcard potentialNamesOfAtomOnGroup1 = null; // search all atoms break; } } List<String> potentialNamesOfAtomOnGroup2 = linkage.getPDBNameOfPotentialAtomsOnComponent2(); for (String name : potentialNamesOfAtomOnGroup2) { if (name.equals("*")) { // wildcard potentialNamesOfAtomOnGroup2 = null; // search all atoms break; } } for (Group g1 : groups1) { for (Group g2 : groups2) { if (g1.equals(g2)) { continue; } // only for wildcard match of two residues boolean ignoreNCLinkage = potentialNamesOfAtomOnGroup1 == null && potentialNamesOfAtomOnGroup2 == null && residues.contains(g1) && residues.contains(g2); Atom[] atoms = StructureUtil.findNearestAtomLinkage( g1, g2, potentialNamesOfAtomOnGroup1, potentialNamesOfAtomOnGroup2, ignoreNCLinkage, bondLengthTolerance); if (atoms!=null) { list.add(atoms); } } } if (list.isEmpty()) { // broken linkage break; } matchedAtomsOfLinkages.add(list); } return matchedAtomsOfLinkages; } /** Assembly the matched linkages * * @param matchedAtomsOfLinkages * @param mod * @param ret ModifiedCompound will be stored here */ private void assembleLinkages(List<List<Atom[]>> matchedAtomsOfLinkages, ProteinModification mod, List<ModifiedCompound> ret) { ModificationCondition condition = mod.getCondition(); List<ModificationLinkage> modLinks = condition.getLinkages(); int nLink = matchedAtomsOfLinkages.size(); int[] indices = new int[nLink]; Set<ModifiedCompound> identifiedCompounds = new HashSet<ModifiedCompound>(); while (indices[0]<matchedAtomsOfLinkages.get(0).size()) { List<Atom[]> atomLinkages = new ArrayList<Atom[]>(nLink); for (int iLink=0; iLink<nLink; iLink++) { Atom[] atoms = matchedAtomsOfLinkages.get(iLink).get(indices[iLink]); atomLinkages.add(atoms); } if (matchLinkages(modLinks, atomLinkages)) { // matched int n = atomLinkages.size(); List<StructureAtomLinkage> linkages = new ArrayList<StructureAtomLinkage>(n); for (int i=0; i<n; i++) { Atom[] linkage = atomLinkages.get(i); StructureAtomLinkage link = StructureUtil.getStructureAtomLinkage( linkage[0], residues.contains(linkage[0].getGroup()), linkage[1], residues.contains(linkage[1].getGroup())); linkages.add(link); } ModifiedCompound mc = new ModifiedCompoundImpl(mod, linkages); if (!identifiedCompounds.contains(mc)) { ret.add(mc); identifiedCompounds.add(mc); } } // indices++ (e.g. [0,0,1]=>[0,0,2]=>[1,2,0]) int i = nLink-1; while (i>=0) { if (i==0 || indices[i]<matchedAtomsOfLinkages.get(i).size()-1) { indices[i]++; break; } else { indices[i] = 0; i--; } } } } /** * * @param linkages * @param atomLinkages * @return true if atomLinkages satisfy the condition; false, otherwise. */ private boolean matchLinkages(List<ModificationLinkage> linkages, List<Atom[]> atomLinkages) { int nLink = linkages.size(); if (nLink != atomLinkages.size()) { return false; } for (int i=0; i<nLink-1; i++) { ModificationLinkage link1 = linkages.get(i); Atom[] atoms1 = atomLinkages.get(i); for (int j=i+1; j<nLink; j++) { ModificationLinkage link2 = linkages.get(j); Atom[] atoms2 = atomLinkages.get(j); // check components if (((link1.getIndexOfComponent1()==link2.getIndexOfComponent1()) != (atoms1[0].getGroup().equals(atoms2[0].getGroup()))) || ((link1.getIndexOfComponent1()==link2.getIndexOfComponent2()) != (atoms1[0].getGroup().equals(atoms2[1].getGroup()))) || ((link1.getIndexOfComponent2()==link2.getIndexOfComponent1()) != (atoms1[1].getGroup().equals(atoms2[0].getGroup()))) || ((link1.getIndexOfComponent2()==link2.getIndexOfComponent2()) != (atoms1[1].getGroup().equals(atoms2[1].getGroup())))) { return false; } // check atoms String label11 = link1.getLabelOfAtomOnComponent1(); String label12 = link1.getLabelOfAtomOnComponent2(); String label21 = link2.getLabelOfAtomOnComponent1(); String label22 = link2.getLabelOfAtomOnComponent2(); if ((label11!=null && label21!=null && label11.equals(label21)) != (atoms1[0].equals(atoms2[0])) || (label11!=null && label22!=null && label11.equals(label22)) != (atoms1[0].equals(atoms2[1])) || (label12!=null && label21!=null && label12.equals(label21)) != (atoms1[1].equals(atoms2[0])) || (label12!=null && label22!=null && label12.equals(label22)) != (atoms1[1].equals(atoms2[1]))) { return false; } } } return true; } }