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;
+        }
+
     }
+
 }