You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by mb...@apache.org on 2011/01/14 21:41:36 UTC
svn commit: r1059159 -
/incubator/bval/sandbox/lang3-work/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/PathNavigation.java
Author: mbenson
Date: Fri Jan 14 20:41:36 2011
New Revision: 1059159
URL: http://svn.apache.org/viewvc?rev=1059159&view=rev
Log:
rewrite path navigation to do true parsing, balancing brackets, etc.
Modified:
incubator/bval/sandbox/lang3-work/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/PathNavigation.java
Modified: incubator/bval/sandbox/lang3-work/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/PathNavigation.java
URL: http://svn.apache.org/viewvc/incubator/bval/sandbox/lang3-work/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/PathNavigation.java?rev=1059159&r1=1059158&r2=1059159&view=diff
==============================================================================
--- incubator/bval/sandbox/lang3-work/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/PathNavigation.java (original)
+++ incubator/bval/sandbox/lang3-work/bval-jsr303/src/main/java/org/apache/bval/jsr303/util/PathNavigation.java Fri Jan 14 20:41:36 2011
@@ -16,11 +16,12 @@
*/
package org.apache.bval.jsr303.util;
-import java.util.StringTokenizer;
+import java.io.StringWriter;
+import java.text.ParsePosition;
import javax.validation.ValidationException;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.StringEscapeUtils;
/**
* Defines a path navigation algorithm and a means of interacting with same.
@@ -74,7 +75,7 @@ public class PathNavigation {
}
/**
- * Complete this CallbackProcedure. Default implementation is noop.
+ * Complete this CallbackProcedure. Default implementation is noop.
*/
protected void complete() {
}
@@ -87,141 +88,216 @@ public class PathNavigation {
}
/**
- * Navigate a path using the specified callback.
+ * Navigate a path using the specified callback, returning its result.
*
+ * @param <T>
* @param propertyPath
+ * , null is assumed empty/root
* @param callback
+ * @return T result
*/
- public static void navigate(String propertyPath, Callback<?> callback) {
- navigateAndReturn(propertyPath, callback);
+ public static <T> T navigateAndReturn(CharSequence propertyPath, Callback<? extends T> callback) {
+ navigate(propertyPath, callback);
+ return callback.result();
}
/**
- * Navigate a path using the specified callback, returning its result.
+ * Navigate a path using the specified callback.
*
- * @param <T>
* @param propertyPath
- * , null is assumed empty/root
* @param callback
- * @return T result
*/
- public static <T> T navigateAndReturn(String propertyPath, Callback<? extends T> callback) {
+ public static void navigate(CharSequence propertyPath, Callback<?> callback) {
try {
- StringTokenizer tokens = new StringTokenizer(StringUtils.defaultString(propertyPath), ".[]", true);
- TokenType state = TokenType.START;
-
- String token = null;
- while (state != TokenType.END) {
- if (state == TokenType.KEY) {
- callback.handleIndexOrKey(token);
- } else if (state == TokenType.PROPERTY) {
- callback.handleProperty(token);
- }
-
- TokenType next;
- if (tokens.hasMoreTokens()) {
- token = tokens.nextToken();
- next = state.parseNext(token);
- } else {
- next = TokenType.END;
- }
- state.test(next);
- if (state == TokenType.LBRACK && next == TokenType.RBRACK) {
- callback.handleGenericInIterable();
- }
- state = next;
- }
- return callback.result();
+ parse(propertyPath == null ? "" : propertyPath, new PathPosition(callback));
} catch (ValidationException ex) {
throw ex;
} catch (Exception ex) {
- throw new ValidationException("invalid property: " + propertyPath, ex);
+ throw new ValidationException(String.format("invalid property: %s", propertyPath), ex);
}
}
- private enum TokenType {
- START("Expected property, index/key, or end of expression") {
- @Override
- boolean allowedNext(TokenType next) {
- return next == PROPERTY || next == LBRACK || next == END;
- }
- },
- PROPERTY("Expected property path separator, index/key, or end of expression") {
- @Override
- boolean allowedNext(TokenType next) {
- return next == DOT || next == LBRACK || next == END;
- }
- },
- LBRACK("Expected index/key or ]") {
- @Override
- boolean allowedNext(TokenType next) {
- return next == RBRACK || next == KEY;
+ private static void parse(CharSequence path, PathPosition pos) throws Exception {
+ int len = path.length();
+ boolean sep = true;
+ while (pos.getIndex() < len) {
+ int here = pos.getIndex();
+ char c = path.charAt(here);
+ switch (c) {
+ case ']':
+ throw new IllegalStateException(String.format("Position %s: unexpected '%s'", here, c));
+ case '[':
+ handleIndex(path, pos.next());
+ break;
+ case '.':
+ if (sep) {
+ throw new IllegalStateException(String.format("Position %s: expected property, index/key, or end of expression", here));
+ }
+ sep = true;
+ pos.next();
+ // fall through:
+ default:
+ if (!sep) {
+ throw new IllegalStateException(String.format("Position %s: expected property path separator, index/key, or end of expression", here));
+ }
+ pos.handleProperty(parseProperty(path, pos));
}
+ sep = false;
+ }
+ }
- /**
- * {@inheritDoc}
- */
- @Override
- TokenType parseNext(String token) {
- TokenType next = super.parseNext(token);
- return next == PROPERTY ? KEY : next;
- }
- },
- KEY("Expected ]") {
- @Override
- boolean allowedNext(TokenType next) {
- return next == RBRACK;
+ private static String parseProperty(CharSequence path, PathPosition pos) throws Exception {
+ int len = path.length();
+ int start = pos.getIndex();
+ loop: while (pos.getIndex() < len) {
+ switch (path.charAt(pos.getIndex())) {
+ case '[':
+ case ']':
+ case '.':
+ break loop;
}
+ pos.next();
+ }
+ if (pos.getIndex() > start) {
+ return path.subSequence(start, pos.getIndex()).toString();
+ }
+ throw new IllegalStateException(String.format("Position %s: expected property", start));
+ }
- },
- RBRACK("Expected property path separator or end of expression") {
- @Override
- boolean allowedNext(TokenType next) {
- return next == DOT || next == END;
+ /**
+ * Handles an index/key. If the text contained between [] is surrounded by a pair of " or ', it will be treated as a
+ * string which may contain Java escape sequences.
+ *
+ * @param path
+ * @param pos
+ * @throws Exception
+ */
+ private static void handleIndex(CharSequence path, PathPosition pos) throws Exception {
+ int len = path.length();
+ int start = pos.getIndex();
+ if (start < len) {
+ char first = path.charAt(pos.getIndex());
+ if (first == '"' || first == '\'') {
+ String s = parseQuotedString(path, pos);
+ if (s != null && path.charAt(pos.getIndex()) == ']') {
+ pos.handleIndexOrKey(s);
+ pos.next();
+ return;
+ }
}
- },
- DOT("Expected property after path separator") {
- @Override
- boolean allowedNext(TokenType next) {
- return next == PROPERTY;
+ // no quoted string; match ] greedily
+ while (pos.getIndex() < len) {
+ int here = pos.getIndex();
+ try {
+ if (path.charAt(here) == ']') {
+ if (here == start) {
+ pos.handleGenericInIterable();
+ } else {
+ pos.handleIndexOrKey(path.subSequence(start, here).toString());
+ }
+ return;
+ }
+ } finally {
+ pos.next();
+ }
}
- },
- END("") {
- @Override
- boolean allowedNext(TokenType next) {
- return false;
+ }
+ throw new IllegalStateException(String.format("Position %s: unparsable index", start));
+ }
+
+ private static String parseQuotedString(CharSequence path, PathPosition pos) throws Exception {
+ int len = path.length();
+ int start = pos.getIndex();
+ if (start < len) {
+ char quote = path.charAt(start);
+ pos.next();
+ StringWriter w = new StringWriter();
+ while (pos.getIndex() < len) {
+ int here = pos.getIndex();
+ // look for matching quote
+ if (path.charAt(here) == quote) {
+ return w.toString();
+ }
+ int codePoints = StringEscapeUtils.UNESCAPE_JAVA.translate(path, here, w);
+ if (codePoints == 0) {
+ w.write(Character.toChars(Character.codePointAt(path, here)));
+ pos.next();
+ } else {
+ for (int i = 0; i < codePoints; i++) {
+ pos.plus(Character.charCount(Character.codePointAt(path, pos.getIndex())));
+ }
+ }
}
- };
+ // if reached, reset due to no ending quote found
+ pos.setIndex(start);
+ }
+ return null;
+ }
- final String violationMessage;
+ /**
+ * ParsePosition/Callback
+ */
+ private static class PathPosition extends ParsePosition implements Callback<Object> {
+ final Callback<?> delegate;
/**
- * @param violationMessage
- * @param allowedNext
+ * Create a new {@link PathPosition} instance.
+ *
+ * @param delegate
*/
- private TokenType(String violationMessage) {
- this.violationMessage = violationMessage;
+ private PathPosition(Callback<?> delegate) {
+ super(0);
+ this.delegate = delegate;
}
- abstract boolean allowedNext(TokenType next);
+ /**
+ * Increment and return this.
+ *
+ * @return this
+ */
+ public PathPosition next() {
+ return plus(1);
+ }
- final void test(TokenType next) {
- if (!allowedNext(next)) {
- throw new IllegalStateException(violationMessage);
- }
+ /**
+ * Increase position and return this.
+ *
+ * @param addend
+ * @return this
+ */
+ public PathPosition plus(int addend) {
+ setIndex(getIndex() + addend);
+ return this;
}
- TokenType parseNext(String token) {
- if (".".equals(token)) {
- return DOT;
- }
- if ("[".equals(token)) {
- return LBRACK;
- }
- if ("]".equals(token)) {
- return RBRACK;
- }
- return PROPERTY;
+ /**
+ * {@inheritDoc}
+ */
+ public void handleProperty(String name) {
+ delegate.handleProperty(name);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleIndexOrKey(String value) {
+ delegate.handleIndexOrKey(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void handleGenericInIterable() {
+ delegate.handleGenericInIterable();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object result() {
+ return null;
+ }
+
}
+
}