Implementing a new Structure Checker

    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 /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;
            }
        }
    The `ExternalStructureChecker` superclass requires a `String` 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 the `getReactionSide()` 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.

    images/download/attachments/1806529/highlight_msketch.png