/** * Copyright 2005-2012 Akiban Technologies, 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 com.persistit; import java.math.BigDecimal; import java.math.BigInteger; import java.text.ParseException; import java.util.ArrayList; import com.persistit.util.Util; /** * Parses String values as Key or KeyFilter values. * * @version 1.0 */ public class KeyParser { private final String _source; private final StringBuilder _sb = new StringBuilder(); boolean _dp; private int _index; private final int _start; private final int _end; private static class ProtoTerm { byte[] _leftBytes; byte[] _rightBytes; boolean _leftInclusive; boolean _rightInclusive; private KeyFilter.Term term() { if (_rightBytes == null) { return new KeyFilter.SimpleTerm(_leftBytes); } else { return new KeyFilter.RangeTerm(_leftBytes, _rightBytes, _leftInclusive, _rightInclusive); } } } /** * Construct a <code>KeyParser</code> for the specified string. * * @param source * The string to be parsed. */ public KeyParser(final String source) { _source = source; _index = 0; _start = 0; _end = source.length(); } /** * Construct a <code>KeyParser</code> for a substring of the specified * string. * * @param source * The string * * @param offset * Offset of the first character of the substring to parse * * @param size * Size of the substring */ public KeyParser(final String source, final int offset, final int size) { if (offset < 0 || offset + size > source.length()) { throw new IllegalArgumentException(); } _source = source; _start = offset; _end = offset + size; _index = offset; } /** * Parse a key value from the string or substring from which this * <code>KeyParser</code> was constructed, modifying the supplied * <code>Key</code> to contain the result. * * @return <code>true</code> if the key value was parsed successfully. * <code>false</code> if the string or substring was invalid as a <a * href="Key.html#_stringRepresentation"> string representation</a> * of a <code>Key</code>. */ public boolean parseKey(final Key key) { final int index = _index; boolean result = false; boolean first = true; try { if (matchNonWhiteChar('{')) { for (;;) { final int c = getNonWhiteChar(); if (c == '}') { result = true; break; } if (first) back(); else if (c != ',') break; if (!parseKeySegment(key)) break; first = false; } if (result) result = getNonWhiteChar() == -1; } } finally { if (!result) _index = index; } return result; } /** * Parse and returns a <code>KeyFilter</code> from the string or substring * from which this <code>KeyParser</code> was constructed. * * @return A <code>KeyFilter</code> or <code>null</code> if the string or * substring was invalid as a <a * href="KeyFilter.html#_stringRepresentation"> string * representation</a> of a <code>KeyFilter</code>. */ public KeyFilter parseKeyFilter() { final int saveIndex = _index; final ArrayList vector = new ArrayList(); // accumulate Terms boolean result = false; boolean minDepthSet = false; boolean maxDepthSet = false; int minDepth = 0; int maxDepth = Integer.MAX_VALUE; final Key workKey = new Key((Persistit) null); final ProtoTerm protoTerm = new ProtoTerm(); int depth = 0; try { if (matchNonWhiteChar('{')) { for (;;) { int c = getNonWhiteChar(); if (c == '}') { result = true; break; } if (depth == 0) back(); else { if (c != ',') break; } c = getNonWhiteChar(); if (c == '>' && !minDepthSet) { minDepth = depth + 1; minDepthSet = true; c = getNonWhiteChar(); } if (c == '{') { final KeyFilter.Term[] array = parseFilterTermArray(workKey, protoTerm); if (array == null || !matchNonWhiteChar('}')) break; else vector.add(KeyFilter.orTerm(array)); } else { back(); final KeyFilter.Term term = parseFilterTerm(workKey, protoTerm); if (term == null) break; vector.add(term); } if (matchNonWhiteChar('<') && !maxDepthSet) { maxDepth = depth + 1; maxDepthSet = true; } depth++; } if (result) result = getNonWhiteChar() == -1; } } finally { if (!result) _index = saveIndex; } if (!result) return null; final KeyFilter.Term[] terms = new KeyFilter.Term[vector.size()]; for (int index = 0; index < terms.length; index++) { terms[index] = (KeyFilter.Term) vector.get(index); } return new KeyFilter(terms, minDepth, maxDepth); } /** * Return the current index for parsing. This indicates the first character * that did not conform * * @return The index */ public int getIndex() { return _index; } /** * Attempt to parse a chunk of the source string as a key segment. If * successful, append the segment to the key. * * @param key * @return <code>true</code> a valid key segment was parsed */ private boolean parseKeySegment(final Key key) { final int index = _index; final int size = key.getEncodedSize(); boolean result = false; try { int c = getNonWhiteChar(); if (c == '\"' && matchQuotedStringTail()) { result = true; key.append(_sb); } else if (c == '(') { back(); if (matchExactString(Key.PREFIX_BOOLEAN)) { result = true; if (matchExactString("true")) key.append(true); else if (matchExactString("false")) key.append(false); else result = false; } else if (matchExactString(Key.PREFIX_BYTE)) { if (matchNumber(false, false)) { key.append(Byte.parseByte(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_SHORT)) { if (matchNumber(false, false)) { key.append(Short.parseShort(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_CHAR)) { if (matchNumber(false, false)) { key.append((char) Integer.parseInt(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_INT)) { if (matchNumber(false, false)) { key.append(Integer.parseInt(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_LONG)) { if (matchNumber(false, false)) { key.append(Long.parseLong(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_FLOAT)) { if (matchNumber(true, false)) { key.append(Float.parseFloat(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_DOUBLE)) { if (matchNumber(true, false)) { key.append(Double.parseDouble(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_BIG_INTEGER) || matchExactString(Key.PREFIX_BIG_INTEGER0)) { if (matchNumber(false, false)) { key.append(new BigInteger(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_BIG_DECIMAL) || matchExactString(Key.PREFIX_BIG_DECIMAL0)) { if (matchNumber(true, false)) { key.append(new BigDecimal(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_STRING) || matchExactString(Key.PREFIX_STRING0)) { c = getNonWhiteChar(); if (c == '\"' && matchQuotedStringTail()) { key.append(_sb); result = true; } } else if (matchExactString(Key.PREFIX_DATE) || matchExactString(Key.PREFIX_DATE0)) { if (matchNumber(true, true)) { key.append(Key.SDF.parse(_sb.toString())); result = true; } } else if (matchExactString(Key.PREFIX_BYTE_ARRAY)) { if (matchUntil(',', '}')) { back(); key.append(Util.hexToBytes(_sb.toString())); result = true; } } } else { back(); if (matchNumber(true, false)) { if (_dp) key.append(Double.parseDouble(_sb.toString())); else key.append(Integer.parseInt(_sb.toString())); result = true; } else if (matchExactString("null")) { key.append(null); result = true; } else if (matchExactString("true")) { key.append(true); result = true; } else if (matchExactString("false")) { key.append(false); result = true; } } } catch (final NumberFormatException nfe) { } catch (final ParseException pe) { } finally { if (!result) { _index = index; key.setEncodedSize(size); } } return result; } private KeyFilter.Term[] parseFilterTermArray(final Key workKey, final ProtoTerm protoTerm) { final ArrayList<KeyFilter.Term> list = new ArrayList<KeyFilter.Term>(); for (;;) { final KeyFilter.Term term = parseFilterTerm(workKey, protoTerm); if (term == null) return null; list.add(term); final int c = getNonWhiteChar(); if (c == '}') { back(); final KeyFilter.Term[] array = new KeyFilter.Term[list.size()]; for (int index = 0; index < array.length; index++) { array[index] = list.get(index); } return array; } else if (c != ',') return null; } } private KeyFilter.Term parseFilterTerm(final Key workKey, final ProtoTerm protoTerm) { int c = getNonWhiteChar(); if (c == '*') { return KeyFilter.ALL; } back(); protoTerm._leftBytes = null; protoTerm._rightBytes = null; protoTerm._leftInclusive = true; protoTerm._rightInclusive = true; KeyFilter.Term term = null; boolean okay = parseKeyFilterRange(workKey, protoTerm); if (okay) term = protoTerm.term(); else { c = getNonWhiteChar(); if (c == '(' || c == '[') { protoTerm._leftInclusive = (c == '['); okay = parseKeyFilterRange(workKey, protoTerm); if (okay) { c = getNonWhiteChar(); if (c == ')' || c == ']') { protoTerm._rightInclusive = (c == ']'); term = protoTerm.term(); } } } } return term; } boolean parseKeyFilterRange(final Key workKey, final ProtoTerm protoTerm) { workKey.clear(); int c = getNonWhiteChar(); back(); if (c == ':') { workKey.append(Key.BEFORE); } else { if (!parseKeySegment(workKey)) return false; } protoTerm._leftBytes = segmentBytes(workKey); protoTerm._rightBytes = null; if (matchNonWhiteChar(':')) { workKey.clear(); c = getNonWhiteChar(); back(); if (c == ')' || c == ']' || c == ',' || c == '}' || c == -1) { workKey.append(Key.AFTER); } else { if (!parseKeySegment(workKey)) return false; } protoTerm._rightBytes = segmentBytes(workKey); } return true; } static byte[] segmentBytes(final Key key) { final int start = key.getIndex(); int end = key.nextElementIndex(start); if (end == -1) end = key.getEncodedSize(); final byte[] bytes = new byte[end - start]; System.arraycopy(key.getEncodedBytes(), start, bytes, 0, end - start); return bytes; } private int getNonWhiteChar() { int c = getChar(); while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = getChar(); } return c; } private boolean matchNonWhiteChar(final int c) { return getNonWhiteChar() == c ? true : back(); } private boolean matchUntil(final int c1, final int c2) { _sb.setLength(0); int c; while ((c = getChar()) != -1) { if (c == c1 || c == c2) return true; else _sb.append((char) c); } return false; } private boolean matchNumber(boolean decimalPoint, boolean tzsign) { _sb.setLength(0); _dp = false; int c = getNonWhiteChar(); if (c != '-' && (c < '0' || c > '9') && (c != '.' || !decimalPoint)) { back(); return false; } _sb.append((char) c); if (c == '.') { decimalPoint = false; _dp = true; } while ((c = getChar()) >= '0' && c <= '9' || (decimalPoint && c == '.') || (tzsign & (c == '-' || c == '+'))) { _sb.append((char) c); if (c == '.') { decimalPoint = false; _dp = true; } if (c == '-' || c == '+') tzsign = false; } back(); return true; } private boolean matchExactString(final String s) { if (_source.regionMatches(_index, s, 0, s.length())) { _index += s.length(); return true; } return false; } private boolean matchQuotedStringTail() { boolean escape = false; _sb.setLength(0); for (;;) { int c = getChar(); if (c == -1) return false; if (c == '\\') { if (escape) _sb.append('\\'); escape = !escape; } else if (escape) { switch (c) { case 'r': c = '\r'; break; case 'n': c = '\n'; break; case 'f': c = '\f'; break; case 'b': c = '\b'; break; case 't': c = '\t'; break; case 'u': c = unicode(); if (c == -1) return false; break; default: } _sb.append((char) c); escape = false; } else { if (c == '\\') escape = true; else if (c == '\"') { return true; } else _sb.append((char) c); } } } private int unicode() { if (_index + 4 > _end) return -1; final int u = Integer.parseInt(_source.substring(_index, _index + 4), 16); _index += 4; return u; } private int getChar() { if (_index >= 0 && _index < _end) return _source.charAt(_index++); else return -1; } private boolean back() { if (_index > _start) _index--; return false; } }