Implementing a new Structure Checker
Structure Checkers meant to detect well-defined issues in chemical structures. A Checker should be simple, and focus on a single problem. Although a Checker can have options to fine-tune its behavior, it is not recommended to create Checkers focusing more issues.
Preparations
You will need Java 6 SDK and Marvin Beans 6.0 (or newer) installed on your computer. An Integrated Development Environment (IDE) such as Eclipse, IntelliJ IDEA or NetBeans is recommended. This training material is using Eclipse.
Create a new Java project, and add the MarvinBeans.jar to the class path.
Hint: The location of MarvinBeans.jar is the <installation path>/MarvinBeans/lib folder. To add the jar to the class path in Eclipse, go to Project/Properties/Java Build Path/Libraries; press the Add External Library button, and browse the MarvinBeans.jar from the file system.
Create a new Structure Checker
We are going to implement a new Structure Checker to detect atom map duplications in reactions only. The Checker will report if the same mapping is used for more than one atom in the reactant or in the product side. With an option, the user can specify which side of the reaction should be checked for map duplications.
Create a new class
Create a new DuplicateAtomMapChecker
class to custom.checkers
package that extends the chemaxon.checkers.ExternalStructureChecker
class provided by MarvinBeans.jar.
Download the example and find "DuplicateAtomMapChecker.java".
package custom.checkers; import chemaxon.checkers.ExternalStructureChecker; import chemaxon.checkers.result.StructureCheckerResult; import chemaxon.struc.Molecule; public class DuplicateAtomMapChecker extends ExternalStructureChecker { /** error code of duplicate atom map checker */ public static final String DUPLICATE_ATOM_MAP_CHECKER_ERROR = "duplicateAtomMapCheckerError"; /** * Constructs a duplicate atom map checker with default settings. */ public DuplicateAtomMapChecker() { super(DUPLICATE_ATOM_MAP_CHECKER_ERROR); } @Override protected StructureCheckerResult check1(Molecule molecule) { // TODO Auto-generated method stub return null; } }
TheExternalStructureChecker
superclass requires aString
argument to identify the type of problem this checker can detect. We can use this identifier to offer fixers for the problem later.
Note: Since fixers are not bound to checkers, but to error types, different Structure Checkers using the same error type will share the compatible fixers as well.
Adding an Option
To fulfill the requirement of specifying the side of the reaction to be checked for duplicate mappings, the code must be enhanced with:
- an enumeration type with possible values;
- a data member holding the selected value;
- getter and setter to handle this option.
/** * Specifies the reaction side to check for duplicate mappings. */ public enum ReactionSide { /** check the reactant side only */ REACTANT, /** check the product side only */ PRODUCT, /** check both sides */ BOTH } private ReactionSide reactionSide; /** * Returns the reaction side to check for duplicate mappings. * * @return the reaction side to check for duplicate mappings */ public ReactionSide getReactionSide() { return reactionSide; } /** * Sets the reaction side to check for duplicate mappings. * * @param reactionSide * the reaction side to check */ public void setReactionSide(ReactionSide reactionSide) { ReactionSide oldValue = getReactionSide(); this.reactionSide = reactionSide; propertyChangeSupport.firePropertyChange(REACTION_SIDE, oldValue, reactionSide); }
Making it persistent
To properly use the new checker in ChemAxon applications, it is required to make the reaction side parameter persistent. That is achieved with:
- annotate reaction side member, and set up a default value;
- a new constructor with a Map argument.
@Persistent private ReactionSide reactionSide = ReactionSide.BOTH; /** * Constructs a duplicate atom map checker with specified settings. * * @param params * the settings to use */ // NOTE: this constructor is required by StructureCheckerFactory // if checker has parameters. public DuplicateAtomMapChecker(Map<String, String> params) { super(DUPLICATE_ATOM_MAP_CHECKER_ERROR); this.reactionSide = ReactionSide.BOTH; if (params.containsKey(REACTION_SIDE)) { String value = params.get(REACTION_SIDE).toUpperCase(); try { this.reactionSide = ReactionSide.valueOf(value); } catch (IllegalArgumentException e) { // invalid argument set, using default } } }
The@Persistent
annotation tells the Structure Checker API to save the value of the member when exporting the Checker to a configuration file. To retrieve the current value, a corresponding getter will be called, in this case thegetReactionSide()
function.
When a Checker has a parameter, it must have a constructor with Map<String, String>
argument. The Structure Checker API will try to create the Checker instance by passing key value pairs according to the parameters.
Business Logic and Metadata
The new Checker will display properly if it has a @CheckerInfo
annotation set, and to make it work, it is required to add the logic to the check1
method.
@CheckerInfo( name = "Duplicate Atom Map Checker", description = "Checks for mapping duplicates in a reaction.", noErrorMessage = "No duplicate mappings found", moreErrorMessage = "duplicate mappings found", actionStringToken= "duplicateatommap") public class DuplicateAtomMapChecker extends ExternalStructureChecker { @Override protected StructureCheckerResult check1(Molecule molecule) { // we are checking only reactions if (molecule.isReaction()) { // create a list for atoms List<MolAtom> atomList = new ArrayList<MolAtom>(); if (ReactionSide.REACTANT.equals(getReactionSide()) || ReactionSide.BOTH.equals(getReactionSide())) { // if we are checking reactants, add the duplicates to the list atomList.addAll(getAtomsWithMappingDuplicates(RxnMolecule .getReaction(molecule).getReactants())); } if (ReactionSide.PRODUCT.equals(getReactionSide()) || ReactionSide.BOTH.equals(getReactionSide())) { // if we are checking products, add the duplicates to the list atomList.addAll(getAtomsWithMappingDuplicates(RxnMolecule .getReaction(molecule).getProducts())); } if (!atomList.isEmpty()) { // create and return the result return new DefaultExternalStructureCheckerResult(this, atomList, Collections.<MolBond> emptyList(), molecule, DUPLICATE_ATOM_MAP_CHECKER_ERROR); } } // return with no result return null; } /** * Returns a list of atoms that have the same mapping in the input set. * * @param molecules * the input set * @return a list of atoms that have the same mapping in the input set */ protected static List<MolAtom> getAtomsWithMappingDuplicates( Molecule[] molecules) { // create a list for results List<MolAtom> list = new ArrayList<MolAtom>(); // create a map for mapping - atom data Map<Integer, MolAtom> mappings = new HashMap<Integer, MolAtom>(); // for each molecule in the input set for (Molecule molecule : molecules) { // iterate all atoms in the molecule for (MolAtom atom : molecule.getAtomArray()) { int atomMap = atom.getAtomMap(); // get the atom map // if atom has mapping if (atomMap != 0) { // check if mapping already found if (mappings.containsKey(atomMap)) { // if the list not contains the other atom with same // mapping, add it to the list if (!list.contains(mappings.get(atomMap))) { list.add(mappings.get(atomMap)); } list.add(atom); // add atom to the error list } else { mappings.put(atomMap, atom); // add mapping to the // mappings set } } } } // return the result return list; }
After implementing the checker, it will highlight the errors in MarvinSketch.