Repository: cayenne
Updated Branches:
  refs/heads/master 666c96de1 -> c44ccfde3


http://git-wip-us.apache.org/repos/asf/cayenne/blob/c44ccfde/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/JavaCharStream.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/JavaCharStream.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/JavaCharStream.java
index 7394b28..4065980 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/JavaCharStream.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/JavaCharStream.java
@@ -29,8 +29,7 @@ import java.io.IOException;
  * contain only ASCII characters (with java-like unicode escape processing).
  */
 
-public class JavaCharStream
-{
+public class JavaCharStream {
 
     // optimizing internal Exception by reusing the exception per CAY-1667. 
This exception
     // never reaches the end user, so we can suppress stack trace creation and 
make it
@@ -40,547 +39,603 @@ public class JavaCharStream
         @Override
         public synchronized Throwable fillInStackTrace() {
             return this;
-        };
+        }
     };
-    
-/** Whether parser is static. */
-  public static final boolean staticFlag = false;
-  static final int hexval(char c) throws java.io.IOException {
-    switch(c)
-    {
-       case '0' :
-          return 0;
-       case '1' :
-          return 1;
-       case '2' :
-          return 2;
-       case '3' :
-          return 3;
-       case '4' :
-          return 4;
-       case '5' :
-          return 5;
-       case '6' :
-          return 6;
-       case '7' :
-          return 7;
-       case '8' :
-          return 8;
-       case '9' :
-          return 9;
-
-       case 'a' :
-       case 'A' :
-          return 10;
-       case 'b' :
-       case 'B' :
-          return 11;
-       case 'c' :
-       case 'C' :
-          return 12;
-       case 'd' :
-       case 'D' :
-          return 13;
-       case 'e' :
-       case 'E' :
-          return 14;
-       case 'f' :
-       case 'F' :
-          return 15;
-    }
-
-    throw new java.io.IOException(); // Should never come here
-  }
-
-/** Position in buffer. */
-  public int bufpos = -1;
-  int bufsize;
-  int available;
-  int tokenBegin;
-  protected int bufline[];
-  protected int bufcolumn[];
-
-  protected int column = 0;
-  protected int line = 1;
-
-  protected boolean prevCharIsCR = false;
-  protected boolean prevCharIsLF = false;
-
-  protected java.io.Reader inputStream;
-
-  protected char[] nextCharBuf;
-  protected char[] buffer;
-  protected int maxNextCharInd = 0;
-  protected int nextCharInd = -1;
-  protected int inBuf = 0;
-  protected int tabSize = 8;
-  private boolean closed;
-
-  protected void setTabSize(int i) { tabSize = i; }
-  protected int getTabSize(int i) { return tabSize; }
-
-  protected void ExpandBuff(boolean wrapAround)
-  {
-     char[] newbuffer = new char[bufsize + 2048];
-     int newbufline[] = new int[bufsize + 2048];
-     int newbufcolumn[] = new int[bufsize + 2048];
-
-     try
-     {
-        if (wrapAround)
-        {
-           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - 
tokenBegin);
-           System.arraycopy(buffer, 0, newbuffer,
-                                             bufsize - tokenBegin, bufpos);
-           buffer = newbuffer;
-
-           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - 
tokenBegin);
-           System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, 
bufpos);
-           bufline = newbufline;
-
-           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - 
tokenBegin);
-           System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, 
bufpos);
-           bufcolumn = newbufcolumn;
-
-           bufpos += (bufsize - tokenBegin);
+
+    /**
+     * Whether parser is static.
+     */
+    public static final boolean staticFlag = false;
+
+    static final int hexval(char c) throws java.io.IOException {
+        switch (c) {
+            case '0':
+                return 0;
+            case '1':
+                return 1;
+            case '2':
+                return 2;
+            case '3':
+                return 3;
+            case '4':
+                return 4;
+            case '5':
+                return 5;
+            case '6':
+                return 6;
+            case '7':
+                return 7;
+            case '8':
+                return 8;
+            case '9':
+                return 9;
+
+            case 'a':
+            case 'A':
+                return 10;
+            case 'b':
+            case 'B':
+                return 11;
+            case 'c':
+            case 'C':
+                return 12;
+            case 'd':
+            case 'D':
+                return 13;
+            case 'e':
+            case 'E':
+                return 14;
+            case 'f':
+            case 'F':
+                return 15;
         }
-        else
-        {
-           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - 
tokenBegin);
-           buffer = newbuffer;
 
-           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - 
tokenBegin);
-           bufline = newbufline;
+        throw new java.io.IOException(); // Should never come here
+    }
+
+    /**
+     * Position in buffer.
+     */
+    public int bufpos = -1;
+    int bufsize;
+    int available;
+    int tokenBegin;
+    protected int bufline[];
+    protected int bufcolumn[];
+
+    protected int column = 0;
+    protected int line = 1;
+
+    protected boolean prevCharIsCR = false;
+    protected boolean prevCharIsLF = false;
+
+    protected java.io.Reader inputStream;
+
+    protected char[] nextCharBuf;
+    protected char[] buffer;
+    protected int maxNextCharInd = 0;
+    protected int nextCharInd = -1;
+    protected int inBuf = 0;
+    protected int tabSize = 8;
+    private boolean closed;
+
+    protected void setTabSize(int i) {
+        tabSize = i;
+    }
+
+    protected int getTabSize(int i) {
+        return tabSize;
+    }
+
+    protected void ExpandBuff(boolean wrapAround) {
+        char[] newbuffer = new char[bufsize + 2048];
+        int newbufline[] = new int[bufsize + 2048];
+        int newbufcolumn[] = new int[bufsize + 2048];
+
+        try {
+            if (wrapAround) {
+                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - 
tokenBegin);
+                System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, 
bufpos);
+                buffer = newbuffer;
+
+                System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - 
tokenBegin);
+                System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, 
bufpos);
+                bufline = newbufline;
+
+                System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, 
bufsize - tokenBegin);
+                System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - 
tokenBegin, bufpos);
+                bufcolumn = newbufcolumn;
+
+                bufpos += (bufsize - tokenBegin);
+            } else {
+                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - 
tokenBegin);
+                buffer = newbuffer;
 
