Package org.exolab.castor.jdo.oql

Source Code of org.exolab.castor.jdo.oql.Lexer

/**
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
*    statements and notices.  Redistributions must also contain a
*    copy of this document.
*
* 2. Redistributions in binary form must reproduce the
*    above copyright notice, this list of conditions and the
*    following disclaimer in the documentation and/or other
*    materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
*    products derived from this Software without prior written
*    permission of Intalio, Inc.  For written permission,
*    please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
*    nor may "Exolab" appear in their names without prior written
*    permission of Intalio, Inc. Exolab is a registered
*    trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
*    (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 1999 (C) Intalio, Inc. All Rights Reserved.
*
* $Id: Lexer.java,v 1.1.1.1 2003/03/03 07:08:22 kvisco Exp $
*/


package org.exolab.castor.jdo.oql;

import java.lang.String;
import java.lang.StringBuffer;
import java.util.Hashtable;

/**
* Seperates an input string of OQL into a stream of {@link Token}s.
*
* @author  <a href="nissim@nksystems.com">Nissim Karpenstein</a>
* @version $Revision: 1.1.1.1 $ $Date: 2003/03/03 07:08:22 $
*/
public class Lexer implements TokenTypes {

  String _queryString;
  int _pos;
  Hashtable keywords;
  boolean endOfQueryPassed = false;
 
  /**
   * Creates a lexer which will seperate a string query into a series of
   * tokens.
   *
   * @param oqlQueryString The string representation of the OQL Query
   */
  public Lexer(String oqlQueryString) {
    _queryString = oqlQueryString;
   
    //Set up the Hashtable of keywords
    keywords = new Hashtable();
    keywords.put("select", new Integer(KEYWORD_SELECT));
    keywords.put("as", new Integer(KEYWORD_AS));
    keywords.put("from", new Integer(KEYWORD_FROM));
    keywords.put("in", new Integer(KEYWORD_IN));
    keywords.put("where", new Integer(KEYWORD_WHERE));
    keywords.put("or", new Integer(KEYWORD_OR));
    keywords.put("and", new Integer(KEYWORD_AND));
    keywords.put("like", new Integer(KEYWORD_LIKE));
    keywords.put("mod", new Integer(KEYWORD_MOD));
    keywords.put("abs", new Integer(KEYWORD_ABS));
    keywords.put("not", new Integer(KEYWORD_NOT));
    keywords.put("nil", new Integer(KEYWORD_NIL));
    keywords.put("undefined", new Integer(KEYWORD_UNDEFINED));
    keywords.put("between", new Integer(KEYWORD_BETWEEN));
    keywords.put("distinct", new Integer(KEYWORD_DISTINCT));
    keywords.put("is_defined", new Integer(KEYWORD_IS_DEFINED));
    keywords.put("is_undefined", new Integer(KEYWORD_IS_UNDEFINED));
    keywords.put("list", new Integer(KEYWORD_LIST));
    keywords.put("order", new Integer(KEYWORD_ORDER));
    keywords.put("by", new Integer(KEYWORD_BY));
    keywords.put("asc", new Integer(KEYWORD_ASC));
    keywords.put("desc", new Integer(KEYWORD_DESC));
    keywords.put("count", new Integer(KEYWORD_COUNT));
    keywords.put("sum", new Integer(KEYWORD_SUM));
    keywords.put("min", new Integer(KEYWORD_MIN));
    keywords.put("max", new Integer(KEYWORD_MAX));
    keywords.put("avg", new Integer(KEYWORD_AVG));
    keywords.put("limit", new Integer(KEYWORD_LIMIT));
       
  }

  /**
   * Checks whether the query strin has been fully consumed.
   *
   * @return True if there is text in the query which hasn't been tokenized,
   *    otherwise false.
   */
  public boolean hasMoreTokens() {
    return !endOfQueryPassed;
  }

