Lines 1-520
Link Here
|
1 |
/* |
|
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* Contributor(s): |
28 |
* |
29 |
* The Original Software is NetBeans. The Initial Developer of the Original |
30 |
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2010 Sun |
31 |
* Microsystems, Inc. All Rights Reserved. |
32 |
* |
33 |
* If you wish your version of this file to be governed by only the CDDL |
34 |
* or only the GPL Version 2, indicate your decision by adding |
35 |
* "[Contributor] elects to include this software in this distribution |
36 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
37 |
* single choice of license, a recipient has the option to distribute |
38 |
* your version of this file under either the CDDL, the GPL Version 2 or |
39 |
* to extend the choice of license to its licensees as provided above. |
40 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
41 |
* Version 2 license, then the option applies only if the new code is |
42 |
* made subject to such option by the copyright holder. |
43 |
*/ |
44 |
|
45 |
package org.netbeans.modules.db.sql.editor; |
46 |
|
47 |
import org.netbeans.editor.Syntax; |
48 |
import org.netbeans.editor.TokenID; |
49 |
import org.netbeans.modules.db.api.sql.SQLKeywords; |
50 |
|
51 |
/** |
52 |
* This class implements SQL syntax recognition |
53 |
* |
54 |
* @author Jesse Beaumont, Andrei Badea |
55 |
*/ |
56 |
public class SQLSyntax extends Syntax { |
57 |
|
58 |
private static final int ISI_WHITESPACE = 2; // inside white space |
59 |
private static final int ISI_LINE_COMMENT = 4; // inside line comment -- |
60 |
private static final int ISI_BLOCK_COMMENT = 5; // inside block comment /* ... */ |
61 |
private static final int ISI_STRING = 6; // inside string constant |
62 |
private static final int ISI_STRING_A_QUOTE = 7; // inside string constant after ' |
63 |
private static final int ISI_IDENTIFIER = 10; // inside identifier |
64 |
private static final int ISA_SLASH = 11; // slash char |
65 |
private static final int ISA_OPERATOR = 12; // after '=', '/', '+' |
66 |
private static final int ISA_MINUS = 13; |
67 |
private static final int ISA_STAR = 20; // after '*' |
68 |
private static final int ISA_STAR_I_BLOCK_COMMENT_END = 21; // after '*' in a block comment |
69 |
private static final int ISA_EXCLAMATION = 26; // after '!' |
70 |
private static final int ISA_ZERO = 27; // after '0' |
71 |
private static final int ISI_INT = 28; // integer number |
72 |
private static final int ISI_DOUBLE = 30; // double number |
73 |
private static final int ISA_DOT = 33; // after '.' |
74 |
private static final int ISA_COMMA = 34; // after ',' |
75 |
private static final int ISA_SEMICOLON = 35; //after ';' |
76 |
private static final int ISA_LPAREN = 36; //after ( |
77 |
private static final int ISA_RPAREN = 37; //after ) |
78 |
private static final int ISA_BACK_SLASH_IN_STRING = 38; // after \ in string |
79 |
private static final int ISA_HASH = 39; // '#' char |
80 |
|
81 |
private int startQuoteChar = -1; |
82 |
|
83 |
/** |
84 |
* Creates a new instance of SQLSyntax |
85 |
*/ |
86 |
public SQLSyntax() { |
87 |
tokenContextPath = SQLTokenContext.contextPath; |
88 |
} |
89 |
|
90 |
/** |
91 |
* Parse the next token |
92 |
*/ |
93 |
@Override |
94 |
protected TokenID parseToken() { |
95 |
char actChar; //the current character |
96 |
|
97 |
//while we still have stuff to parse, do so |
98 |
while(offset < stopOffset) { |
99 |
actChar = buffer[offset]; |
100 |
|
101 |
//do the appropriate stuff based on what state the parser is in |
102 |
switch (state) { |
103 |
//the initial state (start of a new token) |
104 |
case INIT: |
105 |
switch (actChar) { |
106 |
case '\'': // NOI18N |
107 |
state = ISI_STRING; |
108 |
break; |
109 |
case '/': |
110 |
state = ISA_SLASH; |
111 |
break; |
112 |
case '#': |
113 |
state = ISA_HASH; |
114 |
break; |
115 |
case '=': |
116 |
case '>': |
117 |
case '<': |
118 |
case '+': |
119 |
case ',': |
120 |
case ')': |
121 |
case '(': |
122 |
case ';': |
123 |
case '*': |
124 |
case '!': |
125 |
offset++; |
126 |
state = INIT; |
127 |
return SQLTokenContext.OPERATOR; |
128 |
case '-': |
129 |
state = ISA_MINUS; |
130 |
break; |
131 |
case '0': |
132 |
state = ISA_ZERO; |
133 |
break; |
134 |
case '.': |
135 |
state = ISA_DOT; |
136 |
break; |
137 |
default: |
138 |
// Check for whitespace |
139 |
if (Character.isWhitespace(actChar)) { |
140 |
state = ISI_WHITESPACE; |
141 |
break; |
142 |
} |
143 |
|
144 |
// Check for digit |
145 |
if (Character.isDigit(actChar)) { |
146 |
state = ISI_INT; |
147 |
break; |
148 |
} |
149 |
|
150 |
// otherwise it's an identifier |
151 |
state = ISI_IDENTIFIER; |
152 |
if (isStartQuoteChar(actChar)) { |
153 |
startQuoteChar = actChar; |
154 |
} |
155 |
break; |
156 |
} |
157 |
break; |
158 |
//if we are currently in a whitespace token |
159 |
case ISI_WHITESPACE: // white space |
160 |
if (!Character.isWhitespace(actChar)) { |
161 |
state = INIT; |
162 |
return SQLTokenContext.WHITESPACE; |
163 |
} |
164 |
break; |
165 |
|
166 |
//if we are currently in a line comment |
167 |
case ISI_LINE_COMMENT: |
168 |
if (actChar == '\n') { |
169 |
state = INIT; |
170 |
return SQLTokenContext.LINE_COMMENT; |
171 |
} |
172 |
break; |
173 |
|
174 |
//if we are currently in a block comment |
175 |
case ISI_BLOCK_COMMENT: |
176 |
if(actChar =='*') { |
177 |
state = ISA_STAR_I_BLOCK_COMMENT_END; |
178 |
} |
179 |
break; |
180 |
|
181 |
//if we are currently in a string literal |
182 |
case ISI_STRING: |
183 |
switch (actChar) { |
184 |
case '\\': // NOI18N |
185 |
// escape possible single quote #152325 |
186 |
state = ISA_BACK_SLASH_IN_STRING; |
187 |
break; |
188 |
case '\'': // NOI18N |
189 |
offset++; |
190 |
state = INIT; |
191 |
return SQLTokenContext.STRING; |
192 |
} |
193 |
break; |
194 |
|
195 |
// If we are after a back slash (\) in string. |
196 |
case ISA_BACK_SLASH_IN_STRING: |
197 |
state = ISI_STRING; |
198 |
break; |
199 |
|
200 |
//if we are currently in an identifier (e.g. a variable name) |
201 |
case ISI_IDENTIFIER: |
202 |
if (startQuoteChar != -1) { |
203 |
if (!isEndQuoteChar(startQuoteChar, actChar)) { |
204 |
break; |
205 |
} else { |
206 |
offset++; |
207 |
} |
208 |
} else { |
209 |
if (Character.isLetterOrDigit(actChar) || actChar == '_' || actChar == '#') { |
210 |
break; |
211 |
} |
212 |
} |
213 |
state = INIT; |
214 |
startQuoteChar = -1; |
215 |
TokenID tid = matchKeyword(buffer, tokenOffset, offset - tokenOffset); |
216 |
if (tid != null) { |
217 |
return tid; |
218 |
} else { |
219 |
return SQLTokenContext.IDENTIFIER; |
220 |
} |
221 |
|
222 |
//if we are after a slash (/) |
223 |
case ISA_SLASH: |
224 |
switch (actChar) { |
225 |
case '*': |
226 |
state = ISI_BLOCK_COMMENT; |
227 |
break; |
228 |
default: |
229 |
state = INIT; |
230 |
return SQLTokenContext.OPERATOR; |
231 |
} |
232 |
break; |
233 |
|
234 |
//if we are after a - |
235 |
case ISA_MINUS: |
236 |
switch (actChar) { |
237 |
case '-': |
238 |
state = ISI_LINE_COMMENT; |
239 |
break; |
240 |
default: |
241 |
state = INIT; |
242 |
return SQLTokenContext.OPERATOR; |
243 |
} |
244 |
break; |
245 |
|
246 |
//if we are after a # |
247 |
case ISA_HASH: |
248 |
if (Character.isWhitespace(actChar)) { |
249 |
// only in MySQL # starts line comment |
250 |
state = ISI_LINE_COMMENT; |
251 |
if (actChar == '\n') { |
252 |
return SQLTokenContext.LINE_COMMENT; |
253 |
} |
254 |
} else { |
255 |
// otherwise it can be identifier (issue 172904) |
256 |
if (offset > 1) { |
257 |
state = ISI_IDENTIFIER; |
258 |
} else { |
259 |
state = ISI_LINE_COMMENT; |
260 |
} |
261 |
} |
262 |
break; |
263 |
|
264 |
//if we are in the middle of a possible block comment end token |
265 |
case ISA_STAR_I_BLOCK_COMMENT_END: |
266 |
switch (actChar) { |
267 |
case '/': |
268 |
offset++; |
269 |
state = INIT; |
270 |
return SQLTokenContext.BLOCK_COMMENT; |
271 |
default: |
272 |
offset--; |
273 |
state = ISI_BLOCK_COMMENT; |
274 |
break; |
275 |
} |
276 |
break; |
277 |
|
278 |
//if we are after a 0 |
279 |
case ISA_ZERO: |
280 |
switch (actChar) { |
281 |
case '.': |
282 |
state = ISI_DOUBLE; |
283 |
break; |
284 |
default: |
285 |
if (Character.isDigit(actChar)) { |
286 |
state = ISI_INT; |
287 |
break; |
288 |
} else { |
289 |
state = INIT; |
290 |
return SQLTokenContext.INT_LITERAL; |
291 |
} |
292 |
} |
293 |
break; |
294 |
|
295 |
//if we are after an integer |
296 |
case ISI_INT: |
297 |
switch (actChar) { |
298 |
case '.': |
299 |
state = ISI_DOUBLE; |
300 |
break; |
301 |
default: |
302 |
if (Character.isDigit(actChar)) { |
303 |
state = ISI_INT; |
304 |
break; |
305 |
} else { |
306 |
state = INIT; |
307 |
return SQLTokenContext.INT_LITERAL; |
308 |
} |
309 |
} |
310 |
break; |
311 |
|
312 |
//if we are in the middle of what we believe is a floating point |
313 |
//number |
314 |
case ISI_DOUBLE: |
315 |
if (actChar >= '0' && actChar <= '9') { |
316 |
state = ISI_DOUBLE; |
317 |
break; |
318 |
} else { |
319 |
state = INIT; |
320 |
return SQLTokenContext.DOUBLE_LITERAL; |
321 |
} |
322 |
|
323 |
//if we are after a period |
324 |
case ISA_DOT: |
325 |
if (Character.isDigit(actChar)) { |
326 |
state = ISI_DOUBLE; |
327 |
} else { // only single dot |
328 |
state = INIT; |
329 |
return SQLTokenContext.DOT; |
330 |
} |
331 |
break; |
332 |
|
333 |
} // end of switch(state) |
334 |
|
335 |
offset++; |
336 |
} // end of while(offset...) |
337 |
|
338 |
/* |
339 |
* At this stage there's no more text in the scanned buffer. |
340 |
* Scanner first checks whether this is completely the last |
341 |
* available buffer. |
342 |
*/ |
343 |
if (lastBuffer) { |
344 |
switch(state) { |
345 |
case ISI_WHITESPACE: |
346 |
state = INIT; |
347 |
return SQLTokenContext.WHITESPACE; |
348 |
case ISI_IDENTIFIER: |
349 |
state = INIT; |
350 |
startQuoteChar = -1; |
351 |
TokenID tid = |
352 |
matchKeyword(buffer, tokenOffset, offset - tokenOffset); |
353 |
if(tid != null) { |
354 |
return tid; |
355 |
} |
356 |
else { |
357 |
return SQLTokenContext.IDENTIFIER; |
358 |
} |
359 |
case ISI_LINE_COMMENT: |
360 |
// stay in line-comment state |
361 |
return SQLTokenContext.LINE_COMMENT; |
362 |
case ISI_BLOCK_COMMENT: |
363 |
case ISA_STAR_I_BLOCK_COMMENT_END: |
364 |
// stay in block-comment state |
365 |
return SQLTokenContext.BLOCK_COMMENT; |
366 |
case ISI_STRING: |
367 |
case ISA_BACK_SLASH_IN_STRING: |
368 |
return SQLTokenContext.INCOMPLETE_STRING; |
369 |
case ISA_ZERO: |
370 |
case ISI_INT: |
371 |
state = INIT; |
372 |
return SQLTokenContext.INT_LITERAL; |
373 |
case ISI_DOUBLE: |
374 |
state = INIT; |
375 |
return SQLTokenContext.DOUBLE_LITERAL; |
376 |
case ISA_DOT: |
377 |
state = INIT; |
378 |
return SQLTokenContext.DOT; |
379 |
case ISA_SLASH: |
380 |
state = INIT; |
381 |
return SQLTokenContext.OPERATOR; |
382 |
} |
383 |
} |
384 |
|
385 |
/* |
386 |
* At this stage there's no more text in the scanned buffer, but |
387 |
* this buffer is not the last so the |
388 |
* scan will continue on another buffer. |
389 |
* The scanner tries to minimize the amount of characters |
390 |
* that will be prescanned in the next buffer by returning the token |
391 |
* where possible. |
392 |
*/ |
393 |
|
394 |
switch (state) { |
395 |
case ISI_WHITESPACE: |
396 |
return SQLTokenContext.WHITESPACE; |
397 |
} |
398 |
|
399 |
return null; // nothing found |
400 |
} |
401 |
|
402 |
/** |
403 |
* Returns the state name for the state id |
404 |
*/ |
405 |
@Override |
406 |
public String getStateName(int stateNumber) { |
407 |
switch(stateNumber) { |
408 |
case ISI_WHITESPACE: |
409 |
return "ISI_WHITESPACE"; // NOI18N |
410 |
case ISI_LINE_COMMENT: |
411 |
return "ISI_LINE_COMMENT"; // NOI18N |
412 |
case ISI_BLOCK_COMMENT: |
413 |
return "ISI_BLOCK_COMMENT"; // NOI18N |
414 |
case ISI_STRING: |
415 |
return "ISI_STRING"; // NOI18N |
416 |
case ISI_STRING_A_QUOTE: |
417 |
return "ISI_STRING_A_QUOTE"; // NOI18N |
418 |
case ISI_IDENTIFIER: |
419 |
return "ISI_IDENTIFIER"; // NOI18N |
420 |
case ISA_SLASH: |
421 |
return "ISA_SLASH"; // NOI18N |
422 |
case ISA_OPERATOR: |
423 |
return "ISA_OPERATOR"; // NOI18N |
424 |
case ISA_MINUS: |
425 |
return "ISA_MINUS"; // NOI18N |
426 |
case ISA_STAR: |
427 |
return "ISA_STAR"; // NOI18N |
428 |
case ISA_STAR_I_BLOCK_COMMENT_END: |
429 |
return "ISA_STAR_I_BLOCK_COMMENT_END"; // NOI18N |
430 |
case ISA_ZERO: |
431 |
return "ISA_ZERO"; // NOI18N |
432 |
case ISI_INT: |
433 |
return "ISI_INT"; // NOI18N |
434 |
case ISI_DOUBLE: |
435 |
return "ISI_DOUBLE"; // NOI18N |
436 |
case ISA_DOT: |
437 |
return "ISA_DOT"; // NOI18N |
438 |
case ISA_COMMA: |
439 |
return "ISA_COMMA"; // NOI18N |
440 |
|
441 |
default: |
442 |
return super.getStateName(stateNumber); |
443 |
} |
444 |
} |
445 |
|
446 |
@Override |
447 |
public void loadInitState() { |
448 |
startQuoteChar = -1; |
449 |
super.loadInitState(); |
450 |
} |
451 |
|
452 |
@Override |
453 |
public StateInfo createStateInfo() { |
454 |
return new SQLStateInfo(); |
455 |
} |
456 |
|
457 |
@Override |
458 |
public void loadState(StateInfo stateInfo) { |
459 |
startQuoteChar = ((SQLStateInfo) stateInfo).getStartQuoteChar(); |
460 |
super.loadState(stateInfo); |
461 |
} |
462 |
|
463 |
@Override |
464 |
public void storeState(StateInfo stateInfo) { |
465 |
((SQLStateInfo)stateInfo).setStartQuoteChar(startQuoteChar); |
466 |
super.storeState(stateInfo); |
467 |
} |
468 |
|
469 |
@Override |
470 |
public int compareState(StateInfo stateInfo) { |
471 |
if (stateInfo != null) { |
472 |
if (((SQLStateInfo)stateInfo).getStartQuoteChar() == startQuoteChar) { |
473 |
return super.compareState(stateInfo); |
474 |
} |
475 |
} |
476 |
return DIFFERENT_STATE; |
477 |
} |
478 |
|
479 |
private static boolean isStartQuoteChar(int start) { |
480 |
return start == '\"' || // SQL-99 |
481 |
start == '`' || // MySQL |
482 |
start == '['; // MS SQL Server |
483 |
} |
484 |
|
485 |
|
486 |
private static boolean isEndQuoteChar(int start, int end) { |
487 |
return start == '\"' && end == start || // SQL-99 |
488 |
start == '`' && end == start || // MySQL |
489 |
start == '[' && end == ']'; // MS SQL Server |
490 |
} |
491 |
|
492 |
/** |
493 |
* Tries to match the specified sequence of characters to a SQL |
494 |
* keyword. |
495 |
* |
496 |
* @return the KEYWORD id or null if no match was found |
497 |
*/ |
498 |
public TokenID matchKeyword(char[] buffer, int offset, int len) { |
499 |
String keywordCandidate = new String(buffer, offset, len); |
500 |
|
501 |
if (SQLKeywords.isSQL99Keyword(keywordCandidate.toUpperCase(), true)) { |
502 |
return SQLTokenContext.KEYWORD; |
503 |
} |
504 |
|
505 |
return null; |
506 |
} |
507 |
|
508 |
private static final class SQLStateInfo extends BaseStateInfo { |
509 |
|
510 |
private int startQuoteChar; |
511 |
|
512 |
public int getStartQuoteChar() { |
513 |
return startQuoteChar; |
514 |
} |
515 |
|
516 |
public void setStartQuoteChar(int startQuoteChar) { |
517 |
this.startQuoteChar = startQuoteChar; |
518 |
} |
519 |
} |
520 |
} |