1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.graph.query.parse;
25
26 import static org.modeshape.common.text.TokenStream.ANY_VALUE;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import org.modeshape.common.CommonI18n;
33 import org.modeshape.common.text.ParsingException;
34 import org.modeshape.common.text.Position;
35 import org.modeshape.common.text.TokenStream;
36 import org.modeshape.common.text.TokenStream.CharacterStream;
37 import org.modeshape.common.text.TokenStream.Tokenizer;
38 import org.modeshape.common.text.TokenStream.Tokens;
39 import org.modeshape.common.xml.XmlCharacters;
40 import org.modeshape.graph.GraphI18n;
41 import org.modeshape.graph.property.ValueFormatException;
42 import org.modeshape.graph.query.model.And;
43 import org.modeshape.graph.query.model.ArithmeticOperand;
44 import org.modeshape.graph.query.model.ArithmeticOperator;
45 import org.modeshape.graph.query.model.Between;
46 import org.modeshape.graph.query.model.BindVariableName;
47 import org.modeshape.graph.query.model.ChildNode;
48 import org.modeshape.graph.query.model.ChildNodeJoinCondition;
49 import org.modeshape.graph.query.model.Column;
50 import org.modeshape.graph.query.model.Comparison;
51 import org.modeshape.graph.query.model.Constraint;
52 import org.modeshape.graph.query.model.DescendantNode;
53 import org.modeshape.graph.query.model.DescendantNodeJoinCondition;
54 import org.modeshape.graph.query.model.DynamicOperand;
55 import org.modeshape.graph.query.model.EquiJoinCondition;
56 import org.modeshape.graph.query.model.FullTextSearch;
57 import org.modeshape.graph.query.model.FullTextSearchScore;
58 import org.modeshape.graph.query.model.Join;
59 import org.modeshape.graph.query.model.JoinCondition;
60 import org.modeshape.graph.query.model.JoinType;
61 import org.modeshape.graph.query.model.Length;
62 import org.modeshape.graph.query.model.Limit;
63 import org.modeshape.graph.query.model.Literal;
64 import org.modeshape.graph.query.model.LowerCase;
65 import org.modeshape.graph.query.model.NamedSelector;
66 import org.modeshape.graph.query.model.NodeDepth;
67 import org.modeshape.graph.query.model.NodeLocalName;
68 import org.modeshape.graph.query.model.NodeName;
69 import org.modeshape.graph.query.model.NodePath;
70 import org.modeshape.graph.query.model.Not;
71 import org.modeshape.graph.query.model.Operator;
72 import org.modeshape.graph.query.model.Or;
73 import org.modeshape.graph.query.model.Order;
74 import org.modeshape.graph.query.model.Ordering;
75 import org.modeshape.graph.query.model.PropertyExistence;
76 import org.modeshape.graph.query.model.PropertyValue;
77 import org.modeshape.graph.query.model.Query;
78 import org.modeshape.graph.query.model.QueryCommand;
79 import org.modeshape.graph.query.model.ReferenceValue;
80 import org.modeshape.graph.query.model.SameNode;
81 import org.modeshape.graph.query.model.SameNodeJoinCondition;
82 import org.modeshape.graph.query.model.Selector;
83 import org.modeshape.graph.query.model.SelectorName;
84 import org.modeshape.graph.query.model.SetCriteria;
85 import org.modeshape.graph.query.model.SetQuery;
86 import org.modeshape.graph.query.model.Source;
87 import org.modeshape.graph.query.model.StaticOperand;
88 import org.modeshape.graph.query.model.Subquery;
89 import org.modeshape.graph.query.model.TypeSystem;
90 import org.modeshape.graph.query.model.UpperCase;
91 import org.modeshape.graph.query.model.FullTextSearch.Term;
92 import org.modeshape.graph.query.model.SetQuery.Operation;
93 import org.modeshape.graph.query.model.TypeSystem.TypeFactory;
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456 public class SqlQueryParser implements QueryParser {
457
458 public static final String LANGUAGE = "SQL";
459
460
461
462
463
464
465 public String getLanguage() {
466 return LANGUAGE;
467 }
468
469
470
471
472
473
474 @Override
475 public String toString() {
476 return getLanguage();
477 }
478
479
480
481
482
483
484 @Override
485 public boolean equals( Object obj ) {
486 if (obj == this) return true;
487 if (obj instanceof QueryParser) {
488 QueryParser that = (QueryParser)obj;
489 return this.getLanguage().equals(that.getLanguage());
490 }
491 return false;
492 }
493
494
495
496
497
498
499 public QueryCommand parseQuery( String query,
500 TypeSystem typeSystem ) {
501 Tokenizer tokenizer = new SqlTokenizer(false);
502 TokenStream tokens = new TokenStream(query, tokenizer, false);
503 tokens.start();
504 return parseQueryCommand(tokens, typeSystem);
505 }
506
507 protected QueryCommand parseQueryCommand( TokenStream tokens,
508 TypeSystem typeSystem ) {
509 QueryCommand command = null;
510 if (tokens.matches("SELECT")) {
511 command = parseQuery(tokens, typeSystem);
512 while (tokens.hasNext()) {
513 if (tokens.matchesAnyOf("UNION", "INTERSECT", "EXCEPT")) {
514 command = parseSetQuery(tokens, command, typeSystem);
515 } else if (tokens.matches(')')) {
516
517 break;
518 } else {
519 Position pos = tokens.previousPosition();
520 String msg = GraphI18n.unexpectedToken.text(tokens.consume(), pos.getLine(), pos.getColumn());
521 throw new ParsingException(pos, msg);
522 }
523 }
524 } else {
525
526 Position pos = tokens.nextPosition();
527 String msg = GraphI18n.unexpectedToken.text(tokens.consume(), pos.getLine(), pos.getColumn());
528 throw new ParsingException(pos, msg);
529 }
530 return command;
531 }
532
533 protected Query parseQuery( TokenStream tokens,
534 TypeSystem typeSystem ) {
535 AtomicBoolean isDistinct = new AtomicBoolean(false);
536 List<ColumnExpression> columnExpressions = parseSelect(tokens, isDistinct, typeSystem);
537 Source source = parseFrom(tokens, typeSystem);
538 Constraint constraint = parseWhere(tokens, typeSystem, source);
539
540 List<? extends Ordering> orderings = parseOrderBy(tokens, typeSystem, source);
541 Limit limit = parseLimit(tokens);
542 if (orderings == null) parseOrderBy(tokens, typeSystem, source);
543
544
545 List<Column> columns = new ArrayList<Column>(columnExpressions.size());
546 for (ColumnExpression expression : columnExpressions) {
547 SelectorName selectorName = expression.getSelectorName();
548 String propertyName = expression.getPropertyName();
549 if (selectorName == null) {
550 if (source instanceof Selector) {
551 selectorName = ((Selector)source).aliasOrName();
552 } else {
553 Position pos = expression.getPosition();
554 String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(expression, pos.getLine(), pos.getColumn());
555 throw new ParsingException(pos, msg);
556 }
557 }
558 columns.add(column(selectorName, propertyName, expression.getColumnName()));
559 }
560
561 return query(source, constraint, orderings, columns, limit, isDistinct.get());
562 }
563
564 protected SetQuery parseSetQuery( TokenStream tokens,
565 QueryCommand leftHandSide,
566 TypeSystem typeSystem ) {
567 Operation operation = null;
568 if (tokens.canConsume("UNION")) {
569 operation = Operation.UNION;
570 } else if (tokens.canConsume("INTERSECT")) {
571 operation = Operation.INTERSECT;
572 } else {
573 tokens.consume("EXCEPT");
574 operation = Operation.EXCEPT;
575 }
576 boolean all = tokens.canConsume("ALL");
577
578 QueryCommand rightQuery = parseQuery(tokens, typeSystem);
579 return setQuery(leftHandSide, operation, rightQuery, all);
580 }
581
582 protected List<ColumnExpression> parseSelect( TokenStream tokens,
583 AtomicBoolean isDistinct,
584 TypeSystem typeSystem ) {
585 tokens.consume("SELECT");
586 if (tokens.canConsume("DISTINCT")) isDistinct.set(true);
587 if (tokens.canConsume('*')) {
588 return Collections.emptyList();
589 }
590 List<ColumnExpression> columns = new ArrayList<ColumnExpression>();
591 do {
592 Position position = tokens.nextPosition();
593 String propertyName = parseName(tokens, typeSystem);
594 SelectorName selectorName = null;
595 if (tokens.canConsume('.')) {
596
597 selectorName = new SelectorName(propertyName);
598 propertyName = parseName(tokens, typeSystem);
599 }
600 String alias = propertyName;
601 if (tokens.canConsume("AS")) alias = parseName(tokens, typeSystem);
602 columns.add(new ColumnExpression(selectorName, propertyName, alias, position));
603 } while (tokens.canConsume(','));
604 return columns;
605 }
606
607 protected Source parseFrom( TokenStream tokens,
608 TypeSystem typeSystem ) {
609 Source source = null;
610 tokens.consume("FROM");
611 source = parseNamedSelector(tokens, typeSystem);
612 while (tokens.hasNext()) {
613 JoinType joinType = null;
614 if (tokens.canConsume("JOIN") || tokens.canConsume("INNER", "JOIN")) {
615 joinType = JoinType.INNER;
616 } else if (tokens.canConsume("OUTER", "JOIN") || tokens.canConsume("LEFT", "JOIN")
617 || tokens.canConsume("LEFT", "OUTER", "JOIN")) {
618 joinType = JoinType.LEFT_OUTER;
619 } else if (tokens.canConsume("RIGHT", "OUTER", "JOIN") || tokens.canConsume("RIGHT", "OUTER")) {
620 joinType = JoinType.RIGHT_OUTER;
621 } else if (tokens.canConsume("FULL", "OUTER", "JOIN") || tokens.canConsume("FULL", "OUTER")) {
622 joinType = JoinType.FULL_OUTER;
623 } else if (tokens.canConsume("CROSS", "JOIN") || tokens.canConsume("CROSS")) {
624 joinType = JoinType.CROSS;
625 }
626 if (joinType == null) break;
627
628 NamedSelector right = parseNamedSelector(tokens, typeSystem);
629
630 JoinCondition joinCondition = parseJoinCondition(tokens, typeSystem);
631
632 source = join(source, joinType, right, joinCondition);
633 }
634 return source;
635 }
636
637 protected JoinCondition parseJoinCondition( TokenStream tokens,
638 TypeSystem typeSystem ) {
639 tokens.consume("ON");
640 if (tokens.canConsume("ISSAMENODE", "(")) {
641 SelectorName selector1Name = parseSelectorName(tokens, typeSystem);
642 tokens.consume(',');
643 SelectorName selector2Name = parseSelectorName(tokens, typeSystem);
644 if (tokens.canConsume('.')) {
645 String path = parsePath(tokens, typeSystem);
646 tokens.consume(')');
647 return sameNodeJoinCondition(selector1Name, selector2Name, path);
648 }
649 tokens.consume(')');
650 return sameNodeJoinCondition(selector1Name, selector2Name);
651 }
652 if (tokens.canConsume("ISCHILDNODE", "(")) {
653 SelectorName child = parseSelectorName(tokens, typeSystem);
654 tokens.consume(',');
655 SelectorName parent = parseSelectorName(tokens, typeSystem);
656 tokens.consume(')');
657 return childNodeJoinCondition(parent, child);
658 }
659 if (tokens.canConsume("ISDESCENDANTNODE", "(")) {
660 SelectorName descendant = parseSelectorName(tokens, typeSystem);
661 tokens.consume(',');
662 SelectorName ancestor = parseSelectorName(tokens, typeSystem);
663 tokens.consume(')');
664 return descendantNodeJoinCondition(ancestor, descendant);
665 }
666 SelectorName selector1 = parseSelectorName(tokens, typeSystem);
667 tokens.consume('.');
668 String property1 = parseName(tokens, typeSystem);
669 tokens.consume('=');
670 SelectorName selector2 = parseSelectorName(tokens, typeSystem);
671 tokens.consume('.');
672 String property2 = parseName(tokens, typeSystem);
673 return equiJoinCondition(selector1, property1, selector2, property2);
674 }
675
676 protected Constraint parseWhere( TokenStream tokens,
677 TypeSystem typeSystem,
678 Source source ) {
679 if (tokens.canConsume("WHERE")) {
680 return parseConstraint(tokens, typeSystem, source);
681 }
682 return null;
683 }
684
685 protected Constraint parseConstraint( TokenStream tokens,
686 TypeSystem typeSystem,
687 Source source ) {
688 Constraint constraint = null;
689 Position pos = tokens.nextPosition();
690 if (tokens.canConsume("(")) {
691 constraint = parseConstraint(tokens, typeSystem, source);
692 tokens.consume(")");
693 } else if (tokens.canConsume("NOT")) {
694 tokens.canConsume('(');
695 constraint = not(parseConstraint(tokens, typeSystem, source));
696 tokens.canConsume(')');
697 } else if (tokens.canConsume("CONTAINS", "(")) {
698
699 String first = tokens.consume();
700 SelectorName selectorName = null;
701 String propertyName = null;
702 if (tokens.canConsume(".", "*")) {
703 selectorName = new SelectorName(removeBracketsAndQuotes(first));
704 } else if (tokens.canConsume('.')) {
705 selectorName = new SelectorName(removeBracketsAndQuotes(first));
706 propertyName = parseName(tokens, typeSystem);
707 } else {
708 if (!(source instanceof Selector)) {
709 String msg = GraphI18n.functionIsAmbiguous.text("CONTAINS()", pos.getLine(), pos.getColumn());
710 throw new ParsingException(pos, msg);
711 }
712 selectorName = ((Selector)source).name();
713 propertyName = removeBracketsAndQuotes(first);
714 }
715 tokens.consume(',');
716
717
718 String expression = removeBracketsAndQuotes(tokens.consume(), false);
719 Term term = parseFullTextSearchExpression(expression, tokens.previousPosition());
720 tokens.consume(")");
721 constraint = fullTextSearch(selectorName, propertyName, expression, term);
722 } else if (tokens.canConsume("ISSAMENODE", "(")) {
723 SelectorName selectorName = null;
724 if (tokens.matches(ANY_VALUE, ")")) {
725 if (!(source instanceof Selector)) {
726 String msg = GraphI18n.functionIsAmbiguous.text("ISSAMENODE()", pos.getLine(), pos.getColumn());
727 throw new ParsingException(pos, msg);
728 }
729 selectorName = ((Selector)source).name();
730 } else {
731 selectorName = parseSelectorName(tokens, typeSystem);
732 tokens.consume(',');
733 }
734 String path = parsePath(tokens, typeSystem);
735 tokens.consume(')');
736 constraint = sameNode(selectorName, path);
737 } else if (tokens.canConsume("ISCHILDNODE", "(")) {
738 SelectorName selectorName = null;
739 if (tokens.matches(ANY_VALUE, ")")) {
740 if (!(source instanceof Selector)) {
741 String msg = GraphI18n.functionIsAmbiguous.text("ISCHILDNODE()", pos.getLine(), pos.getColumn());
742 throw new ParsingException(pos, msg);
743 }
744 selectorName = ((Selector)source).name();
745 } else {
746 selectorName = parseSelectorName(tokens, typeSystem);
747 tokens.consume(',');
748 }
749 String path = parsePath(tokens, typeSystem);
750 tokens.consume(')');
751 constraint = childNode(selectorName, path);
752 } else if (tokens.canConsume("ISDESCENDANTNODE", "(")) {
753 SelectorName selectorName = null;
754 if (tokens.matches(ANY_VALUE, ")")) {
755 if (!(source instanceof Selector)) {
756 String msg = GraphI18n.functionIsAmbiguous.text("ISDESCENDANTNODE()", pos.getLine(), pos.getColumn());
757 throw new ParsingException(pos, msg);
758 }
759 selectorName = ((Selector)source).name();
760 } else {
761 selectorName = parseSelectorName(tokens, typeSystem);
762 tokens.consume(',');
763 }
764 String path = parsePath(tokens, typeSystem);
765 tokens.consume(')');
766 constraint = descendantNode(selectorName, path);
767 } else {
768
769 Position pos2 = tokens.nextPosition();
770 constraint = parsePropertyExistance(tokens, typeSystem, source);
771 if (constraint == null) {
772
773 DynamicOperand left = parseDynamicOperand(tokens, typeSystem, source);
774 if (left != null) {
775 if (tokens.matches('(') && left instanceof PropertyValue) {
776
777 String name = ((PropertyValue)left).propertyName();
778 String msg = GraphI18n.expectingConstraintCondition.text(name, pos2.getLine(), pos2.getColumn());
779 throw new ParsingException(pos, msg);
780 }
781 if (tokens.matches("IN", "(") || tokens.matches("NOT", "IN", "(")) {
782 boolean not = tokens.canConsume("NOT");
783 Collection<StaticOperand> staticOperands = parseInClause(tokens, typeSystem);
784 constraint = setCriteria(left, staticOperands);
785 if (not) constraint = not(constraint);
786 } else if (tokens.matches("BETWEEN") || tokens.matches("NOT", "BETWEEN")) {
787 boolean not = tokens.canConsume("NOT");
788 tokens.consume("BETWEEN");
789 StaticOperand lowerBound = parseStaticOperand(tokens, typeSystem);
790 boolean lowerInclusive = !tokens.canConsume("EXCLUSIVE");
791 tokens.consume("AND");
792 StaticOperand upperBound = parseStaticOperand(tokens, typeSystem);
793 boolean upperInclusive = !tokens.canConsume("EXCLUSIVE");
794 constraint = between(left, lowerBound, upperBound, lowerInclusive, upperInclusive);
795 if (not) constraint = not(constraint);
796 } else {
797 Operator operator = parseComparisonOperator(tokens);
798 StaticOperand right = parseStaticOperand(tokens, typeSystem);
799 constraint = comparison(left, operator, right);
800 }
801 }
802
803 }
804 }
805 if (constraint == null) {
806 String msg = GraphI18n.expectingConstraintCondition.text(tokens.consume(), pos.getLine(), pos.getColumn());
807 throw new ParsingException(pos, msg);
808 }
809
810 while (tokens.canConsume("AND")) {
811 Constraint rhs = parseConstraint(tokens, typeSystem, source);
812 if (rhs != null) constraint = and(constraint, rhs);
813 }
814 while (tokens.canConsume("OR")) {
815 Constraint rhs = parseConstraint(tokens, typeSystem, source);
816 if (rhs != null) constraint = or(constraint, rhs);
817 }
818 return constraint;
819 }
820
821 protected List<StaticOperand> parseInClause( TokenStream tokens,
822 TypeSystem typeSystem ) {
823 List<StaticOperand> result = new ArrayList<StaticOperand>();
824 tokens.consume("IN");
825 tokens.consume("(");
826 if (!tokens.canConsume(")")) {
827
828 do {
829 result.add(parseStaticOperand(tokens, typeSystem));
830 } while (tokens.canConsume(','));
831 tokens.consume(")");
832 }
833 return result;
834 }
835
836 protected Term parseFullTextSearchExpression( String expression,
837 Position startOfExpression ) {
838 try {
839 return new FullTextSearchParser().parse(expression);
840 } catch (ParsingException e) {
841
842 Position queryPos = startOfExpression.add(e.getPosition());
843 throw new ParsingException(queryPos, e.getMessage());
844 }
845 }
846
847 protected Operator parseComparisonOperator( TokenStream tokens ) {
848 if (tokens.canConsume("=")) return Operator.EQUAL_TO;
849 if (tokens.canConsume("LIKE")) return Operator.LIKE;
850 if (tokens.canConsume("!", "=")) return Operator.NOT_EQUAL_TO;
851 if (tokens.canConsume("<", ">")) return Operator.NOT_EQUAL_TO;
852 if (tokens.canConsume("<", "=")) return Operator.LESS_THAN_OR_EQUAL_TO;
853 if (tokens.canConsume(">", "=")) return Operator.GREATER_THAN_OR_EQUAL_TO;
854 if (tokens.canConsume("<")) return Operator.LESS_THAN;
855 if (tokens.canConsume(">")) return Operator.GREATER_THAN;
856 Position pos = tokens.nextPosition();
857 String msg = GraphI18n.expectingComparisonOperator.text(tokens.consume(), pos.getLine(), pos.getColumn());
858 throw new ParsingException(pos, msg);
859 }
860
861 protected List<Ordering> parseOrderBy( TokenStream tokens,
862 TypeSystem typeSystem,
863 Source source ) {
864 if (tokens.canConsume("ORDER", "BY")) {
865 List<Ordering> orderings = new ArrayList<Ordering>();
866 do {
867 orderings.add(parseOrdering(tokens, typeSystem, source));
868 } while (tokens.canConsume(','));
869 return orderings;
870 }
871 return null;
872 }
873
874 protected Ordering parseOrdering( TokenStream tokens,
875 TypeSystem typeSystem,
876 Source source ) {
877 DynamicOperand operand = parseDynamicOperand(tokens, typeSystem, source);
878 Order order = Order.ASCENDING;
879 if (tokens.canConsume("DESC")) order = Order.DESCENDING;
880 if (tokens.canConsume("ASC")) order = Order.ASCENDING;
881 return ordering(operand, order);
882 }
883
884 protected Constraint parsePropertyExistance( TokenStream tokens,
885 TypeSystem typeSystem,
886 Source source ) {
887 if (tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS", "NOT", "NULL")
888 || tokens.matches(ANY_VALUE, ".", ANY_VALUE, "IS", "NULL") || tokens.matches(ANY_VALUE, "IS", "NOT", "NULL")
889 || tokens.matches(ANY_VALUE, "IS", "NULL")) {
890 Position pos = tokens.nextPosition();
891 String firstWord = tokens.consume();
892 SelectorName selectorName = null;
893 String propertyName = null;
894 if (tokens.canConsume('.')) {
895
896 selectorName = new SelectorName(firstWord);
897 propertyName = parseName(tokens, typeSystem);
898 } else {
899
900 if (!(source instanceof Selector)) {
901 String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(firstWord, pos.getLine(), pos.getColumn());
902 throw new ParsingException(pos, msg);
903 }
904 selectorName = ((Selector)source).name();
905 propertyName = parseName(firstWord, typeSystem);
906 }
907 if (tokens.canConsume("IS", "NOT", "NULL")) {
908 return propertyExistence(selectorName, propertyName);
909 }
910 tokens.consume("IS", "NULL");
911 return not(propertyExistence(selectorName, propertyName));
912 }
913 return null;
914 }
915
916 protected StaticOperand parseStaticOperand( TokenStream tokens,
917 TypeSystem typeSystem ) {
918 if (tokens.canConsume('$')) {
919
920 String value = tokens.consume();
921 if (!XmlCharacters.isValidNcName(value)) {
922 Position pos = tokens.previousPosition();
923 String msg = GraphI18n.bindVariableMustConformToNcName.text(value, pos.getLine(), pos.getColumn());
924 throw new ParsingException(pos, msg);
925 }
926 return bindVariableName(value);
927 }
928 if (tokens.canConsume('(')) {
929
930 StaticOperand result = parseStaticOperand(tokens, typeSystem);
931 tokens.consume(')');
932 return result;
933 }
934 if (tokens.matches("SELECT")) {
935
936 QueryCommand subqueryExpression = parseQueryCommand(tokens, typeSystem);
937 return subquery(subqueryExpression);
938 }
939 return parseLiteral(tokens, typeSystem);
940 }
941
942 protected Subquery subquery( QueryCommand queryCommand ) {
943 return new Subquery(queryCommand);
944 }
945
946 protected Literal parseLiteral( TokenStream tokens,
947 TypeSystem typeSystem ) {
948 if (tokens.canConsume("CAST", "(")) {
949
950 Position pos = tokens.nextPosition();
951 Object value = parseLiteralValue(tokens, typeSystem);
952
953 tokens.consume("AS");
954 String typeName = tokens.consume();
955 TypeFactory<?> typeFactory = typeSystem.getTypeFactory(typeName);
956 if (typeFactory == null) {
957 Position typePos = tokens.previousPosition();
958 String msg = GraphI18n.invalidPropertyType.text(tokens.consume(), typePos.getLine(), typePos.getColumn());
959 throw new ParsingException(typePos, msg);
960 }
961
962 tokens.consume(')');
963 try {
964 Object literal = typeFactory.create(value);
965 return literal(typeSystem, literal);
966 } catch (ValueFormatException e) {
967 String msg = GraphI18n.valueCannotBeCastToSpecifiedType.text(value,
968 pos.getLine(),
969 pos.getColumn(),
970 typeFactory.getTypeName(),
971 e.getMessage());
972 throw new ParsingException(pos, msg);
973 }
974 }
975
976 return literal(typeSystem, parseLiteralValue(tokens, typeSystem));
977 }
978
979 protected Object parseLiteralValue( TokenStream tokens,
980 TypeSystem typeSystem ) {
981 if (tokens.matches(SqlTokenizer.QUOTED_STRING)) {
982 return removeBracketsAndQuotes(tokens.consume());
983 }
984 TypeFactory<Boolean> booleanFactory = typeSystem.getBooleanFactory();
985 if (booleanFactory != null) {
986 if (tokens.canConsume("TRUE")) return booleanFactory.asString(Boolean.TRUE);
987 if (tokens.canConsume("FALSE")) return booleanFactory.asString(Boolean.FALSE);
988 }
989
990
991 Position pos = tokens.nextPosition();
992 String sign = "";
993 if (tokens.canConsume('-')) sign = "-";
994 else if (tokens.canConsume('+')) sign = "";
995
996
997 String integral = tokens.consume();
998 TypeFactory<Double> doubleFactory = typeSystem.getDoubleFactory();
999 if (doubleFactory != null) {
1000 String decimal = null;
1001 if (tokens.canConsume('.')) {
1002 decimal = tokens.consume();
1003 String value = sign + integral + "." + decimal;
1004 if (decimal.endsWith("e") && (tokens.matches('+') || tokens.matches('-'))) {
1005
1006 value = value + tokens.consume() + tokens.consume();
1007 }
1008 try {
1009
1010 return doubleFactory.asString(doubleFactory.create(value));
1011 } catch (ValueFormatException e) {
1012 String msg = GraphI18n.expectingLiteralAndUnableToParseAsDouble.text(value, pos.getLine(), pos.getColumn());
1013 throw new ParsingException(pos, msg);
1014 }
1015 }
1016 }
1017 TypeFactory<?> dateTimeFactory = typeSystem.getDateTimeFactory();
1018 if (dateTimeFactory != null) {
1019 if (tokens.canConsume('-')) {
1020
1021
1022 String year = integral;
1023 String month = tokens.consume();
1024 tokens.consume('-');
1025 String dateAndHour = tokens.consume();
1026 tokens.consume(':');
1027 String minutes = tokens.consume();
1028 tokens.consume(':');
1029 String seconds = tokens.consume();
1030 tokens.consume('.');
1031 String subSeconds = tokens.consume();
1032
1033 String tzSign = "+";
1034 String tzHours = "00";
1035 String tzMinutes = "00";
1036 String tzDelim = ":";
1037 if (tokens.canConsume('+')) {
1038
1039 tzHours = tokens.consume();
1040 if (tokens.canConsume(':')) tzMinutes = tokens.consume();
1041 } else if (tokens.canConsume('-')) {
1042
1043 tzSign = "-";
1044 tzHours = tokens.consume();
1045 if (tokens.canConsume(':')) tzMinutes = tokens.consume();
1046 } else if (tokens.canConsume(':')) {
1047
1048 tzHours = tzSign = "";
1049 if (tokens.canConsume(':')) tzMinutes = tokens.consume();
1050 } else if (subSeconds.endsWith("Z")) {
1051 tzSign = tzMinutes = tzDelim = tzHours = "";
1052 } else if (subSeconds.endsWith("UTC")) {
1053 subSeconds = subSeconds.length() > 3 ? subSeconds.substring(0, subSeconds.length() - 3) : subSeconds;
1054 }
1055 String value = sign + year + "-" + month + "-" + dateAndHour + ":" + minutes + ":" + seconds + "." + subSeconds
1056 + tzSign + tzHours + tzDelim + tzMinutes;
1057 try {
1058
1059 Object dateTime = dateTimeFactory.create(value);
1060 return dateTimeFactory.asString(dateTime);
1061 } catch (ValueFormatException e) {
1062 String msg = GraphI18n.expectingLiteralAndUnableToParseAsDate.text(value, pos.getLine(), pos.getColumn());
1063 throw new ParsingException(pos, msg);
1064 }
1065 }
1066 }
1067 TypeFactory<Long> longFactory = typeSystem.getLongFactory();
1068
1069 String value = sign + integral;
1070 try {
1071
1072 return longFactory.asString(longFactory.create(value));
1073 } catch (ValueFormatException e) {
1074 String msg = GraphI18n.expectingLiteralAndUnableToParseAsLong.text(value, pos.getLine(), pos.getColumn());
1075 throw new ParsingException(pos, msg);
1076 }
1077 }
1078
1079 protected DynamicOperand parseDynamicOperand( TokenStream tokens,
1080 TypeSystem typeSystem,
1081 Source source ) {
1082 DynamicOperand result = null;
1083 Position pos = tokens.nextPosition();
1084 if (tokens.canConsume('(')) {
1085 result = parseDynamicOperand(tokens, typeSystem, source);
1086 tokens.consume(")");
1087 } else if (tokens.canConsume("LENGTH", "(")) {
1088 result = length(parsePropertyValue(tokens, typeSystem, source));
1089 tokens.consume(")");
1090 } else if (tokens.canConsume("LOWER", "(")) {
1091 result = lowerCase(parseDynamicOperand(tokens, typeSystem, source));
1092 tokens.consume(")");
1093 } else if (tokens.canConsume("UPPER", "(")) {
1094 result = upperCase(parseDynamicOperand(tokens, typeSystem, source));
1095 tokens.consume(")");
1096 } else if (tokens.canConsume("NAME", "(")) {
1097 if (tokens.canConsume(")")) {
1098 if (source instanceof Selector) {
1099 return nodeName(((Selector)source).name());
1100 }
1101 String msg = GraphI18n.functionIsAmbiguous.text("NAME()", pos.getLine(), pos.getColumn());
1102 throw new ParsingException(pos, msg);
1103 }
1104 result = nodeName(parseSelectorName(tokens, typeSystem));
1105 tokens.consume(")");
1106 } else if (tokens.canConsume("LOCALNAME", "(")) {
1107 if (tokens.canConsume(")")) {
1108 if (source instanceof Selector) {
1109 return nodeLocalName(((Selector)source).name());
1110 }
1111 String msg = GraphI18n.functionIsAmbiguous.text("LOCALNAME()", pos.getLine(), pos.getColumn());
1112 throw new ParsingException(pos, msg);
1113 }
1114 result = nodeLocalName(parseSelectorName(tokens, typeSystem));
1115 tokens.consume(")");
1116 } else if (tokens.canConsume("SCORE", "(")) {
1117 if (tokens.canConsume(")")) {
1118 if (source instanceof Selector) {
1119 return fullTextSearchScore(((Selector)source).name());
1120 }
1121 String msg = GraphI18n.functionIsAmbiguous.text("SCORE()", pos.getLine(), pos.getColumn());
1122 throw new ParsingException(pos, msg);
1123 }
1124 result = fullTextSearchScore(parseSelectorName(tokens, typeSystem));
1125 tokens.consume(")");
1126 } else if (tokens.canConsume("DEPTH", "(")) {
1127 if (tokens.canConsume(")")) {
1128 if (source instanceof Selector) {
1129 return nodeDepth(((Selector)source).name());
1130 }
1131 String msg = GraphI18n.functionIsAmbiguous.text("DEPTH()", pos.getLine(), pos.getColumn());
1132 throw new ParsingException(pos, msg);
1133 }
1134 result = nodeDepth(parseSelectorName(tokens, typeSystem));
1135 tokens.consume(")");
1136 } else if (tokens.canConsume("PATH", "(")) {
1137 if (tokens.canConsume(")")) {
1138 if (source instanceof Selector) {
1139 return nodePath(((Selector)source).name());
1140 }
1141 String msg = GraphI18n.functionIsAmbiguous.text("PATH()", pos.getLine(), pos.getColumn());
1142 throw new ParsingException(pos, msg);
1143 }
1144 result = nodePath(parseSelectorName(tokens, typeSystem));
1145 tokens.consume(")");
1146 } else if (tokens.canConsume("REFERENCE", "(")) {
1147 result = parseReferenceValue(tokens, typeSystem, source);
1148 } else {
1149 result = parsePropertyValue(tokens, typeSystem, source);
1150 }
1151
1152
1153 ArithmeticOperator arithmeticOperator = null;
1154 if (tokens.canConsume('+')) {
1155 arithmeticOperator = ArithmeticOperator.ADD;
1156 } else if (tokens.canConsume('-')) {
1157 arithmeticOperator = ArithmeticOperator.SUBTRACT;
1158 } else if (tokens.canConsume('*')) {
1159 arithmeticOperator = ArithmeticOperator.MULTIPLY;
1160 } else if (tokens.canConsume('/')) {
1161 arithmeticOperator = ArithmeticOperator.DIVIDE;
1162 }
1163 if (arithmeticOperator != null) {
1164 if (tokens.matches('(')) {
1165
1166 DynamicOperand right = parseDynamicOperand(tokens, typeSystem, source);
1167 result = arithmeticOperand(result, arithmeticOperator, right);
1168 } else {
1169
1170 DynamicOperand right = parseDynamicOperand(tokens, typeSystem, source);
1171 if (right instanceof ArithmeticOperand) {
1172
1173 ArithmeticOperand arithRhs = (ArithmeticOperand)right;
1174 ArithmeticOperator rhsOperator = arithRhs.operator();
1175 if (arithmeticOperator.precedes(rhsOperator)) {
1176
1177 DynamicOperand newRhs = arithRhs.right();
1178 DynamicOperand newLhs = new ArithmeticOperand(result, arithmeticOperator, arithRhs.left());
1179 result = arithmeticOperand(newLhs, rhsOperator, newRhs);
1180 } else {
1181 result = arithmeticOperand(result, arithmeticOperator, right);
1182 }
1183 } else {
1184
1185 result = arithmeticOperand(result, arithmeticOperator, right);
1186 }
1187 }
1188 }
1189 return result;
1190 }
1191
1192 protected PropertyValue parsePropertyValue( TokenStream tokens,
1193 TypeSystem typeSystem,
1194 Source source ) {
1195 Position pos = tokens.nextPosition();
1196 String firstWord = parseName(tokens, typeSystem);
1197 SelectorName selectorName = null;
1198 if (tokens.canConsume('.')) {
1199
1200 selectorName = new SelectorName(firstWord);
1201 String propertyName = parseName(tokens, typeSystem);
1202 return propertyValue(selectorName, propertyName);
1203 }
1204
1205 if (source instanceof Selector) {
1206 selectorName = ((Selector)source).aliasOrName();
1207 return propertyValue(selectorName, firstWord);
1208 }
1209 String msg = GraphI18n.mustBeScopedAtLineAndColumn.text(firstWord, pos.getLine(), pos.getColumn());
1210 throw new ParsingException(pos, msg);
1211 }
1212
1213 protected ReferenceValue parseReferenceValue( TokenStream tokens,
1214 TypeSystem typeSystem,
1215 Source source ) {
1216 Position pos = tokens.nextPosition();
1217 SelectorName selectorName = null;
1218 if (tokens.canConsume(')')) {
1219
1220 if (source instanceof Selector) {
1221 selectorName = ((Selector)source).aliasOrName();
1222 return referenceValue(selectorName);
1223 }
1224 String msg = GraphI18n.functionIsAmbiguous.text("REFERENCE()", pos.getLine(), pos.getColumn());
1225 throw new ParsingException(pos, msg);
1226 }
1227
1228 String firstWord = parseName(tokens, typeSystem);
1229 if (tokens.canConsume('.')) {
1230
1231 selectorName = new SelectorName(firstWord);
1232 String propertyName = parseName(tokens, typeSystem);
1233 return referenceValue(selectorName, propertyName);
1234 }
1235 tokens.consume(")");
1236
1237
1238 if (source instanceof Selector) {
1239 Selector selector = (Selector)source;
1240
1241 selectorName = new SelectorName(firstWord);
1242 if (selectorName.equals(selector.name()) || (selector.hasAlias() && selectorName.equals(selector.alias()))) {
1243
1244 return referenceValue(selectorName);
1245 }
1246
1247 return referenceValue(selector.aliasOrName(), firstWord);
1248 }
1249
1250 selectorName = new SelectorName(firstWord);
1251 return referenceValue(selectorName);
1252 }
1253
1254 protected Limit parseLimit( TokenStream tokens ) {
1255 if (tokens.canConsume("LIMIT")) {
1256 int first = tokens.consumeInteger();
1257 if (tokens.canConsume(',')) {
1258
1259 int to = tokens.consumeInteger();
1260 int offset = to - first;
1261 if (offset < 0) {
1262 Position pos = tokens.previousPosition();
1263 String msg = GraphI18n.secondValueInLimitRangeCannotBeLessThanFirst.text(first,
1264 to,
1265 pos.getLine(),
1266 pos.getColumn());
1267 throw new ParsingException(pos, msg);
1268 }
1269 return limit(offset, first);
1270 }
1271 if (tokens.canConsume("OFFSET")) {
1272 int offset = tokens.consumeInteger();
1273 return limit(first, offset);
1274 }
1275
1276 return limit(first, 0);
1277 }
1278 return null;
1279 }
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289 protected String removeBracketsAndQuotes( String text ) {
1290 return removeBracketsAndQuotes(text, true);
1291 }
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302 protected String removeBracketsAndQuotes( String text,
1303 boolean recursive ) {
1304 if (text.length() > 0) {
1305 char firstChar = text.charAt(0);
1306 switch (firstChar) {
1307 case '\'':
1308 case '"':
1309 assert text.charAt(text.length() - 1) == firstChar;
1310 String removed = text.substring(1, text.length() - 1);
1311 return recursive ? removeBracketsAndQuotes(removed, recursive) : removed;
1312 case '[':
1313 assert text.charAt(text.length() - 1) == ']';
1314 removed = text.substring(1, text.length() - 1);
1315 return recursive ? removeBracketsAndQuotes(removed, recursive) : removed;
1316 }
1317 }
1318 return text;
1319 }
1320
1321 protected NamedSelector parseNamedSelector( TokenStream tokens,
1322 TypeSystem typeSystem ) {
1323 SelectorName name = parseSelectorName(tokens, typeSystem);
1324 SelectorName alias = null;
1325 if (tokens.canConsume("AS")) alias = parseSelectorName(tokens, typeSystem);
1326 return new NamedSelector(name, alias);
1327 }
1328
1329 protected SelectorName parseSelectorName( TokenStream tokens,
1330 TypeSystem typeSystem ) {
1331 return new SelectorName(parseName(tokens, typeSystem));
1332 }
1333
1334 protected String parsePath( TokenStream tokens,
1335 TypeSystem typeSystem ) {
1336 return removeBracketsAndQuotes(tokens.consume());
1337 }
1338
1339 protected String parseName( TokenStream tokens,
1340 TypeSystem typeSystem ) {
1341 return removeBracketsAndQuotes(tokens.consume());
1342 }
1343
1344 protected String parseName( String token,
1345 TypeSystem typeSystem ) {
1346 return removeBracketsAndQuotes(token);
1347 }
1348
1349 protected Query query( Source source,
1350 Constraint constraint,
1351 List<? extends Ordering> orderings,
1352 List<? extends Column> columns,
1353 Limit limit,
1354 boolean distinct ) {
1355 return new Query(source, constraint, orderings, columns, limit, distinct);
1356 }
1357
1358 protected SetQuery setQuery( QueryCommand leftQuery,
1359 Operation operation,
1360 QueryCommand rightQuery,
1361 boolean all ) {
1362 return new SetQuery(leftQuery, operation, rightQuery, all);
1363 }
1364
1365 protected Length length( PropertyValue propertyValue ) {
1366 return new Length(propertyValue);
1367 }
1368
1369 protected LowerCase lowerCase( DynamicOperand operand ) {
1370 return new LowerCase(operand);
1371 }
1372
1373 protected UpperCase upperCase( DynamicOperand operand ) {
1374 return new UpperCase(operand);
1375 }
1376
1377 protected NodeName nodeName( SelectorName selector ) {
1378 return new NodeName(selector);
1379 }
1380
1381 protected NodeLocalName nodeLocalName( SelectorName selector ) {
1382 return new NodeLocalName(selector);
1383 }
1384
1385 protected NodeDepth nodeDepth( SelectorName selector ) {
1386 return new NodeDepth(selector);
1387 }
1388
1389 protected NodePath nodePath( SelectorName selector ) {
1390 return new NodePath(selector);
1391 }
1392
1393 protected EquiJoinCondition equiJoinCondition( SelectorName selector1,
1394 String property1,
1395 SelectorName selector2,
1396 String property2 ) {
1397 return new EquiJoinCondition(selector1, property1, selector2, property2);
1398 }
1399
1400 protected DescendantNodeJoinCondition descendantNodeJoinCondition( SelectorName ancestor,
1401 SelectorName descendant ) {
1402 return new DescendantNodeJoinCondition(ancestor, descendant);
1403 }
1404
1405 protected ChildNodeJoinCondition childNodeJoinCondition( SelectorName parent,
1406 SelectorName child ) {
1407 return new ChildNodeJoinCondition(parent, child);
1408 }
1409
1410 protected SameNodeJoinCondition sameNodeJoinCondition( SelectorName selector1,
1411 SelectorName selector2 ) {
1412 return new SameNodeJoinCondition(selector1, selector2);
1413 }
1414
1415 protected SameNodeJoinCondition sameNodeJoinCondition( SelectorName selector1,
1416 SelectorName selector2,
1417 String path ) {
1418 return new SameNodeJoinCondition(selector1, selector2, path);
1419 }
1420
1421 protected Limit limit( int rowCount,
1422 int offset ) {
1423 return new Limit(rowCount, offset);
1424 }
1425
1426 protected Column column( SelectorName selectorName,
1427 String propertyName,
1428 String columnName ) {
1429 return new Column(selectorName, propertyName, columnName);
1430 }
1431
1432 protected Join join( Source left,
1433 JoinType joinType,
1434 Source right,
1435 JoinCondition joinCondition ) {
1436 return new Join(left, joinType, right, joinCondition);
1437 }
1438
1439 protected Not not( Constraint constraint ) {
1440 return new Not(constraint);
1441 }
1442
1443 protected And and( Constraint constraint1,
1444 Constraint constraint2 ) {
1445 return new And(constraint1, constraint2);
1446 }
1447
1448 protected Or or( Constraint constraint1,
1449 Constraint constraint2 ) {
1450 return new Or(constraint1, constraint2);
1451 }
1452
1453 protected Between between( DynamicOperand operand,
1454 StaticOperand lowerBound,
1455 StaticOperand upperBound,
1456 boolean lowerInclusive,
1457 boolean upperInclusive ) {
1458 return new Between(operand, lowerBound, upperBound, lowerInclusive, upperInclusive);
1459 }
1460
1461 protected SetCriteria setCriteria( DynamicOperand operand,
1462 Collection<? extends StaticOperand> values ) {
1463 return new SetCriteria(operand, values);
1464 }
1465
1466 protected FullTextSearch fullTextSearch( SelectorName name,
1467 String propertyName,
1468 String expression,
1469 Term term ) {
1470 return new FullTextSearch(name, propertyName, expression, term);
1471 }
1472
1473 protected SameNode sameNode( SelectorName name,
1474 String path ) {
1475 return new SameNode(name, path);
1476 }
1477
1478 protected ChildNode childNode( SelectorName name,
1479 String path ) {
1480 return new ChildNode(name, path);
1481 }
1482
1483 protected DescendantNode descendantNode( SelectorName name,
1484 String path ) {
1485 return new DescendantNode(name, path);
1486 }
1487
1488 protected Comparison comparison( DynamicOperand left,
1489 Operator operator,
1490 StaticOperand right ) {
1491 return new Comparison(left, operator, right);
1492 }
1493
1494 protected Ordering ordering( DynamicOperand operand,
1495 Order order ) {
1496 return new Ordering(operand, order);
1497 }
1498
1499 protected PropertyExistence propertyExistence( SelectorName selector,
1500 String propertyName ) {
1501 return new PropertyExistence(selector, propertyName);
1502 }
1503
1504 protected FullTextSearchScore fullTextSearchScore( SelectorName selector ) {
1505 return new FullTextSearchScore(selector);
1506 }
1507
1508 protected ArithmeticOperand arithmeticOperand( DynamicOperand leftOperand,
1509 ArithmeticOperator operator,
1510 DynamicOperand rightOperand ) {
1511 return new ArithmeticOperand(leftOperand, operator, rightOperand);
1512 }
1513
1514 protected PropertyValue propertyValue( SelectorName selector,
1515 String propertyName ) {
1516 return new PropertyValue(selector, propertyName);
1517 }
1518
1519 protected ReferenceValue referenceValue( SelectorName selector ) {
1520 return new ReferenceValue(selector);
1521 }
1522
1523 protected ReferenceValue referenceValue( SelectorName selector,
1524 String propertyName ) {
1525 return new ReferenceValue(selector, propertyName);
1526 }
1527
1528 protected BindVariableName bindVariableName( String variableName ) {
1529 return new BindVariableName(variableName);
1530 }
1531
1532 protected Literal literal( TypeSystem typeSystem,
1533 Object value ) throws ValueFormatException {
1534 return new Literal(value);
1535 }
1536
1537
1538
1539
1540
1541
1542
1543
1544 public static class SqlTokenizer implements TokenStream.Tokenizer {
1545
1546
1547
1548
1549 public static final int WORD = 1;
1550
1551
1552
1553
1554 public static final int SYMBOL = 2;
1555
1556
1557
1558 public static final int OTHER = 3;
1559
1560
1561
1562 public static final int QUOTED_STRING = 4;
1563
1564
1565
1566
1567 public static final int COMMENT = 6;
1568
1569 private final boolean useComments;
1570
1571 public SqlTokenizer( boolean useComments ) {
1572 this.useComments = useComments;
1573 }
1574
1575
1576
1577
1578
1579
1580 public void tokenize( CharacterStream input,
1581 Tokens tokens ) throws ParsingException {
1582 while (input.hasNext()) {
1583 char c = input.next();
1584 switch (c) {
1585 case ' ':
1586 case '\t':
1587 case '\n':
1588 case '\r':
1589
1590 break;
1591 case '(':
1592 case ')':
1593 case '{':
1594 case '}':
1595 case '*':
1596 case '.':
1597 case ',':
1598 case ';':
1599 case '+':
1600 case '%':
1601 case '?':
1602 case '$':
1603 case ']':
1604 case '!':
1605 case '<':
1606 case '>':
1607 case '|':
1608 case '=':
1609 case ':':
1610 tokens.addToken(input.position(input.index()), input.index(), input.index() + 1, SYMBOL);
1611 break;
1612 case '\'':
1613 case '[':
1614 case '\"':
1615 int startIndex = input.index();
1616 char closingChar = c == '[' ? ']' : c;
1617 Position pos = input.position(startIndex);
1618 boolean foundClosingQuote = false;
1619 while (input.hasNext()) {
1620 c = input.next();
1621 if (c == '\\' && input.isNext(closingChar)) {
1622 c = input.next();
1623 } else if (c == closingChar) {
1624 foundClosingQuote = true;
1625 break;
1626 }
1627 }
1628 if (!foundClosingQuote) {
1629 String msg = CommonI18n.noMatchingDoubleQuoteFound.text(pos.getLine(), pos.getColumn());
1630 if (closingChar == '\'') {
1631 msg = CommonI18n.noMatchingSingleQuoteFound.text(pos.getLine(), pos.getColumn());
1632 } else if (closingChar == ']') {
1633 msg = GraphI18n.noMatchingBracketFound.text(pos.getLine(), pos.getColumn());
1634 }
1635 throw new ParsingException(pos, msg);
1636 }
1637 int endIndex = input.index() + 1;
1638 tokens.addToken(pos, startIndex, endIndex, QUOTED_STRING);
1639 break;
1640 case '-':
1641 startIndex = input.index();
1642 pos = input.position(input.index());
1643 if (input.isNext('-')) {
1644
1645 boolean foundLineTerminator = false;
1646 while (input.hasNext()) {
1647 c = input.next();
1648 if (c == '\n' || c == '\r') {
1649 foundLineTerminator = true;
1650 break;
1651 }
1652 }
1653 endIndex = input.index();
1654 if (!foundLineTerminator) ++endIndex;
1655 if (c == '\r' && input.isNext('\n')) input.next();
1656 if (useComments) {
1657 tokens.addToken(pos, startIndex, endIndex, COMMENT);
1658 }
1659 } else {
1660 tokens.addToken(input.position(input.index()), input.index(), input.index() + 1, SYMBOL);
1661 break;
1662 }
1663 break;
1664 case '/':
1665 startIndex = input.index();
1666 pos = input.position(input.index());
1667 if (input.isNext('*')) {
1668
1669 while (input.hasNext() && !input.isNext('*', '/')) {
1670 c = input.next();
1671 }
1672 if (input.hasNext()) input.next();
1673 if (input.hasNext()) input.next();
1674 if (useComments) {
1675 endIndex = input.index() + 1;
1676 tokens.addToken(pos, startIndex, endIndex, COMMENT);
1677 }
1678 } else {
1679 tokens.addToken(input.position(input.index()), input.index(), input.index() + 1, SYMBOL);
1680 break;
1681 }
1682 break;
1683 default:
1684 startIndex = input.index();
1685 pos = input.position(input.index());
1686
1687 int tokenType = (Character.isLetterOrDigit(c) || c == '_') ? WORD : OTHER;
1688 while (input.isNextLetterOrDigit() || input.isNext('_')) {
1689 c = input.next();
1690 }
1691 endIndex = input.index() + 1;
1692 tokens.addToken(pos, startIndex, endIndex, tokenType);
1693 }
1694 }
1695 }
1696 }
1697 }