  /**
   * Returns the next {@link Token} from the stream.
   *
   * @return The next token in the stream.
   * @throws NoMoreTokensException if the last character was already consumed.
   * @throws InvalidCharException if an invalid character is found while
   *        generating the next Token.
   */
  public Token nextToken() throws NoMoreTokensException, InvalidCharException {
 
    char curChar, oldChar;

    try {
      oldChar = _queryString.charAt(_pos-1);
    }
    catch (IndexOutOfBoundsException e) {
        oldChar = '\0';
    }

    try {
      curChar = _queryString.charAt(_pos);
    }
    catch (IndexOutOfBoundsException e) {
      if (!endOfQueryPassed) {
        endOfQueryPassed = true;
        return (new Token(END_OF_QUERY, ""));
      }
      else
        throw (new NoMoreTokensException());
    }
 
    //consume white space
    while (isWhiteSpace(curChar)) {
      oldChar = curChar;
      _pos++;
      try {
        curChar = _queryString.charAt(_pos);
      }
      catch (IndexOutOfBoundsException e) {
        if (!endOfQueryPassed) {
          endOfQueryPassed = true;
          return (new Token(END_OF_QUERY, ""));
        }
      }
    }
 
    if ( isDigit(curChar) )
      return numericLiteral();

    if ( isLetter(curChar) )
      return identifier( oldChar );
   
    char nextChar = 0;
    try {
      nextChar = _queryString.charAt(_pos + 1);
    }
    catch (IndexOutOfBoundsException e) {}

    if (curChar == '!' && nextChar == '=') {
      _pos += 2;
      return new Token( NOT_EQUAL, "!=" );
    }

    if (curChar == '>' && nextChar == '=') {
      _pos += 2;
      return new Token( GTE, ">=" );
    }

    if (curChar == '<' && nextChar == '=') {
      _pos += 2;
      return new Token( LTE, "<=" );
    }

    if (curChar == '|' && nextChar == '|') {
      _pos += 2;
      return new Token( CONCAT, "||" );
    }

    if (curChar == '-' && nextChar == '>') {
      _pos += 2;
      return new Token( ARROW, "->" );
    }

   
    Token retToken = null;
   
    switch (curChar) {
      case '\'':
        return charLiteral();
      case '"':
        return stringLiteral();
      case ':':
        retToken = new Token( COLON, ":" );
        break;
      case '=':
        retToken = new Token( EQUAL, "=" );
        break;
      case '<':
        retToken = new Token( LT, "<" );
        break;
      case '>':
        retToken = new Token( GT, ">" );
        break;
      case '+':
        retToken = new Token( PLUS, "+" );
        break;
      case '-':
        retToken = new Token( MINUS, "-" );
        break;
      case '*':
        retToken = new Token( TIMES, "*" );
        break;
      case '/':
        retToken = new Token( DIVIDE, "/" );
        break;
      case '(':
        retToken = new Token( LPAREN, "(" );
        break;
      case ')':
        retToken = new Token( RPAREN, ")" );
        break;
      case '.':
        retToken = new Token( DOT, "." );
        break;
      case '$':
        retToken = new Token( DOLLAR, "$" );
        break;
      case ',':
        retToken = new Token( COMMA, "," );
        break;
    }
    if ( retToken == null )
      throw (new InvalidCharException("An invalid character was found in the query at position " + _pos));
    else {
      _pos++;
      return retToken;
    }
  }

  /**
   * Tests whether a character is white space.
   *
   * @param theChar The character to test
   * @return true if the character is whitespace, otherwise false
   */
  private boolean isWhiteSpace(char theChar) {
    switch (theChar) {
      case ' ': case '\t': case '\r': case '\n':
        return true;
      default:
        return false;
    }
  }

  /**
   * Tests whether a character is a digit.
   *
   * @param theChar The character to test
   * @return true if the character is a digit, otherwise false
   */
  private boolean isDigit(char theChar) {
    return Character.isDigit(theChar);
  }

