package org.swellrt.server.box.events;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.waveprotocol.wave.model.id.ModernIdSerialiser;
import com.google.common.base.Preconditions;
public class ExpressionParser {
public final static String EXP_OP_VALUE = "$";
public final static String EXP_OP_HASH = "$hash";
public final static String EXP_OP_AUTHOR = "$author";
public final static String EXP_OP_AUTHOR_ID = "$author_id";
public final static String EXP_OP_TIMESTAMP = "$timestamp";
public final static String EXP_OP_PARTICIPANT = "$participant";
public final static String EXP_OP_PARTICIPANT_ID = "$participant_id";
public final static String EXP_OP_OBJECT_ID = "$objectId";
public final static String EXP_OP_OBJECT_TYPE = "$objectType";
public final static String EXP_OP_APP = "$app";
public final static String EXP_OP_PATH = "$path";
public final static String[] EXP_NON_PATH = {EXP_OP_AUTHOR, EXP_OP_AUTHOR_ID, EXP_OP_TIMESTAMP,
EXP_OP_PARTICIPANT, EXP_OP_PARTICIPANT_ID, EXP_OP_OBJECT_ID, EXP_OP_OBJECT_TYPE, EXP_OP_APP,
EXP_OP_PATH};
// The reg exp for expresion
private static final Pattern EXP_PATTERN = Pattern
.compile("(\\$||\\$hash)\\{(([a-zA-Z0-9_]*|\\?)\\.)*(([a-zA-Z0-9_]*|\\?)*)\\}");
protected static String extractExpressionPath(String expresion)
throws InvalidEventExpressionException {
if (expresion == null)
throw new InvalidEventExpressionException("Unable to extract path from a null expression.");
Matcher m = EXP_PATTERN.matcher(expresion);
if (m.matches()) {
if (m.group(1).equals(EXP_OP_VALUE)) {
return expresion.substring(EXP_OP_VALUE.length() + 1, expresion.length() - 1);
} else if (m.group(1).equals(EXP_OP_HASH)) {
return expresion.substring(EXP_OP_HASH.length() + 1, expresion.length() - 1);
}
}
throw new InvalidEventExpressionException("Expresion has wrong syntax");
}
protected static String getParticipantAddress(String participant) {
int separatorIndex = participant.indexOf("@");
if (separatorIndex != -1)
return participant.substring(0, separatorIndex);
else
return participant;
}
protected static String evaluateExpression(Event event, String expression)
throws InvalidEventExpressionException {
// No path-based expresions
if (expression.equals(EXP_OP_AUTHOR)) {
return getParticipantAddress(event.getAuthor());
} else if (expression.equals(EXP_OP_AUTHOR_ID)) {
return event.getAuthor();
} else if (expression.equals(EXP_OP_PARTICIPANT)) {
return getParticipantAddress(event.getParticipant());
} else if (expression.equals(EXP_OP_PARTICIPANT_ID)) {
return event.getParticipant();
} else if (expression.equals(EXP_OP_TIMESTAMP)) {
return String.valueOf(event.getTimestamp());
} else if (expression.equals(EXP_OP_APP)) {
return event.getApp() != null ? event.getApp() : "<null>";
} else if (expression.equals(EXP_OP_OBJECT_ID)) {
return ModernIdSerialiser.INSTANCE.serialiseWaveId(event.getWaveId());
} else if (expression.equals(EXP_OP_OBJECT_TYPE)) {
return event.getDataType() != null ? event.getDataType() : "<null>";
} else if (expression.equals(EXP_OP_PATH)) {
return event.getPath() != null ? event.getPath() : "<null>";
}
// Path-based expresions
String path = extractExpressionPath(expression);
String value = event.getContextData().get(path);
if (value == null) return "<null>";
if (expression.startsWith(EXP_OP_HASH)) {
value = String.valueOf(value.hashCode());
}
// Secure JSON, delete string delimeter chars
value = value.replaceAll("\"", "");
value = value.replaceAll("'", "");
return value;
}
public static boolean isNonPathExpresion(String expresion) {
for (String exp : EXP_NON_PATH) {
if (expresion.equals(exp)) return true;
}
return false;
}
public static boolean isPathExpresion(String expresion) {
return (expresion.startsWith((EXP_OP_VALUE + "{")) || expresion.startsWith((EXP_OP_HASH + "{")));
}
protected boolean isPathExpStart(String s, int pos) {
String n = s.substring(pos);
return n.startsWith(EXP_OP_VALUE + "{") || n.startsWith(EXP_OP_HASH + "{");
}
/**
* Expresions are like this: root.list.?.field
* Paths are like this: root.list.2.field
*
* The only supported wildcard is ?
*
* @param expr
* @param path
* @return
*/
protected static boolean comparePaths(String expr, String path) {
if (expr == null && path == null) return true;
if (expr == null || path == null) return false;
if (expr.equals(path)) return true;
Preconditions.checkNotNull(expr);
Preconditions.checkNotNull(path);
String[] exprParts = expr.split("\\.");
String[] actualParts = path.split("\\.");
if (exprParts.length != actualParts.length) return false;
// Paths must point to object under root obejct
if (exprParts.length <= 1 || actualParts.length <= 1) return false;
boolean doesItMatch = true;
for (int i = 1; i < exprParts.length; i++) {
if (!exprParts[i].equals("?")) {
doesItMatch = exprParts[i].equals(actualParts[i]);
}
if (!doesItMatch) break;
}
return doesItMatch;
}
/**
* Create a new path replacing part of an expression with a provided path
* part.
*
*
* if
*
* expr = root.list.?.array.?.field path = root.list.2.array.5
*
* then
*
* return root.list.2.array.5.field
*
*
* @param expr
* @param path
* @return
*/
protected static String matchSubpath(String expr, String path) {
if (expr == null || path == null) return null;
String[] exprParts = expr.split("\\.");
String[] pathParts = path.split("\\.");
if (pathParts.length > exprParts.length) return null;
boolean isMatch = true;
String newPath = "";
for (int i = 0; i < exprParts.length; i++) {
if (i < pathParts.length) {
if (!pathParts[i].equals(exprParts[i])) {
if (exprParts[i].equals("?")) {
if (newPath.length() == 0)
newPath = pathParts[i];
else
newPath += "." + pathParts[i];
} else {
isMatch = false;
break;
}
} else {
if (newPath.length() == 0)
newPath = exprParts[i];
else
newPath += "." + exprParts[i];
}
} else {
if (newPath.length() == 0)
newPath = exprParts[i];
else
newPath += "." + exprParts[i];
}
}
if (!isMatch) return null;
return newPath;
}
public interface Operation {
public String onExpression(String expression);
}
private final String string;
private String rstring;
private final Operation op;
public ExpressionParser(String string, Operation op) {
this.string = string;
this.rstring = new String(string);
this.op = op;
}
protected int extractNonPathExp(String s, int pos) {
String n = s.substring(pos);
for (String exp : EXP_NON_PATH) {
if (n.startsWith(exp)) {
return pos + exp.length();
}
}
return -1;
}
protected String escapeInsecureChars(String s) {
s = s.replace('\"', '\0');
s = s.replace('\'', '\0');
return s;
}
public void parse() {
// Looks for expressions in the value
int exprMark = string.indexOf("$");
while (exprMark >= 0) {
if (isPathExpStart(string, exprMark)) {
int startMark = string.indexOf("{", exprMark);
int endMark = string.indexOf("}", exprMark);
if (exprMark < startMark && startMark < endMark) {
op.onExpression(string.substring(exprMark, endMark + 1));
}
} else {
int endExprPos = extractNonPathExp(string, exprMark);
if (endExprPos > 0) {
op.onExpression(string.substring(exprMark, endExprPos));
// go to next $
exprMark = endExprPos;
}
}
exprMark = string.indexOf("$", exprMark + 1);
}
}
public String replaceParse() {
// Looks for expressions in the value
int exprMark = rstring.indexOf("$");
while (exprMark >= 0) {
if (isPathExpStart(rstring, exprMark)) {
int startMark = rstring.indexOf("{", exprMark);
int endMark = rstring.indexOf("}", exprMark);
if (exprMark < startMark && startMark < endMark) {
// We must avoid exceptions if expressions can't be evaluated in the
// model
String value = op.onExpression(rstring.substring(exprMark, endMark + 1));
if (value == null) value = "<missing value>";
// An effective way to replace the expression
rstring =
rstring.substring(0, exprMark) + escapeInsecureChars(value)
+ rstring.substring(endMark + 1);
}
} else {
int endExprPos = extractNonPathExp(rstring, exprMark);
if (endExprPos > 0) {
String replacement =
escapeInsecureChars(op.onExpression(rstring.substring(exprMark, endExprPos)));
rstring = rstring.substring(0, exprMark) + replacement + rstring.substring(endExprPos);
}
}
// go to next $
exprMark = rstring.indexOf("$", exprMark + 1);
}
return rstring;
}
}