From f924784e83a6ecf87a811e5671a7c22ecb31609f Mon Sep 17 00:00:00 2001 From: John May Date: Wed, 1 Jun 2016 22:23:31 +0100 Subject: [PATCH] Correct parsing and building of reaction SMARTS queries. Relatively simple adaption to existing parser. --- .../matchers/smarts/ReactionRole.java | 65 +++++++++++++++++++ .../cdk/smiles/smarts/parser/ASTGroup.java | 17 +++++ .../smarts/parser/SmartsQueryVisitor.java | 42 +++++++++++- .../cdk/smiles/smarts/parser/SMARTSParser.jjt | 26 ++++++-- .../smarts/parser/SmartsQueryVisitorTest.java | 24 +++++++ 5 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 tool/smarts/src/main/java/org/openscience/cdk/isomorphism/matchers/smarts/ReactionRole.java diff --git a/tool/smarts/src/main/java/org/openscience/cdk/isomorphism/matchers/smarts/ReactionRole.java b/tool/smarts/src/main/java/org/openscience/cdk/isomorphism/matchers/smarts/ReactionRole.java new file mode 100644 index 00000000000..def8ad24059 --- /dev/null +++ b/tool/smarts/src/main/java/org/openscience/cdk/isomorphism/matchers/smarts/ReactionRole.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2016 John May + * + * Contact: cdk-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. All we ask is that proper credit is given + * for our work, which includes - but is not limited to - adding the above + * copyright notice to the beginning of your source code files, and to any + * copyright notice that you may distribute with programs based on this work. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 U + */ +package org.openscience.cdk.isomorphism.matchers.smarts; + +import org.openscience.cdk.interfaces.IAtom; +import org.openscience.cdk.interfaces.IChemObjectBuilder; + +/** + * Matches atoms with a particular role in a reaction. + */ +public class ReactionRole extends SMARTSAtom { + + public static final int ROLE_REACTANT = 0x1; + public static final int ROLE_AGENT = 0x2; + public static final int ROLE_PRODUCT = 0x4; + public static final int ROLE_ANY = ROLE_REACTANT | ROLE_PRODUCT | ROLE_AGENT; + + private final int role; + + public final static ReactionRole RoleReactant = new ReactionRole(null, ROLE_REACTANT); + public final static ReactionRole RoleAgent = new ReactionRole(null, ROLE_AGENT); + public final static ReactionRole RoleProduct = new ReactionRole(null, ROLE_PRODUCT); + + public ReactionRole(IChemObjectBuilder builder, int role) { + super(builder); + this.role = role; + } + + @Override + public boolean matches(IAtom atom) { + return true; // TODO + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if ((role & ROLE_REACTANT) != 0) + sb.append("Reactant"); + if ((role & ROLE_AGENT) != 0) + sb.append("Agent"); + if ((role & ROLE_PRODUCT) != 0) + sb.append("Product"); + return "ReactionRole(" + sb.toString() + ")"; + } +} diff --git a/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/ASTGroup.java b/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/ASTGroup.java index a1c3c05da53..b16fd65b4e6 100644 --- a/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/ASTGroup.java +++ b/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/ASTGroup.java @@ -17,6 +17,8 @@ */ package org.openscience.cdk.smiles.smarts.parser; +import org.openscience.cdk.isomorphism.matchers.smarts.ReactionRole; + /** * An AST node. It represents the group notation (.) of smarts. * @@ -28,6 +30,13 @@ */ class ASTGroup extends SimpleNode { + static final int ROLE_REACTANT = ReactionRole.ROLE_REACTANT; + static final int ROLE_AGENT = ReactionRole.ROLE_AGENT; + static final int ROLE_PRODUCT = ReactionRole.ROLE_PRODUCT; + static final int ROLE_ANY = ReactionRole.ROLE_ANY; + + private int role = ROLE_ANY; + /** * Creates a new instance * @@ -47,6 +56,14 @@ public ASTGroup(SMARTSParser p, int id) { super(p, id); } + public void setRole(int role) { + this.role = role; + } + + public int getRole() { + return this.role; + } + /* * (non-Javadoc) * @see diff --git a/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitor.java b/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitor.java index aa4b85cecbf..a343360e0f1 100644 --- a/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitor.java +++ b/tool/smarts/src/main/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitor.java @@ -50,6 +50,7 @@ import org.openscience.cdk.isomorphism.matchers.smarts.NonCHHeavyAtom; import org.openscience.cdk.isomorphism.matchers.smarts.OrderQueryBond; import org.openscience.cdk.isomorphism.matchers.smarts.PeriodicGroupNumberAtom; +import org.openscience.cdk.isomorphism.matchers.smarts.ReactionRole; import org.openscience.cdk.isomorphism.matchers.smarts.RecursiveSmartsAtom; import org.openscience.cdk.isomorphism.matchers.smarts.RingBond; import org.openscience.cdk.isomorphism.matchers.smarts.RingIdentifierAtom; @@ -211,13 +212,48 @@ public Object visit(ASTStart node, Object data) { return node.jjtGetChild(0).jjtAccept(this, data); } - // TODO: No QueryReaction API public Object visit(ASTReaction node, Object data) { - return node.jjtGetChild(0).jjtAccept(this, data); + IAtomContainer query = new QueryAtomContainer(builder); + for (int grpIdx = 0; grpIdx < node.jjtGetNumChildren(); grpIdx++) { + + int rollback = query.getAtomCount(); + + ASTGroup group = (ASTGroup) node.jjtGetChild(grpIdx); + group.jjtAccept(this, query); + + // fill in the roles for newly create atoms + if (group.getRole() != ASTGroup.ROLE_ANY) { + IQueryAtom roleQueryAtom = null; + + // use single instances + switch (group.getRole()) { + case ASTGroup.ROLE_REACTANT: + roleQueryAtom = ReactionRole.RoleReactant; + break; + case ASTGroup.ROLE_AGENT: + roleQueryAtom = ReactionRole.RoleAgent; + break; + case ASTGroup.ROLE_PRODUCT: + roleQueryAtom = ReactionRole.RoleProduct; + break; + } + + if (roleQueryAtom != null) { + while (rollback < query.getAtomCount()) { + query.setAtom(rollback, LogicalOperatorAtom.and(roleQueryAtom, (IQueryAtom) query.getAtom(rollback))); + rollback++; + } + } + } + } + return query; } public Object visit(ASTGroup node, Object data) { - IAtomContainer fullQuery = new QueryAtomContainer(builder); + IAtomContainer fullQuery = (IAtomContainer) data; + + if (fullQuery == null) + fullQuery = new QueryAtomContainer(builder); // keeps track of component grouping int[] components = new int[0]; diff --git a/tool/smarts/src/main/jjtree/org/openscience/cdk/smiles/smarts/parser/SMARTSParser.jjt b/tool/smarts/src/main/jjtree/org/openscience/cdk/smiles/smarts/parser/SMARTSParser.jjt index 5a9b51af204..727a20e0f96 100644 --- a/tool/smarts/src/main/jjtree/org/openscience/cdk/smiles/smarts/parser/SMARTSParser.jjt +++ b/tool/smarts/src/main/jjtree/org/openscience/cdk/smiles/smarts/parser/SMARTSParser.jjt @@ -295,8 +295,7 @@ TOKEN_MGR_DECLS : { /** * Start ::= <#_WS> - * ReactionExpression ::= (">>" )? | - * ">" ">" | ">>" + * ReactionExpression ::= ? (">" ? ">" ?)? * GroupExpression ::= ["("] [")"] ( "." ["("] [")"] )* * SmartsExpression ::= ( ( [ ] ( | ) ) | @@ -341,11 +340,24 @@ ASTStart Start() #Start : {} void ReactionExpression() #Reaction : {} { - GroupExpression() ( ">>" [GroupExpression()] )? - | - ">" GroupExpression() ">" - | - ">>" GroupExpression() + [GroupExpression()] + ( + ">" + { + if(jjtree.nodeArity() > 0) + ((ASTGroup)jjtree.peekNode()).setRole(ASTGroup.ROLE_REACTANT); + } + [ + GroupExpression() + { + ((ASTGroup)jjtree.peekNode()).setRole(ASTGroup.ROLE_AGENT); + } + ] + ">" + [ + GroupExpression(){((ASTGroup)jjtree.peekNode()).setRole(ASTGroup.ROLE_PRODUCT);} + ] + )? } void GroupExpression() #Group : {} diff --git a/tool/smarts/src/test/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitorTest.java b/tool/smarts/src/test/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitorTest.java index 0a9c437f67a..d7b089141a2 100644 --- a/tool/smarts/src/test/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitorTest.java +++ b/tool/smarts/src/test/java/org/openscience/cdk/smiles/smarts/parser/SmartsQueryVisitorTest.java @@ -1281,4 +1281,28 @@ public void testRing() throws Exception { visit("[$([C;#12]=1CCCCC1)]"); } + @Test + public void reaction() throws Exception { + visit("CCO.CC(=O)O>[H+]>CCOC(=O)C.O"); + } + + @Test + public void reactionNoAgents() throws Exception { + visit("CCO.CC(=O)O>>CCOC(=O)C.O"); + } + + @Test + public void reactionNoProduct() throws Exception { + visit("CCO.CC(=O)O>>"); + } + + @Test + public void reactionNoReactant() throws Exception { + visit(">>CCOC(=O)C.O"); + } + + @Test + public void reactionOnlyAgents() throws Exception { + visit(">>CCOC(=O)C.O"); + } }