/*
* Copyright (c) 2018 NextMove Software
*
* 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 USA
*/
package org.openscience.cdk.isomorphism.matchers;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.ReactionRole;
import org.openscience.cdk.config.Elements;
import org.openscience.cdk.graph.Cycles;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IAtomType;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.isomorphism.DfPattern;
import static org.openscience.cdk.isomorphism.matchers.Expr.Type.*;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.Objects;
/**
* A expression stores a predicate tree for checking properties of atoms
* and bonds.
*
* Expr expr = new Expr(ELEMENT, 6);
* if (expr.matches(atom)) {
* // expression matches if atom is a carbon!
* }
*
* An expression is composed of an {@link Type}, an optional 'value', and
* optionally one or more 'sub-expressions'. Each expr can either be a leaf or
* an intermediate (logical) node. The simplest expression trees contain a
* single leaf node:
*
* new Expr(IS_AROMATIC); // matches any aromatic atom
* new Expr(ELEMENT, 6); // matches any carbon atom (atomic num=6)
* new Expr(VALENCE, 4); // matches an atom with valence 4
* new Expr(DEGREE, 1); // matches a terminal atom, e.g. -OH, =O
* new Expr(IS_IN_RING); // matches any atom marked as in a ring
* new Expr(IS_HETERO); // matches anything other than carbon or nitrogen
* new Expr(TRUE); // any atom
*
* Logical internal nodes combine one or two sub-expressions with conjunction
* (and), disjunction (or), and negation (not).
*
* Consider the following expression tree, is matches fluorine, chlorine, or
* bromine.
*
* OR
* / \
* F OR
* / \
* Cl Br
*
* We can construct this tree as follows:
*
* Expr expr = new Expr(ELEMENT, 9) // F
* .or(new Expr(ELEMENT, 17)) // Cl
* .or(new Expr(ELEMENT, 35)) // Br
* A more verbose construction could also be used:
*
* Expr leafF = new Expr(ELEMENT, 9); // F
* Expr leafCl = new Expr(ELEMENT, 17); // Cl
* Expr leafBr = new Expr(ELEMENT, 35); // Br
* Expr node4 = new Expr(OR, leaf2, leaf3);
* Expr node5 = new Expr(OR, leaf1, node4);
*
*
* Expressions can be used to match bonds. Note some expressions apply to either
* atoms or bonds.
*
* new Expr(TRUE); // any bond
* new Expr(IS_IN_RING); // any ring bond
* new Expr(ALIPHATIC_ORDER, 2); // double bond
*
* See the documentation for {@link Type}s for a detail explanation of
* each type.
*/
public final class Expr {
/** Sentinel value for indicating the stereochemistry configuration
* is not yet known. Since stereochemistry depends on the ordering
* of neighbors we can't check this until those neighbors are
* matched. */
public static final int UNKNOWN_STEREO = -1;
// the expression type
private Type type;
// used for primitive leaf types
private int value;
// used for unary and binary types; not, and, or
private Expr left, right;
// user for recursive expression types
private IAtomContainer query;
private DfPattern ptrn;
/**
* Creates an atom expression that will always match ({@link Type#TRUE}).
*/
public Expr() {
this(Type.TRUE);
}
/**
* Creates an atom expression for the specified primitive.
*/
public Expr(Type op) {
setPrimitive(op);
}
/**
* Creates an atom expression for the specified primitive and 'value'.
*/
public Expr(Type op, int val) {
setPrimitive(op, val);
}
/**
* Creates a logical atom expression for the specified.
*/
public Expr(Type op, Expr left, Expr right) {
setLogical(op, left, right);
}
/**
* Creates a recursive atom expression.
*/
public Expr(Type op, IAtomContainer mol) {
setRecursive(op, mol);
}
/**
* Copy-constructor, creates a shallow copy of the provided expression.
*
* @param expr the expre
*/
public Expr(final Expr expr) {
set(expr);
}
private static boolean eq(Integer a, int b) {
return a != null && a == b;
}
private static int unbox(Integer x) {
return x != null ? x : 0;
}
private static boolean isInRingSize(IAtom atom, IBond prev, IAtom beg,
int size, int req) {
atom.setFlag(CDKConstants.VISITED, true);
for (IBond bond : atom.bonds()) {
if (bond == prev)
continue;
IAtom nbr = bond.getOther(atom);
if (nbr.equals(beg))
return size == req;
else if (size < req &&
!nbr.getFlag(CDKConstants.VISITED) &&
isInRingSize(nbr, bond, beg, size + 1, req))
return true;
}
atom.setFlag(CDKConstants.VISITED, false);
return false;
}
private static boolean isInRingSize(IAtom atom, int size) {
for (IAtom a : atom.getContainer().atoms())
a.setFlag(CDKConstants.VISITED, false);
return isInRingSize(atom, null, atom, 1, size);
}
private static boolean isInSmallRingSize(IAtom atom, int size) {
IAtomContainer mol = atom.getContainer();
int[] distTo = new int[mol.getAtomCount()];
Arrays.fill(distTo, 1 + distTo.length);
distTo[atom.getIndex()] = 0;
Deque queue = new ArrayDeque<>();
queue.push(atom);
int smallest = 1 + distTo.length;
while (!queue.isEmpty()) {
IAtom a = queue.poll();
int dist = 1 + distTo[a.getIndex()];
for (IBond b : a.bonds()) {
IAtom nbr = b.getOther(a);
if (dist < distTo[nbr.getIndex()]) {
distTo[nbr.getIndex()] = dist;
queue.add(nbr);
} else if (dist != 2 + distTo[nbr.getIndex()]) {
int tmp = dist + distTo[nbr.getIndex()];
if (tmp < smallest)
smallest = tmp;
}
}
if (2 * dist > 1 + size)
break;
}
return smallest == size;
}
/**
* Internal match methods - does not null check.
*
* @param type the type
* @param atom the atom
* @return the expression matches
*/
private boolean matches(Type type, IAtom atom, int stereo) {
switch (type) {
// predicates
case TRUE:
return true;
case FALSE:
return false;
case IS_AROMATIC:
return atom.isAromatic();
case IS_ALIPHATIC:
return !atom.isAromatic();
case IS_IN_RING:
return atom.isInRing();
case IS_IN_CHAIN:
return !atom.isInRing();
case IS_HETERO:
return !eq(atom.getAtomicNumber(), 6) &&
!eq(atom.getAtomicNumber(), 1);
case HAS_IMPLICIT_HYDROGEN:
return atom.getImplicitHydrogenCount() != null &&
atom.getImplicitHydrogenCount() > 0;
case HAS_ISOTOPE:
return atom.getMassNumber() != null;
case HAS_UNSPEC_ISOTOPE:
return atom.getMassNumber() == null;
case UNSATURATED:
for (IBond bond : atom.bonds())
if (bond.getOrder() == IBond.Order.DOUBLE)
return true;
return false;
// value primitives
case ELEMENT:
return eq(atom.getAtomicNumber(), value);
case ALIPHATIC_ELEMENT:
return !atom.isAromatic() &&
eq(atom.getAtomicNumber(), value);
case AROMATIC_ELEMENT:
return atom.isAromatic() &&
eq(atom.getAtomicNumber(), value);
case IMPL_H_COUNT:
return eq(atom.getImplicitHydrogenCount(), value);
case TOTAL_H_COUNT:
if (atom.getImplicitHydrogenCount() != null
&& atom.getImplicitHydrogenCount() > value)
return false;
return getTotalHCount(atom) == value;
case DEGREE:
return atom.getBondCount() == value;
case HEAVY_DEGREE: // XXX: CDK quirk
return atom.getBondCount() - (getTotalHCount(atom) - atom.getImplicitHydrogenCount()) == value;
case TOTAL_DEGREE:
int x = atom.getBondCount() + unbox(atom.getImplicitHydrogenCount());
return x == value;
case VALENCE:
int v = unbox(atom.getImplicitHydrogenCount());
if (v > value)
return false;
for (IBond bond : atom.bonds()) {
if (bond.getOrder() != null)
v += bond.getOrder().numeric();
}
return v == value;
case ISOTOPE:
return eq(atom.getMassNumber(), value);
case FORMAL_CHARGE:
return eq(atom.getFormalCharge(), value);
case RING_BOND_COUNT:
if (!atom.isInRing() || atom.getBondCount() < value)
return false;
int rbonds = 0;
for (IBond bond : atom.bonds())
rbonds += bond.isInRing() ? 1 : 0;
return rbonds == value;
case RING_COUNT:
return atom.isInRing() && getRingCount(atom) == value;
case RING_SMALLEST:
return atom.isInRing() && isInSmallRingSize(atom, value);
case RING_SIZE:
return atom.isInRing() && isInRingSize(atom, value);
case HETERO_SUBSTITUENT_COUNT:
if (atom.getBondCount() < value)
return false;
int q = 0;
for (IBond bond : atom.bonds())
q += matches(Type.IS_HETERO, bond.getOther(atom), stereo) ? 1 : 0;
return q == value;
case INSATURATION:
int db = 0;
for (IBond bond : atom.bonds())
if (bond.getOrder() == IBond.Order.DOUBLE)
db++;
return db == value;
case HYBRIDISATION_NUMBER:
IAtomType.Hybridization hyb = atom.getHybridization();
if (hyb == null)
return false;
switch (value) {
case 1:
return hyb == IAtomType.Hybridization.SP1;
case 2:
return hyb == IAtomType.Hybridization.SP2;
case 3:
return hyb == IAtomType.Hybridization.SP3;
case 4:
return hyb == IAtomType.Hybridization.SP3D1;
case 5:
return hyb == IAtomType.Hybridization.SP3D2;
case 6:
return hyb == IAtomType.Hybridization.SP3D3;
case 7:
return hyb == IAtomType.Hybridization.SP3D4;
case 8:
return hyb == IAtomType.Hybridization.SP3D5;
default:
return false;
}
case PERIODIC_GROUP:
return atom.getAtomicNumber() != null &&
Elements.ofNumber(atom.getAtomicNumber()).group() == value;
case STEREOCHEMISTRY:
return stereo == UNKNOWN_STEREO || stereo == value;
case REACTION_ROLE:
ReactionRole role = atom.getProperty(CDKConstants.REACTION_ROLE);
return role != null && role.ordinal() == value;
case AND:
return left.matches(left.type, atom, stereo) &&
right.matches(right.type, atom, stereo);
case OR:
return left.matches(left.type, atom, stereo) ||
right.matches(right.type, atom, stereo);
case NOT:
return !left.matches(left.type, atom, stereo) ||
// XXX: ugly but needed, when matching stereo
(stereo == UNKNOWN_STEREO &&
(left.type == STEREOCHEMISTRY ||
left.type == OR && left.left.type == STEREOCHEMISTRY));
case RECURSIVE:
if (ptrn == null)
ptrn = DfPattern.findSubstructure(query);
return ptrn.matchesRoot(atom);
default:
throw new IllegalArgumentException("Cannot match AtomExpr, type=" + type);
}
}
public boolean matches(IBond bond, int stereo) {
switch (type) {
case TRUE:
return true;
case FALSE:
return false;
case ALIPHATIC_ORDER:
return !bond.isAromatic() &&
bond.getOrder() != null &&
bond.getOrder().numeric() == value;
case ORDER:
return bond.getOrder() != null &&
bond.getOrder().numeric() == value;
case IS_AROMATIC:
return bond.isAromatic();
case IS_ALIPHATIC:
return !bond.isAromatic();
case IS_IN_RING:
return bond.isInRing();
case IS_IN_CHAIN:
return !bond.isInRing();
case SINGLE_OR_AROMATIC:
return bond.isAromatic() ||
IBond.Order.SINGLE.equals(bond.getOrder());
case DOUBLE_OR_AROMATIC:
return bond.isAromatic() ||
IBond.Order.DOUBLE.equals(bond.getOrder());
case SINGLE_OR_DOUBLE:
return IBond.Order.SINGLE.equals(bond.getOrder()) ||
IBond.Order.DOUBLE.equals(bond.getOrder());
case STEREOCHEMISTRY:
return stereo == UNKNOWN_STEREO || value == stereo;
case AND:
return left.matches(bond, stereo) && right.matches(bond, stereo);
case OR:
return left.matches(bond, stereo) || right.matches(bond, stereo);
case NOT:
return !left.matches(bond, stereo) ||
// XXX: ugly but needed, when matching stereo
(stereo == UNKNOWN_STEREO &&
(left.type == STEREOCHEMISTRY ||
left.type == OR && left.left.type == STEREOCHEMISTRY));
default:
throw new IllegalArgumentException("Cannot match BondExpr, type=" + type);
}
}
public boolean matches(IBond bond) {
return matches(bond, UNKNOWN_STEREO);
}
private static int getTotalHCount(IAtom atom) {
int h = unbox(atom.getImplicitHydrogenCount());
for (IBond bond : atom.bonds())
if (eq(bond.getOther(atom).getAtomicNumber(), 1))
h++;
return h;
}
/**
* Test whether this expression matches an atom instance.
*
* @param atom an atom (nullable)
* @return the atom matches
*/
public boolean matches(final IAtom atom) {
return atom != null && matches(type, atom, UNKNOWN_STEREO);
}
public boolean matches(final IAtom atom, final int stereo) {
return atom != null && matches(type, atom, stereo);
}
/**
* Utility, combine this expression with another, using conjunction.
* The expression will only match if both conditions are met.
*
* @param expr the other expression
* @return self for chaining
*/
public Expr and(Expr expr) {
if (type == Type.TRUE) {
set(expr);
} else if (expr.type != Type.TRUE) {
if (type.isLogical() && !expr.type.isLogical()) {
if (type == AND)
right.and(expr);
else if (type != NOT)
setLogical(Type.AND, expr, new Expr(this));
else
setLogical(Type.AND, expr, new Expr(this));
} else {
setLogical(Type.AND, new Expr(this), expr);
}
}
return this;
}
/**
* Utility, combine this expression with another, using disjunction.
* The expression will match if either conditions is met.
* @param expr the other expression
* @return self for chaining
*/
public Expr or(Expr expr) {
if (type == Type.TRUE ||
type == Type.FALSE ||
type == NONE) {
set(expr);
} else if (expr.type != Type.TRUE &&
expr.type != Type.FALSE &&
expr.type != Type.NONE) {
if (type.isLogical() && !expr.type.isLogical()) {
if (type == OR)
right.or(expr);
else if (type != NOT)
setLogical(Type.OR, expr, new Expr(this));
else
setLogical(Type.OR, new Expr(this), expr);
}
else
setLogical(Type.OR, new Expr(this), expr);
}
return this;
}
/**
* Negate the expression, the expression will not return true only if
* the condition is not met. Some expressions have explicit types
* that are more efficient to use, for example:
* IS_IN_RING => NOT(IS_IN_RING) => IS_IN_CHAIN
. This
* negation method will use the more efficient type where possible.
* {@code
* Expr e = new Expr(ELEMENT, 8); // SMARTS: [#8]
* e.negate(); // SMARTS: [!#8]
* }
* @return self for chaining
*/
public Expr negate() {
switch (type) {
case TRUE:
type = Type.FALSE;
break;
case FALSE:
type = Type.TRUE;
break;
case HAS_ISOTOPE:
type = Type.HAS_UNSPEC_ISOTOPE;
break;
case HAS_UNSPEC_ISOTOPE:
type = Type.HAS_ISOTOPE;
break;
case IS_AROMATIC:
type = Type.IS_ALIPHATIC;
break;
case IS_ALIPHATIC:
type = Type.IS_AROMATIC;
break;
case IS_IN_RING:
type = Type.IS_IN_CHAIN;
break;
case IS_IN_CHAIN:
type = Type.IS_IN_RING;
break;
case NOT:
set(this.left);
break;
default:
setLogical(Type.NOT, new Expr(this), null);
break;
}
return this;
}
/**
* Set the primitive value of this atom expression.
*
* @param type the type of expression
* @param val the value to check
*/
public void setPrimitive(Type type, int val) {
if (type.hasValue()) {
this.type = type;
this.value = val;
this.left = null;
this.right = null;
this.query = null;
} else {
throw new IllegalArgumentException("Value provided for non-value "
+ "expression type!");
}
}
/**
* Set the primitive value of this atom expression.
*
* @param type the type of expression
*/
public void setPrimitive(Type type) {
if (!type.hasValue() && !type.isLogical()) {
this.type = type;
this.value = -1;
this.left = null;
this.right = null;
this.query = null;
} else {
throw new IllegalArgumentException("Expression type requires a value!");
}
}
/**
* Set the logical value of this atom expression.
*
* @param type the type of expression
* @param left the left sub-expression
* @param right the right sub-expression
*/
public void setLogical(Type type, Expr left, Expr right) {
switch (type) {
case AND:
case OR:
this.type = type;
this.value = 0;
this.left = left;
this.right = right;
this.query = null;
break;
case NOT:
this.type = type;
if (left != null && right == null)
this.left = left;
else if (left == null && right != null)
this.left = right;
else if (left != null)
throw new IllegalArgumentException("Only one sub-expression"
+ " should be provided"
+ " for NOT expressions!");
this.query = null;
this.value = 0;
break;
default:
throw new IllegalArgumentException("Left/Right sub expressions "
+ "supplied for "
+ " non-logical operator!");
}
}
/**
* Set the recursive value of this atom expression.
*
* @param type the type of expression
* @param mol the recursive pattern
*/
private void setRecursive(Type type, IAtomContainer mol) {
switch (type) {
case RECURSIVE:
this.type = type;
this.value = 0;
this.left = null;
this.right = null;
this.query = mol;
this.ptrn = null;
break;
default:
throw new IllegalArgumentException();
}
}
/**
* Set this expression to another (shallow copy).
*
* @param expr the other expression
*/
public void set(Expr expr) {
this.type = expr.type;
this.value = expr.value;
this.left = expr.left;
this.right = expr.right;
this.query = expr.query;
}
/**
* Access the type of the atom expression.
*
* @return the expression type
*/
public Type type() {
return type;
}
/**
* Access the value of this atom expression being
* tested.
*
* @return the expression value
*/
public int value() {
return value;
}
/**
* Access the left sub-expression of this atom expression being
* tested.
*
* @return the expression value
*/
public Expr left() {
return left;
}
/**
* Access the right sub-expression of this atom expression being
* tested.
*
* @return the expression value
*/
public Expr right() {
return right;
}
/**
* Access the sub-query, only applicable to recursive types.
* @return the sub-query
* @see Type#RECURSIVE
*/
public IAtomContainer subquery() {
return query;
}
/* Property Caches */
private static LoadingCache cacheRCounts;
private static int[] getRingCounts(IAtomContainer mol) {
int[] rcounts = new int[mol.getAtomCount()];
for (int[] path : Cycles.mcb(mol).paths()) {
for (int i = 1; i < path.length; i++) {
rcounts[path[i]]++;
}
}
return rcounts;
}
private static int getRingCount(IAtom atom) {
final IAtomContainer mol = atom.getContainer();
if (cacheRCounts == null) {
cacheRCounts = CacheBuilder.newBuilder()
.maximumWeight(1000) // 4KB
.weigher(new Weigher() {
@Override
public int weigh(IAtomContainer key,
int[] value) {
return value.length;
}
})
.build(new CacheLoader() {
@Override
public int[] load(IAtomContainer key) throws Exception {
return getRingCounts(key);
}
});
}
return cacheRCounts.getUnchecked(mol)[atom.getIndex()];
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Expr atomExpr = (Expr) o;
return type == atomExpr.type &&
value == atomExpr.value &&
Objects.equals(left, atomExpr.left) && Objects.equals(right, atomExpr.right);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hash(type, value, left, right);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(type);
if (type.isLogical()) {
switch (type) {
case NOT:
sb.append('(').append(left).append(')');
break;
case OR:
case AND:
sb.append('(').append(left).append(',').append(right).append(')');
break;
}
} else if (type.hasValue()) {
sb.append('=').append(value);
} else if (type == Type.RECURSIVE) {
sb.append("(...)");
}
return sb.toString();
}
/**
* Types of expression, for use in the {@link Expr} tree object.
*/
public enum Type {
/** Always returns true. */
TRUE,
/** Always returns false. */
FALSE,
/** Return true if {@link IAtom#isAromatic} or {@link IBond#isAromatic} is
* true. */
IS_AROMATIC,
/** Return true if {@link IAtom#isAromatic} or {@link IBond#isAromatic} is
* flase. */
IS_ALIPHATIC,
/** Return true if {@link IAtom#isInRing()} or {@link IBond#isInRing} is
* true. */
IS_IN_RING,
/** Return true if {@link IAtom#isInRing()} or {@link IBond#isInRing} is
* false. */
IS_IN_CHAIN,
/** Return true if {@link IAtom#getAtomicNumber()} is neither 6 (carbon)
* nor 1 (hydrogen). */
IS_HETERO,
/** Return true if {@link IAtom#getAtomicNumber()} is neither 6 (carbon)
* nor 1 (hydrogen) and the atom is aliphatic. */
IS_ALIPHATIC_HETERO,
/** True if the hydrogen count ({@link IAtom#getImplicitHydrogenCount()})
* is > 0. */
HAS_IMPLICIT_HYDROGEN,
/** True if the atom mass ({@link IAtom#getMassNumber()}) is non-null. */
HAS_ISOTOPE,
/** True if the atom mass ({@link IAtom#getMassNumber()}) is null
* (unspecified). */
HAS_UNSPEC_ISOTOPE,
/** True if the atom is adjacent to a hetero atom. */
HAS_HETERO_SUBSTITUENT,
/** True if the atom is adjacent to an aliphatic hetero atom. */
HAS_ALIPHATIC_HETERO_SUBSTITUENT,
/** True if the atom is unsaturated.
* TODO: check if CACTVS if double bond to non-carbons are counted. */
UNSATURATED,
/** True if the bond order ({@link IBond#getOrder()}) is single or double. */
SINGLE_OR_DOUBLE,
/** True if the bond order ({@link IBond#getOrder()}) is single or the bond
* is marked as aromatic ({@link IBond#isAromatic()}). */
SINGLE_OR_AROMATIC,
/** True if the bond order ({@link IBond#getOrder()}) is double or the bond
* is marked as aromatic ({@link IBond#isAromatic()}). */
DOUBLE_OR_AROMATIC,
/* Expressions that take values */
/** True if the atomic number ({@link IAtom#getAtomicNumber()} ()})
* of an atom equals the specified 'value'. */
ELEMENT,
/** True if the atomic number ({@link IAtom#getAtomicNumber()} ()})
* of an atom equals the specified 'value' and {@link IAtom#isAromatic()}
* is false. */
ALIPHATIC_ELEMENT,
/** True if the atomic number ({@link IAtom#getAtomicNumber()} ()})
* of an atom equals the specified 'value' and {@link IAtom#isAromatic()}
* is true. */
AROMATIC_ELEMENT,
/** True if the hydrogen count ({@link IAtom#getImplicitHydrogenCount()})
* of an atom equals the specified 'value'. */
IMPL_H_COUNT,
/** True if the total hydrogen count of an atom equals the specified
* 'value'. */
TOTAL_H_COUNT,
/** True if the degree ({@link IAtom#getBondCount()}) of an atom
* equals the specified 'value'. */
DEGREE,
/** True if the total degree ({@link IAtom#getBondCount()} +
* {@link IAtom#getImplicitHydrogenCount()}) of an atom equals the
* specified 'value'. */
TOTAL_DEGREE,
/** True if the degree ({@link IAtom#getBondCount()}) - any hydrogen atoms
x* equals the specified 'value'. */
HEAVY_DEGREE,
/** True if the valence of an atom equals the specified 'value'. */
VALENCE,
/** True if the mass ({@link IAtom#getMassNumber()}) of an atom equals the
* specified 'value'. */
ISOTOPE,
/** True if the formal charge ({@link IAtom#getFormalCharge()}) of an atom
* equals the specified 'value'. */
FORMAL_CHARGE,
/** True if the ring bond count of an atom equals the specified 'value'. */
RING_BOND_COUNT,
/** True if the number of rings this atom belongs to matches the specified
* 'value'. Here a ring means a member of the Minimum Cycle Basis (MCB)
* (aka Smallest Set of Smallest Rings). Since the MCB is non-unique the
* numbers often don't make sense of bicyclo systems. */
RING_COUNT,
/** True if the smallest ring this atom belongs to equals the specified
* 'value' */
RING_SMALLEST,
/** True if the this atom belongs to a ring equal to the specified
* 'value' */
RING_SIZE,
/** True if the this atom hybridisation ({@link IAtom#getHybridization()})
* is equal to the specified 'value'. SP1=1, SP2=2, SP3=3, SP3D1=4,
* SP3D2=5, SP3D3=6, SP3D4=7, SP3D5=8. */
HYBRIDISATION_NUMBER,
/** True if the number hetero atoms (see {@link #IS_HETERO}) this atom is
* next to is equal to the specified value. */
HETERO_SUBSTITUENT_COUNT,
/** True if the number hetero atoms (see {@link #IS_ALIPHATIC_HETERO}) this atom is
* next to is equal to the specified value. */
ALIPHATIC_HETERO_SUBSTITUENT_COUNT,
/** True if the periodic table group of this atom is equal to the specified
* value. For example halogens are Group '17'.*/
PERIODIC_GROUP,
/** True if the number of double bonds equals the specified value.
* TODO: check if CACTVS if double bond to non-carbons are counted. */
INSATURATION,
/** True if an atom has the specified reaction role. */
REACTION_ROLE,
/** True if an atom or bond has the specified stereochemistry value, see
* ({@link org.openscience.cdk.interfaces.IStereoElement}) for a list of
* values.*/
STEREOCHEMISTRY,
/** True if the bond order {@link IBond#getOrder()} equals the specified
* value and the bond is not marked as aromatic
* ({@link IAtom#isAromatic()}). */
ALIPHATIC_ORDER,
/** True if the bond order {@link IBond#getOrder()} equals the specified
* value and the bond, aromaticity is not check. */
ORDER,
/* Binary/unary internal nodes */
/** True if both the subexpressions are true. */
AND,
/** True if both either subexpressions are true. */
OR,
/** True if the subexpression is not true. */
NOT,
/** Recursive query. */
RECURSIVE,
/** Undefined expression type. */
NONE;
boolean isLogical() {
switch (this) {
case OR:
case NOT:
case AND:
return true;
default:
return false;
}
}
boolean hasValue() {
switch (this) {
case OR:
case NOT:
case AND:
case TRUE:
case FALSE:
case NONE:
case IS_AROMATIC:
case IS_ALIPHATIC:
case IS_IN_RING:
case IS_IN_CHAIN:
case IS_HETERO:
case IS_ALIPHATIC_HETERO:
case HAS_IMPLICIT_HYDROGEN:
case HAS_ISOTOPE:
case HAS_UNSPEC_ISOTOPE:
case HAS_ALIPHATIC_HETERO_SUBSTITUENT:
case HAS_HETERO_SUBSTITUENT:
case UNSATURATED:
case SINGLE_OR_AROMATIC:
case SINGLE_OR_DOUBLE:
case DOUBLE_OR_AROMATIC:
case RECURSIVE:
return false;
default:
return true;
}
}
}
}