  /**
   * Tests whether a character is a letter.
   *
   * @param theChar The character to test
   * @return true if the character is a letter, otherwise false
   */
  private boolean isLetter(char theChar) {
    return Character.isLetter(theChar);
  }

  /**
   * Consumes characters of a string literal. Throws exceptions if quotes
   * aren't matched properly.
   *
   * @return A Token with STRING_LITERAL type containing the string literal
   * @throws NoMoreTokensException if the method is erroniously called when
   *    all of the characters in the query have already been consumed.
   * @throws InvalidCharException if the first char is not a double quote (")
   *    or a matching end quote is not found and all the characters are
   *    consumed.
   */
  private Token stringLiteral() throws NoMoreTokensException, InvalidCharException {
 
    char curChar;
    try {
      curChar = _queryString.charAt(_pos);
    }
    catch (IndexOutOfBoundsException e) {
      throw (new NoMoreTokensException());
        }
     
    if (curChar != '"')
      throw (new InvalidCharException("stringLiteral() was called when the next character was not a double quote.  Position: " + _pos));
   
    StringBuffer sb = new StringBuffer("\"");
    _pos++;
    try {
      curChar = _queryString.charAt(_pos);
      while (curChar != '"') {
        sb.append(curChar);
        //if the current char is backslash, don't check append the next char
        //without checking whether it's a quote.
        if (curChar == '\\') {
          _pos++;
          curChar = _queryString.charAt(_pos);
          sb.append(curChar);
        }
        _pos++;
        curChar = _queryString.charAt(_pos);
      }
      //append and consume the closing quote
      sb.append(curChar);
      _pos++;
    }
    catch (IndexOutOfBoundsException e) {
      throw ( new InvalidCharException( "End of query string reached, but \" expected." ) );
        }
   
    return ( new Token( STRING_LITERAL, sb.toString() ) );
  }

  /**
   * Consumes characters of a char literal. Throws exceptions if quotes
   * aren't matched properly.
   *
   * @return A Token with CHAR_LITERAL type containing the char literal
   * @throws NoMoreTokensException if the method is erroniously called when
   *    all of the characters in the query have already been consumed.
   * @throws InvalidCharException if the first char is not a single quote (')
   *    or a matching end quote is not found after a single character is
   *    consumed.
   */
  private Token charLiteral() throws NoMoreTokensException, InvalidCharException {
 
    char curChar;
    try {
      curChar = _queryString.charAt(_pos);
    }
    catch (IndexOutOfBoundsException e) {
      throw (new NoMoreTokensException());
        }
     
    if (curChar != '\'')
      throw (new InvalidCharException("charLiteral() was called when the next character was not a double quote.  Position: " + _pos));
   
    StringBuffer sb = new StringBuffer("'");
    _pos++;
    try {
      curChar = _queryString.charAt(_pos);
      //append the first char after the '
      sb.append(curChar);
      _pos++;
     
      //if the current char is backslash, consume escaped char
      if (curChar == '\\') {
        curChar = _queryString.charAt(_pos);
        sb.append(curChar);
        _pos++;
      }
      curChar = _queryString.charAt(_pos);
      if (curChar == '\'') {
        sb.append(curChar);
        _pos++;
      }
      else
        throw ( new InvalidCharException( "Character literals may only contain a single character or escape sequence.  Position: " + _pos ) );
    }
    catch (IndexOutOfBoundsException e) {
      throw ( new InvalidCharException( "End of query string reached, but \' expected." ) );
    }

    return ( new Token( CHAR_LITERAL, sb.toString() ) );
  }

  /**
   * Returns the character at the current position in the queryString.
   *
   * @return The character in the queryString at the current position or 0 if
   *      the current position is passed the last char
   */
  private char getChar() {
    try {
      return (_queryString.charAt(_pos));
    }
    catch (IndexOutOfBoundsException e) {
      return 0;
    }
  }

