/**
* 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();
}
}