1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.databinding;
17
18import android.databinding.parser.BindingExpressionBaseVisitor;
19import android.databinding.parser.BindingExpressionLexer;
20import android.databinding.parser.BindingExpressionParser;
21import android.databinding.parser.BindingExpressionParser.AndOrOpContext;
22import android.databinding.parser.BindingExpressionParser.BinaryOpContext;
23import android.databinding.parser.BindingExpressionParser.BindingSyntaxContext;
24import android.databinding.parser.BindingExpressionParser.BitShiftOpContext;
25import android.databinding.parser.BindingExpressionParser.ComparisonOpContext;
26import android.databinding.parser.BindingExpressionParser.DefaultsContext;
27import android.databinding.parser.BindingExpressionParser.DotOpContext;
28import android.databinding.parser.BindingExpressionParser.ExpressionContext;
29import android.databinding.parser.BindingExpressionParser.GroupingContext;
30import android.databinding.parser.BindingExpressionParser.LiteralContext;
31import android.databinding.parser.BindingExpressionParser.MathOpContext;
32import android.databinding.parser.BindingExpressionParser.PrimaryContext;
33import android.databinding.parser.BindingExpressionParser.PrimitiveTypeContext;
34import android.databinding.parser.BindingExpressionParser.QuestionQuestionOpContext;
35import android.databinding.parser.BindingExpressionParser.ResourceContext;
36import android.databinding.parser.BindingExpressionParser.StringLiteralContext;
37import android.databinding.parser.BindingExpressionParser.TernaryOpContext;
38import android.databinding.parser.BindingExpressionParser.UnaryOpContext;
39
40import org.antlr.v4.runtime.ANTLRInputStream;
41import org.antlr.v4.runtime.CommonTokenStream;
42import org.antlr.v4.runtime.Token;
43import org.antlr.v4.runtime.misc.NotNull;
44import org.antlr.v4.runtime.tree.TerminalNode;
45import org.junit.Test;
46
47import java.io.StringReader;
48
49import static org.junit.Assert.assertEquals;
50import static org.junit.Assert.assertNotNull;
51import static org.junit.Assert.assertNull;
52import static org.junit.Assert.assertTrue;
53
54public class BindingExpressionParserTest {
55
56    @Test
57    public void testSingleQuoteStringLiteral() throws Exception {
58        String expr = "`test`";
59        LiteralContext literal = parseLiteral(expr);
60        assertNotNull(literal);
61        StringLiteralContext stringLiteral = literal.stringLiteral();
62        assertNotNull(stringLiteral);
63        TerminalNode singleQuote = stringLiteral.SingleQuoteString();
64        Token token = singleQuote.getSymbol();
65        assertEquals("`test`", token.getText());
66    }
67
68    @Test
69    public void testDoubleQuoteStringLiteral() throws Exception {
70        String expr = "\"test\"";
71
72        LiteralContext literal = parseLiteral(expr);
73        StringLiteralContext stringLiteral = literal.stringLiteral();
74        TerminalNode singleQuote = stringLiteral.DoubleQuoteString();
75        Token token = singleQuote.getSymbol();
76        assertEquals("\"test\"", token.getText());
77    }
78
79    @Test
80    public void testSingleQuoteEscapeStringLiteral() throws Exception {
81        String expr = "`\"t\\`est\"`";
82        LiteralContext literal = parseLiteral(expr);
83        StringLiteralContext stringLiteral = literal.stringLiteral();
84        TerminalNode singleQuote = stringLiteral.SingleQuoteString();
85        Token token = singleQuote.getSymbol();
86        assertEquals("`\"t\\`est\"`", token.getText());
87    }
88
89    @Test
90    public void testCharLiteral() throws Exception {
91        LiteralContext literal = parseLiteral("'c'");
92        assertEquals("'c'", literal.getText());
93        literal = parseLiteral("'\\u0054'");
94        assertEquals("'\\u0054'", literal.getText());
95        literal = parseLiteral("'\\''");
96        assertEquals("'\\''", literal.getText());
97    }
98
99    @Test
100    public void testIntLiterals() throws Exception {
101        compareIntLiteral("123");
102        compareIntLiteral("123l");
103        compareIntLiteral("1_2_3l");
104        compareIntLiteral("123L");
105        compareIntLiteral("0xdeadbeef");
106        compareIntLiteral("0xdeadbeefl");
107        compareIntLiteral("0Xdeadbeef");
108        compareIntLiteral("0xdead_beefl");
109        compareIntLiteral("0xdead_beefL");
110        compareIntLiteral("01234567");
111        compareIntLiteral("01234567L");
112        compareIntLiteral("01234567l");
113        compareIntLiteral("0123_45_67l");
114        compareIntLiteral("0b0101");
115        compareIntLiteral("0b0101_0101");
116        compareIntLiteral("0B0101_0101");
117        compareIntLiteral("0B0101_0101L");
118        compareIntLiteral("0B0101_0101l");
119    }
120
121    @Test
122    public void testFloatLiterals() throws Exception {
123        compareFloatLiteral("0.12345");
124        compareFloatLiteral("0.12345f");
125        compareFloatLiteral("0.12345F");
126        compareFloatLiteral("132450.12345F");
127        compareFloatLiteral("132450.12345");
128        compareFloatLiteral("132450e123");
129        compareFloatLiteral("132450.4e123");
130    }
131
132    @Test
133    public void testBoolLiterals() throws Exception {
134        compareBoolLiteral("true");
135        compareBoolLiteral("false");
136    }
137
138    @Test
139    public void testNullLiteral() throws Exception {
140        LiteralContext literal = parseLiteral("null");
141        String token = literal.getText();
142        assertEquals("null", token);
143    }
144
145    @Test
146    public void testPrimitiveClassExtraction() throws Exception {
147        PrimaryContext primary = parsePrimary("int.class");
148        PrimitiveTypeContext type = primary.classExtraction().type().primitiveType();
149        assertEquals("int", type.getText());
150    }
151
152    @Test
153    public void testIdentifier() throws Exception {
154        PrimaryContext primary = parsePrimary("abcdEfg");
155        assertEquals("abcdEfg", primary.identifier().getText());
156    }
157
158    @Test
159    public void testUnaryOperators() throws Exception {
160        compareUnaryOperators("+");
161        compareUnaryOperators("-");
162        compareUnaryOperators("!");
163        compareUnaryOperators("~");
164    }
165
166    @Test
167    public void testMathOperators() throws Exception {
168        compareMathOperators("+");
169        compareMathOperators("-");
170        compareMathOperators("*");
171        compareMathOperators("/");
172        compareMathOperators("%");
173    }
174
175    @Test
176    public void testBitShiftOperators() throws Exception {
177        compareBitShiftOperators(">>>");
178        compareBitShiftOperators("<<");
179        compareBitShiftOperators(">>");
180    }
181
182    @Test
183    public void testComparisonShiftOperators() throws Exception {
184        compareComparisonOperators("<");
185        compareComparisonOperators(">");
186        compareComparisonOperators("<=");
187        compareComparisonOperators(">=");
188        compareComparisonOperators("==");
189        compareComparisonOperators("!=");
190    }
191
192    @Test
193    public void testAndOrOperators() throws Exception {
194        compareAndOrOperators("&&");
195        compareAndOrOperators("||");
196    }
197
198    @Test
199    public void testBinaryOperators() throws Exception {
200        compareBinaryOperators("&");
201        compareBinaryOperators("|");
202        compareBinaryOperators("^");
203    }
204
205    @Test
206    public void testTernaryOperator() throws Exception {
207        TernaryOpContext expression = parseExpression("true ? 1 : 0");
208        assertEquals(5, expression.getChildCount());
209        assertEquals("true",
210                ((PrimaryContext) expression.left).literal().javaLiteral().getText());
211        assertEquals("?", expression.op.getText());
212        assertEquals("1",
213                ((PrimaryContext) expression.iftrue).literal().javaLiteral().getText());
214        assertEquals(":", expression.getChild(3).getText());
215        assertEquals("0", ((PrimaryContext) expression.iffalse).literal().javaLiteral().getText());
216    }
217
218    @Test
219    public void testDot() throws Exception {
220        DotOpContext expression = parseExpression("one.two.three");
221        assertEquals(3, expression.getChildCount());
222        assertEquals("three", expression.Identifier().getText());
223        assertEquals(".", expression.getChild(1).getText());
224        DotOpContext left = (DotOpContext) expression.expression();
225        assertEquals("two", left.Identifier().getText());
226        assertEquals(".", left.getChild(1).getText());
227        assertEquals("one", ((PrimaryContext) left.expression()).identifier().getText());
228    }
229
230    @Test
231    public void testQuestionQuestion() throws Exception {
232        QuestionQuestionOpContext expression = parseExpression("one ?? two");
233        assertEquals(3, expression.getChildCount());
234        assertEquals("one", ((PrimaryContext) expression.left).identifier().getText());
235        assertEquals("two", ((PrimaryContext) expression.right).identifier().getText());
236        assertEquals("??", expression.op.getText());
237    }
238
239    @Test
240    public void testResourceReference() throws Exception {
241        compareResource("@id/foo_bar");
242        compareResource("@transition/foo_bar");
243        compareResource("@anim/foo_bar");
244        compareResource("@animator/foo_bar");
245        compareResource("@android:id/foo_bar");
246        compareResource("@app:id/foo_bar");
247    }
248
249    @Test
250    public void testDefaults() throws Exception {
251        BindingSyntaxContext syntax = parseExpressionString("foo.bar, default = @id/foo_bar");
252        BindingExpressionParser.DefaultsContext defaults = syntax
253                .accept(new BindingExpressionBaseVisitor<DefaultsContext>() {
254                    @Override
255                    public BindingExpressionParser.DefaultsContext visitDefaults(
256                            @NotNull BindingExpressionParser.DefaultsContext ctx) {
257                        return ctx;
258                    }
259                });
260        assertEquals("@id/foo_bar", defaults.constantValue().ResourceReference().getText());
261    }
262
263    @Test
264    public void testParentheses() throws Exception {
265        GroupingContext grouping = parseExpression("(1234)");
266        assertEquals("1234", grouping.expression().getText());
267    }
268
269    // ---------------------- Helpers --------------------
270
271    private void compareResource(String value) throws Exception {
272        ResourceContext resourceContext = parseExpression(value);
273        assertEquals(value, resourceContext.getText());
274    }
275
276    private void compareUnaryOperators(String op) throws Exception {
277        UnaryOpContext expression = parseExpression(op + " 2");
278        assertEquals(2, expression.getChildCount());
279        assertEquals(op, expression.op.getText());
280        assertEquals("2",
281                ((PrimaryContext) expression.expression()).literal().javaLiteral()
282                        .getText());
283    }
284
285    private void compareBinaryOperators(String op) throws Exception {
286        BinaryOpContext expression = parseExpression("1 " + op + " 2");
287        assertEquals(3, expression.getChildCount());
288        assertTrue(expression.left instanceof ExpressionContext);
289        String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
290        assertEquals("1", one);
291        assertEquals(op, expression.op.getText());
292        assertTrue(expression.right instanceof ExpressionContext);
293        String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
294        assertEquals("2", two);
295    }
296
297    private void compareMathOperators(String op) throws Exception {
298        MathOpContext expression = parseExpression("1 " + op + " 2");
299        assertEquals(3, expression.getChildCount());
300        assertTrue(expression.left instanceof ExpressionContext);
301        String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
302        assertEquals("1", one);
303        assertEquals(op, expression.op.getText());
304        assertTrue(expression.right instanceof ExpressionContext);
305        String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
306        assertEquals("2", two);
307    }
308
309    private void compareBitShiftOperators(String op) throws Exception {
310        BitShiftOpContext expression = parseExpression("1 " + op + " 2");
311        assertEquals(3, expression.getChildCount());
312        assertTrue(expression.left instanceof ExpressionContext);
313        String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
314        assertEquals("1", one);
315        assertEquals(op, expression.op.getText());
316        assertTrue(expression.right instanceof ExpressionContext);
317        String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
318        assertEquals("2", two);
319    }
320
321    private void compareComparisonOperators(String op) throws Exception {
322        ComparisonOpContext expression = parseExpression("1 " + op + " 2");
323        assertEquals(3, expression.getChildCount());
324        assertTrue(expression.left instanceof ExpressionContext);
325        String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
326        assertEquals("1", one);
327        assertEquals(op, expression.op.getText());
328        assertTrue(expression.right instanceof ExpressionContext);
329        String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
330        assertEquals("2", two);
331    }
332
333    private void compareAndOrOperators(String op) throws Exception {
334        AndOrOpContext expression = parseExpression("1 " + op + " 2");
335        assertEquals(3, expression.getChildCount());
336        assertTrue(expression.left instanceof ExpressionContext);
337        String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
338        assertEquals("1", one);
339        assertEquals(op, expression.op.getText());
340        assertTrue(expression.right instanceof ExpressionContext);
341        String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
342        assertEquals("2", two);
343    }
344
345    private void compareIntLiteral(String constant) throws Exception {
346        LiteralContext literal = parseLiteral(constant);
347        String token = literal.javaLiteral().getText();
348        assertEquals(constant, token);
349    }
350
351    private void compareFloatLiteral(String constant) throws Exception {
352        LiteralContext literal = parseLiteral(constant);
353        String token = literal.javaLiteral().getText();
354        assertEquals(constant, token);
355    }
356
357    private void compareBoolLiteral(String constant) throws Exception {
358        LiteralContext literal = parseLiteral(constant);
359        String token = literal.javaLiteral().getText();
360        assertEquals(constant, token);
361    }
362
363    private BindingSyntaxContext parse(String value) throws Exception {
364        return parseExpressionString(value);
365    }
366
367    private <T extends ExpressionContext> T parseExpression(String value) throws Exception {
368        return (T) parse(value).accept(new BindingExpressionBaseVisitor<ExpressionContext>() {
369            @Override
370            public ExpressionContext visitRootExpr(
371                    @NotNull BindingExpressionParser.RootExprContext ctx) {
372                return ctx.expression();
373            }
374        });
375    }
376
377    private PrimaryContext parsePrimary(String value) throws Exception {
378        return parseExpression(value);
379    }
380
381    private LiteralContext parseLiteral(String value) throws Exception {
382        return parsePrimary(value).literal();
383    }
384
385    BindingExpressionParser.BindingSyntaxContext parseExpressionString(String s) throws Exception {
386        ANTLRInputStream input = new ANTLRInputStream(new StringReader(s));
387        BindingExpressionLexer lexer = new BindingExpressionLexer(input);
388        CommonTokenStream tokens = new CommonTokenStream(lexer);
389        BindingExpressionParser parser = new BindingExpressionParser(tokens);
390        return parser.bindingSyntax();
391    }
392}