1 /*
2  * Copyright (c) 2018 John Mayfield <jwmay@users.sf.net>
3  *
4  * Contact: cdk-devel@lists.sourceforge.net
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or (at
9  * your option) any later version. All we ask is that proper credit is given
10  * for our work, which includes - but is not limited to - adding the above
11  * copyright notice to the beginning of your source code files, and to any
12  * copyright notice that you may distribute with programs based on this work.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22  */
23 
24 package org.openscience.cdk.smarts;
25 
26 import org.junit.Test;
27 import org.openscience.cdk.AtomRef;
28 import org.openscience.cdk.BondRef;
29 import org.openscience.cdk.CDKConstants;
30 import org.openscience.cdk.config.Elements;
31 import org.openscience.cdk.interfaces.IAtom;
32 import org.openscience.cdk.interfaces.IAtomContainer;
33 import org.openscience.cdk.interfaces.IBond;
34 import org.openscience.cdk.interfaces.IStereoElement;
35 import org.openscience.cdk.isomorphism.matchers.Expr;
36 import org.openscience.cdk.isomorphism.matchers.QueryAtom;
37 import org.openscience.cdk.isomorphism.matchers.QueryAtomContainer;
38 import org.openscience.cdk.isomorphism.matchers.QueryBond;
39 import org.openscience.cdk.silent.AtomContainer;
40 import org.openscience.cdk.smarts.Smarts;
41 
42 import static org.hamcrest.CoreMatchers.anyOf;
43 import static org.hamcrest.CoreMatchers.is;
44 import static org.junit.Assert.assertFalse;
45 import static org.junit.Assert.assertThat;
46 import static org.junit.Assert.assertTrue;
47 import static org.openscience.cdk.isomorphism.matchers.Expr.Type.*;
48 
49 public class SmartsExprReadTest {
50 
expr(Expr.Type type)51     static Expr expr(Expr.Type type) {
52         return new Expr(type);
53     }
54 
expr(Expr.Type type, int val)55     static Expr expr(Expr.Type type, int val) {
56         return new Expr(type, val);
57     }
58 
and(Expr a, Expr b)59     static Expr and(Expr a, Expr b) {
60         return new Expr(AND, a, b);
61     }
62 
or(Expr a, Expr b)63     static Expr or(Expr a, Expr b) {
64         return new Expr(OR, a, b);
65     }
66 
getAtomExpr(IAtom atom)67     private static Expr getAtomExpr(IAtom atom) {
68         return ((QueryAtom) AtomRef.deref(atom)).getExpression();
69     }
70 
getBondExpr(IBond bond)71     private static Expr getBondExpr(IBond bond) {
72         return ((QueryBond) BondRef.deref(bond)).getExpression();
73     }
74 
getAtomExpr(String sma, int flav)75     static Expr getAtomExpr(String sma, int flav) {
76         IAtomContainer mol = new AtomContainer();
77         assertTrue(Smarts.parse(mol, sma, flav));
78         return getAtomExpr(mol.getAtom(0));
79     }
80 
getAtomExpr(String sma)81     static Expr getAtomExpr(String sma) {
82         return getAtomExpr(sma, Smarts.FLAVOR_LOOSE);
83     }
84 
getBondExpr(String sma, int flav)85     static Expr getBondExpr(String sma, int flav) {
86         IAtomContainer mol = new AtomContainer();
87         assertTrue(Smarts.parse(mol, sma, flav));
88         return getBondExpr(mol.getBond(0));
89     }
90 
getBondExpr(String sma)91     static Expr getBondExpr(String sma) {
92         return getBondExpr(sma, Smarts.FLAVOR_LOOSE);
93     }
94 
95     @Test
trailingOperator()96     public void trailingOperator() {
97         IAtomContainer mol = new AtomContainer();
98         assertFalse(Smarts.parse(mol, "[a#6,]"));
99         assertFalse(Smarts.parse(mol, "[a#6;]"));
100         assertFalse(Smarts.parse(mol, "[a#6&]"));
101         assertFalse(Smarts.parse(mol, "[a#6!]"));
102     }
103 
104     @Test
leadingOperator()105     public void leadingOperator() {
106         IAtomContainer mol = new AtomContainer();
107         assertFalse(Smarts.parse(mol, "[,a#6]"));
108         assertFalse(Smarts.parse(mol, "[;a#6]"));
109         assertFalse(Smarts.parse(mol, "[&a#6]"));
110         assertTrue(Smarts.parse(mol, "[!a#6]"));
111     }
112 
113     @Test
trailingBondOperator()114     public void trailingBondOperator() {
115         IAtomContainer mol = new AtomContainer();
116         assertFalse(Smarts.parse(mol, "*-,*"));
117         assertFalse(Smarts.parse(mol, "*-;*"));
118         assertFalse(Smarts.parse(mol, "*-&*"));
119         assertFalse(Smarts.parse(mol, "*-!*"));
120     }
121 
122     @Test
leadingBondOperator()123     public void leadingBondOperator() {
124         IAtomContainer mol = new AtomContainer();
125         assertFalse(Smarts.parse(mol, "*,-*"));
126         assertFalse(Smarts.parse(mol, "*;-*"));
127         assertFalse(Smarts.parse(mol, "*&-*"));
128         assertTrue(Smarts.parse(mol, "*!-*"));
129     }
130 
131     @Test
opPrecedence1()132     public void opPrecedence1() {
133         IAtomContainer mol = new AtomContainer();
134         assertTrue(Smarts.parse(mol, "[a#6,a#7]"));
135         Expr actual = getAtomExpr(mol.getAtom(0));
136         Expr expected = or(and(expr(IS_AROMATIC), expr(ELEMENT, 6)),
137                            and(expr(IS_AROMATIC), expr(ELEMENT, 7)));
138         assertThat(actual, is(expected));
139     }
140 
141     @Test
opPrecedence2()142     public void opPrecedence2() {
143         IAtomContainer mol = new AtomContainer();
144         assertTrue(Smarts.parse(mol, "[a;#6,#7]"));
145         Expr actual = getAtomExpr(mol.getAtom(0));
146         Expr expected = and(expr(IS_AROMATIC),
147                             or(expr(ELEMENT, 6), expr(ELEMENT, 7)));
148         assertThat(actual, is(expected));
149     }
150 
151     @Test
opPrecedence3()152     public void opPrecedence3() {
153         IAtomContainer mol = new AtomContainer();
154         assertTrue(Smarts.parse(mol, "[#6,#7;a]"));
155         Expr actual = getAtomExpr(mol.getAtom(0));
156         Expr expected = and(expr(IS_AROMATIC),
157                             or(expr(ELEMENT, 6), expr(ELEMENT, 7)));
158         assertThat(actual, is(expected));
159     }
160 
161     @Test
opPrecedence4()162     public void opPrecedence4() {
163         IAtomContainer mol = new AtomContainer();
164         assertTrue(Smarts.parse(mol, "[#6,#7a]"));
165         Expr actual = getAtomExpr(mol.getAtom(0));
166         Expr expected = or(expr(ELEMENT, 6),
167                            and(expr(ELEMENT, 7), expr(IS_AROMATIC)));
168         assertThat(actual, is(expected));
169     }
170 
171     @Test
opPrecedence5()172     public void opPrecedence5() {
173         IAtomContainer mol = new AtomContainer();
174         assertTrue(Smarts.parse(mol, "[#6&a,#7]"));
175         Expr actual = getAtomExpr(mol.getAtom(0));
176         Expr expected = or(expr(ELEMENT, 7),
177                            and(expr(ELEMENT, 6), expr(IS_AROMATIC)));
178         assertThat(actual, is(expected));
179     }
180 
181     @Test
orList()182     public void orList() {
183         IAtomContainer mol = new AtomContainer();
184         assertTrue(Smarts.parse(mol, "[F,Cl,Br,I]"));
185         Expr actual = getAtomExpr(mol.getAtom(0));
186         Expr expected = or(expr(ELEMENT, 9),
187                            or(expr(ELEMENT, 17),
188                               or(expr(ELEMENT, 35),
189                                  expr(ELEMENT, 53))));
190         assertThat(actual, is(expected));
191     }
192 
193     @Test
explicitHydrogen()194     public void explicitHydrogen() {
195         IAtomContainer mol = new AtomContainer();
196         assertTrue(Smarts.parse(mol, "[2H+]"));
197         Expr actual = getAtomExpr(mol.getAtom(0));
198         Expr expected = and(expr(ISOTOPE, 2),
199                             and(expr(ELEMENT, 1), expr(FORMAL_CHARGE, 1)));
200         assertThat(actual, is(expected));
201     }
202 
203     @Test
explicitHydrogenNeg()204     public void explicitHydrogenNeg() {
205         IAtomContainer mol = new AtomContainer();
206         assertTrue(Smarts.parse(mol, "[H-]"));
207         Expr actual = getAtomExpr(mol.getAtom(0));
208         Expr expected = and(expr(ELEMENT, 1),
209                             expr(FORMAL_CHARGE, -1));
210         assertThat(actual, is(expected));
211     }
212 
213     @Test
explicitHydrogenWithAtomMap()214     public void explicitHydrogenWithAtomMap() {
215         IAtomContainer mol = new AtomContainer();
216         assertTrue(Smarts.parse(mol, "[2H+:2]"));
217         Expr actual = getAtomExpr(mol.getAtom(0));
218         Expr expected = and(expr(ISOTOPE, 2),
219                             and(expr(ELEMENT, 1),
220                                 expr(FORMAL_CHARGE, 1)));
221         assertThat(mol.getAtom(0).getProperty(CDKConstants.ATOM_ATOM_MAPPING,
222                                               Integer.class),
223                    is(2));
224         assertThat(actual, is(expected));
225     }
226 
227     @Test
explicitHydrogenWithBadAtomMap()228     public void explicitHydrogenWithBadAtomMap() {
229         IAtomContainer mol = new AtomContainer();
230         assertFalse(Smarts.parse(mol, "[2H+:]"));
231     }
232 
233     @Test
nonExplicitHydrogen()234     public void nonExplicitHydrogen() {
235         IAtomContainer mol = new AtomContainer();
236         assertTrue(Smarts.parse(mol, "[2&H+]"));
237         Expr actual = getAtomExpr(mol.getAtom(0));
238         Expr expected = and(expr(ISOTOPE, 2),
239                             and(expr(TOTAL_H_COUNT, 1),
240                                 expr(FORMAL_CHARGE, +1)));
241         assertThat(actual, is(expected));
242     }
243 
244     @Test
nonExplicitHydrogen2()245     public void nonExplicitHydrogen2() {
246         IAtomContainer mol = new AtomContainer();
247         assertTrue(Smarts.parse(mol, "[2,H+]"));
248         Expr actual = getAtomExpr(mol.getAtom(0));
249         Expr expected = or(expr(ISOTOPE, 2),
250                            and(expr(TOTAL_H_COUNT, 1),
251                                expr(FORMAL_CHARGE, +1)));
252         assertThat(actual, is(expected));
253     }
254 
255     @Test
nonExplicitHydrogen3()256     public void nonExplicitHydrogen3() {
257         IAtomContainer mol = new AtomContainer();
258         assertTrue(Smarts.parse(mol, "[2H1+]"));
259         Expr actual = getAtomExpr(mol.getAtom(0));
260         Expr expected = and(expr(ISOTOPE, 2),
261                             and(expr(TOTAL_H_COUNT, 1),
262                                 expr(FORMAL_CHARGE, 1)));
263         assertThat(actual, is(expected));
264     }
265 
266     @Test
specifiedIsotope()267     public void specifiedIsotope() {
268         Expr actual   = getAtomExpr("[!0]");
269         Expr expected = expr(HAS_ISOTOPE);
270         assertThat(actual, is(expected));
271     }
272 
273     @Test
unspecifiedIsotope()274     public void unspecifiedIsotope() {
275         Expr actual   = getAtomExpr("[0]");
276         Expr expected = expr(HAS_UNSPEC_ISOTOPE);
277         assertThat(actual, is(expected));
278     }
279 
280     @Test
ringMembership()281     public void ringMembership() {
282         Expr actual   = getAtomExpr("[R]");
283         Expr expected = expr(IS_IN_RING);
284         assertThat(actual, is(expected));
285     }
286 
287     @Test
ringMembership2()288     public void ringMembership2() {
289         Expr actual   = getAtomExpr("[!R0]");
290         Expr expected = expr(IS_IN_RING);
291         assertThat(actual, is(expected));
292     }
293 
294     @Test
chainMembership()295     public void chainMembership() {
296         Expr actual   = getAtomExpr("[R0]");
297         Expr expected = expr(IS_IN_CHAIN);
298         assertThat(actual, is(expected));
299     }
300 
301     @Test
chainMembership2()302     public void chainMembership2() {
303         Expr actual   = getAtomExpr("[!R]");
304         Expr expected = expr(IS_IN_CHAIN);
305         assertThat(actual, is(expected));
306     }
307 
308     @Test
chainMembership3()309     public void chainMembership3() {
310         Expr actual   = getAtomExpr("[r0]");
311         Expr expected = expr(IS_IN_CHAIN);
312         assertThat(actual, is(expected));
313     }
314 
315     @Test
chainMembership4()316     public void chainMembership4() {
317         Expr actual   = getAtomExpr("[x0]");
318         Expr expected = expr(IS_IN_CHAIN);
319         assertThat(actual, is(expected));
320     }
321 
322     @Test
aromatic()323     public void aromatic() {
324         Expr actual   = getAtomExpr("[a]");
325         Expr expected = expr(IS_AROMATIC);
326         assertThat(actual, is(expected));
327     }
328 
329     @Test
aromatic2()330     public void aromatic2() {
331         Expr actual   = getAtomExpr("[!A]");
332         Expr expected = expr(IS_AROMATIC);
333         assertThat(actual, is(expected));
334     }
335 
336     @Test
aliphatic()337     public void aliphatic() {
338         Expr actual   = getAtomExpr("[A]");
339         Expr expected = expr(IS_ALIPHATIC);
340         assertThat(actual, is(expected));
341     }
342 
343     @Test
aliphatic2()344     public void aliphatic2() {
345         Expr actual   = getAtomExpr("[!a]");
346         Expr expected = expr(IS_ALIPHATIC);
347         assertThat(actual, is(expected));
348     }
349 
350     @Test
notTrue()351     public void notTrue() {
352         Expr actual   = getAtomExpr("[!*]");
353         Expr expected = expr(FALSE);
354         assertThat(actual, is(expected));
355     }
356 
357     @Test
notNotTrue()358     public void notNotTrue() {
359         Expr actual   = getAtomExpr("[!!*]");
360         Expr expected = expr(TRUE);
361         assertThat(actual, is(expected));
362     }
363 
364     @Test
ringCountDefault()365     public void ringCountDefault() {
366         Expr actual   = getAtomExpr("[R]");
367         Expr expected = expr(IS_IN_RING);
368         assertThat(actual, is(expected));
369     }
370 
371     @Test
ringCount0()372     public void ringCount0() {
373         Expr actual   = getAtomExpr("[R0]");
374         Expr expected = expr(IS_IN_CHAIN);
375         assertThat(actual, is(expected));
376     }
377 
378     @Test
ringCount()379     public void ringCount() {
380         Expr actual   = getAtomExpr("[R1]");
381         Expr expected = expr(RING_COUNT, 1);
382         assertThat(actual, is(expected));
383     }
384 
385     @Test
ringCountOEChem()386     public void ringCountOEChem() {
387         Expr actual   = getAtomExpr("[R2]", Smarts.FLAVOR_OECHEM);
388         Expr expected = expr(RING_BOND_COUNT, 2);
389         assertThat(actual, is(expected));
390     }
391 
392     @Test
ringSmallest()393     public void ringSmallest() {
394         Expr actual   = getAtomExpr("[r5]");
395         Expr expected = expr(RING_SMALLEST, 5);
396         assertThat(actual, is(expected));
397     }
398 
399     @Test
ringSmallestDefault()400     public void ringSmallestDefault() {
401         Expr actual   = getAtomExpr("[r]");
402         Expr expected = expr(IS_IN_RING);
403         assertThat(actual, is(expected));
404     }
405 
406     @Test
ringSmallestInvalid()407     public void ringSmallestInvalid() {
408         IAtomContainer mol = new AtomContainer();
409         assertTrue(Smarts.parse(mol, "[r0]")); // not in ring
410         assertFalse(Smarts.parse(mol, "[r1]"));
411         assertFalse(Smarts.parse(mol, "[r2]"));
412         assertTrue(Smarts.parse(mol, "[r3]"));
413     }
414 
415     // make sure not read as C & r
416     @Test
chromium()417     public void chromium() {
418         Expr actual   = getAtomExpr("[Cr]");
419         Expr expected = expr(ELEMENT, Elements.Chromium.number());
420         assertThat(actual, is(expected));
421     }
422 
423     @Test
hetero()424     public void hetero() {
425         Expr actual   = getAtomExpr("[#X]");
426         Expr expected = expr(IS_HETERO);
427         assertThat(actual, is(expected));
428     }
429 
430     @Test
ringSize()431     public void ringSize() {
432         IAtomContainer mol = new AtomContainer();
433         assertTrue(Smarts.parse(mol, "[Z8]", Smarts.FLAVOR_DAYLIGHT));
434         Expr actual   = getAtomExpr(mol.getAtom(0));
435         Expr expected = expr(RING_SIZE, 8);
436         assertThat(actual, is(expected));
437     }
438 
439     @Test
ringSize0()440     public void ringSize0() {
441         IAtomContainer mol = new AtomContainer();
442         assertTrue(Smarts.parse(mol, "[Z0]", Smarts.FLAVOR_DAYLIGHT));
443         Expr actual   = getAtomExpr(mol.getAtom(0));
444         Expr expected = expr(IS_IN_CHAIN);
445         assertThat(actual, is(expected));
446     }
447 
448     @Test
ringSizeDefault()449     public void ringSizeDefault() {
450         IAtomContainer mol = new AtomContainer();
451         assertTrue(Smarts.parse(mol, "[Z]", Smarts.FLAVOR_DAYLIGHT));
452         Expr actual   = getAtomExpr(mol.getAtom(0));
453         Expr expected = expr(IS_IN_RING);
454         assertThat(actual, is(expected));
455     }
456 
457     @Test
adjacentHeteroCount()458     public void adjacentHeteroCount() {
459         IAtomContainer mol = new AtomContainer();
460         assertTrue(Smarts.parse(mol, "[Z2]", Smarts.FLAVOR_CACTVS));
461         Expr actual   = getAtomExpr(mol.getAtom(0));
462         Expr expected = expr(ALIPHATIC_HETERO_SUBSTITUENT_COUNT, 2);
463         assertThat(actual, is(expected));
464     }
465 
466     @Test
adjacentHetero()467     public void adjacentHetero() {
468         IAtomContainer mol = new AtomContainer();
469         assertTrue(Smarts.parse(mol, "[Z]", Smarts.FLAVOR_CACTVS));
470         Expr actual   = getAtomExpr(mol.getAtom(0));
471         Expr expected = expr(HAS_ALIPHATIC_HETERO_SUBSTITUENT);
472         assertThat(actual, is(expected));
473     }
474 
475     @Test
adjacentHetero0()476     public void adjacentHetero0() {
477         IAtomContainer mol = new AtomContainer();
478         assertTrue(Smarts.parse(mol, "[Z0]", Smarts.FLAVOR_CACTVS));
479         Expr actual   = getAtomExpr(mol.getAtom(0));
480         Expr expected = expr(HAS_ALIPHATIC_HETERO_SUBSTITUENT).negate();
481         assertThat(actual, is(expected));
482     }
483 
484     @Test
valence()485     public void valence() {
486         Expr actual   = getAtomExpr("[v4]");
487         Expr expected = expr(VALENCE, 4);
488         assertThat(actual, is(expected));
489     }
490 
491     @Test
valenceDefault()492     public void valenceDefault() {
493         Expr actual   = getAtomExpr("[v]");
494         Expr expected = expr(VALENCE, 1);
495         assertThat(actual, is(expected));
496     }
497 
498     @Test
degree()499     public void degree() {
500         Expr actual   = getAtomExpr("[D4]");
501         Expr expected = expr(DEGREE, 4);
502         assertThat(actual, is(expected));
503     }
504 
505     @Test
degreeDefault()506     public void degreeDefault() {
507         Expr actual   = getAtomExpr("[D]");
508         Expr expected = expr(DEGREE, 1);
509         assertThat(actual, is(expected));
510     }
511 
512     @Test
degreeCDKLegacy()513     public void degreeCDKLegacy() {
514         Expr actual   = getAtomExpr("[D4]", Smarts.FLAVOR_CDK_LEGACY);
515         Expr expected = expr(HEAVY_DEGREE, 4);
516         assertThat(actual, is(expected));
517     }
518 
519     @Test
degreeCDKLegacyDefault()520     public void degreeCDKLegacyDefault() {
521         Expr actual   = getAtomExpr("[D]", Smarts.FLAVOR_CDK_LEGACY);
522         Expr expected = expr(HEAVY_DEGREE, 1);
523         assertThat(actual, is(expected));
524     }
525 
526     @Test
connectivity()527     public void connectivity() {
528         Expr actual   = getAtomExpr("[X4]");
529         Expr expected = expr(TOTAL_DEGREE, 4);
530         assertThat(actual, is(expected));
531     }
532 
533     @Test
connectivityDefault()534     public void connectivityDefault() {
535         Expr actual   = getAtomExpr("[X]");
536         Expr expected = expr(TOTAL_DEGREE, 1);
537         assertThat(actual, is(expected));
538     }
539 
540     @Test
totalHCount()541     public void totalHCount() {
542         Expr actual   = getAtomExpr("[H2]");
543         Expr expected = expr(TOTAL_H_COUNT, 2);
544         assertThat(actual, is(expected));
545     }
546 
547     @Test
implHCount()548     public void implHCount() {
549         Expr actual   = getAtomExpr("[h2]");
550         Expr expected = expr(IMPL_H_COUNT, 2);
551         assertThat(actual, is(expected));
552     }
553 
554     @Test
hasImplHCount()555     public void hasImplHCount() {
556         Expr actual   = getAtomExpr("[h]");
557         Expr expected = expr(HAS_IMPLICIT_HYDROGEN);
558         assertThat(actual, is(expected));
559     }
560 
561     @Test
ringBondCount()562     public void ringBondCount() {
563         Expr actual   = getAtomExpr("[x2]");
564         Expr expected = expr(RING_BOND_COUNT, 2);
565         assertThat(actual, is(expected));
566     }
567 
568     @Test
ringBondCount0()569     public void ringBondCount0() {
570         Expr actual   = getAtomExpr("[x0]");
571         Expr expected = expr(IS_IN_CHAIN);
572         assertThat(actual, is(expected));
573     }
574 
575     @Test
ringBondCount1()576     public void ringBondCount1() {
577         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[x1]"));
578     }
579 
580     @Test
ringBondCountDefault()581     public void ringBondCountDefault() {
582         Expr actual   = getAtomExpr("[x]");
583         Expr expected = expr(IS_IN_RING);
584         assertThat(actual, is(expected));
585     }
586 
587     @Test
formalChargeNeg()588     public void formalChargeNeg() {
589         Expr actual   = getAtomExpr("[-1]");
590         Expr expected = expr(FORMAL_CHARGE, -1);
591         assertThat(actual, is(expected));
592     }
593 
594     @Test
formalChargeNegNeg()595     public void formalChargeNegNeg() {
596         Expr actual   = getAtomExpr("[--]");
597         Expr expected = expr(FORMAL_CHARGE, -2);
598         assertThat(actual, is(expected));
599     }
600 
601     @Test
formalChargePos()602     public void formalChargePos() {
603         Expr actual   = getAtomExpr("[+]");
604         Expr expected = expr(FORMAL_CHARGE, +1);
605         assertThat(actual, is(expected));
606     }
607 
608     @Test
formalChargePosPos()609     public void formalChargePosPos() {
610         Expr actual   = getAtomExpr("[++]");
611         Expr expected = expr(FORMAL_CHARGE, +2);
612         assertThat(actual, is(expected));
613     }
614 
615     @Test
atomMaps()616     public void atomMaps() {
617         IAtomContainer mol = new QueryAtomContainer(null);
618         assertFalse(Smarts.parse(mol, "[:10]"));
619         assertTrue(Smarts.parse(mol, "[*:10]"));
620         assertThat(mol.getAtom(0).getProperty(CDKConstants.ATOM_ATOM_MAPPING, Integer.class),
621                    is(10));
622     }
623 
624     @Test
periodicTableGroup()625     public void periodicTableGroup() {
626         Expr actual   = getAtomExpr("[#G16]", Smarts.FLAVOR_MOE);
627         Expr expected = expr(PERIODIC_GROUP, 16);
628         assertThat(actual, is(expected));
629     }
630 
631     @Test
periodicTableGroupCDKLegacy()632     public void periodicTableGroupCDKLegacy() {
633         Expr actual   = getAtomExpr("[G16]", Smarts.FLAVOR_CDK_LEGACY);
634         Expr expected = expr(PERIODIC_GROUP, 16);
635         assertThat(actual, is(expected));
636     }
637 
638     @Test
insaturationCactvs()639     public void insaturationCactvs() {
640         Expr actual   = getAtomExpr("[G1]", Smarts.FLAVOR_CACTVS);
641         Expr expected = expr(INSATURATION, 1);
642         assertThat(actual, is(expected));
643     }
644 
645     @Test
insaturationCactvsOrMoe()646     public void insaturationCactvsOrMoe() {
647         assertThat(getAtomExpr("[i1]", Smarts.FLAVOR_CACTVS),
648                    is(expr(INSATURATION, 1)));
649         assertThat(getAtomExpr("[i1]", Smarts.FLAVOR_MOE),
650                    is(expr(INSATURATION, 1)));
651     }
652 
653     @Test
heteroSubCountCactvs()654     public void heteroSubCountCactvs() {
655         assertThat(getAtomExpr("[z]", Smarts.FLAVOR_CACTVS),
656                    is(expr(HAS_HETERO_SUBSTITUENT)));
657         assertThat(getAtomExpr("[z1]", Smarts.FLAVOR_CACTVS),
658                    is(expr(HETERO_SUBSTITUENT_COUNT, 1)));
659     }
660 
661     @Test
hybridisationNumber()662     public void hybridisationNumber() {
663         Expr actual   = getAtomExpr("[^2]", Smarts.FLAVOR_OECHEM);
664         Expr expected = expr(HYBRIDISATION_NUMBER, 2);
665         assertThat(actual, is(expected));
666     }
667 
668     @Test
hybridisationNumberDaylight()669     public void hybridisationNumberDaylight() {
670         assertFalse(Smarts.parse(new QueryAtomContainer(null),
671                                  "[^2]",
672                                  Smarts.FLAVOR_DAYLIGHT));
673     }
674 
675     @Test
atomStereoLeft()676     public void atomStereoLeft() {
677         Expr actual   = getAtomExpr("[@]");
678         Expr expected = expr(STEREOCHEMISTRY, IStereoElement.LEFT);
679         assertThat(actual, is(expected));
680     }
681 
682     @Test
atomStereoRight()683     public void atomStereoRight() {
684         Expr actual   = getAtomExpr("[@@]");
685         Expr expected = expr(STEREOCHEMISTRY, IStereoElement.RIGHT);
686         assertThat(actual, is(expected));
687     }
688 
689     @Test
atomStereoLeftOrUnspec()690     public void atomStereoLeftOrUnspec() {
691         Expr actual   = getAtomExpr("[@?]");
692         Expr expected = or(expr(STEREOCHEMISTRY, IStereoElement.LEFT),
693                            expr(STEREOCHEMISTRY, 0));
694         assertThat(actual, is(expected));
695     }
696 
697     @Test
atomStereoSimpleLeft()698     public void atomStereoSimpleLeft() {
699         Expr actual   = getAtomExpr("[C@H]");
700         assertThat(actual, is(new Expr(ALIPHATIC_ELEMENT, 6)
701                                       .and(new Expr(STEREOCHEMISTRY, 1))
702                                       .and(new Expr(TOTAL_H_COUNT, 1))));
703     }
704 
705     @Test
badExprs()706     public void badExprs() {
707         IAtomContainer mol = new AtomContainer();
708         assertFalse(Smarts.parse(mol, "*-,*"));
709         assertFalse(Smarts.parse(mol, "*-;*"));
710         assertFalse(Smarts.parse(mol, "*-!*"));
711         assertFalse(Smarts.parse(mol, "*-&*"));
712         assertFalse(Smarts.parse(mol, "*!*"));
713         assertFalse(Smarts.parse(mol, "*,*"));
714         assertFalse(Smarts.parse(mol, "*;*"));
715         assertFalse(Smarts.parse(mol, "*&*"));
716         assertFalse(Smarts.parse(mol, "*,-*"));
717     }
718 
719     @Test
singleOrAromatic()720     public void singleOrAromatic() {
721         Expr actual   = getBondExpr("**");
722         Expr expected = expr(SINGLE_OR_AROMATIC);
723         assertThat(expected, is(actual));
724     }
725 
726     @Test
singleBond()727     public void singleBond() {
728         Expr actual   = getBondExpr("*-*");
729         Expr expected = expr(ALIPHATIC_ORDER, 1);
730         assertThat(expected, is(actual));
731     }
732 
733     @Test
doubleBond()734     public void doubleBond() {
735         Expr actual   = getBondExpr("*=*");
736         Expr expected = expr(ALIPHATIC_ORDER, 2);
737         assertThat(expected, is(actual));
738     }
739 
740     @Test
tripleBond()741     public void tripleBond() {
742         Expr actual   = getBondExpr("*#*");
743         Expr expected = expr(ALIPHATIC_ORDER, 3);
744         assertThat(expected, is(actual));
745     }
746 
747     @Test
quadBond()748     public void quadBond() {
749         Expr actual   = getBondExpr("*$*");
750         Expr expected = expr(ALIPHATIC_ORDER, 4);
751         assertThat(expected, is(actual));
752     }
753 
754     @Test
aromaticBond()755     public void aromaticBond() {
756         Expr actual   = getBondExpr("*:*");
757         Expr expected = expr(IS_AROMATIC);
758         assertThat(expected, is(actual));
759     }
760 
761     @Test
aliphaticBond()762     public void aliphaticBond() {
763         Expr actual   = getBondExpr("*!:*");
764         Expr expected = expr(IS_ALIPHATIC);
765         assertThat(expected, is(actual));
766     }
767 
768     @Test
chainBond()769     public void chainBond() {
770         Expr actual   = getBondExpr("*!@*");
771         Expr expected = expr(IS_IN_CHAIN);
772         assertThat(expected, is(actual));
773     }
774 
775     @Test
anyBond()776     public void anyBond() {
777         Expr actual   = getBondExpr("*~*");
778         Expr expected = expr(TRUE);
779         assertThat(expected, is(actual));
780     }
781 
782     @Test
singleOrDouble()783     public void singleOrDouble() {
784         Expr actual = getBondExpr("*-,=*");
785         Expr expected = or(expr(ALIPHATIC_ORDER, 1),
786                            expr(ALIPHATIC_ORDER, 2));
787         assertThat(expected, is(actual));
788     }
789 
790     @Test
operatorPrecedence()791     public void operatorPrecedence() {
792         Expr actual = getBondExpr("*@;-,=*");
793         Expr expected = and(expr(IS_IN_RING),
794                             or(expr(ALIPHATIC_ORDER, 1),
795                                expr(ALIPHATIC_ORDER, 2)));
796         assertThat(expected, is(actual));
797     }
798 
799     @Test
notInRing()800     public void notInRing() {
801         Expr actual   = getBondExpr("*!@*");
802         Expr expected = expr(IS_IN_CHAIN);
803         assertThat(expected, is(actual));
804     }
805 
806     @Test
notAromatic()807     public void notAromatic() {
808         Expr actual   = getBondExpr("*!:*");
809         Expr expected = expr(IS_ALIPHATIC);
810         assertThat(expected, is(actual));
811     }
812 
813     @Test
notNotWildcard()814     public void notNotWildcard() {
815         Expr actual   = getBondExpr("*!!~*");
816         Expr expected = expr(TRUE);
817         assertThat(expected, is(actual));
818     }
819 
820     @Test
testAliphaticSymbols()821     public void testAliphaticSymbols() {
822         for (Elements e : Elements.values()) {
823             int len = e.symbol().length();
824             if (len == 1 || len == 2) {
825                 String             smarts = "[" + e.symbol() + "]";
826                 QueryAtomContainer mol    = new QueryAtomContainer(null);
827                 assertTrue(smarts, Smarts.parse(mol, smarts));
828                 Expr expr = getAtomExpr(mol.getAtom(0));
829                 assertThat(expr, anyOf(is(new Expr(ELEMENT, e.number())),
830                                        is(new Expr(ALIPHATIC_ELEMENT, e.number()))));
831             }
832         }
833     }
834 
835     @Test
testAromaticSymbols()836     public void testAromaticSymbols() {
837         assertThat(getAtomExpr("[b]"), is(new Expr(AROMATIC_ELEMENT, 5)));
838         assertThat(getAtomExpr("[c]"), is(new Expr(AROMATIC_ELEMENT, 6)));
839         assertThat(getAtomExpr("[n]"), is(new Expr(AROMATIC_ELEMENT, 7)));
840         assertThat(getAtomExpr("[o]"), is(new Expr(AROMATIC_ELEMENT, 8)));
841         assertThat(getAtomExpr("[al]"), is(new Expr(AROMATIC_ELEMENT, 13)));
842         assertThat(getAtomExpr("[si]"), is(new Expr(AROMATIC_ELEMENT, 14)));
843         assertThat(getAtomExpr("[p]"), is(new Expr(AROMATIC_ELEMENT, 15)));
844         assertThat(getAtomExpr("[s]"), is(new Expr(AROMATIC_ELEMENT, 16)));
845         assertThat(getAtomExpr("[as]"), is(new Expr(AROMATIC_ELEMENT, 33)));
846         assertThat(getAtomExpr("[se]"), is(new Expr(AROMATIC_ELEMENT, 34)));
847         assertThat(getAtomExpr("[sb]"), is(new Expr(AROMATIC_ELEMENT, 51)));
848         assertThat(getAtomExpr("[te]"), is(new Expr(AROMATIC_ELEMENT, 52)));
849     }
850 
851     @Test
testBadSymbols()852     public void testBadSymbols() {
853         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[L]"));
854         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[J]"));
855         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[Q]"));
856         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[G]"));
857         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[T]"));
858         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[M]"));
859         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[E]"));
860         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[t]"));
861         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[?]"));
862     }
863 
864     @Test
testRecursive()865     public void testRecursive() {
866         assertTrue(Smarts.parse(new QueryAtomContainer(null), "[$(*OC)]"));
867         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[$*OC)]"));
868         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[$(*OC]"));
869         assertTrue(Smarts.parse(new QueryAtomContainer(null), "[$((*[O-].[Na+]))]"));
870         assertFalse(Smarts.parse(new QueryAtomContainer(null), "[$([J])]"));
871     }
872 
873     // recursive SMARTS with single atoms should be 'lifted' up to a single
874     // non-recursive expression
875     @Test
testTrivialRecursive()876     public void testTrivialRecursive() {
877         Expr expr = getAtomExpr("[$(F),$(Cl),$(Br)]");
878         assertThat(expr, is(or(expr(ELEMENT, 9),
879                                or(expr(ELEMENT, 17),
880                                   expr(ELEMENT, 35)))));
881     }
882 
883     // must always be read/written in SMARTS as recursive but we can lift
884     // the expression up to the top level
885     @Test
testTrivialRecursive2()886     public void testTrivialRecursive2() {
887         Expr expr = getAtomExpr("[!$([F,Cl,Br])]");
888         assertThat(expr, is(or(expr(ELEMENT, 9),
889                                 or(expr(ELEMENT, 17),
890                                   expr(ELEMENT, 35))).negate()));
891     }
892 
893     @Test
ringOpenCloseInconsistency()894     public void ringOpenCloseInconsistency() {
895         assertFalse(Smarts.parse(new QueryAtomContainer(null), "C=1CC-,=1"));
896         assertFalse(Smarts.parse(new QueryAtomContainer(null), "C=1CC-1"));;
897     }
898 
899     @Test
ringOpenCloseConsistency()900     public void ringOpenCloseConsistency() {
901         assertTrue(Smarts.parse(new QueryAtomContainer(null), "C-,=1CC-,=1"));
902         assertTrue(Smarts.parse(new QueryAtomContainer(null), "C!~1CC!~1"));
903     }
904 
905     @Test
degreeRange()906     public void degreeRange() {
907         Expr expr = getAtomExpr("[D{1-3}]");
908         assertThat(expr, is(or(expr(DEGREE, 1),
909                                or(expr(DEGREE, 2),
910                                   expr(DEGREE, 3)))));
911     }
912 
913     @Test
implHRange()914     public void implHRange() {
915         Expr expr = getAtomExpr("[h{1-3}]");
916         assertThat(expr, is(or(expr(IMPL_H_COUNT, 1),
917                                or(expr(IMPL_H_COUNT, 2),
918                                   expr(IMPL_H_COUNT, 3)))));
919     }
920 
921     @Test
totalHCountRange()922     public void totalHCountRange() {
923         Expr expr = getAtomExpr("[H{1-3}]");
924         assertThat(expr, is(or(expr(TOTAL_H_COUNT, 1),
925                                or(expr(TOTAL_H_COUNT, 2),
926                                   expr(TOTAL_H_COUNT, 3)))));
927     }
928 
929     @Test
valenceRange()930     public void valenceRange() {
931         Expr expr = getAtomExpr("[v{1-3}]");
932         assertThat(expr, is(or(expr(VALENCE, 1),
933                                or(expr(VALENCE, 2),
934                                   expr(VALENCE, 3)))));
935     }
936 
937     @Test
ringBondCountRange()938     public void ringBondCountRange() {
939         Expr expr = getAtomExpr("[x{2-4}]");
940         assertThat(expr, is(or(expr(RING_BOND_COUNT, 2),
941                                or(expr(RING_BOND_COUNT, 3),
942                                   expr(RING_BOND_COUNT, 4)))));
943     }
944 
945     @Test
ringSmallestSizeCountRange()946     public void ringSmallestSizeCountRange() {
947         Expr expr = getAtomExpr("[r{5-7}]");
948         assertThat(expr, is(or(expr(RING_SMALLEST, 5),
949                                or(expr(RING_SMALLEST, 6),
950                                   expr(RING_SMALLEST, 7)))));
951     }
952 
953     @Test
supportInsaturatedByDefault()954     public void supportInsaturatedByDefault() {
955         Expr expr = getAtomExpr("[i]");
956         assertThat(expr, is(expr(UNSATURATED)));
957     }
958 
959     @Test
supportHGt()960     public void supportHGt() {
961         Expr expr = getAtomExpr("[H>1]");
962         assertThat(expr, is(and(expr(TOTAL_H_COUNT, 0).negate(),
963                                 expr(TOTAL_H_COUNT, 1).negate())));
964     }
965 
966     @Test
supportHLt()967     public void supportHLt() {
968         Expr expr = getAtomExpr("[H<2]");
969         assertThat(expr, is(or(expr(TOTAL_H_COUNT, 0),
970                                expr(TOTAL_H_COUNT, 1))));
971     }
972 
973     @Test
supportDGt()974     public void supportDGt() {
975         Expr expr = getAtomExpr("[D>1]");
976         assertThat(expr, is(and(expr(DEGREE, 0).negate(),
977                                 expr(DEGREE, 1).negate())));
978     }
979 
980     @Test
supportDLt()981     public void supportDLt() {
982         Expr expr = getAtomExpr("[D<2]");
983         assertThat(expr, is(or(expr(DEGREE, 0),
984                                expr(DEGREE, 1))));
985     }
986 }