0001: /* Copyright (c) 1995-2000, The Hypersonic SQL Group.
0002: * All rights reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * Redistributions of source code must retain the above copyright notice, this
0008: * list of conditions and the following disclaimer.
0009: *
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * Neither the name of the Hypersonic SQL Group nor the names of its
0015: * contributors may be used to endorse or promote products derived from this
0016: * software without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021: * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
0022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: *
0030: * This software consists of voluntary contributions made by many individuals
0031: * on behalf of the Hypersonic SQL Group.
0032: *
0033: *
0034: * For work added by the HSQL Development Group:
0035: *
0036: * Copyright (c) 2001-2005, The HSQL Development Group
0037: * All rights reserved.
0038: *
0039: * Redistribution and use in source and binary forms, with or without
0040: * modification, are permitted provided that the following conditions are met:
0041: *
0042: * Redistributions of source code must retain the above copyright notice, this
0043: * list of conditions and the following disclaimer.
0044: *
0045: * Redistributions in binary form must reproduce the above copyright notice,
0046: * this list of conditions and the following disclaimer in the documentation
0047: * and/or other materials provided with the distribution.
0048: *
0049: * Neither the name of the HSQL Development Group nor the names of its
0050: * contributors may be used to endorse or promote products derived from this
0051: * software without specific prior written permission.
0052: *
0053: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0054: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0055: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0056: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
0057: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0058: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0059: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0060: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0061: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0062: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0063: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0064: */
0065:
0066: package org.hsqldb;
0067:
0068: import java.math.BigDecimal;
0069: import java.util.Locale;
0070:
0071: import org.hsqldb.lib.IntValueHashMap;
0072: import org.hsqldb.store.ValuePool;
0073: import org.hsqldb.lib.java.JavaSystem;
0074:
0075: // fredt@users 20020218 - patch 455785 by hjbusch@users - large DECIMAL inserts
0076: // also Long.MIM_VALUE (bug 473388) inserts - applied to different parts
0077: // fredt@users 20020408 - patch 1.7.0 by fredt - exact integral types
0078: // integral values are cast into the smallest type that can hold them
0079: // fredt@users 20020501 - patch 550970 by boucherb@users - fewer StringBuffers
0080: // fredt@users 20020611 - patch 1.7.0 by fredt - correct statement logging
0081: // changes to the working of getLastPart() to return the correct statement for
0082: // logging in the .script file.
0083: // also restructuring to reduce use of objects and speed up tokenising of
0084: // strings and quoted identifiers
0085: // fredt@users 20021112 - patch 1.7.2 by Nitin Chauhan - use of switch
0086: // rewrite of the majority of multiple if(){}else{} chains with switch(){}
0087: // fredt@users 20030610 - patch 1.7.2 - no StringBuffers
0088:
0089: /**
0090: * Provides the ability to tokenize SQL character sequences.
0091: *
0092: * Extensively rewritten and extended in successive versions of HSQLDB.
0093: *
0094: * @author Thomas Mueller (Hypersonic SQL Group)
0095: * @version 1.8.0
0096: * @since Hypersonic SQL
0097: */
0098: public class Tokenizer {
0099:
0100: private static final int NO_TYPE = 0, NAME = 1, LONG_NAME = 2,
0101: SPECIAL = 3, NUMBER = 4, FLOAT = 5, STRING = 6, LONG = 7,
0102: DECIMAL = 8, BOOLEAN = 9, DATE = 10, TIME = 11,
0103: TIMESTAMP = 12, NULL = 13, NAMED_PARAM = 14;
0104:
0105: // used only internally
0106: private static final int QUOTED_IDENTIFIER = 15, REMARK_LINE = 16,
0107: REMARK = 17;
0108: private String sCommand;
0109: private int iLength;
0110: private int iIndex;
0111: private int tokenIndex;
0112: private int nextTokenIndex;
0113: private int beginIndex;
0114: private int iType;
0115: private String sToken;
0116: private int indexLongNameFirst = -1;
0117: private String sLongNameFirst = null;
0118: private int typeLongNameFirst;
0119:
0120: // getToken() will clear LongNameFirst unless retainFirst is set.
0121: private boolean retainFirst = false;
0122:
0123: // private String sLongNameLast;
0124: // WAIT. Don't do anything before popping another Token (because the
0125: // state variables aren't set properly due to a call of wait()).
0126: private boolean bWait;
0127: private boolean lastTokenQuotedID;
0128:
0129: // literals that are values
0130: static IntValueHashMap valueTokens;
0131:
0132: static {
0133: valueTokens = new IntValueHashMap();
0134:
0135: valueTokens.put(Token.T_NULL, NULL);
0136: valueTokens.put(Token.T_TRUE, BOOLEAN);
0137: valueTokens.put(Token.T_FALSE, BOOLEAN);
0138: }
0139:
0140: public Tokenizer() {
0141: }
0142:
0143: public Tokenizer(String s) {
0144:
0145: sCommand = s;
0146: iLength = s.length();
0147: iIndex = 0;
0148: }
0149:
0150: public void reset(String s) {
0151:
0152: sCommand = s;
0153: iLength = s.length();
0154: iIndex = 0;
0155: tokenIndex = 0;
0156: nextTokenIndex = 0;
0157: beginIndex = 0;
0158: iType = NO_TYPE;
0159: typeLongNameFirst = NO_TYPE;
0160: sToken = null;
0161: indexLongNameFirst = -1;
0162: sLongNameFirst = null;
0163:
0164: // sLongNameLast = null;
0165: bWait = false;
0166: lastTokenQuotedID = false;
0167: retainFirst = false;
0168: }
0169:
0170: /**
0171: *
0172: * @throws HsqlException
0173: */
0174: void back() throws HsqlException {
0175:
0176: if (bWait) {
0177: Trace.doAssert(false, "Querying state when in Wait mode");
0178: }
0179:
0180: nextTokenIndex = iIndex;
0181: iIndex = (indexLongNameFirst != -1) ? indexLongNameFirst
0182: : tokenIndex;
0183: bWait = true;
0184: }
0185:
0186: /**
0187: * get the given token or throw
0188: *
0189: * for commands and simple unquoted identifiers only
0190: *
0191: * @param match
0192: *
0193: * @throws HsqlException
0194: */
0195: String getThis(String match) throws HsqlException {
0196:
0197: getToken();
0198: matchThis(match);
0199:
0200: return sToken;
0201: }
0202:
0203: /**
0204: * for commands and simple unquoted identifiers only
0205: */
0206: void matchThis(String match) throws HsqlException {
0207:
0208: if (bWait) {
0209: Trace.doAssert(false, "Querying state when in Wait mode");
0210: }
0211:
0212: if (!sToken.equals(match) || iType == QUOTED_IDENTIFIER
0213: || iType == LONG_NAME) {
0214: String token = iType == LONG_NAME ? sLongNameFirst : sToken;
0215:
0216: throw Trace
0217: .error(Trace.UNEXPECTED_TOKEN,
0218: Trace.TOKEN_REQUIRED, new Object[] { token,
0219: match });
0220: }
0221: }
0222:
0223: void throwUnexpected() throws HsqlException {
0224:
0225: String token = iType == LONG_NAME ? sLongNameFirst : sToken;
0226:
0227: throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
0228: }
0229:
0230: /**
0231: * Used for commands only
0232: *
0233: *
0234: * @param match
0235: */
0236: public boolean isGetThis(String match) throws HsqlException {
0237:
0238: getToken();
0239:
0240: if (iType != QUOTED_IDENTIFIER && iType != LONG_NAME
0241: && sToken.equals(match)) {
0242: return true;
0243: }
0244:
0245: back();
0246:
0247: return false;
0248: }
0249:
0250: /**
0251: * this methode is called before other wasXXX methods and takes
0252: * precedence
0253: */
0254: boolean wasValue() throws HsqlException {
0255:
0256: if (bWait) {
0257: Trace.doAssert(false, "Querying state when in Wait mode");
0258: }
0259:
0260: switch (iType) {
0261:
0262: case STRING:
0263: case NUMBER:
0264: case LONG:
0265: case FLOAT:
0266: case DECIMAL:
0267: case BOOLEAN:
0268: case NULL:
0269: return true;
0270:
0271: default:
0272: return false;
0273: }
0274: }
0275:
0276: boolean wasQuotedIdentifier() throws HsqlException {
0277:
0278: if (bWait) {
0279: Trace.doAssert(false, "Querying state when in Wait mode");
0280: }
0281:
0282: return lastTokenQuotedID;
0283:
0284: // iType won't help for LONG_NAMEs.
0285: //return iType == QUOTED_IDENTIFIER;
0286: }
0287:
0288: boolean wasFirstQuotedIdentifier() throws HsqlException {
0289:
0290: if (bWait) {
0291: Trace.doAssert(false, "Querying state when in Wait mode");
0292: }
0293:
0294: return (typeLongNameFirst == QUOTED_IDENTIFIER);
0295: }
0296:
0297: /**
0298: * Method declaration
0299: *
0300: *
0301: * @return
0302: */
0303: boolean wasLongName() throws HsqlException {
0304:
0305: if (bWait) {
0306: Trace.doAssert(false, "Querying state when in Wait mode");
0307: }
0308:
0309: return iType == LONG_NAME;
0310: }
0311:
0312: /**
0313: * Simple Name means a quoted or unquoted identifier without
0314: * qualifiers provided it is not in the hKeyword list.
0315: *
0316: * @return
0317: */
0318: boolean wasSimpleName() throws HsqlException {
0319:
0320: if (bWait) {
0321: Trace.doAssert(false, "Querying state when in Wait mode");
0322: }
0323:
0324: if (iType == QUOTED_IDENTIFIER && sToken.length() != 0) {
0325: return true;
0326: }
0327:
0328: if (iType != NAME) {
0329: return false;
0330: }
0331:
0332: return !Token.isKeyword(sToken);
0333: }
0334:
0335: /**
0336: * checks whether the previously obtained token was a (named) parameter
0337: *
0338: * @return true if the previously obtained token was a (named) parameter
0339: */
0340: boolean wasParameter() throws HsqlException {
0341:
0342: Trace.doAssert(!bWait, "Querying state when in Wait mode");
0343:
0344: return (iType == NAMED_PARAM);
0345: }
0346:
0347: /**
0348: * Name means all quoted and unquoted identifiers plus any word not in the
0349: * hKeyword list.
0350: *
0351: * @return true if it's a name
0352: */
0353: boolean wasName() throws HsqlException {
0354:
0355: if (bWait) {
0356: Trace.doAssert(false, "Querying state when in Wait mode");
0357: }
0358:
0359: if (iType == QUOTED_IDENTIFIER) {
0360: return true;
0361: }
0362:
0363: if (iType != NAME && iType != LONG_NAME) {
0364: return false;
0365: }
0366:
0367: return !Token.isKeyword(sToken);
0368: }
0369:
0370: String getLongNamePre() throws HsqlException {
0371: return null;
0372: }
0373:
0374: /**
0375: * Return first part of long name
0376: *
0377: *
0378: * @return
0379: */
0380: String getLongNameFirst() throws HsqlException {
0381:
0382: if (bWait) {
0383: Trace.doAssert(false, "Querying state when in Wait mode");
0384: }
0385:
0386: return sLongNameFirst;
0387: }
0388:
0389: boolean wasSimpleToken() throws HsqlException {
0390: return iType != QUOTED_IDENTIFIER && iType != LONG_NAME
0391: && iType != STRING && iType != NAMED_PARAM;
0392: }
0393:
0394: String getSimpleToken() throws HsqlException {
0395:
0396: getToken();
0397:
0398: if (!wasSimpleToken()) {
0399: String token = iType == LONG_NAME ? sLongNameFirst : sToken;
0400:
0401: throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
0402: }
0403:
0404: return sToken;
0405: }
0406:
0407: public boolean wasThis(String match) throws HsqlException {
0408:
0409: if (sToken.equals(match) && iType != QUOTED_IDENTIFIER
0410: && iType != LONG_NAME && iType != STRING) {
0411: return true;
0412: }
0413:
0414: return false;
0415: }
0416:
0417: /**
0418: * getName() is more broad than getSimpleName() in that it includes
0419: * 2-part names as well
0420: *
0421: * @return popped name
0422: * @throws HsqlException if next token is not an AName
0423: */
0424: public String getName() throws HsqlException {
0425:
0426: getToken();
0427:
0428: if (!wasName()) {
0429: throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
0430: }
0431:
0432: return sToken;
0433: }
0434:
0435: /**
0436: * Returns a single, unqualified name (identifier)
0437: *
0438: * @return name
0439: * @throws HsqlException
0440: */
0441: public String getSimpleName() throws HsqlException {
0442:
0443: getToken();
0444:
0445: if (!wasSimpleName()) {
0446: String token = iType == LONG_NAME ? sLongNameFirst : sToken;
0447:
0448: throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
0449: }
0450:
0451: return sToken;
0452: }
0453:
0454: /**
0455: * Return any token.
0456: *
0457: *
0458: * @return
0459: *
0460: * @throws HsqlException
0461: */
0462: public String getString() throws HsqlException {
0463:
0464: getToken();
0465:
0466: return sToken;
0467: }
0468:
0469: int getInt() throws HsqlException {
0470:
0471: long v = getBigint();
0472:
0473: if (v > Integer.MAX_VALUE || v < Integer.MIN_VALUE) {
0474: throw Trace.error(Trace.WRONG_DATA_TYPE, Types
0475: .getTypeString(getType()));
0476: }
0477:
0478: return (int) v;
0479: }
0480:
0481: static BigDecimal LONG_MAX_VALUE_INCREMENT = BigDecimal.valueOf(
0482: Long.MAX_VALUE).add(BigDecimal.valueOf(1));
0483:
0484: long getBigint() throws HsqlException {
0485:
0486: boolean minus = false;
0487:
0488: getToken();
0489:
0490: if (sToken.equals("-")) {
0491: minus = true;
0492:
0493: getToken();
0494: }
0495:
0496: Object o = getAsValue();
0497: int t = getType();
0498:
0499: switch (t) {
0500:
0501: case Types.INTEGER:
0502: case Types.BIGINT:
0503: break;
0504:
0505: case Types.DECIMAL:
0506:
0507: // only Long.MAX_VALUE + 1 together with minus is acceptable
0508: if (minus && LONG_MAX_VALUE_INCREMENT.equals(o)) {
0509: return Long.MIN_VALUE;
0510: }
0511: default:
0512: throw Trace.error(Trace.WRONG_DATA_TYPE, Types
0513: .getTypeString(t));
0514: }
0515:
0516: long v = ((Number) o).longValue();
0517:
0518: return minus ? -v : v;
0519: }
0520:
0521: Object getInType(int type) throws HsqlException {
0522:
0523: getToken();
0524:
0525: Object o = getAsValue();
0526: int t = getType();
0527:
0528: if (t != type) {
0529: throw Trace.error(Trace.WRONG_DATA_TYPE, Types
0530: .getTypeString(t));
0531: }
0532:
0533: return o;
0534: }
0535:
0536: /**
0537: *
0538: *
0539: *
0540: * @return
0541: */
0542: public int getType() throws HsqlException {
0543:
0544: if (bWait) {
0545: Trace.doAssert(false, "Querying state when in Wait mode");
0546: }
0547:
0548: // todo: make sure it's used only for Values!
0549: // todo: synchronize iType with hColumn
0550: switch (iType) {
0551:
0552: case STRING:
0553: return Types.VARCHAR;
0554:
0555: case NUMBER:
0556: return Types.INTEGER;
0557:
0558: case LONG:
0559: return Types.BIGINT;
0560:
0561: case FLOAT:
0562: return Types.DOUBLE;
0563:
0564: case DECIMAL:
0565: return Types.DECIMAL;
0566:
0567: case BOOLEAN:
0568: return Types.BOOLEAN;
0569:
0570: case DATE:
0571: return Types.DATE;
0572:
0573: case TIME:
0574: return Types.TIME;
0575:
0576: case TIMESTAMP:
0577: return Types.TIMESTAMP;
0578:
0579: default:
0580: return Types.NULL;
0581: }
0582: }
0583:
0584: /**
0585: * Method declaration
0586: *
0587: *
0588: * @return
0589: *
0590: * @throws HsqlException
0591: */
0592: Object getAsValue() throws HsqlException {
0593:
0594: if (!wasValue()) {
0595: throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
0596: }
0597:
0598: switch (iType) {
0599:
0600: case NULL:
0601: return null;
0602:
0603: case STRING:
0604:
0605: //fredt - no longer returning string with a singlequote as last char
0606: return sToken;
0607:
0608: case LONG:
0609: return ValuePool.getLong(Long.parseLong(sToken));
0610:
0611: case NUMBER:
0612:
0613: // fredt - this returns unsigned values which are later negated.
0614: // as a result Integer.MIN_VALUE or Long.MIN_VALUE are promoted
0615: // to a wider type.
0616: if (sToken.length() < 11) {
0617: try {
0618: return ValuePool.getInt(Integer.parseInt(sToken));
0619: } catch (Exception e1) {
0620: }
0621: }
0622:
0623: if (sToken.length() < 20) {
0624: try {
0625: iType = LONG;
0626:
0627: return ValuePool.getLong(Long.parseLong(sToken));
0628: } catch (Exception e2) {
0629: }
0630: }
0631:
0632: iType = DECIMAL;
0633:
0634: return new BigDecimal(sToken);
0635:
0636: case FLOAT:
0637: double d = JavaSystem.parseDouble(sToken);
0638: long l = Double.doubleToLongBits(d);
0639:
0640: return ValuePool.getDouble(l);
0641:
0642: case DECIMAL:
0643: return new BigDecimal(sToken);
0644:
0645: case BOOLEAN:
0646: return sToken.equalsIgnoreCase("TRUE") ? Boolean.TRUE
0647: : Boolean.FALSE;
0648:
0649: case DATE:
0650: return HsqlDateTime.dateValue(sToken);
0651:
0652: case TIME:
0653: return HsqlDateTime.timeValue(sToken);
0654:
0655: case TIMESTAMP:
0656: return HsqlDateTime.timestampValue(sToken);
0657:
0658: default:
0659: return sToken;
0660: }
0661: }
0662:
0663: /**
0664: * return the current position to be used for VIEW processing
0665: *
0666: * @return
0667: */
0668: int getPosition() {
0669: return iIndex;
0670: }
0671:
0672: /**
0673: * mark the current position to be used for future getLastPart() calls
0674: *
0675: * @return
0676: */
0677: String getPart(int begin, int end) {
0678: return sCommand.substring(begin, end);
0679: }
0680:
0681: /**
0682: * mark the current position to be used for future getLastPart() calls
0683: *
0684: * @return
0685: */
0686: int getPartMarker() {
0687: return beginIndex;
0688: }
0689:
0690: /**
0691: * mark the current position to be used for future getLastPart() calls
0692: *
0693: */
0694: void setPartMarker() {
0695: beginIndex = iIndex;
0696: }
0697:
0698: /**
0699: * mark the position to be used for future getLastPart() calls
0700: *
0701: */
0702: void setPartMarker(int position) {
0703: beginIndex = position;
0704: }
0705:
0706: /**
0707: * return part of the command string from the last marked position
0708: *
0709: * @return
0710: */
0711: String getLastPart() {
0712: return sCommand.substring(beginIndex, iIndex);
0713: }
0714:
0715: // fredt@users 20020910 - patch 1.7.1 by Nitin Chauhan - rewrite as switch
0716:
0717: /**
0718: * Method declaration
0719: *
0720: *
0721: * @throws HsqlException
0722: */
0723: private void getToken() throws HsqlException {
0724:
0725: if (bWait) {
0726: bWait = false;
0727: iIndex = nextTokenIndex;
0728:
0729: return;
0730: }
0731:
0732: if (!retainFirst) {
0733: sLongNameFirst = null;
0734: indexLongNameFirst = -1;
0735: typeLongNameFirst = NO_TYPE;
0736: }
0737:
0738: while (iIndex < iLength
0739: && Character.isWhitespace(sCommand.charAt(iIndex))) {
0740: iIndex++;
0741: }
0742:
0743: sToken = "";
0744: tokenIndex = iIndex;
0745:
0746: if (iIndex >= iLength) {
0747: iType = NO_TYPE;
0748:
0749: return;
0750: }
0751:
0752: char c = sCommand.charAt(iIndex);
0753: boolean point = false, digit = false, exp = false, afterexp = false;
0754: boolean end = false;
0755: char cfirst = 0;
0756:
0757: lastTokenQuotedID = false;
0758:
0759: if (Character.isJavaIdentifierStart(c)) {
0760: iType = NAME;
0761: } else if (Character.isDigit(c)) {
0762: iType = NUMBER;
0763: digit = true;
0764: } else {
0765: switch (c) {
0766:
0767: case '(':
0768: sToken = Token.T_OPENBRACKET;
0769: iType = SPECIAL;
0770:
0771: iIndex++;
0772:
0773: return;
0774:
0775: case ')':
0776: sToken = Token.T_CLOSEBRACKET;
0777: iType = SPECIAL;
0778:
0779: iIndex++;
0780:
0781: return;
0782:
0783: case ',':
0784: sToken = Token.T_COMMA;
0785: iType = SPECIAL;
0786:
0787: iIndex++;
0788:
0789: return;
0790:
0791: case '*':
0792: sToken = Token.T_MULTIPLY;
0793: iType = SPECIAL;
0794:
0795: iIndex++;
0796:
0797: return;
0798:
0799: case '=':
0800: sToken = Token.T_EQUALS;
0801: iType = SPECIAL;
0802:
0803: iIndex++;
0804:
0805: return;
0806:
0807: case ';':
0808: sToken = Token.T_SEMICOLON;
0809: iType = SPECIAL;
0810:
0811: iIndex++;
0812:
0813: return;
0814:
0815: case '+':
0816: sToken = Token.T_PLUS;
0817: iType = SPECIAL;
0818:
0819: iIndex++;
0820:
0821: return;
0822:
0823: case '%':
0824: sToken = Token.T_PERCENT;
0825: iType = SPECIAL;
0826:
0827: iIndex++;
0828:
0829: return;
0830:
0831: case '?':
0832: sToken = Token.T_QUESTION;
0833: iType = SPECIAL;
0834:
0835: iIndex++;
0836:
0837: return;
0838:
0839: case ':':
0840: Trace.check(++iIndex < iLength,
0841: Trace.UNEXPECTED_END_OF_COMMAND);
0842:
0843: c = sCommand.charAt(iIndex);
0844:
0845: Trace.check(Character.isJavaIdentifierStart(c),
0846: Trace.INVALID_IDENTIFIER, ":" + c);
0847:
0848: iType = NAMED_PARAM;
0849: break;
0850:
0851: case '\"':
0852: lastTokenQuotedID = true;
0853: iType = QUOTED_IDENTIFIER;
0854:
0855: iIndex++;
0856:
0857: sToken = getString('"');
0858:
0859: if (iIndex == sCommand.length()) {
0860: return;
0861: }
0862:
0863: c = sCommand.charAt(iIndex);
0864:
0865: if (c == '.') {
0866: sLongNameFirst = sToken;
0867: indexLongNameFirst = tokenIndex;
0868: typeLongNameFirst = iType;
0869:
0870: iIndex++;
0871:
0872: if (retainFirst) {
0873: throw Trace.error(Trace.THREE_PART_IDENTIFIER);
0874: }
0875:
0876: // fredt - todo - avoid recursion - this has problems when there is whitespace
0877: // after the dot - the same with NAME
0878: retainFirst = true;
0879:
0880: getToken();
0881:
0882: retainFirst = false;
0883: iType = LONG_NAME;
0884: }
0885:
0886: return;
0887:
0888: case '\'':
0889: iType = STRING;
0890:
0891: iIndex++;
0892:
0893: sToken = getString('\'');
0894:
0895: return;
0896:
0897: case '!':
0898: case '<':
0899: case '>':
0900: case '|':
0901: case '/':
0902: case '-':
0903: cfirst = c;
0904: iType = SPECIAL;
0905: break;
0906:
0907: case '.':
0908: iType = DECIMAL;
0909: point = true;
0910: break;
0911:
0912: default:
0913: throw Trace.error(Trace.UNEXPECTED_TOKEN, String
0914: .valueOf(c));
0915: }
0916: }
0917:
0918: int start = iIndex++;
0919:
0920: while (true) {
0921: if (iIndex >= iLength) {
0922: c = ' ';
0923: end = true;
0924:
0925: Trace.check(iType != STRING
0926: && iType != QUOTED_IDENTIFIER,
0927: Trace.UNEXPECTED_END_OF_COMMAND);
0928: } else {
0929: c = sCommand.charAt(iIndex);
0930: }
0931:
0932: switch (iType) {
0933:
0934: case NAMED_PARAM:
0935: case NAME:
0936: if (Character.isJavaIdentifierPart(c)) {
0937: break;
0938: }
0939:
0940: // fredt - todo new char[] to back sToken
0941: sToken = sCommand.substring(start, iIndex).toUpperCase(
0942: Locale.ENGLISH);
0943:
0944: // the following only for NAME, not for NAMED_PARAM
0945: if (iType == NAMED_PARAM) {
0946: return;
0947: }
0948:
0949: if (c == '.') {
0950: typeLongNameFirst = iType;
0951: sLongNameFirst = sToken;
0952: indexLongNameFirst = tokenIndex;
0953:
0954: iIndex++;
0955:
0956: if (retainFirst) {
0957: throw Trace.error(Trace.THREE_PART_IDENTIFIER);
0958: }
0959:
0960: retainFirst = true;
0961:
0962: getToken(); // todo: eliminate recursion
0963:
0964: retainFirst = false;
0965: iType = LONG_NAME;
0966: } else if (c == '(') {
0967:
0968: // it is a function call
0969: } else {
0970:
0971: // if in value list then it is a value
0972: int type = valueTokens.get(sToken, -1);
0973:
0974: if (type != -1) {
0975: iType = type;
0976: }
0977: }
0978:
0979: return;
0980:
0981: case QUOTED_IDENTIFIER:
0982: case STRING:
0983:
0984: // shouldn't get here
0985: break;
0986:
0987: case REMARK:
0988: if (end) {
0989:
0990: // unfinished remark
0991: // maybe print error here
0992: iType = NO_TYPE;
0993:
0994: return;
0995: } else if (c == '*') {
0996: iIndex++;
0997:
0998: if (iIndex < iLength
0999: && sCommand.charAt(iIndex) == '/') {
1000:
1001: // using recursion here
1002: iIndex++;
1003:
1004: getToken();
1005:
1006: return;
1007: }
1008: }
1009: break;
1010:
1011: case REMARK_LINE:
1012: if (end) {
1013: iType = NO_TYPE;
1014:
1015: return;
1016: } else if (c == '\r' || c == '\n') {
1017:
1018: // using recursion here
1019: getToken();
1020:
1021: return;
1022: }
1023: break;
1024:
1025: case SPECIAL:
1026: if (c == '/' && cfirst == '/') {
1027: iType = REMARK_LINE;
1028:
1029: break;
1030: } else if (c == '-' && cfirst == '-') {
1031: iType = REMARK_LINE;
1032:
1033: break;
1034: } else if (c == '*' && cfirst == '/') {
1035: iType = REMARK;
1036:
1037: break;
1038: } else if (c == '>' || c == '=' || c == '|') {
1039: break;
1040: }
1041:
1042: sToken = sCommand.substring(start, iIndex);
1043:
1044: return;
1045:
1046: case NUMBER:
1047: case FLOAT:
1048: case DECIMAL:
1049: if (Character.isDigit(c)) {
1050: digit = true;
1051: } else if (c == '.') {
1052: iType = DECIMAL;
1053:
1054: if (point) {
1055: throw Trace.error(Trace.UNEXPECTED_TOKEN, ".");
1056: }
1057:
1058: point = true;
1059: } else if (c == 'E' || c == 'e') {
1060: if (exp) {
1061: throw Trace.error(Trace.UNEXPECTED_TOKEN, "E");
1062: }
1063:
1064: // HJB-2001-08-2001 - now we are sure it's a float
1065: iType = FLOAT;
1066:
1067: // first character after exp may be + or -
1068: afterexp = true;
1069: point = true;
1070: exp = true;
1071: } else if (c == '-' && afterexp) {
1072: afterexp = false;
1073: } else if (c == '+' && afterexp) {
1074: afterexp = false;
1075: } else {
1076: afterexp = false;
1077:
1078: if (!digit) {
1079: if (point && start == iIndex - 1) {
1080: sToken = ".";
1081: iType = SPECIAL;
1082:
1083: return;
1084: }
1085:
1086: throw Trace.error(Trace.UNEXPECTED_TOKEN,
1087: String.valueOf(c));
1088: }
1089:
1090: sToken = sCommand.substring(start, iIndex);
1091:
1092: return;
1093: }
1094: }
1095:
1096: iIndex++;
1097: }
1098: }
1099:
1100: // fredt - strings are constructed from new char[] objects to avoid slack
1101: // because these strings might end up as part of internal data structures
1102: // or table elements.
1103: // we may consider using pools to avoid recreating the strings
1104: private String getString(char quoteChar) throws HsqlException {
1105:
1106: try {
1107: int nextIndex = iIndex;
1108: boolean quoteInside = false;
1109:
1110: for (;;) {
1111: nextIndex = sCommand.indexOf(quoteChar, nextIndex);
1112:
1113: if (nextIndex < 0) {
1114: throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND);
1115: }
1116:
1117: if (nextIndex < iLength - 1
1118: && sCommand.charAt(nextIndex + 1) == quoteChar) {
1119: quoteInside = true;
1120: nextIndex += 2;
1121:
1122: continue;
1123: }
1124:
1125: break;
1126: }
1127:
1128: char[] chBuffer = new char[nextIndex - iIndex];
1129:
1130: sCommand.getChars(iIndex, nextIndex, chBuffer, 0);
1131:
1132: int j = chBuffer.length;
1133:
1134: if (quoteInside) {
1135: j = 0;
1136:
1137: // fredt - loop assumes all occurences of quoteChar are paired
1138: // this has already been checked by the preprocessing loop
1139: for (int i = 0; i < chBuffer.length; i++, j++) {
1140: if (chBuffer[i] == quoteChar) {
1141: i++;
1142: }
1143:
1144: chBuffer[j] = chBuffer[i];
1145: }
1146: }
1147:
1148: iIndex = ++nextIndex;
1149:
1150: return new String(chBuffer, 0, j);
1151: } catch (HsqlException e) {
1152: throw e;
1153: } catch (Exception e) {
1154: e.toString();
1155: }
1156:
1157: return null;
1158: }
1159:
1160: /**
1161: * Method declaration
1162: *
1163: *
1164: * @return
1165: */
1166: int getLength() {
1167: return iLength;
1168: }
1169: }
|