ExpressionParser { public Token parse(String expression) { int length = expression.length(); char firstChar = expression.charAt(0); if (firstChar == '"' || firstChar == '\'') { if (length <= 1 || expression.charAt(length - 1) != firstChar) throw new Error("\" or \' is not closed, expression=" + expression); return new ValueToken("\"" + expression.substring(1, length - 1) + "\"", String.class); } for (int i = 0; i < length; i++) { char ch = expression.charAt(i); if (ch == '(') { return parseMethod(expression, i); } else if (ch == '.') { if (i == length - 1) { throw new Error("expression must not end with '.'"); } String field = expression.substring(0, i); if (NUMBER_PATTERN.matcher(field).matches()) continue; if (!FIELD_PATTERN.matcher(field).matches()) throw new Error("invalid field name, field=" + field); var token = new FieldToken(field); token.next = parse(expression.substring(i + 1)); if (token.next instanceof ValueToken) throw new Error("value token must not be followed by '.', expression=" + expression); return token; } } if (NUMBER_PATTERN.matcher(expression).matches()) { return new ValueToken(expression, Number.class); } else { if (!FIELD_PATTERN.matcher(expression).matches()) throw new Error("invalid field name, field=" + expression); return new FieldToken(expression); } }  Token parse(String expression);  }
@Test void singleValue() { Token stringValue = parser.parse("\"text\""); assertThat(stringValue).isInstanceOf(ValueToken.class); assertEquals("\"text\"", ((ValueToken) stringValue).value); Token numberValue = parser.parse("12.00"); assertThat(numberValue).isInstanceOf(ValueToken.class); assertEquals("12.00", ((ValueToken) numberValue).value); }
@Test void singleQuoteString() { Token stringValue = parser.parse("'text'"); assertThat(stringValue).isInstanceOf(ValueToken.class); assertEquals("\"text\"", ((ValueToken) stringValue).value); }
@Test void singleField() { Token token = parser.parse("field"); assertThat(token).isInstanceOf(FieldToken.class); FieldToken fieldToken = (FieldToken) token; assertEquals("field", fieldToken.name); assertNull(fieldToken.next); }
@Test void singleMethod() { Token token = parser.parse("method()"); assertThat(token).isInstanceOf(MethodToken.class); MethodToken methodToken = (MethodToken) token; assertEquals("method", methodToken.name); assertTrue(methodToken.params.isEmpty()); assertNull(methodToken.next); }
@Test void methodChain() { Token token = parser.parse("method1().method2()"); assertThat(token).isInstanceOf(MethodToken.class); MethodToken methodToken = (MethodToken) token; assertThat(methodToken.name).isEqualTo("method1"); assertThat(methodToken.params).isEmpty(); assertThat(methodToken.next).isInstanceOf(MethodToken.class); MethodToken methodToken2 = (MethodToken) ((MethodToken) token).next; assertThat(methodToken2.name).isEqualTo("method2"); assertThat(methodToken2.params).isEmpty(); }
@Test void expression() { Token token = parser.parse("f1.f2.m1(f3.m2(), \"v1\", f4).f5"); FieldToken f1 = (FieldToken) token; assertEquals("f1", f1.name); FieldToken f2 = (FieldToken) f1.next; assertEquals("f2", f2.name); MethodToken m1 = (MethodToken) f2.next; assertEquals("m1", m1.name); assertEquals(3, m1.params.size()); FieldToken f3 = (FieldToken) m1.params.get(0); assertEquals("f3", f3.name); MethodToken m2 = (MethodToken) f3.next; assertEquals("m2", m2.name); assertTrue(m2.params.isEmpty()); ValueToken v1 = (ValueToken) m1.params.get(1); assertEquals("\"v1\"", v1.value); FieldToken f4 = (FieldToken) m1.params.get(2); assertEquals("f4", f4.name); FieldToken f5 = (FieldToken) m1.next; assertEquals("f5", f5.name); }
@Test void invalidExpression() { assertThatThrownBy(() -> parser.parse("method().")) .isInstanceOf(Error.class) .hasMessageContaining("method can only be followed by field or method"); assertThatThrownBy(() -> parser.parse("method().5")) .isInstanceOf(Error.class) .hasMessageContaining("value token must not be followed by '.'"); }