  /**
   * Consumes characters of a numeric literal.
   *
   * @return A Token with LONG_LITERAL type containing the long literal if
   *    there is no decimal point, otherwise a token with DOUBLE_LITERAL type
   *    containing the double literal.
   * @throws NoMoreTokensException if the method is erroniously called when
   *    all of the characters in the query have already been consumed.
   * @throws InvalidCharException if the first char is not a digit or if a
   *    decimal point is not followed by a digit or if an E or e exponent char
   *    is not followed by a +, - or digit, etc.
   */
  private Token numericLiteral() throws NoMoreTokensException, InvalidCharException {
 
    char curChar;
    try {
      curChar = _queryString.charAt(_pos);
    }
    catch (IndexOutOfBoundsException e) {
      throw (new NoMoreTokensException());
        }
     
    if ( ! isDigit(curChar) )
      throw (new InvalidCharException("numericLiteral() was called when the next character was not a digit.  Position: " + _pos));
   
    StringBuffer sb = new StringBuffer().append(curChar);
    _pos++;
    curChar = getChar();
         
    while ( isDigit(curChar) ) {
      sb.append(curChar);
      _pos++;
      curChar = getChar();
    }

    //is this a long or a double
    if (curChar == '.') {
      //it's a double
      sb.append('.');
      _pos++;
      curChar = getChar();
           
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException( "Digit expected after decimal point in double literal. Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = getChar();
      }
     
      if (curChar == 'E' || curChar == 'e') {
        sb.append(curChar);
        _pos++;
        curChar = getChar();
       
        boolean isExponentSigned = false;
        if (curChar == '+' || curChar == '-') {
          isExponentSigned = true;
          sb.append(curChar);
          _pos++;
          curChar = getChar();
        }
       
        if ( ! isDigit(curChar) )
          if (isExponentSigned)
            throw (new InvalidCharException( "Digit expected after sign in exponent (double literal).  Position: " + _pos ));
          else
            throw (new InvalidCharException( "Digit expected after exponent character (double literal).  Position: " + _pos ));
        else {
          sb.append(curChar);
          _pos++;
          curChar = getChar();
          while ( isDigit(curChar) ) {
            sb.append(curChar);
            _pos++;
            curChar = getChar();
          }
        }
      }
     
      return (new Token( DOUBLE_LITERAL, sb.toString() ) );
    }
    else
      //it's a long
      return (new Token( LONG_LITERAL, sb.toString() ) );
  }

  /**
   * Consumes characters of a token which starts with a letter.  This may end
   * up being a token of type IDENTIFIER, KEYWORD, BOOLEAN_LITERAL,
   * DATE_LITERAL, TIME_LITERAL, or TIMESTAMP_LITERAL.  This is determined by
   * comparing the text to a list of keywords, and calling the appropriate
   * function if necessary (e.g. to consume the rest of the text in a DATE, TIME
   * or TIMESTAMP literal).
   *
   * @return A Token with one of the token types described above.
   * @throws NoMoreTokensException if the method is erroniously called when
   *    all of the characters in the query have already been consumed.
   * @throws InvalidCharException if the first char is not a letter.
   */
  private Token identifier( char oldChar ) throws NoMoreTokensException, InvalidCharException {
 
    char curChar;
    try {
      curChar = _queryString.charAt(_pos);
    }
    catch (IndexOutOfBoundsException e) {
      throw (new NoMoreTokensException());
        }
     
    if ( ! isLetter(curChar) )
      throw (new InvalidCharException("identifier() was called when the next character was not a letter.  Position: " + _pos));
   
    StringBuffer sb = new StringBuffer().append(curChar);
    _pos++;
    curChar = getChar();
         
    while ( isDigit(curChar) || isLetter(curChar) || curChar == '_' ) {
      sb.append(curChar);
      _pos++;
      curChar = getChar();
    }

    String ident = sb.toString().toLowerCase();

    // If there's a dot before, we'll assume that it's an identifier in any case.
    if( oldChar == '.' )
        return (new Token( IDENTIFIER, sb.toString() ));

    if (ident.equals("date"))
      return dateLiteral(sb.toString());
    if (ident.equals("time"))
      return timeLiteral(sb.toString());
    if (ident.equals("timestamp"))
      return timeStampLiteral(sb.toString());
     
    if (ident.equals("true") || ident.equals("false"))
      return (new Token( BOOLEAN_LITERAL, sb.toString() ));

    if (keywords.containsKey(ident))
      return (new Token( ((Integer)keywords.get(ident)).intValue(),
                         sb.toString() ));

    return (new Token( IDENTIFIER, sb.toString() ));

  }

