~ubuntu-branches/ubuntu/raring/apgdiff/raring

« back to all changes in this revision

Viewing changes to src/main/java/cz/startnet/utils/pgdiff/parsers/Parser.java

  • Committer: Bazaar Package Importer
  • Author(s): Christoph Berg
  • Date: 2010-10-11 09:08:18 UTC
  • mfrom: (2.1.7 sid)
  • Revision ID: james.westby@ubuntu.com-20101011090818-sdw8yfemrnxo328k
Tags: 2.2.2-1
* New upstream version.
* Using changelog included in zipfile, thanks Miroslav for providing this.
* Update manpage.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * Copyright 2006 StartNet s.r.o.
 
3
 *
 
4
 * Distributed under MIT license
 
5
 */
 
6
package cz.startnet.utils.pgdiff.parsers;
 
7
 
 
8
import cz.startnet.utils.pgdiff.Resources;
 
9
import java.text.MessageFormat;
 
10
import java.util.Locale;
 
11
 
 
12
/**
 
13
 * Class for parsing strings.
 
14
 * 
 
15
 * @author fordfrog
 
16
 */
 
17
public final class Parser {
 
18
 
 
19
    /**
 
20
     * String to be parsed.
 
21
     */
 
22
    private String string;
 
23
    /**
 
24
     * Current position.
 
25
     */
 
26
    private int position;
 
27
 
 
28
    /**
 
29
     * Creates new instance of Parser.
 
30
     *
 
31
     * @param string {@link #string}
 
32
     */
 
33
    public Parser(final String string) {
 
34
        this.string = string;
 
35
        skipWhitespace();
 
36
    }
 
37
 
 
38
    /**
 
39
     * Checks whether the string contains given word on current position. If not
 
40
     * then throws an exception.
 
41
     * 
 
42
     * @param words list of words to check
 
43
     */
 
44
    public void expect(final String... words) {
 
45
        for (final String word : words) {
 
46
            expect(word, false);
 
47
        }
 
48
    }
 
49
 
 
50
    /**
 
51
     * Checks whether the string contains given word on current position. If not
 
52
     * and expectation is optional then position is not changed and method
 
53
     * returns true. If expectation is not optional, exception with error
 
54
     * description is thrown. If word is found, position is moved at first
 
55
     * non-whitespace character following the word.
 
56
     *
 
57
     * @param word word to expect
 
58
     * @param optional true if word is optional, otherwise false
 
59
     *
 
60
     * @return true if word was found, otherwise false
 
61
     */
 
62
    public boolean expect(final String word, final boolean optional) {
 
63
        final int wordEnd = position + word.length();
 
64
 
 
65
        if (wordEnd <= string.length()
 
66
                && string.substring(position, wordEnd).equalsIgnoreCase(word)
 
67
                && (wordEnd == string.length()
 
68
                || !Character.isLetter(string.charAt(wordEnd))
 
69
                || "(".equals(word) || ",".equals(word))) {
 
70
            position = wordEnd;
 
71
            skipWhitespace();
 
72
 
 
73
            return true;
 
74
        }
 
75
 
 
76
        if (optional) {
 
77
            return false;
 
78
        }
 
79
 
 
80
        throw new ParserException(MessageFormat.format(
 
81
                Resources.getString("CannotParseStringExpectedWord"), string,
 
82
                word, position + 1, string.substring(position, position + 20)));
 
83
    }
 
84
 
 
85
    /**
 
86
     * Checks whether string contains at current position sequence of the words.
 
87
     *
 
88
     * @param words array of words
 
89
     *
 
90
     * @return true if whole sequence was found, otherwise false
 
91
     */
 
92
    public boolean expectOptional(final String... words) {
 
93
        final boolean found = expect(words[0], true);
 
94
 
 
95
        if (!found) {
 
96
            return false;
 
97
        }
 
98
 
 
99
        for (int i = 1; i < words.length; i++) {
 
100
            skipWhitespace();
 
101
            expect(words[i]);
 
102
        }
 
103
 
 
104
        return true;
 
105
    }
 
106
 
 
107
    /**
 
108
     * Moves position in the string to next non-whitespace string.
 
109
     */
 
110
    public void skipWhitespace() {
 
111
        for (; position < string.length(); position++) {
 
112
            if (!Character.isWhitespace(string.charAt(position))) {
 
113
                break;
 
114
            }
 
115
        }
 
116
    }
 
117
 
 
118
    /**
 
119
     * Parses identifier from current position. If identifier is quoted, it is
 
120
     * returned quoted. If the identifier is not quoted, it is converted to
 
121
     * lowercase. If identifier does not start with letter then exception is
 
122
     * thrown. Position is placed at next first non-whitespace character.
 
123
     * 
 
124
     * @return parsed identifier
 
125
     */
 
126
    public String parseIdentifier() {
 
127
        String identifier = parseIdentifierInternal();
 
128
 
 
129
        if (string.charAt(position) == '.') {
 
130
            position++;
 
131
            identifier += '.' + parseIdentifierInternal();
 
132
        }
 
133
 
 
134
        skipWhitespace();
 
135
 
 
136
        return identifier;
 
137
    }
 
138
 
 
139
    /**
 
140
     * Parses single part of the identifier.
 
141
     *
 
142
     * @return parsed identifier
 
143
     */
 
144
    private String parseIdentifierInternal() {
 
145
        final boolean quoted = string.charAt(position) == '"';
 
146
 
 
147
        if (quoted) {
 
148
            final int endPos = string.indexOf('"', position + 1);
 
149
            final String result = string.substring(position, endPos + 1);
 
150
            position = endPos + 1;
 
151
 
 
152
            return result;
 
153
        } else {
 
154
            int endPos = position;
 
155
 
 
156
            for (; endPos < string.length(); endPos++) {
 
157
                final char chr = string.charAt(endPos);
 
158
 
 
159
                if (Character.isWhitespace(chr) || chr == ',' || chr == ')'
 
160
                        || chr == '(' || chr == ';' || chr == '.') {
 
161
                    break;
 
162
                }
 
163
            }
 
164
 
 
165
            final String result =
 
166
                    string.substring(position, endPos).toLowerCase(
 
167
                    Locale.ENGLISH);
 
168
 
 
169
            position = endPos;
 
170
 
 
171
            return result;
 
172
        }
 
173
    }
 
174
 
 
175
    /**
 
176
     * Returns rest of the string. If the string ends with ';' then it is
 
177
     * removed from the string before returned. If there is nothing more in the
 
178
     * string, null is returned.
 
179
     *
 
180
     * @return rest of the string, without trailing ';' if present, or null if
 
181
     * there is nothing more in the string
 
182
     */
 
183
    public String getRest() {
 
184
        final String result;
 
185
 
 
186
        if (string.charAt(string.length() - 1) == ';') {
 
187
            if (position == string.length() - 1) {
 
188
                return null;
 
189
            } else {
 
190
                result = string.substring(position, string.length() - 1);
 
191
            }
 
192
        } else {
 
193
            result = string.substring(position);
 
194
        }
 
195
 
 
196
        position = string.length();
 
197
 
 
198
        return result;
 
199
    }
 
200
 
 
201
    /**
 
202
     * Parses integer from the string. If next word is not integer then
 
203
     * exception is thrown.
 
204
     *
 
205
     * @return parsed integer value
 
206
     */
 
207
    public int parseInteger() {
 
208
        int endPos = position;
 
209
 
 
210
        for (; endPos < string.length(); endPos++) {
 
211
            if (!Character.isLetterOrDigit(string.charAt(endPos))) {
 
212
                break;
 
213
            }
 
214
        }
 
215
 
 
216
        try {
 
217
            final int result =
 
218
                    Integer.parseInt(string.substring(position, endPos));
 
219
 
 
220
            position = endPos;
 
221
            skipWhitespace();
 
222
 
 
223
            return result;
 
224
        } catch (final NumberFormatException ex) {
 
225
            throw new ParserException(MessageFormat.format(
 
226
                    Resources.getString("CannotParseStringExpectedInteger"),
 
227
                    string, position + 1,
 
228
                    string.substring(position, position + 20)), ex);
 
229
        }
 
230
    }
 
231
 
 
232
    /**
 
233
     * Parses string from the string. String can be either quoted or unqouted.
 
234
     * Quoted string is parsed till next unescaped quote. Unquoted string is
 
235
     * parsed till whitespace, ',' ')' or ';' is found. If string should be
 
236
     * empty, exception is thrown.
 
237
     *
 
238
     * @return parsed string, if quoted then including quotes
 
239
     */
 
240
    public String parseString() {
 
241
        final boolean quoted = string.charAt(position) == '\'';
 
242
 
 
243
        if (quoted) {
 
244
            boolean escape = false;
 
245
            int endPos = position + 1;
 
246
 
 
247
            for (; endPos < string.length(); endPos++) {
 
248
                final char chr = string.charAt(endPos);
 
249
 
 
250
                if (chr == '\\') {
 
251
                    escape = !escape;
 
252
                } else if (!escape && chr == '\'') {
 
253
                    if (endPos + 1 < string.length()
 
254
                            && string.charAt(endPos + 1) == '\'') {
 
255
                        endPos++;
 
256
                    } else {
 
257
                        break;
 
258
                    }
 
259
                }
 
260
            }
 
261
 
 
262
            final String result = string.substring(position, endPos + 1);
 
263
 
 
264
            position = endPos + 1;
 
265
            skipWhitespace();
 
266
 
 
267
            return result;
 
268
        } else {
 
269
            int endPos = position;
 
270
 
 
271
            for (; endPos < string.length(); endPos++) {
 
272
                final char chr = string.charAt(endPos);
 
273
 
 
274
                if (Character.isWhitespace(chr) || chr == ',' || chr == ')'
 
275
                        || chr == ';') {
 
276
                    break;
 
277
                }
 
278
            }
 
279
 
 
280
            if (position == endPos) {
 
281
                throw new ParserException(MessageFormat.format(
 
282
                        Resources.getString("CannotParseStringExpectedString"),
 
283
                        string, position + 1));
 
284
            }
 
285
 
 
286
            final String result = string.substring(position, endPos);
 
287
 
 
288
            position = endPos;
 
289
            skipWhitespace();
 
290
 
 
291
            return result;
 
292
        }
 
293
    }
 
294
 
 
295
    /**
 
296
     * Returns expression that is ended either with ',', ')' or with end of the
 
297
     * string. If expression is empty then exception is thrown.
 
298
     *
 
299
     * @return expression string
 
300
     */
 
301
    public String getExpression() {
 
302
        final int endPos = getExpressionEnd();
 
303
 
 
304
        if (position == endPos) {
 
305
            throw new ParserException(MessageFormat.format(
 
306
                    Resources.getString("CannotParseStringExpectedExpression"),
 
307
                    string, position + 1,
 
308
                    string.substring(position, position + 20)));
 
309
        }
 
310
 
 
311
        final String result = string.substring(position, endPos).trim();
 
312
 
 
313
        position = endPos;
 
314
 
 
315
        return result;
 
316
    }
 
317
 
 
318
    /**
 
319
     * Returns position of last character of single command within
 
320
     * statement (like CREATE TABLE). Last character is either ',' or
 
321
     * ')'. If no such character is found and method reaches the end of the
 
322
     * command then position after the last character in the command is
 
323
     * returned.
 
324
     *
 
325
     * @return end position of the command
 
326
     */
 
327
    private int getExpressionEnd() {
 
328
        int bracesCount = 0;
 
329
        boolean singleQuoteOn = false;
 
330
        int charPos = position;
 
331
 
 
332
        for (; charPos < string.length(); charPos++) {
 
333
            final char chr = string.charAt(charPos);
 
334
 
 
335
            if (chr == '(') {
 
336
                bracesCount++;
 
337
            } else if (chr == ')') {
 
338
                if (bracesCount == 0) {
 
339
                    break;
 
340
                } else {
 
341
                    bracesCount--;
 
342
                }
 
343
            } else if (chr == '\'') {
 
344
                singleQuoteOn = !singleQuoteOn;
 
345
            } else if ((chr == ',') && !singleQuoteOn && (bracesCount == 0)) {
 
346
                break;
 
347
            } else if (chr == ';' && bracesCount == 0 && !singleQuoteOn) {
 
348
                break;
 
349
            }
 
350
        }
 
351
 
 
352
        return charPos;
 
353
    }
 
354
 
 
355
    /**
 
356
     * Returns current position in the string.
 
357
     *
 
358
     * @return current position in the string
 
359
     */
 
360
    public int getPosition() {
 
361
        return position;
 
362
    }
 
363
 
 
364
    /**
 
365
     * Returns parsed string.
 
366
     *
 
367
     * @return parsed string
 
368
     */
 
369
    public String getString() {
 
370
        return string;
 
371
    }
 
372
 
 
373
    /**
 
374
     * Throws exception about unsupported command in statement.
 
375
     */
 
376
    public void throwUnsupportedCommand() {
 
377
        throw new ParserException(MessageFormat.format(
 
378
                Resources.getString("CannotParseStringUnsupportedCommand"),
 
379
                string, position + 1,
 
380
                string.substring(position, position + 20)));
 
381
    }
 
382
 
 
383
    /**
 
384
     * Checks whether one of the words is present at current position. If the
 
385
     * word is present then the word is returned and position is updated.
 
386
     *
 
387
     * @param words words to check
 
388
     *
 
389
     * @return found word or null if non of the words has been found
 
390
     *
 
391
     * @see #expectOptional(java.lang.String[])
 
392
     */
 
393
    public String expectOptionalOneOf(final String... words) {
 
394
        for (final String word : words) {
 
395
            if (expectOptional(word)) {
 
396
                return word;
 
397
            }
 
398
        }
 
399
 
 
400
        return null;
 
401
    }
 
402
 
 
403
    /**
 
404
     * Returns substring from the string.
 
405
     *
 
406
     * @param startPos start position
 
407
     * @param endPos end position exclusive
 
408
     *
 
409
     * @return substring
 
410
     */
 
411
    public String getSubString(final int startPos, final int endPos) {
 
412
        return string.substring(startPos, endPos);
 
413
    }
 
414
 
 
415
    /**
 
416
     * Changes current position in the string.
 
417
     *
 
418
     * @param position new position
 
419
     */
 
420
    public void setPosition(final int position) {
 
421
        this.position = position;
 
422
    }
 
423
 
 
424
    /**
 
425
     * Parses data type from the string. Position is updated. If data type
 
426
     * definition is not found then exception is thrown.
 
427
     *
 
428
     * @return data type string
 
429
     */
 
430
    public String parseDataType() {
 
431
        int endPos = position;
 
432
 
 
433
        while (endPos < string.length()
 
434
                && !Character.isWhitespace(string.charAt(endPos))
 
435
                && string.charAt(endPos) != '('
 
436
                && string.charAt(endPos) != ')'
 
437
                && string.charAt(endPos) != ',') {
 
438
            endPos++;
 
439
        }
 
440
 
 
441
        if (endPos == position) {
 
442
            throw new ParserException(MessageFormat.format(
 
443
                    Resources.getString("CannotParseStringExpectedDataType"),
 
444
                    string, position + 1,
 
445
                    string.substring(position, position + 20)));
 
446
        }
 
447
 
 
448
        String dataType = string.substring(position, endPos);
 
449
 
 
450
        position = endPos;
 
451
        skipWhitespace();
 
452
 
 
453
        if ("character".equalsIgnoreCase(dataType)
 
454
                && expectOptional("varying")) {
 
455
            dataType = "character varying";
 
456
        } else if ("double".equalsIgnoreCase(dataType)
 
457
                && expectOptional("precision")) {
 
458
            dataType = "double precision";
 
459
        }
 
460
 
 
461
        final boolean timestamp = "timestamp".equalsIgnoreCase(dataType)
 
462
                || "time".equalsIgnoreCase(dataType);
 
463
 
 
464
        if (string.charAt(position) == '(') {
 
465
            dataType += getExpression();
 
466
        }
 
467
 
 
468
        if (timestamp) {
 
469
            if (expectOptional("with", "time", "zone")) {
 
470
                dataType += " with time zone";
 
471
            } else if (expectOptional("without", "time", "zone")) {
 
472
                dataType += " without time zone";
 
473
            }
 
474
        }
 
475
 
 
476
        if (expectOptional("[")) {
 
477
            expect("]");
 
478
            dataType += "[]";
 
479
        }
 
480
 
 
481
        return dataType;
 
482
    }
 
483
 
 
484
    /**
 
485
     * Checks whether the whole string has been consumed.
 
486
     * 
 
487
     * @return true if there is nothing left to parse, otherwise false
 
488
     */
 
489
    public boolean isConsumed() {
 
490
        return position == string.length()
 
491
                || position + 1 == string.length()
 
492
                && string.charAt(position) == ';';
 
493
    }
 
494
}