/** * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.model.experimental.schema; /** * A checker for regular expressions. * * TODO(user): This needs to be rewritten to properly handle '^' and '$' characters. * */ final class RegularExpressionChecker { /* * The following is a list of what each of the subclasses and static objects * of <code>State</code> represent. * * HeadState: The state directly after an unescaped '(', indicating the start * of a nested group. * * TailState: The state while inside a nested group, not at the beginning of a * nested group, and outside any escape. * * EscapeState: The state directly after an escaping '\', indicating that the * next character should be interpreted as part of the escape. * * BASE_HEAD_STATE: The state at position 0. * * BASE_TAIL_STATE: The state at the top level after position 0 and outside * any escape nested group. */ private abstract static class State { abstract State nextState(int position, char character) throws InvalidSchemaException; abstract void endOfInput() throws InvalidSchemaException; } private static final class HeadState extends State { private final State stack; HeadState(State stack) { this.stack = stack; } @Override State nextState(int position, char character) throws InvalidSchemaException { switch (character) { case '\\': return new EscapeState(new TailState(stack)); case '(': return new HeadState(new TailState(stack)); case ')': return stack; case '*': case '?': throw new InvalidSchemaException( "Unexpected '" + character + "' at position " + position); default: return new TailState(stack); } } @Override void endOfInput() throws InvalidSchemaException { throw new InvalidSchemaException("Unmatched '('"); } } private static final class TailState extends State { private final State stack; TailState(State stack) { this.stack = stack; } @Override State nextState(int position, char character) { switch (character) { case '\\': return new EscapeState(this); case '(': return new HeadState(this); case ')': return stack; default: return this; } } @Override void endOfInput() throws InvalidSchemaException { throw new InvalidSchemaException("Unmatched '('"); } } private static final class EscapeState extends State { private final State stack; EscapeState(State stack) { this.stack = stack; } @Override State nextState(int position, char character) throws InvalidSchemaException { switch (character) { case '\\': case '|': case '*': case '?': case '.': case '(': case ')': return stack; default: throw new InvalidSchemaException( "Unexpected character after backslash at position " + position); } } @Override void endOfInput() throws InvalidSchemaException { throw new InvalidSchemaException("Backslash at end of expression"); } } private static final State BASE_HEAD_STATE = new State() { @Override State nextState(int position, char character) throws InvalidSchemaException { assert position == 0; switch (character) { case '\\': return new EscapeState(BASE_TAIL_STATE); case '(': return new HeadState(BASE_TAIL_STATE); case ')': case '*': case '?': throw new InvalidSchemaException("Unexpected '" + character + "' at position 0"); default: return BASE_TAIL_STATE; } } @Override void endOfInput() {} }; private static final State BASE_TAIL_STATE = new State() { @Override State nextState(int position, char character) throws InvalidSchemaException { switch (character) { case '\\': return new EscapeState(this); case '(': return new HeadState(this); case ')': throw new InvalidSchemaException("Unexpected ')' at position " + position); default: return this; } } @Override void endOfInput() {} }; /** * Checks whether the given string is a regular expression, and throws an * exception if not. * * @param re a string * @throws InvalidSchemaException if the given string is not a regular * expression */ static void checkRegularExpression(String re) throws InvalidSchemaException { State state = BASE_HEAD_STATE; for (int i = 0; i < re.length(); ++i) { state = state.nextState(i, re.charAt(i)); } state.endOfInput(); } }