-           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - 
tokenBegin);
-           bufcolumn = newbufcolumn;
+                System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - 
tokenBegin);
+                bufline = newbufline;
 
-           bufpos -= tokenBegin;
+                System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, 
bufsize - tokenBegin);
+                bufcolumn = newbufcolumn;
+
+                bufpos -= tokenBegin;
+            }
+        } catch (Throwable t) {
+            throw new Error(t.getMessage());
         }
-     }
-     catch (Throwable t)
-     {
-        throw new Error(t.getMessage());
-     }
-
-     available = (bufsize += 2048);
-     tokenBegin = 0;
-  }
-
-  protected void FillBuff() throws java.io.IOException
-  {
-     int i;
-     if (maxNextCharInd == nextCharBuf.length)
-        maxNextCharInd = nextCharInd = 0;
-
-     try {
-        // check for closed status to prevent the underlying Reader from 
-        // throwing IOException that causes performance degradation
-        if (closed || (i = inputStream.read(nextCharBuf, maxNextCharInd,
-                nextCharBuf.length - maxNextCharInd)) == -1)
-        {
-           inputStream.close();
-           closed = true;
-           throw END_OF_STREAM_EXCEPTION;
+
+        available = (bufsize += 2048);
+        tokenBegin = 0;
+    }
+
+    protected void FillBuff() throws java.io.IOException {
+        int i;
+        if (maxNextCharInd == nextCharBuf.length)
+            maxNextCharInd = nextCharInd = 0;
+
+        try {
+            // check for closed status to prevent the underlying Reader from
+            // throwing IOException that causes performance degradation
+            if (closed || (i = inputStream.read(nextCharBuf, maxNextCharInd,
+                    nextCharBuf.length - maxNextCharInd)) == -1) {
+                inputStream.close();
+                closed = true;
+                throw END_OF_STREAM_EXCEPTION;
+            } else
+                maxNextCharInd += i;
+            return;
+        } catch (java.io.IOException e) {
+            if (bufpos != 0) {
+                --bufpos;
+                backup(0);
+            } else {
+                bufline[bufpos] = line;
+                bufcolumn[bufpos] = column;
+            }
+            throw e;
         }
-        else
-           maxNextCharInd += i;
-        return;
-     }
-     catch(java.io.IOException e) {
-        if (bufpos != 0)
-        {
-           --bufpos;
-           backup(0);
+    }
+
+    protected char ReadByte() throws java.io.IOException {
+        if (++nextCharInd >= maxNextCharInd)
+            FillBuff();
+
+        return nextCharBuf[nextCharInd];
+    }
+
+    /**
+     * @return starting character for token.
+     */
+    public char BeginToken() throws java.io.IOException {
+        if (inBuf > 0) {
+            --inBuf;
+
+            if (++bufpos == bufsize)
+                bufpos = 0;
+
+            tokenBegin = bufpos;
+            return buffer[bufpos];
         }
+
+        tokenBegin = 0;
+        bufpos = -1;
+
+        return readChar();
+    }
+
+    protected void AdjustBuffSize() {
+        if (available == bufsize) {
+            if (tokenBegin > 2048) {
+                bufpos = 0;
+                available = tokenBegin;
+            } else
+                ExpandBuff(false);
+        } else if (available > tokenBegin)
+            available = bufsize;
+        else if ((tokenBegin - available) < 2048)
+            ExpandBuff(true);
         else
-        {
-           bufline[bufpos] = line;
-           bufcolumn[bufpos] = column;
+            available = tokenBegin;
+    }
+
+    protected void UpdateLineColumn(char c) {
+        column++;
+
+        if (prevCharIsLF) {
+            prevCharIsLF = false;
+            line += (column = 1);
+        } else if (prevCharIsCR) {
+            prevCharIsCR = false;
+            if (c == '\n') {
+                prevCharIsLF = true;
+            } else
+                line += (column = 1);
         }
-        throw e;
-     }
-  }
-
-  protected char ReadByte() throws java.io.IOException
-  {
-     if (++nextCharInd >= maxNextCharInd)
-        FillBuff();
-
-     return nextCharBuf[nextCharInd];
-  }
-
-/** @return starting character for token. */
-  public char BeginToken() throws java.io.IOException
-  {
-     if (inBuf > 0)
-     {
-        --inBuf;
-
-        if (++bufpos == bufsize)
-           bufpos = 0;
-
-        tokenBegin = bufpos;
-        return buffer[bufpos];
-     }
-
-     tokenBegin = 0;
-     bufpos = -1;
-
-     return readChar();
-  }
-
-  protected void AdjustBuffSize()
-  {
-     if (available == bufsize)
-     {
-        if (tokenBegin > 2048)
-        {
-           bufpos = 0;
-           available = tokenBegin;
+
+        switch (c) {
+            case '\r':
+                prevCharIsCR = true;
+                break;
+            case '\n':
+                prevCharIsLF = true;
+                break;
+            case '\t':
+                column--;
+                column += (tabSize - (column % tabSize));
+                break;
+            default:
+                break;
         }
-        else
-           ExpandBuff(false);
-     }
-     else if (available > tokenBegin)
-        available = bufsize;
-     else if ((tokenBegin - available) < 2048)
-        ExpandBuff(true);
-     else
-        available = tokenBegin;
-  }
-
-  protected void UpdateLineColumn(char c)
-  {
-     column++;
-
-     if (prevCharIsLF)
-     {
-        prevCharIsLF = false;
-        line += (column = 1);
-     }
-     else if (prevCharIsCR)
-     {
-        prevCharIsCR = false;
-        if (c == '\n')
-        {
-           prevCharIsLF = true;
+
+        bufline[bufpos] = line;
+        bufcolumn[bufpos] = column;
+    }
+
+    /**
+     * Read a character.
+     */
+    public char readChar() throws java.io.IOException {
+        if (inBuf > 0) {
+            --inBuf;
+
+            if (++bufpos == bufsize)
+                bufpos = 0;
+
+            return buffer[bufpos];
         }
-        else
-           line += (column = 1);
-     }
-
-     switch (c)
-     {
-        case '\r' :
-           prevCharIsCR = true;
-           break;
-        case '\n' :
-           prevCharIsLF = true;
-           break;
-        case '\t' :
-           column--;
-           column += (tabSize - (column % tabSize));
-           break;
-        default :
-           break;
-     }
-
-     bufline[bufpos] = line;
-     bufcolumn[bufpos] = column;
-  }
-
-/** Read a character. */
-  public char readChar() throws java.io.IOException
-  {
-     if (inBuf > 0)
-     {
-        --inBuf;
-
-        if (++bufpos == bufsize)
-           bufpos = 0;
-
-        return buffer[bufpos];
-     }
-
-     char c;
-
-     if (++bufpos == available)
-        AdjustBuffSize();
-
-     if ((buffer[bufpos] = c = ReadByte()) == '\\')
-     {
-        UpdateLineColumn(c);
-
-        int backSlashCnt = 1;
-
-        for (;;) // Read all the backslashes
-        {
-           if (++bufpos == available)
-              AdjustBuffSize();
-
-           try
-           {
-              if ((buffer[bufpos] = c = ReadByte()) != '\\')
-              {
-                 UpdateLineColumn(c);
-                 // found a non-backslash char.
-                 if ((c == 'u') && ((backSlashCnt & 1) == 1))
-                 {
-                    if (--bufpos < 0)
-                       bufpos = bufsize - 1;
-
-                    break;
-                 }
-
-                 backup(backSlashCnt);
-                 return '\\';
-              }
-           }
-           catch(java.io.IOException e)
-           {
-              if (backSlashCnt > 1)
-                 backup(backSlashCnt-1);
-
-              return '\\';
-           }
-
-           UpdateLineColumn(c);
-           backSlashCnt++;
+
+        char c;
+
+        if (++bufpos == available)
+            AdjustBuffSize();
+
+        if ((buffer[bufpos] = c = ReadByte()) == '\\') {
+            UpdateLineColumn(c);
+
+            int backSlashCnt = 1;
+
+            for (; ; ) // Read all the backslashes
+            {
+                if (++bufpos == available)
+                    AdjustBuffSize();
+
+                try {
+                    if ((buffer[bufpos] = c = ReadByte()) != '\\') {
+                        UpdateLineColumn(c);
+                        // found a non-backslash char.
+                        if ((c == 'u') && ((backSlashCnt & 1) == 1)) {
+                            if (--bufpos < 0)
+                                bufpos = bufsize - 1;
+
+                            break;
+                        }
+
+                        backup(backSlashCnt);
+                        return '\\';
+                    }
+                } catch (java.io.IOException e) {
+                    // We are returning one backslash so we should only backup 
(count-1)
+                    if (backSlashCnt > 1)
+                        backup(backSlashCnt - 1);
+
+                    return '\\';
+                }
+
+                UpdateLineColumn(c);
+                backSlashCnt++;
+            }
+
+            // Here, we have seen an odd number of backslash's followed by a 
'u'
+            try {
+                while ((c = ReadByte()) == 'u')
+                    ++column;
+
+                buffer[bufpos] = c = (char) (hexval(c) << 12 |
+                        hexval(ReadByte()) << 8 |
+                        hexval(ReadByte()) << 4 |
+                        hexval(ReadByte()));
+
+                column += 4;
+            } catch (java.io.IOException e) {
+                throw new Error("Invalid escape character at line " + line +
+                        " column " + column + ".");
+            }
+
+            if (backSlashCnt == 1)
+                return c;
+            else {
+                backup(backSlashCnt - 1);
+                return '\\';
+            }
+        } else {
+            UpdateLineColumn(c);
+            return c;
         }
+    }
 
-        // Here, we have seen an odd number of backslash's followed by a 'u'
-        try
-        {
-           while ((c = ReadByte()) == 'u')
-              ++column;
+    @Deprecated
+    /**
+     * @deprecated
+     * @see #getEndColumn
+     */
+    public int getColumn() {
+        return bufcolumn[bufpos];
+    }
 
-           buffer[bufpos] = c = (char)(hexval(c) << 12 |
-                                       hexval(ReadByte()) << 8 |
-                                       hexval(ReadByte()) << 4 |
-                                       hexval(ReadByte()));
+    @Deprecated
+    /**
+     * @deprecated
+     * @see #getEndLine
+     */
+    public int getLine() {
+        return bufline[bufpos];
+    }
 
-           column += 4;
-        }
-        catch(java.io.IOException e)
-        {
-           throw new Error("Invalid escape character at line " + line +
-                                         " column " + column + ".");
+    /**
+     * Get end column.
+     */
+    public int getEndColumn() {
+        return bufcolumn[bufpos];
+    }
+
+    /**
+     * Get end line.
+     */
+    public int getEndLine() {
+        return bufline[bufpos];
+    }
+
+    /**
+     * @return column of token start
+     */
+    public int getBeginColumn() {
+        return bufcolumn[tokenBegin];
+    }
+
+    /**
+     * @return line number of token start
+     */
+    public int getBeginLine() {
+        return bufline[tokenBegin];
+    }
+
+    /**
+     * Retreat.
+     */
+    public void backup(int amount) {
+
+        inBuf += amount;
+        if ((bufpos -= amount) < 0)
+            bufpos += bufsize;
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.Reader dstream, int startline, int 
startcolumn, int buffersize) {
+        inputStream = dstream;
+        line = startline;
+        column = startcolumn - 1;
+
+        available = bufsize = buffersize;
+        buffer = new char[buffersize];
+        bufline = new int[buffersize];
+        bufcolumn = new int[buffersize];
+        nextCharBuf = new char[buffersize];
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.Reader dstream, int startline, int 
startcolumn) {
+        this(dstream, startline, startcolumn, 4096);
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.Reader dstream) {
+        this(dstream, 1, 1, 4096);
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.Reader dstream) {
+        ReInit(dstream, 1, 1, 4096);
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.InputStream dstream, String encoding, int 
startline,
+                          int startcolumn, int buffersize) throws 
java.io.UnsupportedEncodingException {
+        this(encoding == null ? new java.io.InputStreamReader(dstream) : new 
java.io.InputStreamReader(dstream, encoding), startline, startcolumn, 
buffersize);
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize) {
+        this(new java.io.InputStreamReader(dstream), startline, startcolumn, 
4096);
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.InputStream dstream, String encoding, int 
startline,
+                          int startcolumn) throws 
java.io.UnsupportedEncodingException {
+        this(dstream, encoding, startline, startcolumn, 4096);
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.InputStream dstream, int startline,
+                          int startcolumn) {
+        this(dstream, startline, startcolumn, 4096);
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.InputStream dstream, String encoding) throws 
java.io.UnsupportedEncodingException {
+        this(dstream, encoding, 1, 1, 4096);
+    }
+
+    /**
+     * Constructor.
+     */
+    public JavaCharStream(java.io.InputStream dstream) {
+        this(dstream, 1, 1, 4096);
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.Reader dstream, int startline, int startcolumn, 
int buffersize) {
+        inputStream = dstream;
+        line = startline;
+        column = startcolumn - 1;
+
+        if (buffer == null || buffersize != buffer.length) {
+            available = bufsize = buffersize;
+            buffer = new char[buffersize];
+            bufline = new int[buffersize];
+            bufcolumn = new int[buffersize];
+            nextCharBuf = new char[buffersize];
         }
+        prevCharIsLF = prevCharIsCR = false;
+        tokenBegin = inBuf = maxNextCharInd = 0;
+        nextCharInd = bufpos = -1;
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.Reader dstream, int startline, int startcolumn) 
{
+        ReInit(dstream, startline, startcolumn, 4096);
+    }
 
-        if (backSlashCnt == 1)
-           return c;
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.InputStream dstream, String encoding, int 
startline, int startcolumn, int buffersize) throws 
java.io.UnsupportedEncodingException {
+        ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new 
java.io.InputStreamReader(dstream, encoding), startline, startcolumn, 
buffersize);
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.InputStream dstream, int startline, int 
startcolumn, int buffersize) {
+        ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, 
buffersize);
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.InputStream dstream, String encoding, int 
startline, int startcolumn) throws java.io.UnsupportedEncodingException {
+        ReInit(dstream, encoding, startline, startcolumn, 4096);
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.InputStream dstream, int startline, int 
startcolumn) {
+        ReInit(dstream, startline, startcolumn, 4096);
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.InputStream dstream, String encoding) throws 
java.io.UnsupportedEncodingException {
+        ReInit(dstream, encoding, 1, 1, 4096);
+    }
+
+    /**
+     * Reinitialise.
+     */
+    public void ReInit(java.io.InputStream dstream) {
+        ReInit(dstream, 1, 1, 4096);
+    }
+
+    /**
+     * @return token image as String
+     */
+    public String GetImage() {
+        if (bufpos >= tokenBegin)
+            return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
         else
-        {
-           backup(backSlashCnt - 1);
-           return '\\';
+            return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                    new String(buffer, 0, bufpos + 1);
+    }
+
+    /**
+     * @return suffix
+     */
+    public char[] GetSuffix(int len) {
+        char[] ret = new char[len];
+
+        if ((bufpos + 1) >= len)
+            System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+        else {
+            System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                    len - bufpos - 1);
+            System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
         }
-     }
-     else
-     {
-        UpdateLineColumn(c);
-        return c;
-     }
-  }
-
-  /**
-   * @deprecated
-   * @see #getEndColumn
-   */
-  public int getColumn() {
-     return bufcolumn[bufpos];
-  }
-
-  /**
-   * @deprecated
-   * @see #getEndLine
-   */
-  public int getLine() {
-     return bufline[bufpos];
-  }
-
-/** Get end column. */
-  public int getEndColumn() {
-     return bufcolumn[bufpos];
-  }
-
-/** Get end line. */
-  public int getEndLine() {
-     return bufline[bufpos];
-  }
-
-/** @return column of token start */
-  public int getBeginColumn() {
-     return bufcolumn[tokenBegin];
-  }
-
-/** @return line number of token start */
-  public int getBeginLine() {
-     return bufline[tokenBegin];
-  }
-
-/** Retreat. */
-  public void backup(int amount) {
-
-    inBuf += amount;
-    if ((bufpos -= amount) < 0)
-       bufpos += bufsize;
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.Reader dstream,
-                 int startline, int startcolumn, int buffersize)
-  {
-    inputStream = dstream;
-    line = startline;
-    column = startcolumn - 1;
-
-    available = bufsize = buffersize;
-    buffer = new char[buffersize];
-    bufline = new int[buffersize];
-    bufcolumn = new int[buffersize];
-    nextCharBuf = new char[buffersize];
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.Reader dstream,
-                                        int startline, int startcolumn)
-  {
-     this(dstream, startline, startcolumn, 4096);
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.Reader dstream)
-  {
-     this(dstream, 1, 1, 4096);
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.InputStream dstream, String encoding, int 
startline,
-  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
-  {
-     this(encoding == null ? new java.io.InputStreamReader(dstream) : new 
java.io.InputStreamReader(dstream, encoding), startline, startcolumn, 
buffersize);
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.InputStream dstream, int startline,
-  int startcolumn, int buffersize)
-  {
-     this(new java.io.InputStreamReader(dstream), startline, startcolumn, 
4096);
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.InputStream dstream, String encoding, int 
startline,
-                        int startcolumn) throws 
java.io.UnsupportedEncodingException
-  {
-     this(dstream, encoding, startline, startcolumn, 4096);
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.InputStream dstream, int startline,
-                        int startcolumn)
-  {
-     this(dstream, startline, startcolumn, 4096);
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.InputStream dstream, String encoding) throws 
java.io.UnsupportedEncodingException
-  {
-     this(dstream, encoding, 1, 1, 4096);
-  }
-
-/** Constructor. */
-  public JavaCharStream(java.io.InputStream dstream)
-  {
-     this(dstream, 1, 1, 4096);
-  }
-
-
-
-
-  /** @return token image as String */
-  public String GetImage()
-  {
-     if (bufpos >= tokenBegin)
-        return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
-     else
-        return new String(buffer, tokenBegin, bufsize - tokenBegin) +
-                              new String(buffer, 0, bufpos + 1);
-  }
-
-  /** @return suffix */
-  public char[] GetSuffix(int len)
-  {
-     char[] ret = new char[len];
-
-     if ((bufpos + 1) >= len)
-        System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
-     else
-     {
-        System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
-                                                          len - bufpos - 1);
-        System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
-     }
-
-     return ret;
-  }
-
-  /** Set buffers back to null when finished. */
-  public void Done()
-  {
-     nextCharBuf = null;
-     buffer = null;
-     bufline = null;
-     bufcolumn = null;
-  }
-
-  /**
-   * Method to adjust line and column numbers for the start of a token.
-   */
-  public void adjustBeginLineColumn(int newLine, int newCol)
-  {
-     int start = tokenBegin;
-     int len;
-
-     if (bufpos >= tokenBegin)
-     {
-        len = bufpos - tokenBegin + inBuf + 1;
-     }
-     else
-     {
-        len = bufsize - tokenBegin + bufpos + 1 + inBuf;
-     }
-
-     int i = 0, j = 0, k = 0;
-     int nextColDiff = 0, columnDiff = 0;
-
-     while (i < len &&
-            bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
-     {
-        bufline[j] = newLine;
-        nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
-        bufcolumn[j] = newCol + columnDiff;
-        columnDiff = nextColDiff;
-        i++;
-     }
-
-     if (i < len)
-     {
-        bufline[j] = newLine++;
-        bufcolumn[j] = newCol + columnDiff;
-
-        while (i++ < len)
-        {
-           if (bufline[j = start % bufsize] != bufline[++start % bufsize])
-              bufline[j] = newLine++;
-           else
-              bufline[j] = newLine;
+
+        return ret;
+    }
+
+    /**
+     * Set buffers back to null when finished.
+     */
+    public void Done() {
+        nextCharBuf = null;
+        buffer = null;
+        bufline = null;
+        bufcolumn = null;
+    }
+
+    /**
+     * Method to adjust line and column numbers for the start of a token.
+     */
+    public void adjustBeginLineColumn(int newLine, int newCol) {
+        int start = tokenBegin;
+        int len;
+
+        if (bufpos >= tokenBegin) {
+            len = bufpos - tokenBegin + inBuf + 1;
+        } else {
+            len = bufsize - tokenBegin + bufpos + 1 + inBuf;
         }
-     }
 
-     line = bufline[j];
-     column = bufcolumn[j];
-  }
+        int i = 0, j = 0, k = 0;
+        int nextColDiff = 0, columnDiff = 0;
+
+        while (i < len && bufline[j = start % bufsize] == bufline[k = ++start 
% bufsize]) {
+            bufline[j] = newLine;
+            nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+            bufcolumn[j] = newCol + columnDiff;
+            columnDiff = nextColDiff;
+            i++;
+        }
+
+        if (i < len) {
+            bufline[j] = newLine++;
+            bufcolumn[j] = newCol + columnDiff;
+
+            while (i++ < len) {
+                if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+                    bufline[j] = newLine++;
+                else
+                    bufline[j] = newLine;
+            }
+        }
+
+        line = bufline[j];
+        column = bufcolumn[j];
+    }
 
 }
-/* JavaCC - OriginalChecksum=de7dff2a3eda69c495bc3a754d6314ab (do not edit 
this line) */
+/* JavaCC - OriginalChecksum=42728d5d6de379c28d8e61854c22fc66 (do not edit 
this line) */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c44ccfde/cayenne-server/src/main/jjtree/org/apache/cayenne/exp/parser/ExpressionParser.jjt
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/jjtree/org/apache/cayenne/exp/parser/ExpressionParser.jjt
 
b/cayenne-server/src/main/jjtree/org/apache/cayenne/exp/parser/ExpressionParser.jjt
index 370a223..16e443b 100644
--- 
a/cayenne-server/src/main/jjtree/org/apache/cayenne/exp/parser/ExpressionParser.jjt
+++ 
b/cayenne-server/src/main/jjtree/org/apache/cayenne/exp/parser/ExpressionParser.jjt
@@ -77,82 +77,91 @@ void notCondition() : {}
 
 void simpleCondition() : {} 
 {
-       <TRUE> #True
-       | 
-       <FALSE> #False
-       | 
-         scalarConditionExpression()
-       (
-          simpleNotCondition()
-       | 
-         ("="  | "==") scalarExpression() #Equal(2)
-       | 
-         ("!=" | "<>")  scalarExpression() #NotEqual(2)
+         <TRUE> #True
        |
-         "<=" scalarExpression() #LessOrEqual(2)
-       | 
-         "<" scalarExpression() #Less(2)
-       | 
-         ">"  scalarExpression() #Greater(2)
-       | 
-         ">=" scalarExpression() #GreaterOrEqual(2)
-       |
-               "like" scalarExpression() #Like(2)
+         <FALSE> #False
        |
-           "likeIgnoreCase"  scalarExpression() #LikeIgnoreCase(2)
-       |  
-               "in"  
-               (  namedParameter() | "(" scalarCommaList() ")" )
-               #In(2)
-       |  
-               "between"  scalarExpression() "and" scalarExpression() 
#Between(3)
+         conditionExpression()
+       (
+          ("="  | "==") scalarExpression() #Equal(2)
+        |
+          ("!=" | "<>")  scalarExpression() #NotEqual(2)
+        |
+          "<=" scalarExpression() #LessOrEqual(2)
+        |
+          "<" scalarExpression() #Less(2)
+        |
+          ">"  scalarExpression() #Greater(2)
+        |
+          ">=" scalarExpression() #GreaterOrEqual(2)
+        |
+          "like" scalarExpression() #Like(2)
+        |
+          "likeIgnoreCase"  scalarExpression() #LikeIgnoreCase(2)
+        |
+          "in" ( namedParameter() | "(" scalarCommaList() ")" ) #In(2)
+        |
+          "between"  scalarExpression() "and" scalarExpression() #Between(3)
+        |
+          simpleNotCondition()
        )?
 }
 
 void simpleNotCondition() : {} 
 {
        ("not" | "!" )
-       
        (
                "like" scalarExpression() #NotLike(2)
        |
            "likeIgnoreCase"  scalarExpression() #NotLikeIgnoreCase(2)
-       |  
-               "in"  
-               (  namedParameter() |  "(" scalarCommaList() ")" )
-               #NotIn(2)
-       |  
+       |
+               "in" (  namedParameter() |  "(" scalarCommaList() ")" ) 
#NotIn(2)
+       |
                "between"  scalarExpression() "and" scalarExpression() 
#NotBetween(3)
        )
 }
 
 void scalarCommaList() : {}
 {
-       (
-               scalarConstExpression()
-               ( ","  scalarConstExpression() )*
-       ) #List
+       ( scalarConstExpression() ( ","  scalarConstExpression() )* ) #List
 }
 
-void scalarConditionExpression() : {} 
+void conditionExpression() : {}
 {
    // TODO: once we switch expression package to use AST* from parser package,
    // we might need implement special subclasses for numeric and character 
constant
    // nodes for the purpose of expression evaluation, instead . For now keep 
them as 
    // generic ASTScalar and purge from the parent node to keep compatible with 
QualifierTranslator.
-   
-               scalarNumericExpression()
+
+        // Path without any operator will go to numeric expression.
+        // This done intentionally to remove lookahead that leads
+        // to dramatically perfomance degradation (parser can become 3 times 
slower)
+               numericExpression()
+    |
+        stringExpression()
        |
-               <SINGLE_QUOTED_STRING> { 
jjtThis.setValue(token_source.literalValue); } #Scalar(0)
-       | 
-               <DOUBLE_QUOTED_STRING> { 
jjtThis.setValue(token_source.literalValue); } #Scalar(0)
-       | 
-               <NULL> #Scalar(0) 
+               <NULL> #Scalar(0)
+}
+
+void stringParameter() : {}
+{
+        pathExpression()
+    |
+        stringExpression()
+}
+
+void stringExpression() : {}
+{
+    <SINGLE_QUOTED_STRING> { jjtThis.setValue(token_source.literalValue); } 
#Scalar(0)
+    |
+    <DOUBLE_QUOTED_STRING> { jjtThis.setValue(token_source.literalValue); } 
#Scalar(0)
+    |
+    functionsReturningStrings()
 }
 
 void scalarExpression() : {} 
 {
-          scalarConditionExpression()
+          conditionExpression()
        |
          <TRUE> { jjtThis.setValue(true); }  #Scalar(0)
        | 
@@ -176,7 +185,7 @@ void scalarConstExpression() : {}
          <FALSE> { jjtThis.setValue(false); }  #Scalar(0)
 }
 
-void scalarNumericExpression() : {}
+void numericExpression() : {}
 {
     bitwiseOr()
 }
@@ -259,40 +268,75 @@ void numericPrimary() : {}
                
                "(" orCondition() ")"
        |
-               pathExpression()
-       |
-               namedParameter()
-       |
           <INT_LITERAL> { jjtThis.setValue(token_source.literalValue); } 
#Scalar(0)
        | 
           <FLOAT_LITERAL>{ jjtThis.setValue(token_source.literalValue); }  
#Scalar(0)
+       |
+        namedParameter()
+    |
+        functionsReturningNumerics()
+       |
+        pathExpression()
 }
 
-void namedParameter() : 
+void functionsReturningStrings() : { }
 {
-       Token t;
+       concat() | substring() | trim() | lower() | upper()
 }
+
+void concat() #Concat : { }
 {
-       "$" t = <PROPERTY_PATH> { jjtThis.setValue(t.image); } 
#NamedParameter(0)
+       <CONCAT> "(" stringParameter() "," stringParameter() ")"
+}
+
+void substring() #Substring : { }
+{
+       <SUBSTRING> "(" stringParameter() "," numericExpression() ["," 
numericExpression()] ")"
 }
 
+void trim() #Trim : { }
+{
+       <TRIM> "(" stringParameter() ")"
+}
 
-void pathExpression() : {
-   Token t;
+void lower() #Lower : { }
+{
+       <LOWER> "(" stringParameter() ")"
 }
+
+void upper() #Upper : { }
 {
-   ( 
-     t = <PROPERTY_PATH> { jjtThis.setPath(t.image); } #ObjPath(0)
-   | 
-     "obj:" 
-     t = <PROPERTY_PATH> { jjtThis.setPath(t.image); } #ObjPath(0)
-   |
-     "db:" 
-     t = <PROPERTY_PATH> { jjtThis.setPath(t.image); } #DbPath(0)
-   |
-     "enum:" 
-     t = <PROPERTY_PATH> { jjtThis.setValue(ParserUtil.makeEnum(t.image)); } 
#Scalar(0)
-   )
+       <UPPER> "(" stringParameter() ")"
+}
+
+void functionsReturningNumerics() : { }
+{
+       length() | locate() | abs() | sqrt() | mod()
+}
+
+void length() #Length : { }
+{
+       <LENGTH> "(" stringParameter() ")"
+}
+
+void locate() #Locate : { }
+{
+       <LOCATE> "(" stringParameter() "," stringParameter() ["," 
numericExpression()] ")"
+}
+
+void abs() #Abs : { }
+{
+       <ABS> "(" numericExpression() ")"
+}
+
+void sqrt() #Sqrt : { }
+{
+       <SQRT> "(" numericExpression() ")"
+}
+
+void mod() #Mod : { }
+{
+       <MOD> "(" numericExpression() "," numericExpression() ")"
 }
 
 
@@ -381,10 +425,65 @@ SKIP :
 
 TOKEN : {
        <NULL: "null" | "NULL" >
-|
-       <TRUE: "true" | "TRUE" >
-|
-       <FALSE: "false" | "FALSE" >
+    |   <TRUE: "true" | "TRUE" >
+    |   <FALSE: "false" | "FALSE" >
+}
+
+TOKEN [ IGNORE_CASE ]: /* aggregates */
+{
+       <AVG: "AVG" >
+       |       <MIN: "MIN" >
+       |       <MAX: "MAX" >
+       |       <SUM: "SUM" >
+       |       <COUNT: "COUNT" >
+}
+
+TOKEN [ IGNORE_CASE ]: /* functions returning strings */
+{
+       <CONCAT: "CONCAT" >
+       |       <SUBSTRING: "SUBSTRING" >
+       |       <TRIM: "TRIM" >
+       |       <LOWER: "LOWER" >
+       |       <UPPER: "UPPER" >
+}
+
+TOKEN [ IGNORE_CASE ]: /* functions returning numerics */
+{
+       <LENGTH: "LENGTH" >
+       |       <LOCATE: "LOCATE" >
+       |       <ABS: "ABS" >
+       |       <SQRT: "SQRT" >
+       |       <MOD: "MOD" >
+}
+
+TOKEN [ IGNORE_CASE ]: /* functions returning datetime */
+{
+       <CURRENT_DATE: "CURRENT_DATE" >
+       |       <CURRENT_TIME: "CURRENT_TIME" >
+       |       <CURRENT_TIMESTAMP: "CURRENT_TIMESTAMP" >
+}
+
+void namedParameter() :
+{
+       Token t;
+}
+{
+       "$" t = <PROPERTY_PATH> { jjtThis.setValue(t.image); } 
#NamedParameter(0)
+}
+
+void pathExpression() : {
+   Token t;
+}
+{
+   (
+             t = <PROPERTY_PATH> { jjtThis.setPath(t.image); } #ObjPath(0)
+   |
+     "obj:"  t = <PROPERTY_PATH> { jjtThis.setPath(t.image); } #ObjPath(0)
+   |
+     "db:"   t = <PROPERTY_PATH> { jjtThis.setPath(t.image); } #DbPath(0)
+   |
+     "enum:" t = <PROPERTY_PATH> { 
jjtThis.setValue(ParserUtil.makeEnum(t.image)); } #Scalar(0)
+   )
 }
 
 TOKEN : {
@@ -400,8 +499,7 @@ TOKEN :
   < #DIGIT: ["0"-"9"] >
 }
 
-
-/** 
+/**
  * Quoted Strings, whose object value is stored in the token manager's
  * "literalValue" field. Both single and double qoutes are allowed 
  */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c44ccfde/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
index 3cf4a46..9fc6ac2 100644
--- 
a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
@@ -33,6 +33,7 @@ import java.util.Map;
 
 import org.apache.cayenne.exp.parser.ASTLike;
 import org.apache.cayenne.exp.parser.ASTLikeIgnoreCase;
+import org.apache.cayenne.exp.parser.ASTTrim;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -471,4 +472,16 @@ public class ExpressionFactoryTest {
        public void testDbPathExp() {
                assertEquals("db:abc.xyz", 
ExpressionFactory.dbPathExp("abc.xyz").toString());
        }
+
+       @Test
+       public void testFuncExp() {
+               Expression e = ExpressionFactory.exp("TRIM(abc.xyz)");
+               assertEquals(ASTTrim.class, e.getClass());
+       }
+
+    // CAY-2081
+    @Test(expected = ExpressionException.class)
+    public void testExceptionInParse() {
+        ExpressionFactory.exp("name like %32_65415'");
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c44ccfde/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
index 192a1ad..9033ab9 100644
--- 
a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallMathIT.java
@@ -19,8 +19,12 @@
 
 package org.apache.cayenne.exp.parser;
 
+import java.math.BigDecimal;
+
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.testdo.table_primitives.TablePrimitives;
@@ -80,4 +84,27 @@ public class ASTFunctionCallMathIT extends ServerCase {
         assertEquals(p1, p2);
     }
 
+    @Test
+    public void testASTAbsParse() {
+        Expression exp = ExpressionFactory.exp("ABS(-3)");
+        assertEquals(3.0, exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testASTSqrtParse() {
+        Expression exp = ExpressionFactory.exp("SQRT(16)");
+        assertEquals(4.0, exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testASTModParse() {
+        Expression exp = ExpressionFactory.exp("MOD(11,2)");
+        assertEquals(1.0, exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testComplexParse() {
+        Expression exp = ExpressionFactory.exp("10 - MOD(SQRT(ABS(-9)), 2)");
+        assertEquals(BigDecimal.valueOf(9L), exp.evaluate(new Object()));
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c44ccfde/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
index cbb7514..58a69dd 100644
--- 
a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
@@ -23,6 +23,8 @@ import java.util.Date;
 
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.query.ObjectSelect;
 import org.apache.cayenne.testdo.testmap.Artist;
@@ -152,4 +154,45 @@ public class ASTFunctionCallStringIT extends ServerCase {
         assertEquals(a1, a2);
     }
 
+    @Test
+    public void testASTConcatParse() {
+        Expression exp = ExpressionFactory.exp("CONCAT('abc', 'def')");
+        assertEquals("abcdef", exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testASTSubstringParse() {
+        Expression exp = ExpressionFactory.exp("SUBSTRING('123456789', 3, 2)");
+        assertEquals("45", exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testASTTrimParse() {
+        Expression exp = ExpressionFactory.exp("TRIM(' abc ')");
+        assertEquals("abc", exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testASTLowerParse() {
+        Expression exp = ExpressionFactory.exp("LOWER('AbC')");
+        assertEquals("abc", exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testASTUpperParse() {
+        Expression exp = ExpressionFactory.exp("UPPER('aBc')");
+        assertEquals("ABC", exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testASTLocateParse() {
+        Expression exp = ExpressionFactory.exp("LOCATE('Bc', 'aBc')");
+        assertEquals(2, exp.evaluate(new Object()));
+    }
+
+    @Test
+    public void testComplexParse() {
+        Expression exp = ExpressionFactory.exp("LOCATE(UPPER('Bc'), 
UPPER('aBc')) = LENGTH(SUBSTRING(TRIM(LOWER(CONCAT('   abc', 'def   '))), 3, 
2))");
+        assertEquals(true, exp.evaluate(new Object()));
+    }
 }

Reply via email to