001: /**********************************************************************
002: Copyright (c) 2002 Kelly Grizzle (TJDO) and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015:
016: Contributors:
017: 2003 Andy Jefferson - coding standards
018: 2005 Andy Jefferson - added support for single quoted StringLiteral
019: (including contrib from Tony Lai)
020: ...
021: **********************************************************************/package org.jpox.store.query;
022:
023: import java.math.BigDecimal;
024: import java.math.BigInteger;
025: import java.text.CharacterIterator;
026: import java.text.StringCharacterIterator;
027:
028: import org.jpox.ClassLoaderResolver;
029: import org.jpox.exceptions.ClassNotResolvedException;
030: import org.jpox.exceptions.JPOXUserException;
031: import org.jpox.util.Imports;
032: import org.jpox.util.Localiser;
033:
034: /**
035: * Parser for a Query. The query can be JDOQL, or JPQL.
036: * Allows a class to work its way through the parsed string, obtaining relevant
037: * components with each call, or peeking ahead before deciding what component
038: * to parse next.
039: *
040: * @version $Revision: 1.18 $
041: **/
042: public class Parser {
043: /** Localiser for messages. */
044: protected static final Localiser LOCALISER = Localiser
045: .getInstance("org.jpox.store.Localisation");
046:
047: protected final String input;
048: protected final Imports imports;
049: protected final CharacterIterator ci;
050:
051: /**
052: * Constructor
053: * @param input The input string
054: * @param imports Necessary imports to use in the parse.
055: **/
056: public Parser(String input, Imports imports) {
057: this .input = input;
058: this .imports = imports;
059:
060: ci = new StringCharacterIterator(input);
061: }
062:
063: /**
064: * Accessor for the input string.
065: * @return The input string.
066: */
067: public String getInput() {
068: return input;
069: }
070:
071: /**
072: * Accessor for the current index in the input string.
073: * @return The current index.
074: */
075: public int getIndex() {
076: return ci.getIndex();
077: }
078:
079: /**
080: * Skip over any whitespace from the current position.
081: * @return The new position
082: */
083: public int skipWS() {
084: int startIdx = ci.getIndex();
085: char c = ci.current();
086:
087: while (Character.isWhitespace(c) || c == '\t' || c == '\f'
088: || c == '\n' || c == '\r' || c == '\u0009'
089: || c == '\u000c' || c == '\u0020' || c == '\11'
090: || c == '\12' || c == '\14' || c == '\15' || c == '\40') {
091: c = ci.next();
092: }
093:
094: return startIdx;
095: }
096:
097: /**
098: * Check if END OF TEXT is reach
099: * @return true if END OF TEXT is reach
100: */
101: public boolean parseEOS() {
102: skipWS();
103:
104: return ci.current() == CharacterIterator.DONE;
105: }
106:
107: /**
108: * Check if char <code>c</code> is found
109: * @param c the Character to find
110: * @return true if <code>c</code> is found
111: */
112: public boolean parseChar(char c) {
113: skipWS();
114:
115: if (ci.current() == c) {
116: ci.next();
117: return true;
118: } else {
119: return false;
120: }
121: }
122:
123: /**
124: * Check if char <code>c</code> is found
125: * @param c the Character to find
126: * @param unlessFollowedBy the character to validate it does not follow <code>c</code>
127: * @return true if <code>c</code> is found and not followed by <code>unlessFollowedBy</code>
128: */
129: public boolean parseChar(char c, char unlessFollowedBy) {
130: int savedIdx = skipWS();
131:
132: if (ci.current() == c && ci.next() != unlessFollowedBy) {
133: return true;
134: } else {
135: ci.setIndex(savedIdx);
136: return false;
137: }
138: }
139:
140: /**
141: * Check if String <code>s</code> is found
142: * @param s the String to find
143: * @return true if <code>s</code> is found
144: */
145: public boolean parseString(String s) {
146: int savedIdx = skipWS();
147:
148: int len = s.length();
149: char c = ci.current();
150:
151: for (int i = 0; i < len; ++i) {
152: if (c != s.charAt(i)) {
153: ci.setIndex(savedIdx);
154: return false;
155: }
156:
157: c = ci.next();
158: }
159:
160: return true;
161: }
162:
163: /**
164: * Check if String <code>s</code> is found ignoring the case
165: * @param s the String to find
166: * @return true if <code>s</code> is found
167: */
168: public boolean parseStringIgnoreCase(String s) {
169: String lowerCasedString = s.toLowerCase();
170:
171: int savedIdx = skipWS();
172:
173: int len = lowerCasedString.length();
174: char c = ci.current();
175:
176: for (int i = 0; i < len; ++i) {
177: if (Character.toLowerCase(c) != lowerCasedString.charAt(i)) {
178: ci.setIndex(savedIdx);
179: return false;
180: }
181:
182: c = ci.next();
183: }
184:
185: return true;
186: }
187:
188: /**
189: * Check if String "s" is found ignoring the case, and not moving the cursor position.
190: * @param s the String to find
191: * @return true if string is found
192: */
193: public boolean peekStringIgnoreCase(String s) {
194: String lowerCasedString = s.toLowerCase();
195:
196: int savedIdx = skipWS();
197:
198: int len = lowerCasedString.length();
199: char c = ci.current();
200:
201: for (int i = 0; i < len; ++i) {
202: if (Character.toLowerCase(c) != lowerCasedString.charAt(i)) {
203: ci.setIndex(savedIdx);
204: return false;
205: }
206: c = ci.next();
207: }
208: ci.setIndex(savedIdx);
209:
210: return true;
211: }
212:
213: /**
214: * Parse a java identifier from the current position.
215: * @return The identifier
216: */
217: public String parseIdentifier() {
218: skipWS();
219: char c = ci.current();
220:
221: if (!Character.isJavaIdentifierStart(c) && !(c == ':')) {
222: // Current character is not a valid identifier char, and isn't a
223: // valid JDOQL parameter start character
224: return null;
225: }
226:
227: StringBuffer id = new StringBuffer();
228: id.append(c);
229: while (Character.isJavaIdentifierPart(c = ci.next())) {
230: id.append(c);
231: }
232:
233: return id.toString();
234: }
235:
236: /**
237: * Checks if a java Method is found
238: * @return true if a Method is found
239: */
240: public String parseMethod() {
241: int savedIdx = ci.getIndex();
242:
243: String id;
244:
245: if ((id = parseIdentifier()) == null) {
246: ci.setIndex(savedIdx);
247: return null;
248: }
249:
250: skipWS();
251:
252: if (!parseChar('(')) {
253: ci.setIndex(savedIdx);
254: return null;
255: }
256: ci.setIndex(ci.getIndex() - 1);
257: return id;
258: }
259:
260: /**
261: * Parses the text string (up to the next space) and
262: * returns it. The name includes '.' characters.
263: * This can be used, for example, when parsing a class name wanting to
264: * read in the full name (including package) so that it can then be
265: * checked for existence in the CLASSPATH.
266: * @return The name
267: */
268: public String parseName() {
269: int savedIdx = skipWS();
270: String id;
271:
272: if ((id = parseIdentifier()) == null) {
273: return null;
274: }
275:
276: StringBuffer qn = new StringBuffer(id);
277:
278: while (parseChar('.')) {
279: if ((id = parseIdentifier()) == null) {
280: ci.setIndex(savedIdx);
281: return null;
282: }
283:
284: qn.append('.').append(id);
285: }
286:
287: return qn.toString();
288: }
289:
290: /**
291: * Parse a cast in the query from the current position, returning
292: * the class that is being cast to. Returns null if the current position
293: * doesnt have a cast.
294: * @param clr The ClassLoaderResolver
295: * @param primary The primary class loader to use (if any)
296: * @return The class to cast to
297: */
298: public Class parseCast(ClassLoaderResolver clr, ClassLoader primary) {
299: int savedIdx = skipWS();
300: String typeName;
301:
302: if (!parseChar('(') || (typeName = parseName()) == null
303: || !parseChar(')')) {
304: ci.setIndex(savedIdx);
305: return null;
306: }
307:
308: try {
309: return imports.resolveClassDeclaration(typeName, clr,
310: primary);
311: } catch (ClassNotResolvedException e) {
312: ci.setIndex(savedIdx);
313: throw new JPOXUserException(LOCALISER.msg("021053",
314: typeName));
315: }
316: }
317:
318: /**
319: * Utility to return if a character is a decimal digit.
320: * @param c The character
321: * @return Whether it is a decimal digit
322: */
323: private final static boolean isDecDigit(char c) {
324: return c >= '0' && c <= '9';
325: }
326:
327: /**
328: * Utility to return if a character is a octal digit.
329: * @param c The character
330: * @return Whether it is a octal digit
331: */
332: private final static boolean isOctDigit(char c) {
333: return c >= '0' && c <= '7';
334: }
335:
336: /**
337: * Utility to return if a character is a hexadecimal digit.
338: * @param c The character
339: * @return Whether it is a hexadecimal digit
340: */
341: private final static boolean isHexDigit(char c) {
342: return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A'
343: && c <= 'F';
344: }
345:
346: /**
347: * Parse an integer number from the current position.
348: * @return The integer number parsed (null if not valid).
349: */
350: public BigInteger parseIntegerLiteral() {
351: int savedIdx = skipWS();
352:
353: StringBuffer digits = new StringBuffer();
354: int radix;
355: char c = ci.current();
356:
357: if (c == '0') {
358: c = ci.next();
359:
360: if (c == 'x' || c == 'X') {
361: radix = 16;
362: c = ci.next();
363:
364: while (isHexDigit(c)) {
365: digits.append(c);
366: c = ci.next();
367: }
368: } else if (isOctDigit(c)) {
369: radix = 8;
370:
371: do {
372: digits.append(c);
373: c = ci.next();
374: } while (isOctDigit(c));
375: } else {
376: radix = 10;
377: digits.append('0');
378: }
379: } else {
380: radix = 10;
381:
382: while (isDecDigit(c)) {
383: digits.append(c);
384: c = ci.next();
385: }
386: }
387:
388: if (digits.length() == 0) {
389: ci.setIndex(savedIdx);
390: return null;
391: }
392:
393: if (c == 'l' || c == 'L') {
394: ci.next();
395: }
396:
397: return new BigInteger(digits.toString(), radix);
398: }
399:
400: /**
401: * Parse a floating point number from the current position.
402: * @return The floating point number parsed (null if not valid).
403: */
404: public BigDecimal parseFloatingPointLiteral() {
405: int savedIdx = skipWS();
406: StringBuffer val = new StringBuffer();
407: boolean dotSeen = false;
408: boolean expSeen = false;
409: boolean sfxSeen = false;
410:
411: char c = ci.current();
412:
413: while (isDecDigit(c)) {
414: val.append(c);
415: c = ci.next();
416: }
417:
418: if (c == '.') {
419: dotSeen = true;
420: val.append(c);
421: c = ci.next();
422:
423: while (isDecDigit(c)) {
424: val.append(c);
425: c = ci.next();
426: }
427: }
428:
429: if (val.length() < (dotSeen ? 2 : 1)) {
430: ci.setIndex(savedIdx);
431: return null;
432: }
433:
434: if (c == 'e' || c == 'E') {
435: expSeen = true;
436: val.append(c);
437: c = ci.next();
438:
439: if (c != '+' && c != '-' && !isDecDigit(c)) {
440: ci.setIndex(savedIdx);
441: return null;
442: }
443:
444: do {
445: val.append(c);
446: c = ci.next();
447: } while (isDecDigit(c));
448: }
449:
450: if (c == 'f' || c == 'F' || c == 'd' || c == 'D') {
451: sfxSeen = true;
452: ci.next();
453: }
454:
455: if (!dotSeen && !expSeen && !sfxSeen) {
456: ci.setIndex(savedIdx);
457: return null;
458: }
459:
460: return new BigDecimal(val.toString());
461: }
462:
463: /**
464: * Parse a boolean from the current position.
465: * @return The boolean parsed (null if not valid).
466: */
467: public Boolean parseBooleanLiteral() {
468: int savedIdx = skipWS();
469: String id;
470:
471: if ((id = parseIdentifier()) == null) {
472: return null;
473: }
474:
475: if (id.equals("true")) {
476: return Boolean.TRUE;
477: } else if (id.equals("false")) {
478: return Boolean.FALSE;
479: } else {
480: ci.setIndex(savedIdx);
481: return null;
482: }
483: }
484:
485: /**
486: * Utility to return if the next non-whitespace character is a single quote.
487: * @return Whether it is a single quote at the current point (ignoring whitespace)
488: */
489: public boolean nextIsSingleQuote() {
490: skipWS();
491: return (ci.current() == '\'');
492: }
493:
494: /**
495: * Utility to return if the next character is a dot.
496: * @return Whether it is a dot at the current point
497: */
498: public boolean nextIsDot() {
499: return (ci.current() == '.');
500: }
501:
502: /**
503: * Parse a Character literal.
504: * @return the Character parsed. null if single quotes is found
505: * @throws JPOXUserException if an invalid character is found or the CharacterIterator is finished
506: */
507: public Character parseCharacterLiteral() {
508: skipWS();
509:
510: if (ci.current() != '\'') {
511: return null;
512: }
513:
514: char c = ci.next();
515:
516: if (c == CharacterIterator.DONE) {
517: throw new JPOXUserException("Invalid character literal: "
518: + input);
519: }
520:
521: if (c == '\\') {
522: // Why are we doing this exactly ? If the string is "\\_" then this should be "\_" but doing this
523: // we get an Exception thrown for invalid escape expression. See JPQLParser for correct version IMHO
524: c = parseEscapedCharacter();
525: }
526:
527: if (ci.next() != '\'') {
528: throw new JPOXUserException("Invalid character literal: "
529: + input);
530: }
531:
532: ci.next();
533:
534: return new Character(c);
535: }
536:
537: /**
538: * Parse a String literal.
539: * @return the String parsed. null if single quotes or double quotes is found
540: * @throws JPOXUserException if an invalid character is found or the CharacterIterator is finished
541: */
542: public String parseStringLiteral() {
543: skipWS();
544:
545: // Strings can be surrounded by single or double quotes
546: char quote = ci.current();
547: if (quote != '"' && quote != '\'') {
548: return null;
549: }
550:
551: StringBuffer lit = new StringBuffer();
552: char c;
553:
554: while ((c = ci.next()) != quote) {
555: if (c == CharacterIterator.DONE) {
556: throw new JPOXUserException("Invalid string literal: "
557: + input);
558: }
559:
560: if (c == '\\') {
561: // Why are we doing this exactly ? If the string is "\\_" then this should be "\_" but doing this
562: // we get an Exception thrown for invalid escape expression. See JPQLParser for correct version IMHO
563: c = parseEscapedCharacter();
564: }
565:
566: lit.append(c);
567: }
568:
569: ci.next();
570:
571: return lit.toString();
572: }
573:
574: /**
575: * Parse a escaped character.
576: * @return the escaped char
577: * @throws JPOXUserException if a escaped character is not valid
578: */
579: protected char parseEscapedCharacter() {
580: char c;
581:
582: if (isOctDigit(c = ci.next())) {
583: int i = (c - '0');
584:
585: if (isOctDigit(c = ci.next())) {
586: i = i * 8 + (c - '0');
587:
588: if (isOctDigit(c = ci.next())) {
589: i = i * 8 + (c - '0');
590: } else {
591: ci.previous();
592: }
593: } else {
594: ci.previous();
595: }
596:
597: if (i > 0xff) {
598: throw new JPOXUserException(
599: "Invalid character escape: '\\"
600: + Integer.toOctalString(i) + "'");
601: }
602:
603: return (char) i;
604: } else {
605: switch (c) {
606: case 'b':
607: return '\b';
608: case 't':
609: return '\t';
610: case 'n':
611: return '\n';
612: case 'f':
613: return '\f';
614: case 'r':
615: return '\r';
616: case '"':
617: return '"';
618: case '\'':
619: return '\'';
620: case '\\':
621: return '\\';
622: default:
623: throw new JPOXUserException(
624: "Invalid character escape: '\\" + c + "'");
625: }
626: }
627: }
628:
629: /**
630: * Checks if null literal is parsed
631: * @return true if null literal is found
632: */
633: public boolean parseNullLiteral() {
634: int savedIdx = skipWS();
635: String id;
636:
637: if ((id = parseIdentifier()) == null) {
638: return false;
639: } else if (id.equals("null")) {
640: return true;
641: } else {
642: ci.setIndex(savedIdx);
643: return false;
644: }
645: }
646:
647: public String remaining() {
648: StringBuffer sb = new StringBuffer();
649: char c = ci.current();
650: while (c != CharacterIterator.DONE) {
651: sb.append(c);
652: c = ci.next();
653: }
654: return sb.toString();
655: }
656:
657: public String toString() {
658: return input;
659: }
660: }
|