com.sun.javafx.fxml.expression.Expression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjfx-78-backport Show documentation
Show all versions of openjfx-78-backport Show documentation
This is a backport of OpenJFX 8 to run on Java 7.
The newest version!
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.fxml.expression;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.sun.javafx.fxml.BeanAdapter;
/**
* Abstract base class for expressions. Also provides static methods for
* creating arithmetic and logical expressions as well as accessing namespace
* values by key path.
*/
public abstract class Expression {
// Expression parser class
private static class Parser {
public static class Token {
public Token(TokenType type, Object value) {
this.type = type;
this.value = value;
}
public final TokenType type;
public final Object value;
@Override
public String toString() {
return value.toString();
}
}
public enum TokenType {
LITERAL,
VARIABLE,
FUNCTION,
UNARY_OPERATOR,
BINARY_OPERATOR,
BEGIN_GROUP,
END_GROUP
}
private int c = -1;
private char[] pushbackBuffer = new char[PUSHBACK_BUFFER_SIZE];
private static final int MAX_PRIORITY = 6;
private static final int PUSHBACK_BUFFER_SIZE = 6;
public Expression parse(Reader reader) throws IOException {
LinkedList tokens = tokenize(new PushbackReader(reader, PUSHBACK_BUFFER_SIZE));
LinkedList stack = new LinkedList();
for (Token token : tokens) {
Expression expression;
switch (token.type) {
case LITERAL: {
expression = new LiteralExpression(token.value);
break;
}
case VARIABLE: {
expression = new VariableExpression((KeyPath)token.value);
break;
}
case FUNCTION: {
// TODO Create a new FunctionExpression type; this
// class will have a property of type Method that
// refers to a method defined by the "scope context"
// (e.g. an FXML document controller), which must be
// set prior to evaluating the expression; it will
// also have a list of argument Expressions
expression = null;
break;
}
case UNARY_OPERATOR: {
String operator = (String)token.value;
Expression operand = stack.pop();
if (operator.equals(NEGATE)) {
expression = negate(operand);
} else if (operator.equals(NOT)) {
expression = not(operand);
} else {
throw new UnsupportedOperationException();
}
break;
}
case BINARY_OPERATOR: {
String operator = (String)token.value;
Expression right = stack.pop();
Expression left = stack.pop();
if (operator.equals(ADD)) {
expression = add(left, right);
} else if (operator.equals(SUBTRACT)) {
expression = subtract(left, right);
} else if (operator.equals(MULTIPLY)) {
expression = multiply(left, right);
} else if (operator.equals(DIVIDE)) {
expression = divide(left, right);
} else if (operator.equals(MODULO)) {
expression = modulo(left, right);
} else if (operator.equals(GREATER_THAN)) {
expression = greaterThan(left, right);
} else if (operator.equals(GREATER_THAN_OR_EQUAL_TO)) {
expression = greaterThanOrEqualTo(left, right);
} else if (operator.equals(LESS_THAN)) {
expression = lessThan(left, right);
} else if (operator.equals(LESS_THAN_OR_EQUAL_TO)) {
expression = lessThanOrEqualTo(left, right);
} else if (operator.equals(EQUAL_TO)) {
expression = equalTo(left, right);
} else if (operator.equals(NOT_EQUAL_TO)) {
expression = notEqualTo(left, right);
} else if (operator.equals(AND)) {
expression = and(left, right);
} else if (operator.equals(OR)) {
expression = or(left, right);
} else {
throw new UnsupportedOperationException();
}
break;
}
default: {
throw new UnsupportedOperationException();
}
}
stack.push(expression);
}
if (stack.size() != 1) {
throw new IllegalArgumentException("Invalid expression.");
}
return stack.peek();
}
private LinkedList tokenize(PushbackReader reader) throws IOException {
// Read the string into a postfix list of tokens
LinkedList tokens = new LinkedList();
LinkedList stack = new LinkedList();
c = reader.read();
boolean unary = true;
while (c != -1) {
// Skip whitespace
while (c != -1 && Character.isWhitespace(c)) {
c = reader.read();
}
if (c != -1) {
Token token;
if (c == 'n') {
if (readKeyword(reader, NULL_KEYWORD)) {
token = new Token(TokenType.LITERAL, null);
} else {
token = new Token(TokenType.VARIABLE, KeyPath.parse(reader));
c = reader.read();
}
} else if (c == '"' || c == '\'') {
StringBuilder stringBuilder = new StringBuilder();
// Use the same delimiter to close the string
int t = c;
// Move to the next character after the delimiter
c = reader.read();
while (c != -1 && c != t) {
if (!Character.isISOControl(c)) {
if (c == '\\') {
c = reader.read();
if (c == 'b') {
c = '\b';
} else if (c == 'f') {
c = '\f';
} else if (c == 'n') {
c = '\n';
} else if (c == 'r') {
c = '\r';
} else if (c == 't') {
c = '\t';
} else if (c == 'u') {
StringBuilder unicodeValueBuilder = new StringBuilder();
while (unicodeValueBuilder.length() < 4) {
c = reader.read();
unicodeValueBuilder.append((char)c);
}
String unicodeValue = unicodeValueBuilder.toString();
c = (char)Integer.parseInt(unicodeValue, 16);
} else {
if (!(c == '\\'
|| c == '/'
|| c == '\"'
|| c == '\''
|| c == t)) {
throw new IllegalArgumentException("Unsupported escape sequence.");
}
}
}
stringBuilder.append((char)c);
}
c = reader.read();
}
if (c != t) {
throw new IllegalArgumentException("Unterminated string.");
}
// Move to the next character after the delimiter
c = reader.read();
token = new Token(TokenType.LITERAL, stringBuilder.toString());
} else if (Character.isDigit(c)) {
StringBuilder numberBuilder = new StringBuilder();
boolean integer = true;
while (c != -1 && (Character.isDigit(c) || c == '.'
|| c == 'e' || c == 'E')) {
numberBuilder.append((char)c);
integer &= !(c == '.');
c = reader.read();
}
Number value;
if (integer) {
value = Long.parseLong(numberBuilder.toString());
} else {
value = Double.parseDouble(numberBuilder.toString());
}
token = new Token(TokenType.LITERAL, value);
} else if (c == 't') {
if (readKeyword(reader, TRUE_KEYWORD)) {
token = new Token(TokenType.LITERAL, true);
} else {
token = new Token(TokenType.VARIABLE, KeyPath.parse(reader));
c = reader.read();
}
} else if (c == 'f') {
if (readKeyword(reader, FALSE_KEYWORD)) {
token = new Token(TokenType.LITERAL, false);
} else {
token = new Token(TokenType.VARIABLE, KeyPath.parse(reader));
c = reader.read();
}
} else if (Character.isJavaIdentifierStart(c)) {
reader.unread(c);
// TODO Here (and everywhere else where we call KeyPath.parse()),
// read the path value. If c == '(' when this method returns, the
// path refers to a function; read the arguments and create a
// FUNCTION token
token = new Token(TokenType.VARIABLE, KeyPath.parse(reader));
c = reader.read();
} else {
if (c == NEGATE.charAt(0) && unary) {
token = new Token(TokenType.UNARY_OPERATOR, NEGATE);
} else if (c == NOT.charAt(0) && unary) {
token = new Token(TokenType.UNARY_OPERATOR, NOT);
} else if (c == ADD.charAt(0)) {
token = new Token(TokenType.BINARY_OPERATOR, ADD);
} else if (c == SUBTRACT.charAt(0)) {
token = new Token(TokenType.BINARY_OPERATOR, SUBTRACT);
} else if (c == MULTIPLY.charAt(0)) {
token = new Token(TokenType.BINARY_OPERATOR, MULTIPLY);
} else if (c == DIVIDE.charAt(0)) {
token = new Token(TokenType.BINARY_OPERATOR, DIVIDE);
} else if (c == MODULO.charAt(0)) {
token = new Token(TokenType.BINARY_OPERATOR, MODULO);
} else if (c == EQUAL_TO.charAt(0)) {
c = reader.read();
if (c == EQUAL_TO.charAt(1)) {
token = new Token(TokenType.BINARY_OPERATOR, EQUAL_TO);
} else {
throw new IllegalArgumentException();
}
} else if (c == NOT_EQUAL_TO.charAt(0)) {
c = reader.read();
if (c == NOT_EQUAL_TO.charAt(1)) {
token = new Token(TokenType.BINARY_OPERATOR, NOT_EQUAL_TO);
} else {
throw new IllegalArgumentException();
}
} else if (c == GREATER_THAN.charAt(0)) {
c = reader.read();
if (c == GREATER_THAN_OR_EQUAL_TO.charAt(1)) {
token = new Token(TokenType.BINARY_OPERATOR, GREATER_THAN_OR_EQUAL_TO);
} else {
token = new Token(TokenType.BINARY_OPERATOR, GREATER_THAN);
}
} else if (c == LESS_THAN.charAt(0)) {
c = reader.read();
if (c == LESS_THAN_OR_EQUAL_TO.charAt(1)) {
token = new Token(TokenType.BINARY_OPERATOR, LESS_THAN_OR_EQUAL_TO);
} else {
token = new Token(TokenType.BINARY_OPERATOR, LESS_THAN);
}
} else if (c == AND.charAt(0)) {
c = reader.read();
if (c == AND.charAt(0)) {
token = new Token(TokenType.BINARY_OPERATOR, AND);
} else {
throw new IllegalArgumentException();
}
} else if (c == OR.charAt(0)) {
c = reader.read();
if (c == OR.charAt(0)) {
token = new Token(TokenType.BINARY_OPERATOR, OR);
} else {
throw new IllegalArgumentException();
}
} else if (c == '(') {
token = new Token(TokenType.BEGIN_GROUP, LEFT_PARENTHESIS);
} else if (c == ')') {
token = new Token(TokenType.END_GROUP, RIGHT_PARENTHESIS);
} else {
throw new IllegalArgumentException("Unexpected character in expression.");
}
c = reader.read();
}
// Process the token
switch (token.type) {
case LITERAL:
case VARIABLE: {
tokens.add(token);
break;
}
case UNARY_OPERATOR:
case BINARY_OPERATOR: {
int priority = getPriority((String)token.value);
while (!stack.isEmpty()
&& stack.peek().type != TokenType.BEGIN_GROUP
&& getPriority((String)stack.peek().value) >= priority
&& getPriority((String)stack.peek().value) != MAX_PRIORITY) {
tokens.add(stack.pop());
}
stack.push(token);
break;
}
case BEGIN_GROUP: {
stack.push(token);
break;
}
case END_GROUP: {
for (token = stack.pop(); token.type != TokenType.BEGIN_GROUP; token = stack.pop()) {
tokens.add(token);
}
break;
}
default: {
throw new UnsupportedOperationException();
}
}
unary = !(token.type == TokenType.LITERAL || token.type == TokenType.VARIABLE);
}
}
while (!stack.isEmpty()) {
tokens.add(stack.pop());
}
return tokens;
}
private boolean readKeyword(PushbackReader reader, String keyword) throws IOException {
int n = keyword.length();
int i = 0;
while (c != -1 && i < n) {
pushbackBuffer[i] = (char)c;
if (keyword.charAt(i) != c) {
break;
}
c = reader.read();
i++;
}
boolean result;
if (i < n) {
reader.unread(pushbackBuffer, 0, i + 1);
result = false;
} else {
result = true;
}
return result;
}
private int getPriority(String operator) {
int priority;
if (operator.equals(NEGATE)
|| operator.equals(NOT)) {
priority = MAX_PRIORITY;
} else if (operator.equals(MULTIPLY)
|| operator.equals(DIVIDE)
|| operator.equals(MODULO)) {
priority = MAX_PRIORITY - 1;
} else if (operator.equals(ADD)
|| operator.equals(SUBTRACT)) {
priority = MAX_PRIORITY - 2;
} else if (operator.equals(GREATER_THAN)
|| operator.equals(GREATER_THAN_OR_EQUAL_TO)
|| operator.equals(LESS_THAN)
|| operator.equals(LESS_THAN_OR_EQUAL_TO)) {
priority = MAX_PRIORITY - 3;
} else if (operator.equals(EQUAL_TO)
|| operator.equals(NOT_EQUAL_TO)) {
priority = MAX_PRIORITY - 4;
} else if (operator.equals(AND)) {
priority = MAX_PRIORITY - 5;
} else if (operator.equals(OR)) {
priority = MAX_PRIORITY - 6;
} else {
throw new IllegalArgumentException();
}
return priority;
}
}
private static final String NEGATE = "-";
private static final String NOT = "!";
private static final String ADD = "+";
private static final String SUBTRACT = "-";
private static final String MULTIPLY = "*";
private static final String DIVIDE = "/";
private static final String MODULO = "%";
private static final String GREATER_THAN = ">";
private static final String GREATER_THAN_OR_EQUAL_TO = ">=";
private static final String LESS_THAN = "<";
private static final String LESS_THAN_OR_EQUAL_TO = "<=";
private static final String EQUAL_TO = "==";
private static final String NOT_EQUAL_TO = "!=";
private static final String AND = "&&";
private static final String OR = "||";
private static final String LEFT_PARENTHESIS = "(";
private static final String RIGHT_PARENTHESIS = ")";
private static final String NULL_KEYWORD = "null";
private static final String TRUE_KEYWORD = "true";
private static final String FALSE_KEYWORD = "false";
/**
* Evaluates the expression.
*
* @param namespace
* The namespace against which the expression will be evaluated.
*
* @return
* The result of evaluating the expression.
*/
public abstract Object evaluate(Object namespace);
/**
* Updates the expression value.
*
* @param namespace
* The namespace against which the expression will be evaluated.
*
* @param value
* The value to assign to the expression.
*/
public abstract void update(Object namespace, Object value);
/**
* Tests whether the expression is defined.
*
* @param namespace
* The namespace against which the expression will be evaluated.
*
* @return
* true if the expression is defined; false, otherwise.
*/
public abstract boolean isDefined(Object namespace);
/**
* Tests whether the expression represents an l-value (i.e. can be
* assigned to).
*
* @return
* true if the expression is an l-value; false,
* otherwise.
*/
public abstract boolean isLValue();
/**
* Returns a list of arguments to this expression.
*/
public List getArguments() {
ArrayList arguments = new ArrayList();
getArguments(arguments);
return arguments;
}
/**
* Populates a list of arguments to this expression.
*/
protected abstract void getArguments(List arguments);
/**
* Returns the value at a given path within a namespace.
*
* @param namespace
* @param keyPath
*
* @return
* The value at the given path, or null if no such value exists.
*/
@SuppressWarnings("unchecked")
public static T get(Object namespace, KeyPath keyPath) {
if (keyPath == null) {
throw new NullPointerException();
}
return (T)get(namespace, keyPath.iterator());
}
/**
* Returns the value at a given path within a namespace.
*
* @param namespace
* @param keyPathIterator
*
* @return
* The value at the given path, or null if no such value exists.
*/
@SuppressWarnings("unchecked")
private static T get(Object namespace, Iterator keyPathIterator) {
if (keyPathIterator == null) {
throw new NullPointerException();
}
T value;
if (keyPathIterator.hasNext()) {
// TODO Remove cast to T when build is updated to Java 7
value = (T)get(get(namespace, keyPathIterator.next()), keyPathIterator);
} else {
value = (T)namespace;
}
return value;
}
/**
* Returns the value at a given key within a namespace.
*
* @param namespace
* @param key
*
* @return
* The value at the given key, or null if no such value exists.
*/
@SuppressWarnings("unchecked")
public static T get(Object namespace, String key) {
if (key == null) {
throw new NullPointerException();
}
Object value;
if (namespace instanceof List>) {
List