  /**
   * Consumes characters of a date literal.  The date keyword was already
   * consumed by the {@link identifier} function, and is passed as a parameter.
   *
   * @param alreadyConsumed the part of the date literal already consumed
   *    by the calling function.  This should always be "date".
   * @return A Token with DATE_LITERAL type containing the entire date
   *    literal text.
   * @throws InvalidCharException if the first char is not a quote, or the
   *    date literal is otherwise not of the proper form:
   *    date 'longLiteral-longLiteral-longLiteral'.
   */
  private Token dateLiteral(String alreadyConsumed) throws InvalidCharException {
 
    StringBuffer sb = new StringBuffer(alreadyConsumed);
   
    char curChar = getChar();
    try {
     
      curChar = _queryString.charAt(_pos);
     
      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '\'' )
        throw (new InvalidCharException("The date keyword must be followed by a single quote.  Position: " + _pos));
  
      sb.append(" '");
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in date literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '-' )
        throw (new InvalidCharException("- expected.  Fields in date literal must be separated by a dash.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in date literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '-' )
        throw (new InvalidCharException("- expected.  Fields in date literal must be separated by a dash.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in date literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '\'' )
        throw (new InvalidCharException("' expected.  Date literal must be enclosed by a single quotes.  Position: " + _pos));

      sb.append(curChar);
      _pos++;

      return (new Token( DATE_LITERAL, sb.toString() ));

    }
    catch (IndexOutOfBoundsException e) {
      throw (new InvalidCharException("End of query encountered in the middle of date literal."));
        }
  }

  /**
   * Consumes characters of a time literal.  The time keyword was already
   * consumed by the {@link identifier} function, and is passed as a parameter.
   *
   * @param alreadyConsumed the part of the time literal already consumed
   *    by the calling function.  This should always be "time".
   * @return A Token with TIME_LITERAL type containing the entire time
   *    literal text.
   * @throws InvalidCharException if the first char is not a quote, or the
   *    time literal is otherwise not of the proper form:
   *    time 'longLiteral:longLiteral:longLiteral'.
   */
  private Token timeLiteral(String alreadyConsumed) throws InvalidCharException {
 
    StringBuffer sb = new StringBuffer(alreadyConsumed);
   
    char curChar = getChar();
    try {
     
      curChar = _queryString.charAt(_pos);
     
      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '\'' )
        throw (new InvalidCharException("The time keyword must be followed by a single quote.  Position: " + _pos));
  
      sb.append(" '");
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in time literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != ':' )
        throw (new InvalidCharException(": expected.  Fields in time literal must be separated by a colon.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in time literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != ':' )
        throw (new InvalidCharException(": expected.  Fields in time literal must be separated by a colon.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in time literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      // millisecond part of the time
      if ( curChar == '.' ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);

        if ( ! isDigit(curChar) )
          throw (new InvalidCharException("Digit expected in time literal.  Position: " + _pos));

        while ( isDigit(curChar) ) {
          sb.append(curChar);
          _pos++;
          curChar = _queryString.charAt(_pos);
        }
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '\'' )
        throw (new InvalidCharException("' expected.  Time literal must be enclosed by a single quotes.  Position: " + _pos));

      sb.append(curChar);
      _pos++;

      return (new Token( TIME_LITERAL, sb.toString() ));

    }
    catch (IndexOutOfBoundsException e) {
      throw (new InvalidCharException("End of query encountered in the middle of time literal."));
        }
  }


  /**
   * Consumes characters of a time stamp literal.  The timestamp keyword was
   * already consumed by the {@link identifier} function, and is passed as a
   * parameter.
   *
   * @param alreadyConsumed the part of the timestamp literal already consumed
   *    by the calling function.  This should always be "timestamp".
   * @return A Token with TIMESTAMP_LITERAL type containing the entire
   *    timestamp literal text.
   * @throws InvalidCharException if the first char is not a quote, or the
   *    timestamp literal is otherwise not of the proper form:
   *    timestamp 'longLiteral-longLiteral-longLiteral
   *               longLiteral:longLiteral:longLiteral'.
   */
  private Token timeStampLiteral(String alreadyConsumed) throws InvalidCharException {
 
    StringBuffer sb = new StringBuffer(alreadyConsumed);
   
    char curChar = getChar();
    try {
     
      curChar = _queryString.charAt(_pos);
     
      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '\'' )
        throw (new InvalidCharException("The timestamp keyword must be followed by a single quote.  Position: " + _pos));
  
      sb.append(" '");
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in timestamp literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '-' )
        throw (new InvalidCharException("- expected.  Fields in date part of timestamp literal must be separated by a dash.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in timestamp literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != '-' )
        throw (new InvalidCharException("- expected.  Fields in date part of timestamp literal must be separated by a dash.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in timestamp literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      if ( ! isWhiteSpace(curChar) )
        throw (new InvalidCharException("White space expected in timestamp literal.  Position: " + _pos));
      
      curChar = consumeWhiteSpace(curChar);

      sb.append(' ');
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in timestamp literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != ':' )
        throw (new InvalidCharException(": expected.  Fields in time part of timestamp literal must be separated by a colon.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in timestamp literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      curChar = consumeWhiteSpace(curChar);
     
      if ( curChar != ':' )
        throw (new InvalidCharException(": expected.  Fields in time part of timestamp literal must be separated by a colon.  Position: " + _pos));
  
      sb.append(curChar);
      _pos++;
      curChar = _queryString.charAt(_pos);
          
      curChar = consumeWhiteSpace(curChar);
     
      if ( ! isDigit(curChar) )
        throw (new InvalidCharException("Digit expected in timestamp literal.  Position: " + _pos));
       
      while ( isDigit(curChar) ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);
      }

      // millisecond part of the timestamp
      if ( curChar == '.' ) {
        sb.append(curChar);
        _pos++;
        curChar = _queryString.charAt(_pos);

        if ( ! isDigit(curChar) )
          throw (new InvalidCharException("Digit expected in timestamp literal.  Position: " + _pos));

        while ( isDigit(curChar) ) {
          sb.append(curChar);
          _pos++;
          curChar = _queryString.charAt(_pos);
        }
      }

      curChar = consumeWhiteSpace(curChar);

      if ( curChar != '\'' )
        throw (new InvalidCharException("' expected.  Timestamp literal must be enclosed by a single quotes.  Position: " + _pos));

      sb.append(curChar);
      _pos++;

      return (new Token( TIMESTAMP_LITERAL, sb.toString() ));

    }
    catch (IndexOutOfBoundsException e) {
      throw (new InvalidCharException("End of query encountered in the middle of time literal."));
        }
  }

  /**
   * Consumes whitespace characters.
   *
   * @param @curChar the current character before the call
   * @return The current character after whitespace is consumed
   */
  private char consumeWhiteSpace(char curChar) {
    while (isWhiteSpace(curChar)) {
      _pos++;
      curChar = _queryString.charAt(_pos);
    }
    return (curChar);
  }

}
TOP

Related Classes of org.exolab.castor.jdo.oql.Lexer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.