content: package com.actelion.research.chem.reaction;
  
  import com.actelion.research.chem.Molecule;
  import com.actelion.research.chem.StereoMolecule;
  
  public class TransformerRule extends Object implements Comparable<TransformerRule> {
  	public enum TYPE { CREATE, CHANGE_DIF, CHANGE_ABS, REMOVE, NO_CHANGE, UNKNOWN };
  
  	private int mAtom1,mAtom2,mMapNo1,mMapNo2,mBondQFTypes,mTargetBondType,mTargetBondDif;
  	private TYPE mType;
  
  	public TransformerRule(StereoMolecule mol, int bond) {
  		mAtom1 = mol.getBondAtom(0, bond);
  		mAtom2 = mol.getBondAtom(1, bond);
  		int mapNo1 = mol.getAtomMapNo(mAtom1);
  		int mapNo2 = mol.getAtomMapNo(mAtom2);
  		if (mapNo1 < mapNo2) {
  			mMapNo1 = mapNo1;
  			mMapNo2 = mapNo2;
  		}
  		else {
  			mMapNo1 = mapNo2;
  			mMapNo2 = mapNo1;
  		}
  
  		// Note: we don't support quadruple or quintuple bonds here!
  		mBondQFTypes = mol.getBondQueryFeatures(bond) & Molecule.cBondQFBondTypes;
  
  //		if (mol.getBondType(bond) == Molecule.cBondTypeDelocalized || mol.isDelocalizedBond(bond))
  //			mBondQFTypes |= Molecule.cBondQFDelocalized;
  //		else {
  //			int order = mol.getBondOrder(bond);
  //			if (order == 0)
  //				mBondQFTypes |= Molecule.cBondQFMetalLigand;
  //			else if (order == 1)
  //				mBondQFTypes |= Molecule.cBondQFSingle;
  //			else if (order == 2)
  //				mBondQFTypes |= Molecule.cBondQFDouble;
  //			else if (order == 3)
  //				mBondQFTypes |= Molecule.cBondQFTriple;
  //			}
  
  		// we don't use the delocalized type
  		int bondType = mol.getBondTypeSimple(bond);
  		if (bondType == Molecule.cBondTypeMetalLigand)
  			mBondQFTypes |= Molecule.cBondTypeMetalLigand;
  		else if (bondType == Molecule.cBondTypeDouble)
  			mBondQFTypes |= Molecule.cBondTypeDouble;
  		else if (bondType == Molecule.cBondTypeTriple)
  			mBondQFTypes |= Molecule.cBondTypeTriple;
  		else
  			mBondQFTypes |= Molecule.cBondTypeSingle;
  
  		mType = TYPE.UNKNOWN;
  	}
  
  	public int getAtom1() {
  		return mAtom1;
  	}
  
  	public int getAtom2() {
  		return mAtom2;
  	}
  
  	public TYPE getType() {
  		return mType;
  	}
  
  	@Override
  	public boolean equals(Object o) {
  		return mMapNo1 == ((TransformerRule)o).mMapNo1 && mMapNo2 == ((TransformerRule)o).mMapNo2;
  	}
  
  	@Override
  	public int compareTo(TransformerRule o) {
  		if (mMapNo1 != o.mMapNo1)
  			return mMapNo1 < o.mMapNo1 ? -1 : 1;
  		if (mMapNo2 != o.mMapNo2)
  			return mMapNo2 < o.mMapNo2 ? -1 : 1;
  		return 0;
  	}
  
  	public boolean isNoChange() {
  		return mType == TYPE.NO_CHANGE;
  	}
  
  	public boolean isFinished() {
  		return mType != TYPE.UNKNOWN;
  	}
  
  	public void finishWithProduct(TransformerRule productRule) {
  		if (mBondQFTypes == productRule.mBondQFTypes) {
  			mType = TYPE.NO_CHANGE;
  		}
  		else if (Integer.bitCount(productRule.mBondQFTypes) == 1) {
  			mTargetBondType = productRule.mBondQFTypes == Molecule.cBondTypeSingle ? Molecule.cBondTypeSingle
  					: productRule.mBondQFTypes == Molecule.cBondTypeDouble ? Molecule.cBondTypeCross
  					: productRule.mBondQFTypes == Molecule.cBondTypeTriple ? Molecule.cBondTypeTriple
  					: productRule.mBondQFTypes == Molecule.cBondTypeMetalLigand ? Molecule.cBondTypeMetalLigand
  					: Molecule.cBondTypeDelocalized;
  			mType = TYPE.CHANGE_ABS;
  		}
  		else {
  			int sourceBondOrder = (mBondQFTypes & Molecule.cBondTypeMetalLigand) != 0 ? 0
  					: (mBondQFTypes & Molecule.cBondTypeSingle) != 0 ? 1
  					: (mBondQFTypes & Molecule.cBondTypeDouble) != 0 ? 2 : 3;
  			int targetBondOrder = (productRule.mBondQFTypes & Molecule.cBondTypeMetalLigand) != 0 ? 0
  					: (productRule.mBondQFTypes & Molecule.cBondTypeSingle) != 0 ? 1
  					: (productRule.mBondQFTypes & Molecule.cBondTypeDouble) != 0 ? 2 : 3;
  			if (targetBondOrder == sourceBondOrder)
  				mType = TYPE.NO_CHANGE;
  			else {
  				mTargetBondDif = targetBondOrder - sourceBondOrder;
  				mType = TYPE.CHANGE_DIF;
  			}
  		}
  	}
  
  	public void finishProductOnly() {
  		mType = TYPE.CREATE;
  	}
  
  	public void finishNoProduct() {
  		mType = TYPE.REMOVE;
  	}
  
  	/**
  	 * Make sure that the molecule's helper status is at least cHelperNeighbours
  	 * @param mol
  	 * @param matchAtom
  	 * @return
  	 */
  	public boolean adaptBondOrder(StereoMolecule mol, int[] matchAtom) {
  		int atom1 = matchAtom[mAtom1];
  		int atom2 = matchAtom[mAtom2];
  
  		if (mType == TYPE.CREATE) {
  //			int bondOrder = mTargetBondType == Molecule.cBondTypeMetalLigand ? 0
  //						  : mTargetBondType == Molecule.cBondTypeDouble ? 2
  //						  : mTargetBondType == Molecule.cBondTypeTriple ? 3 : 1;
  
  //			if (mol.getFreeValence(atom1) < bondOrder
  //			 || mol.getFreeValence(atom2) < bondOrder)
  //				return false;
  
  			mol.addBond(atom1, atom2, mTargetBondType);
  			return true;
  		}
  		else {
  			int bond = mol.getBond(atom1, atom2);
  			if (mType == TYPE.CHANGE_ABS) {
  //				int bondOrderChange = (mTargetBondType == Molecule.cBondTypeMetalLigand ? 0
  //									 : mTargetBondType == Molecule.cBondTypeDouble ? 2
  //									 : mTargetBondType == Molecule.cBondTypeTriple ? 3 : 1) - mol.getBondOrder(bond);
  
  //				if (mol.getFreeValence(atom1) < bondOrderChange
  //				 || mol.getFreeValence(atom2) < bondOrderChange)
  //					return false;
  
  				mol.setBondType(bond, mTargetBondType);
  				return true;
  			}
  			else if (mType == TYPE.CHANGE_DIF) {
  //				if (mTargetBondDif > 0
  //				 && (mol.getFreeValence(atom1) < mTargetBondDif
  //				  || mol.getFreeValence(atom2) < mTargetBondDif))
  //					return false;
  
  				int targetBondOrder = mol.getBondOrder(bond) + mTargetBondDif;
  				if (targetBondOrder >= 0 && targetBondOrder <= 3) {
  					mol.setBondType(bond, Molecule.bondOrderToType(targetBondOrder, true));
  					return true;
  				}
  			}
  			else if (mType == TYPE.REMOVE) {
  				mol.markBondForDeletion(bond);
  				return true;
  			}
  		}
  		return false;
  	}
  
  	public int addBondCode(StereoMolecule mol, int[] matchAtom, boolean[] bondHandled) {
  		int atom1 = matchAtom[mAtom1];
  		int atom2 = matchAtom[mAtom2];
  
  		if (mType == TYPE.CREATE)
  			return getBondCode(atom1, atom2, Molecule.bondTypeToOrder(mTargetBondType));
  
  		int bond = mol.getBond(atom1, atom2);
  		if (mType == TYPE.CHANGE_ABS) {
  			bondHandled[bond] = true;
  			return getBondCode(atom1, atom2, Molecule.bondTypeToOrder(mTargetBondType));
  		}
  
  		if (mType == TYPE.CHANGE_DIF) {
  			int targetBondOrder = mol.getBondOrder(bond) + mTargetBondDif;
  			if (targetBondOrder >= 0 && targetBondOrder <= 3) {
  				bondHandled[bond] = true;
  				return getBondCode(atom1, atom2, targetBondOrder);
  			}
  			else {
  				return -1;
  			}
  		}
  
  		if (mType == TYPE.REMOVE) {
  			bondHandled[bond] = true;
  			return -1;
  		}
  
  		return -1;
  	}
  
  	public static int getBondCode(int atom1, int atom2, int bondOrder) {
  		return (atom1 < atom2) ?
  				(atom1 << 17) + (atom2 << 2) + bondOrder
  				: (atom2 << 17) + (atom1 << 2) + bondOrder;
  	}
  }
  