001package gudusoft.gsqlparser.nodes; 002 003import gudusoft.gsqlparser.*; 004import gudusoft.gsqlparser.scriptWriter.TScriptGenerator; 005import gudusoft.gsqlparser.sqlenv.TSQLEnv; 006import gudusoft.gsqlparser.util.gspMD5Utils; 007 008import java.util.ArrayList; 009import java.util.Iterator; 010 011import static java.nio.charset.StandardCharsets.UTF_8; 012 013/** 014 * <h1>TParseTreeNode - Core Parse Tree Node Implementation</h1> 015 * 016 * <h2>Overview</h2> 017 * This class is the root class for all syntax/parse tree nodes in the GSP SQL Parser. 018 * A parse tree node represents a syntactic element of a SQL statement, composed of a sequence 019 * of tokens generated by the lexer. The node maintains references to its start and end tokens 020 * in a double-linked token chain, enabling both traversal and modification of the SQL text. 021 * 022 * <h2>Architecture and Design</h2> 023 * 024 * <h3>Token Chain Structure</h3> 025 * The parse tree is built on a double-linked list of {@link TSourceToken} objects. Each node: 026 * <ul> 027 * <li>Has a {@code startToken} and {@code endToken} that define its boundaries</li> 028 * <li>Can iterate through its tokens using the token chain links</li> 029 * <li>Maintains relationships with other nodes that may share the same tokens</li> 030 * </ul> 031 * 032 * <h3>Critical: Node Nesting and Token Sharing</h3> 033 * <b>Nodes in the parse tree are nested and share tokens.</b> This is fundamental to the design: 034 * <ul> 035 * <li>A WHERE clause node contains an expression node, both sharing the same tokens</li> 036 * <li>A SELECT statement contains multiple clauses, all sharing tokens from the same chain</li> 037 * <li>Each token maintains stacks of nodes that start/end on it (via {@code nodesStartFromThisToken} 038 * and {@code nodesEndWithThisToken})</li> 039 * </ul> 040 * 041 * <h4>Token Sharing Example:</h4> 042 * <pre> 043 * SQL: "SELECT * FROM table WHERE id = 1" 044 * 045 * - SelectStatement node: [SELECT...1] 046 * - SelectList node: [*] 047 * - FromClause node: [FROM table] 048 * - WhereClause node: [WHERE id = 1] 049 * - Expression node: [id = 1] 050 * - LeftOperand: [id] 051 * - Operator: [=] 052 * - RightOperand: [1] 053 * </pre> 054 * 055 * <h3>Modification Strategy for Nested Nodes</h3> 056 * 057 * <h4>Core Rules:</h4> 058 * <ol> 059 * <li><b>Leaf-First Modification:</b> Always modify the most specific (leaf) node when possible</li> 060 * <li><b>Status Propagation:</b> When a node is modified, all overlapping nodes are updated: 061 * <ul> 062 * <li>{@code nsDetached}: Node's tokens were partially modified</li> 063 * <li>{@code nsPartitial}: Node contains modified sub-nodes</li> 064 * <li>{@code nsRemoved}: Node's tokens were completely removed</li> 065 * </ul> 066 * </li> 067 * <li><b>Chain Integrity:</b> The token chain must remain valid after any modification</li> 068 * </ol> 069 * 070 * <h4>Modification Scenarios and Handling:</h4> 071 * 072 * <b>Scenario 1: Modifying a Leaf Node</b> 073 * <pre> 074 * // Original: WHERE id = 1 075 * // Modify the right operand from "1" to "100" 076 * rightOperandNode.setString("100"); 077 * 078 * Result: 079 * - RightOperand node: replaced with new token "100" 080 * - Expression node: status = nsPartitial (contains modified child) 081 * - WhereClause node: status = nsPartitial (contains modified descendant) 082 * </pre> 083 * 084 * <b>Scenario 2: Modifying a Parent Node</b> 085 * <pre> 086 * // Original: WHERE id = 1 087 * // Replace entire WHERE clause 088 * whereClauseNode.setString("WHERE status = 'active'"); 089 * 090 * Result: 091 * - All child nodes (Expression, operands): status = nsDetached 092 * - WhereClause node: completely replaced with new tokens 093 * - SelectStatement: updated to reference new WHERE tokens 094 * </pre> 095 * 096 * <b>Scenario 3: Removing a Node</b> 097 * <pre> 098 * // Remove WHERE clause entirely 099 * whereClauseNode.removeTokens(); 100 * 101 * Result: 102 * - WhereClause and all children: status = nsRemoved 103 * - Tokens removed from chain 104 * - Parent SelectStatement: adjusted boundaries 105 * </pre> 106 * 107 * <h3>Best Practices for API Users</h3> 108 * 109 * <ol> 110 * <li><b>Check Node Status Before Modification:</b> 111 * <pre> 112 * if (node.getNodeStatus() == ENodeStatus.nsNormal) { 113 * // Safe to modify 114 * node.setString(newText); 115 * } 116 * </pre> 117 * </li> 118 * 119 * <li><b>Prefer Leaf Node Modifications:</b> 120 * <pre> 121 * // GOOD: Modify specific value 122 * tableNameNode.setString("new_table"); 123 * 124 * // AVOID: Replacing entire FROM clause when only table name needs change 125 * fromClause.setString("FROM new_table"); 126 * </pre> 127 * </li> 128 * 129 * <li><b>Use toScript() for Complex Modifications:</b> 130 * <pre> 131 * // After multiple modifications, use script generator 132 * if (statement.isChanged()) { 133 * String result = statement.toScript(); 134 * } 135 * </pre> 136 * </li> 137 * 138 * <li><b>Understand Cascading Effects:</b> 139 * <pre> 140 * // Modifying a node affects all overlapping nodes 141 * expression.setString("new_condition"); 142 * // Parent WHERE clause is now marked as modified 143 * assert whereClause.getNodeStatus() == ENodeStatus.nsPartitial; 144 * </pre> 145 * </li> 146 * </ol> 147 * 148 * <h3>Text Modification Mechanism</h3> 149 * The node supports dynamic text modification through the {@link #setString(String)} method: 150 * <ol> 151 * <li><b>Tokenization:</b> New text is tokenized using {@link TSingletonParser} to create new tokens</li> 152 * <li><b>Chain Replacement:</b> Old tokens are removed from the chain and replaced with new ones</li> 153 * <li><b>Relationship Updates:</b> All nodes sharing the modified tokens are updated: 154 * <ul> 155 * <li>{@code updateNodeWithTheSameStartToken()}: Updates nodes starting at modified position</li> 156 * <li>{@code updateMeNodeWithTheSameEndToken()}: Updates nodes ending at modified position</li> 157 * <li>{@code updateStatusOfNodeShareSameTokens()}: Propagates status changes</li> 158 * </ul> 159 * </li> 160 * <li><b>Status Tracking:</b> Node status changes to track modifications</li> 161 * </ol> 162 * 163 * <h3>String Reconstruction ({@link #toString()})</h3> 164 * The toString() method uses a sophisticated two-pass algorithm when {@code doubleLinkedTokenListToString} is true: 165 * <ul> 166 * <li><b>First Pass:</b> Detects chain modifications and marks redundant newlines for deletion</li> 167 * <li><b>Second Pass:</b> Builds the final string by concatenating non-deleted tokens</li> 168 * </ul> 169 * This approach cleanly handles both original parsed text and user modifications. 170 * 171 * <h2>Current Implementation Trade-offs</h2> 172 * 173 * <h3>Design Decisions (Simplicity over Features)</h3> 174 * <ul> 175 * <li><b>Re-tokenization on Every Modification:</b> Simple but potentially expensive. 176 * Accepted trade-off for reliability and consistency.</li> 177 * <li><b>No Partial Token Modification:</b> Cannot modify part of a token; must replace entire token. 178 * Keeps token integrity and chain management simple.</li> 179 * <li><b>Status-based Conflict Resolution:</b> When nodes overlap, status flags determine behavior. 180 * Simple rule-based system rather than complex conflict resolution.</li> 181 * <li><b>Single Token Chain:</b> All nodes reference the same global chain. 182 * Simpler than managing multiple chains but requires careful synchronization.</li> 183 * </ul> 184 * 185 * <h3>Limitations by Design</h3> 186 * <ul> 187 * <li><b>Cannot Preserve Comments in Modified Nodes:</b> When a node is replaced via setString(), 188 * internal comments are lost. This is acceptable for most use cases.</li> 189 * <li><b>No Undo/Redo:</b> Modifications are permanent. Applications must manage their own undo stack.</li> 190 * <li><b>Thread Safety:</b> Not thread-safe by design. Simpler implementation, caller must synchronize.</li> 191 * <li><b>Memory vs Speed:</b> Keeps all tokens in memory for fast access. Not optimized for very large SQLs.</li> 192 * </ul> 193 * 194 * <h2>Implementation Details</h2> 195 * 196 * <h3>Token Sharing Management</h3> 197 * When modifying a node with shared tokens: 198 * <ol> 199 * <li>Calculate token count for all affected nodes ({@code refreshAllNodesTokenCount()})</li> 200 * <li>Identify nodes with same start/end tokens</li> 201 * <li>Update node boundaries based on token count comparison: 202 * <ul> 203 * <li>Same count = sibling node, needs same update</li> 204 * <li>Larger count = parent node, needs boundary adjustment</li> 205 * <li>Smaller count = child node, may become detached</li> 206 * </ul> 207 * </li> 208 * <li>Splice new tokens into chain maintaining prev/next links</li> 209 * </ol> 210 * 211 * <h3>Status Flags and Their Meanings</h3> 212 * <ul> 213 * <li>{@code nsNormal}: Unmodified node with original tokens</li> 214 * <li>{@code nsFlattened}: Node created from user text (no original tokens)</li> 215 * <li>{@code nsPartitial}: Contains modified child nodes but own tokens unchanged</li> 216 * <li>{@code nsDetached}: Tokens modified by parent/sibling operation</li> 217 * <li>{@code nsRemoved}: Tokens removed from chain, node is defunct</li> 218 * </ul> 219 * 220 * <h2>Proposed Future Improvements</h2> 221 * 222 * <h3>1. Decouple Lexer Reference</h3> 223 * <pre> 224 * // Instead of token->lexer reference, use a TokenContext: 225 * class TokenContext { 226 * EDbVendor vendor; 227 * SourceBuffer sourceBuffer; 228 * int sourceId; // Identifies original vs modified source 229 * } 230 * </pre> 231 * 232 * <h3>2. Implement SourceBuffer System</h3> 233 * <pre> 234 * class SourceBuffer { 235 * List<TextSegment> segments; 236 * 237 * class TextSegment { 238 * String text; 239 * SourceType type; // ORIGINAL, USER_MODIFIED, GENERATED 240 * int startOffset; 241 * int endOffset; 242 * } 243 * 244 * String getText(int segmentId, int start, int end); 245 * int addSegment(String text, SourceType type); 246 * } 247 * </pre> 248 * 249 * <h3>3. Token Source Tracking</h3> 250 * <pre> 251 * class TSourceToken { 252 * int sourceBufferId; 253 * int segmentId; 254 * int startOffset; 255 * int endOffset; 256 * SourceType sourceType; 257 * 258 * String getText() { 259 * return SourceBufferManager.getBuffer(sourceBufferId) 260 * .getText(segmentId, startOffset, endOffset); 261 * } 262 * } 263 * </pre> 264 * 265 * <h3>4. Lazy Token Creation</h3> 266 * <ul> 267 * <li>Don't re-tokenize for simple text replacements</li> 268 * <li>Create synthetic tokens only when necessary</li> 269 * <li>Reuse existing tokens when possible</li> 270 * </ul> 271 * 272 * <h3>5. Unified Modification API</h3> 273 * <pre> 274 * class NodeModification { 275 * ModificationType type; // REPLACE, INSERT, DELETE 276 * String newText; 277 * boolean preserveFormatting; 278 * boolean retokenize; 279 * } 280 * 281 * public void modifyNode(NodeModification mod) { 282 * // Single entry point for all modifications 283 * } 284 * </pre> 285 * 286 * <h2>Critical Methods Documentation</h2> 287 * 288 * <h3>{@link #setString(String)}</h3> 289 * Main entry point for modifying node text. Process: 290 * <ol> 291 * <li>Tokenizes new text using TSingletonParser</li> 292 * <li>Creates new token chain from tokenized text</li> 293 * <li>Replaces old tokens in the chain</li> 294 * <li>Updates all affected node relationships</li> 295 * </ol> 296 * 297 * <h3>{@link #toString()}</h3> 298 * Reconstructs text from token chain. Two-pass algorithm: 299 * <ol> 300 * <li><b>Pass 1:</b> Detect modifications and mark redundant formatting</li> 301 * <li><b>Pass 2:</b> Build string from valid tokens</li> 302 * </ol> 303 * 304 * <h3>{@link #setText(TSourceToken, TSourceToken)}</h3> 305 * Low-level token replacement. Core of the modification system: 306 * <ul> 307 * <li>Updates token chain links</li> 308 * <li>Maintains node relationships</li> 309 * <li>Handles edge cases (null tokens, chain boundaries)</li> 310 * </ul> 311 * 312 * <h2>Usage Patterns</h2> 313 * 314 * <h3>Reading Original Text</h3> 315 * <pre> 316 * TParseTreeNode node = ...; 317 * String originalText = node.toString(); // Get text from token chain 318 * </pre> 319 * 320 * <h3>Modifying Node Text</h3> 321 * <pre> 322 * node.setString("new SQL text"); // Replace node's text 323 * String modifiedText = node.toString(); // Get modified text 324 * </pre> 325 * 326 * <h3>Checking Modifications</h3> 327 * <pre> 328 * if (node.isChanged()) { 329 * String newText = node.toScript(); // Use script generator for complex mods 330 * } 331 * </pre> 332 * 333 * <h2>Thread Safety</h2> 334 * This class is NOT thread-safe. Concurrent modifications to the token chain 335 * or node structure may result in inconsistent state or corruption. 336 * 337 * <h2>Performance Considerations</h2> 338 * <ul> 339 * <li>Token chain traversal is O(n) where n is number of tokens</li> 340 * <li>Text modification requires re-tokenization (expensive)</li> 341 * <li>toString() performs two passes over tokens</li> 342 * <li>Node relationship updates can cascade to many nodes</li> 343 * </ul> 344 * 345 * @see TSourceToken 346 * @see TSingletonParser 347 * @see TScriptGenerator 348 * @since 1.0 349 */ 350public abstract class TParseTreeNode implements Visitable,Iterator<TSourceToken>{ 351 352 public void setEvaluateDatatype(Class<?> evaluateDatatype) { 353 this.evaluateDatatype = evaluateDatatype; 354 } 355 356 /** 357 * Support String, Integer, Double and Boolean type 358 * 359 * @return 360 */ 361 public Class<?> getEvaluateDatatype() { 362 return evaluateDatatype; 363 } 364 365 private Class<?> evaluateDatatype; 366 private Object evalValue; 367 368 public void setEvalValue(Object evalValue) { 369 this.evalValue = evalValue; 370 } 371 372 public Object getEvalValue() { 373 return evalValue; 374 } 375 376 private String plainText = null; 377 378 public void setPlainText(String plainText) { 379 this.plainText = plainText; 380 } 381 382 public String getPlainText() { 383 if (plainText == null){ 384 return toString(); 385 }else{ 386 return plainText; 387 } 388 } 389 390 private ESqlClause location = ESqlClause.unknown; 391 392 public void setLocation(ESqlClause location) { 393 this.location = location; 394 } 395 396 /** 397 * SQL clause that include this objectName such as select list, from clause, set clause 398 * 399 * @return SQL clause that include this objectName 400 */ 401 public ESqlClause getLocation() { 402 return location; 403 } 404 405 private String compactString = null; 406 407 /** 408 * representation of this node by removing all spaces, return and comments 409 * @return string representation of this node by removing all spaces, return and comments 410 */ 411 public String getCompactString(){ 412 if (compactString != null) return compactString; 413 414 StringBuilder sb = new StringBuilder(); 415 TSourceToken st = null; 416 currentIterateToken = null; 417 while (this.hasNext()){ 418 st = next(); 419 if ((st.tokencode == TBaseType.lexspace)||(st.tokencode == TBaseType.lexnewline) 420 ||(st.tokencode == TBaseType.cmtslashstar)||(st.tokencode == TBaseType.cmtdoublehyphen) 421 ) { 422 continue; 423 }else{ 424 sb.append(st.toString()); 425 } 426 } 427 428 compactString = sb.toString(); 429 return compactString; 430 } 431 432 private String md5 = null; 433 /** 434 * md5 hash code of the string representation of this node. 435 * If the string of this node is empty, return the md5 value of the class name of this node. 436 * 437 * 1. remove all spaces, return, comments inside the node<br> 438 * 2. turn number constant in where clause into ? character. <br> 439 * 3. turn string constant in where clause into ?? character. <br> 440 * 4. turn all string of the token into uppercase if it's not a delimited identifier. 441 * 442 * @return md5 hash code of the string representation of this node 443 */ 444 public String getMd5(){ 445 if (md5 != null) return md5; 446 447 StringBuilder sb = new StringBuilder(); 448 TSourceToken st = null; 449 currentIterateToken = null; 450 while (this.hasNext()){ 451 st = next(); 452 // cmtslashstar lexspace lexnewline cmtdoublehyphen 453 if ((st.tokencode == TBaseType.lexspace)||(st.tokencode == TBaseType.lexnewline) 454 ||(st.tokencode == TBaseType.cmtslashstar)||(st.tokencode == TBaseType.cmtdoublehyphen) 455 ){ 456 continue; 457 } else if ((st.tokencode == TBaseType.fconst)||(st.tokencode == TBaseType.iconst)){ 458 if (st.location == ESqlClause.where){ 459 sb.append("?"); 460 }else{ 461 sb.append(st.toString()); 462 } 463 } else if (st.tokencode == TBaseType.sconst){ 464 if (st.location == ESqlClause.where){ 465 sb.append("??"); 466 }else{ 467 sb.append(st.toString()); 468 } 469 } else if (TSQLEnv.isDelimitedIdentifier(this.dbvendor,st.toString())){ 470 sb.append(st.toString()); 471 }else { 472 sb.append(st.toString().toUpperCase()); 473 } 474 } 475 byte[] md5InBytes; 476 if (sb.toString().length() > 0){ 477 md5InBytes = gspMD5Utils.digest(sb.toString().getBytes(UTF_8)); 478 }else{ 479 md5InBytes = gspMD5Utils.digest(this.getClass().getName() .getBytes(UTF_8)); 480 } 481 md5 = gspMD5Utils.bytesToHex(md5InBytes); 482 return md5; 483 484 } 485 486 public TParseTreeNode getParentObjectName() { 487 return parent; 488 } 489 490 public void setParent(TParseTreeNode parent) { 491 this.parent = parent; 492 } 493 494 private TParseTreeNode parent = null; 495 496// public void appendAToken(TSourceToken st){ 497// // this token is not belonged to this node 498// this.getEndToken() 499// } 500 public static boolean doubleLinkedTokenListToString = true; 501 502// TSourceToken st = getStartToken(); 503// TSourceToken et = getEndToken(); 504 public void insertAfterAToken(TSourceToken anchorToken){ 505 if ((anchorToken == null)||(this.getEndToken() == null)||(this.getStartToken() == null) )return; 506 this.getEndToken().setNextTokenInChain(anchorToken.getNextTokenInChain()); 507 if (anchorToken.getNextTokenInChain() != null){ 508 anchorToken.getNextTokenInChain().setPrevTokenInChain(this.getEndToken()); 509 }else{ 510 if (anchorToken.getNodesEndWithThisToken().size() > 0){ 511 for(Object o:anchorToken.getNodesEndWithThisToken()){ 512 TParseTreeNode node = (TParseTreeNode)o; 513 node.setEndToken(this.getEndToken()); 514 } 515 } 516 } 517 anchorToken.setNextTokenInChain(this.getStartToken()); 518 this.getStartToken().setPrevTokenInChain(anchorToken); 519 520 } 521 522 public void insertNewNodeBeforeMe(TParseTreeNode newNode, boolean needCommaBefore){ 523 524 updateNodeWithTheSameStartToken(nodeActionInsert,newNode.getStartToken()); 525 526 newNode.getStartToken().setPrevTokenInChain(this.getStartToken().getPrevTokenInChain()); 527 if (this.getStartToken().getPrevTokenInChain() != null){ 528 this.getStartToken().getPrevTokenInChain().setNextTokenInChain(newNode.getStartToken()); 529 } 530 531 newNode.getEndToken().setNextTokenInChain(this.getStartToken()); 532 this.getStartToken().setPrevTokenInChain(newNode.getEndToken()); 533 534 if (needCommaBefore) { 535 TSourceToken commaToken = new TSourceToken(","); 536 newNode.getEndToken().insertANewTokenAfterMe(commaToken); 537 } 538 539 } 540 541 public void appendNewNode(TParseTreeNode newNode, boolean needCommaBefore){ 542 // the new node must be share the same parent node with this node 543 544 refreshAllNodesTokenCount(); 545 updateMeNodeWithTheSameEndToken(nodeActionAppend,newNode.getEndToken()); 546 547 newNode.getEndToken().setNextTokenInChain(this.getEndToken().getNextTokenInChain()); 548 if (this.getEndToken().getNextTokenInChain() != null){ 549 this.getEndToken().getNextTokenInChain().setPrevTokenInChain(newNode.getEndToken()); 550 } 551 552 newNode.getStartToken().setPrevTokenInChain(this.getEndToken()); 553 this.getEndToken().setNextTokenInChain(newNode.getStartToken()); 554 555 if (needCommaBefore) { 556 TSourceToken commaToken = new TSourceToken(","); 557 this.getEndToken().insertANewTokenAfterMe(commaToken); 558 } 559 560 } 561 562 public void replaceWithNewNode(TParseTreeNode newNode){ 563 if ((getStartToken() == null)||(getEndToken() == null)) return; 564 if ((newNode.getStartToken() == null)||(newNode.getEndToken() == null)) return; 565 566 if (getStartToken().getPrevTokenInChain() == null) return; 567 568 refreshAllNodesTokenCount(); 569 updateNodeWithTheSameStartToken(nodeActionUpdate,newNode.startToken); 570 updateMeNodeWithTheSameEndToken(nodeActionUpdate,newNode.endToken); 571 572 getStartToken().getPrevTokenInChain().setNextTokenInChain(newNode.getStartToken()); 573 newNode.getStartToken().setPrevTokenInChain(getStartToken().getPrevTokenInChain()); 574 575 newNode.getEndToken().setNextTokenInChain(getEndToken().getNextTokenInChain()); 576 if (getEndToken().getNextTokenInChain() != null){ 577 getEndToken().getNextTokenInChain().setPrevTokenInChain(newNode.getEndToken()); 578 } 579 580 } 581 582 583 584 TSourceToken currentIterateToken = null; 585 586 587 public void resetIterator(){ 588 currentIterateToken = null; 589 } 590 591 @Override 592 public boolean hasNext() { 593 if ((getStartToken() != null)&&(getEndToken() != null)&&(currentIterateToken != getEndToken())){ 594 return true; 595 } 596 else{ 597 return false; 598 } 599 600 } 601 602 @Override 603 public TSourceToken next() { 604 if (this.hasNext()){ 605 if (currentIterateToken == null){ 606 currentIterateToken = getStartToken(); 607 }else { 608 currentIterateToken = currentIterateToken.getNextTokenInChain(); 609 } 610 611 return currentIterateToken; 612 } 613 else 614 return null; 615 } 616 617 @Override 618 public void remove() { 619 throw new UnsupportedOperationException(); 620 } 621 622// @Override 623// public Iterator<TSourceToken> iterator() { 624// return new TokenSetIterator(); 625// } 626// 627// private class TokenSetIterator implements Iterator { 628// TSourceToken st = getStartToken(); 629// TSourceToken et = getEndToken(); 630// TSourceToken currentToken = null; 631// 632// public boolean hasNext() { 633// if ((st != null)&&(et != null)&&(currentToken != et)){ 634// return true; 635// } 636// else 637// return false; 638// } 639// 640// public TSourceToken next() { 641// if (this.hasNext()){ 642// if (currentToken == null){ 643// currentToken = st; 644// }else { 645// currentToken = currentToken.getNextTokenInChain(); 646// } 647// 648// return currentToken; 649// } 650// else 651// return null; 652// } 653// 654// @Override 655// public void remove() { 656// 657// } 658// } 659 660 public void setNodeStatus(ENodeStatus nodeStatus) { 661 this.nodeStatus = nodeStatus; 662 } 663 664 public ENodeStatus getNodeStatus() { 665 666 return nodeStatus; 667 } 668 669 private ENodeStatus nodeStatus = ENodeStatus.nsNormal; 670 671 672 673 public void addToTokenChain(TSourceToken anchorToken, boolean beforeAnchorToken){ 674 675 }; 676 677 private int tokenCount = -1; 678 679 public int getTokenCount() { 680 if (tokenCount == -1){ 681 calculateTokenCount(); 682 } 683 return tokenCount; 684 } 685 686 public void refreshAllNodesTokenCount(){ 687 if (this.startToken != null){ 688 for(int i=0;i<this.startToken.getNodesStartFromThisToken().size();i++){ 689 TParseTreeNode node = this.startToken.getNodesStartFromThisToken().get(i); 690 node.calculateTokenCount(); 691 } 692 } 693 694 if (this.endToken != null){ 695 for(int i=0;i<this.endToken.getNodesEndWithThisToken().size();i++){ 696 TParseTreeNode node = this.endToken.getNodesEndWithThisToken().get(i); 697 node.calculateTokenCount(); 698 } 699 } 700 701 } 702 703 public void calculateTokenCount(){ 704 int ret = 0; 705 TSourceToken prevst = null; 706 this.resetIterator(); 707 while (this.hasNext()){ 708 ret++; 709 TSourceToken st = this.next(); 710 if (st == null){ 711 if (TBaseType.DEBUG){ 712 System.out.println("==========ERROR: start and token is not in the same chain ===============\n"); 713 System.out.println("Start Token:"+this.getStartToken().toString()); 714 System.out.println("End Token:"+this.getEndToken().toString()); 715 if (prevst != null){ 716 System.out.println("Prev token, code:"+prevst.tokencode+", Text:"+prevst.toString()); 717 }else{ 718 System.out.println("Prev token is null"); 719 } 720 System.out.println("============================\n"); 721 } 722 break; 723 } 724 prevst = st; 725 } 726 this.resetIterator(); 727 this.tokenCount = ret; 728 } 729 730 public final static int nodeActionUnknown = 0; 731 public final static int nodeActionRemove = 1; 732 public final static int nodeActionInsert = 2; 733 public final static int nodeActionUpdate = 3; 734 public final static int nodeActionUpdateText = 4; 735 public final static int nodeActionAppend = 5; 736 public final static int nodeChangeStartToken = 6; 737 public final static int nodeChangeEndToken = 7; 738 739 void updateStatusOfNodeShareSameTokens(int nodeAction){ 740 TSourceToken startToken = getStartToken(); 741 TSourceToken endToken = getEndToken(); 742 if (endToken == null) return; 743 if (startToken == null) return; 744 745 // System.out.println("node token count:"+thisNodeTokenCount); 746 this.resetIterator(); 747 while(this.hasNext()){ 748 TSourceToken st = this.next(); 749 // System.out.println(st.toString()); 750 if (st == null) break; 751 752 for(int i=0;i<st.getNodesStartFromThisToken().size();i++){ 753 TParseTreeNode node = st.getNodesStartFromThisToken().get(i); 754 if (st == startToken){ 755 if (this == node){ 756 switch (nodeAction){ 757 case nodeActionRemove: 758 node.nodeStatus = ENodeStatus.nsRemoved; 759 break; 760 default: 761 node.nodeStatus = ENodeStatus.nsDetached; 762 break; 763 } 764 }else if (node.getTokenCount() == this.getTokenCount()){ 765 switch (nodeAction){ 766 case nodeActionRemove: 767 node.nodeStatus = ENodeStatus.nsRemoved; 768 break; 769 default: 770 node.nodeStatus = ENodeStatus.nsDetached; 771 break; 772 } 773 }else if (node.getTokenCount() > this.getTokenCount()){ 774 node.nodeStatus = ENodeStatus.nsPartitial; 775 }else{ 776 node.nodeStatus = ENodeStatus.nsDetached; 777 } 778 }else{ 779 node.nodeStatus = ENodeStatus.nsDetached; 780 } 781 } 782 783 for(int i=0;i<st.getNodesEndWithThisToken().size();i++){ 784 TParseTreeNode node = st.getNodesEndWithThisToken().get(i); 785 if (st == endToken){ 786 if (this == node){ 787 switch (nodeAction){ 788 case nodeActionRemove: 789 node.nodeStatus = ENodeStatus.nsRemoved; 790 break; 791 default: 792 node.nodeStatus = ENodeStatus.nsDetached; 793 break; 794 } 795 }else if (node.getTokenCount() == this.getTokenCount()){ 796 switch (nodeAction){ 797 case nodeActionRemove: 798 node.nodeStatus = ENodeStatus.nsRemoved; 799 break; 800 default: 801 node.nodeStatus = ENodeStatus.nsDetached; 802 break; 803 } 804 }else if (node.getTokenCount() > this.getTokenCount()){ 805 node.nodeStatus = ENodeStatus.nsPartitial; 806 }else{ 807 switch (nodeAction){ 808 case nodeActionRemove: 809 node.nodeStatus = ENodeStatus.nsRemoved; 810 break; 811 default: 812 node.nodeStatus = ENodeStatus.nsDetached; 813 break; 814 } 815 } 816 }else{ 817 switch (nodeAction){ 818 case nodeActionRemove: 819 node.nodeStatus = ENodeStatus.nsRemoved; 820 break; 821 default: 822 node.nodeStatus = ENodeStatus.nsDetached; 823 break; 824 } 825 } 826 } 827 828 } 829 this.resetIterator(); 830 } 831 832 void updateNodeWithTheSameStartToken(int nodeAction, TSourceToken newStartToken){ 833 TSourceToken oldStartToken = this.getStartToken(); 834 for(int i=0;i<oldStartToken.getNodesStartFromThisToken().size();i++){ 835 TParseTreeNode node = oldStartToken.getNodesStartFromThisToken().get(i); 836 if (node == this){ // myself 837 switch (nodeAction){ 838 case nodeActionRemove: 839 node.startToken = null; 840 break; 841 case nodeActionUpdate: 842 case nodeActionUpdateText: 843 case nodeChangeStartToken: 844 node.startToken = newStartToken; 845 newStartToken.getNodesStartFromThisToken().push(node); 846 default: 847 break; 848 } 849 }else if((node.getTokenCount() == this.getTokenCount())){ // node with same tokens 850 switch (nodeAction){ 851 case nodeActionRemove: 852 node.startToken = null; 853 break; 854 case nodeActionUpdate: 855 case nodeActionUpdateText: 856 case nodeChangeStartToken: 857 node.startToken = newStartToken; 858 newStartToken.getNodesStartFromThisToken().push(node); 859 default: 860 break; 861 } 862 } 863 else if((node.getTokenCount() > this.getTokenCount())) { // parent node 864 node.startToken = newStartToken; 865 newStartToken.getNodesStartFromThisToken().push(node); 866 }else{ // sub node 867 868 } 869 } 870 } 871 872 void updateMeNodeWithTheSameEndToken(int nodeAction, TSourceToken newEndToken){ 873 TSourceToken oldEndToken = this.getEndToken(); 874 for(int i=0;i<oldEndToken.getNodesEndWithThisToken().size();i++){ 875 TParseTreeNode node = oldEndToken.getNodesEndWithThisToken().get(i); 876 if (node == this){ // myself 877 switch (nodeAction){ 878 case nodeActionRemove: 879 node.endToken = null; 880 break; 881 case nodeActionUpdate: 882 case nodeActionUpdateText: 883 case nodeChangeEndToken: 884 node.endToken = newEndToken; 885 newEndToken.getNodesEndWithThisToken().push(node); 886 default: 887 break; 888 } 889 }else if((node.getTokenCount() == this.getTokenCount())){ // node with same tokens 890 switch (nodeAction){ 891 case nodeActionRemove: 892 node.endToken = null; 893 break; 894 case nodeActionUpdate: 895 case nodeActionUpdateText: 896 case nodeChangeEndToken: 897 node.endToken = newEndToken; 898 newEndToken.getNodesEndWithThisToken().push(node); 899 default: 900 break; 901 } 902 } 903 else if((node.getTokenCount() > this.getTokenCount())) { // parent node 904 node.endToken = newEndToken; 905 newEndToken.getNodesEndWithThisToken().push(node); 906 }else{ // sub node 907 908 } 909 } 910 } 911 912 /** 913 * both begin and end token will be removed from the chain 914 * 915 * @param startToken 916 * @param endToken 917 */ 918 public static void removeTokensBetweenToken(TSourceToken startToken, TSourceToken endToken){ 919 if ((startToken == null)||(endToken == null)) return; 920 startToken.removeFromChain(); 921 while (startToken != endToken){ 922 startToken = startToken.getNextTokenInChain(); 923 if (startToken == null) break; 924 startToken.removeFromChain(); 925 } 926 } 927 928 public static void removeTokensBetweenNodes(TParseTreeNode startNode, TParseTreeNode endNode){ 929 if ((startNode == null)||(endNode == null)) return; 930 if ((startNode.getEndToken() == null)||(endNode.getStartToken() == null)) return; 931 TSourceToken startToken = startNode.getEndToken().getNextTokenInChain(); 932 TSourceToken endToken = endNode.getStartToken().getPrevTokenInChain(); 933 if ((startToken == null)||(endToken == null)) return; 934 935 startToken.removeFromChain(); 936 while (startToken != endToken){ 937 startToken = startToken.getNextTokenInChain(); 938 if (startToken == null) break; 939 startToken.removeFromChain(); 940 } 941 } 942 943 public void removeTokens(){ 944 if (this.nodeStatus == ENodeStatus.nsRemoved) return; 945 946 TSourceToken oldStartToken = getStartToken(); 947 TSourceToken oldEndToken = getEndToken(); 948 if (oldEndToken == null) return; 949 if (oldStartToken == null) return; 950 951 refreshAllNodesTokenCount(); 952 updateStatusOfNodeShareSameTokens(nodeActionRemove); 953 954 if (oldStartToken.getPrevTokenInChain() != null){ 955 updateMeNodeWithTheSameEndToken(nodeActionRemove,oldStartToken.getPrevTokenInChain()); 956 oldStartToken.getPrevTokenInChain().setNextTokenInChain(oldEndToken.getNextTokenInChain()); 957 } 958 if (oldEndToken.getNextTokenInChain() != null){ 959 updateNodeWithTheSameStartToken(nodeActionRemove,oldEndToken.getNextTokenInChain()); 960 oldEndToken.getNextTokenInChain().setPrevTokenInChain(oldStartToken.getPrevTokenInChain()); 961 } 962 963 if (this.nodeStatus == ENodeStatus.nsNormal){ 964 this.nodeStatus = ENodeStatus.nsRemoved; 965 } 966 967 this.setStartTokenDirectly(null); 968 this.setEndTokenDirectly(null); 969 970 } 971 972 973 974 /** 975 * SQL dialect of this statement. 976 */ 977 public EDbVendor dbvendor = EDbVendor.dbvgeneric; 978 979 /** 980 * The parser that generate this node. 981 * 982 * @return parser that generate this node 983 */ 984 public TGSqlParser getGsqlparser() { 985 if (gsqlparser == null){ 986 if (getStartToken() != null){ 987 // TODO removed in 2.6.8.1, to avoid memory leak in TTypeName 988 // gsqlparser = getStartToken().getGsqlparser(); 989 } 990 } 991 return gsqlparser; 992 } 993 994 boolean includingComment = true; 995 996 997 public void setIncludingComment(boolean includingComment) { 998 this.includingComment = includingComment; 999 } 1000 1001 private TGSqlParser gsqlparser = null; 1002 1003 public void setGsqlparser(TGSqlParser gsqlparser) { 1004 this.gsqlparser = gsqlparser; 1005 if (gsqlparser != null){ 1006 this.dbvendor = gsqlparser.getDbVendor(); 1007 } 1008 } 1009 1010 private int dummyTag = 0; 1011 1012 public void setDummyTag(int dummyTag) { 1013 this.dummyTag = dummyTag; 1014 } 1015 1016 /** 1017 * A temporary value can be used for any purposes 1018 * 1019 * @return a temporary value used for any purposes 1020 */ 1021 public int getDummyTag() { 1022 return dummyTag; 1023 } 1024 1025 1026 private TSourceToken startToken; 1027 1028 /** 1029 * The first token in this parse tree node 1030 * 1031 * @return the first token of node 1032 */ 1033 public TSourceToken getStartToken() { 1034 return startToken; 1035 } 1036 1037 /** 1038 * The last token of the node 1039 * 1040 * @return the last token of node 1041 */ 1042 public TSourceToken getEndToken() { 1043 return endToken; 1044 } 1045 1046 private TSourceToken endToken; 1047 private long lineNo = -1; 1048 1049 /** 1050 * Column position of the first token of this node 1051 * 1052 * @return column position 1053 */ 1054 public long getColumnNo() { 1055 if (this.getStartToken() != null){ 1056 columnNo = this.getStartToken().columnNo; 1057 } 1058 return columnNo; 1059 } 1060 1061 /** 1062 * Line number of the first token of this node 1063 * 1064 * @return line number 1065 */ 1066 public long getLineNo() { 1067 if (this.getStartToken() != null){ 1068 lineNo = this.getStartToken().lineNo; 1069 } 1070 return lineNo; 1071 } 1072 1073 private long columnNo = -1; 1074 1075 private int nodeType; 1076 /** 1077 * Set the node type for this node. 1078 * 1079 * @param nodeType The node type. 1080 */ 1081 public void setNodeType(int nodeType) 1082 { 1083 this.nodeType = nodeType; 1084 } 1085 1086 /** 1087 * A unique value to distinguish this node from others 1088 * 1089 * @return enum value of node type 1090 * @see gudusoft.gsqlparser.nodes.ENodeType 1091 */ 1092 public int getNodeType() 1093 { 1094 return nodeType; 1095 } 1096 1097 String getastext(){ return "";} 1098 String getasprettytext(){ return "";} 1099 1100 /** 1101 * Initialize a query tree node. 1102 * Used internally 1103 * 1104 * @param arg1 first argument 1105 */ 1106 public void init(Object arg1) 1107 { 1108 } 1109 1110 public void init(Object arg1, Object arg2) 1111 { 1112 } 1113 1114 public void init(Object arg1, Object arg2, Object arg3) 1115 { 1116 } 1117 1118 public void init(Object arg1, Object arg2, Object arg3, Object arg4) 1119 { 1120 } 1121 1122 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) 1123 { 1124 } 1125 1126 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) 1127 { 1128 } 1129 1130 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) 1131 { 1132 } 1133 1134 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8) 1135 { 1136 } 1137 1138 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9) 1139 { 1140 } 1141 1142 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, Object arg10) 1143 { 1144 } 1145 1146 /** 1147 * Analyze the sub-nodes inside this node. 1148 * Such as build the relationship between table and column. 1149 * 1150 * @param psql SQL statement this node belongs to 1151 * @param plocation SQL clause this node belongs to 1152 */ 1153 public void doParse(TCustomSqlStatement psql, ESqlClause plocation){ 1154 1155 } 1156 1157 public void setStartToken(ArrayList startNode) { 1158 if (startNode == null) return; 1159 if (startNode.size() == 0) return; 1160 TParseTreeNode parseTreeNode = (TParseTreeNode)startNode.get(0); 1161 this.setStartToken(parseTreeNode.getStartToken()); 1162 } 1163 1164 public void setEndToken(ArrayList endNode) { 1165 if (endNode == null) return; 1166 if (endNode.size() == 0 ) return; 1167 TParseTreeNode parseTreeNode = (TParseTreeNode)endNode.get(endNode.size()-1); 1168 this.setEndToken(parseTreeNode.getEndToken()); 1169 } 1170 1171 public void setStartToken(TSourceToken newStartToken) { 1172 if (newStartToken == null) return; 1173 TSourceToken oldStartToken = this.startToken; 1174 if (oldStartToken != null){ 1175 if (oldStartToken.getNodesStartFromThisToken().peek() == this){ 1176 oldStartToken.getNodesStartFromThisToken().pop(); 1177 } 1178 } 1179 if (newStartToken.getNodesStartFromThisToken().indexOf(this) == -1) 1180 { 1181 newStartToken.getNodesStartFromThisToken().push(this); 1182 } 1183 1184 this.startToken = newStartToken; 1185 } 1186 1187 public void setStartToken(TSourceTokenList startTokenList) { 1188 if (startTokenList == null) return; 1189 this.setStartToken(startTokenList.get(0)); 1190 } 1191 1192 public void setStartToken(TParseTreeNode startNode) { 1193 if (startNode == null) return; 1194 this.setStartToken(startNode.getStartToken()); 1195 } 1196 1197 public void setStartTokenDirectly(TSourceToken newStartToken) { 1198 this.startToken = newStartToken; 1199 } 1200 public void setEndTokenDirectly(TSourceToken newEndToken) { 1201 this.endToken = newEndToken; 1202 } 1203 1204 public void setEndToken(TSourceToken newEndToken) { 1205 if (newEndToken == null) return; 1206 TSourceToken oldEndToken = this.getEndToken(); 1207 1208 if (oldEndToken != null){ 1209 if (oldEndToken.getNodesEndWithThisToken().peek() == this){ 1210 oldEndToken.getNodesEndWithThisToken().pop(); 1211 } 1212 } 1213 1214 if (newEndToken.getNodesEndWithThisToken().indexOf(this) == -1){ 1215 newEndToken.getNodesEndWithThisToken().push(this); 1216 } 1217 1218 this.endToken = newEndToken; 1219 } 1220 1221 public void setEndToken(TSourceTokenList endTokenList) { 1222 if (endTokenList == null) return; 1223 this.setEndToken(endTokenList.get(endTokenList.size()-1)); 1224 } 1225 1226// public void setEndToken(TParseTreeNodeList endNodeList) { 1227// if (endNodeList == null) return; 1228// this.setEndToken(endNodeList.getEndToken()); 1229// } 1230 1231 1232 1233 public void setEndToken(TFromTableList endNode) { 1234 if (endNode == null) return; 1235 this.setEndToken(endNode.getEndToken()); 1236 } 1237 1238 public void setEndToken(TParseTreeNode endNode) { 1239 if (endNode == null) return; 1240 this.setEndToken(endNode.getEndToken()); 1241 } 1242 1243 private void setStartTokenToNull(){ 1244 this.startToken = null; 1245 } 1246 1247 private void setEndTokenToNull(){ 1248 this.endToken = null; 1249 } 1250 1251 private void resetStartAndEndTokenBeforeRemoveTokens(TSourceTokenList stList, int start, int end,boolean isReplace,TSourceToken relpacedStartToken,TSourceToken replacedEndToken){ 1252 1253 for(int m = start; m<=end;m++){ 1254 if ((m>stList.size()-1) ||(m<0)) break; 1255 TSourceToken deletedToken = stList.get(m); 1256 for(int i=0;i<deletedToken.getNodesStartFromThisToken().size();i++){ 1257 TParseTreeNode node = deletedToken.getNodesStartFromThisToken().get(i); 1258 TSourceToken EndSt = node.getEndToken(); 1259 1260 if (isReplace&&((m==start)||(m==end))){ 1261 node.setStartToken(relpacedStartToken); 1262 }else{ 1263 if (EndSt == null){ 1264 node.setStartTokenToNull(); 1265 }else{ 1266 if (EndSt.posinlist > end){ 1267 node.setStartToken(stList.get(end+1)); 1268 }else if (EndSt.posinlist < start){ 1269 node.setStartTokenToNull(); 1270 node.setEndTokenToNull(); 1271 }else{ 1272 node.setStartTokenToNull(); 1273 node.setEndTokenToNull(); 1274 } 1275 } 1276 } 1277 } // nodes start from this token 1278 1279 for (int i=0;i<deletedToken.getNodesEndWithThisToken().size();i++){ 1280 TParseTreeNode node = deletedToken.getNodesEndWithThisToken().get(i); 1281 TSourceToken startSt = node.getStartToken(); 1282 1283 if (isReplace&&((m==start)||(m==end))){ 1284 node.setEndToken(replacedEndToken); 1285 }else{ 1286 if (startSt == null){ 1287 node.setEndTokenToNull(); 1288 }else{ 1289 if (startSt.posinlist > end){ 1290 node.setStartTokenToNull(); 1291 node.setEndTokenToNull(); 1292 }else if (startSt.posinlist < start){ 1293 node.setEndToken(stList.get(start - 1)); 1294 }else{ 1295 node.setStartTokenToNull(); 1296 node.setEndTokenToNull(); 1297 } 1298 } 1299 } 1300 }// nodes end with this token 1301 1302 }//loop all tokens to be deleted in this parse tree nodes 1303 } 1304 1305 1306 1307 private void fastResetStartAndEndTokenBeforeRemoveTokens(TSourceTokenList stList, int start, int end,TSourceToken replacedToken){ 1308 int[] startEnd; 1309 if (start != end) { 1310 startEnd = new int[2]; 1311 startEnd[0] = start; 1312 startEnd[1] = end; 1313 } 1314 else { 1315 startEnd = new int[1]; 1316 startEnd[0] = start; 1317 } 1318 for(int m : startEnd){ 1319 TSourceToken deletedToken = stList.get(m); 1320 for(int i=0;i<deletedToken.getNodesStartFromThisToken().size();i++){ 1321 TParseTreeNode node = deletedToken.getNodesStartFromThisToken().get(i); 1322 node.setStartToken(replacedToken); 1323 } // nodes start from this token 1324 1325 for (int i=0;i<deletedToken.getNodesEndWithThisToken().size();i++){ 1326 TParseTreeNode node = deletedToken.getNodesEndWithThisToken().get(i); 1327 node.setEndToken(replacedToken); 1328 }// nodes end with this token 1329 1330 }//loop all tokens to be deleted in this parse tree nodes 1331 } 1332 1333 /** 1334 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1335 * Set the new string text of a node will destroy all the sub-node structure 1336 * 1337 * @param sqlSegment that override original text of this node. 1338 */ 1339 1340 /* 1341 * <p>1. new string will be tokenized into a list of source tokens: stlist 1342 * <p>2. link alternativetoken of start token of this node to the first token in stlist generated in step 1. 1343 * <p>3. link back alternative token of last token in stlist to the last token of this node. 1344 * <p>So, if you find a start token of parse tree node has an alternativetoken, then text of this node should be modified by using setString(String sqlSegment) 1345 * <p> if a parse tree node with an alias clause like: tablename as tablealias, 1346 * <p> TTable.setString() will replace whole text "tablename as tablealias" including alias clause. 1347 * 1348 * steps to set new string(sqlSegment) of parse tree node 1349 * length(sqlSegment)=0 or sqlSegment = one space,, means remove this node from parse tree 1350 * length(sqlSegment) > 0 , means replace source tokens of this node with new tokens 1351 * 1352 */ 1353 public void setString2(String sqlSegment){ 1354 1355// TGSqlParser l_sqlparser = new TGSqlParser(getGsqlparser().getDbVendor()); 1356// l_sqlparser.sqltext = sqlSegment; 1357// l_sqlparser.tokenizeSqltext(); 1358 1359 TSingletonParser singletonParser = TSingletonParser.getInstance(); 1360 1361 TSourceTokenList stList; 1362 if (this.dbvendor == EDbVendor.dbvgeneric){ 1363 stList = singletonParser.getTokens(TGSqlParser.currentDBVendor,sqlSegment); 1364 }else{ 1365 stList = singletonParser.getTokens(this.dbvendor,sqlSegment); 1366 } 1367 1368 1369 1370 if ((getStartToken() == null)&&(getEndToken() == null)){ 1371 // this is a manually created parse tree node, not created by yacc 1372// l_sqlparser.sourcetokenlist.get(0).container = l_sqlparser.sourcetokenlist; 1373// l_sqlparser.sourcetokenlist.get(l_sqlparser.sourcetokenlist.size()-1).container = l_sqlparser.sourcetokenlist; 1374 setStartToken(stList.get(0)); 1375 setEndToken(stList.get(stList.size()-1)); 1376 return; 1377 } 1378 1379 TSourceToken lcStartToken = getStartToken(); 1380 TSourceToken lcEndToken = getEndToken(); 1381 1382 // TSourceTokenList stList = lcEndToken.container; 1383 1384 int lcStartTokenPos = lcStartToken.posinlist; 1385 int lcEndTokenPos = lcEndToken.posinlist; 1386 1387 1388 if ((sqlSegment.length()==0) || (sqlSegment == " ")){ 1389 1390 //reset start and end token of those parse tree node 1391 resetStartAndEndTokenBeforeRemoveTokens(stList, lcStartTokenPos, lcEndTokenPos,false,null,null); 1392 1393 // remove token from list 1394 for(int i=lcEndTokenPos;i>=lcStartTokenPos;i--){ 1395 stList.remove(i); 1396 } 1397 1398 // reindex posinlist after remove token from container 1399 for(int i=lcStartTokenPos;i<stList.size();i++){ 1400 stList.get(i).posinlist = i; 1401 } 1402 1403 }else{ 1404 //replace 1405 //reset start and end token of those parse tree node 1406 TSourceToken lcReplacedStartToken = stList.get(0); 1407 TSourceToken lcReplacedEndToken = stList.get(stList.size()-1); 1408 resetStartAndEndTokenBeforeRemoveTokens(stList, lcStartTokenPos, lcEndTokenPos,true,lcReplacedStartToken,lcReplacedEndToken); 1409 1410 // remove token from list 1411 for(int i=lcEndTokenPos;i>=lcStartTokenPos;i--){ 1412 stList.remove(i); 1413 } 1414 1415 1416// for(int i = stList.size()-1; i>=0; i--){ 1417// TSourceToken st = stList.get(i); 1418// st.container = stList; 1419// stList.add(lcStartTokenPos,st); 1420// } 1421 1422 // reindex posinlist after remove token from container 1423 for(int i=lcStartTokenPos;i<stList.size();i++){ 1424 TSourceToken st = stList.get(i); 1425 st.posinlist = i; 1426 } 1427 1428 } 1429 1430 } 1431 1432 /** 1433 * Set the text of the node and update the corresponding source tokens synchronously. 1434 * This way, when the toString() method of the node itself or its parent node is called, 1435 * it will return the modified text. 1436 * 1437 * @param sqlSegment 1438 */ 1439 public void setString(String sqlSegment){ 1440 if (doubleLinkedTokenListToString){ 1441 setText(sqlSegment); 1442 }else{ 1443 setString2(sqlSegment); 1444 } 1445 } 1446 1447 public void fastSetString(String sqlSegment){ 1448 1449 TSourceToken replaceToken = new TSourceToken(sqlSegment); 1450 setText(replaceToken, replaceToken); 1451 } 1452 1453 1454 /** 1455 * return null if no comment is found before this node. 1456 * 1457 * @return the comment before this node 1458 */ 1459 public String getCommentBeforeNode(){ 1460 String ret = null; 1461 TSourceToken lcStartToken; 1462 1463 if (doubleLinkedTokenListToString){ 1464 lcStartToken = getStartToken(); 1465 if (lcStartToken == null) return null; 1466 1467 TSourceToken lcCurrentToken = lcStartToken.getPrevTokenInChain(); 1468 while (lcCurrentToken != null){ 1469 if ((lcCurrentToken.tokentype == ETokenType.ttreturn)||(lcCurrentToken.tokentype == ETokenType.ttwhitespace)) 1470 { 1471 if (ret != null){ 1472 ret = lcCurrentToken.toString() + ret ; 1473 } 1474 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1475 continue; 1476 } 1477 else if (lcCurrentToken.toString().trim().length() == 0) { 1478 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1479 continue; 1480 } 1481 if ((lcCurrentToken.tokencode == TBaseType.cmtslashstar) ||(lcCurrentToken.tokencode == TBaseType.cmtdoublehyphen) 1482 || lcCurrentToken.toString().startsWith("--") || lcCurrentToken.toString().startsWith("/*") 1483 ){ 1484 if (ret == null){ 1485 ret = lcCurrentToken.toString(); 1486 }else{ 1487 ret = lcCurrentToken.toString()+ ret; 1488 } 1489 1490 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1491 continue; 1492 } 1493 1494 break; 1495 } 1496 }else{ 1497 lcStartToken = getStartToken(); 1498 if (lcStartToken == null) return null; 1499 1500 TSourceTokenList stList = lcStartToken.container; 1501 if (stList == null) return null; 1502 1503 int b = lcStartToken.posinlist; 1504 1505 for(int i = b - 1 ; i>=0;i--){ 1506 TSourceToken st = stList.get(i); 1507 if ((st.tokencode == TBaseType.lexspace)|| (st.tokencode == TBaseType.lexnewline)) continue; 1508 if (st.getAstext().trim().length() == 0) continue; 1509 if ((st.tokencode == TBaseType.cmtslashstar) ||(st.tokencode == TBaseType.cmtdoublehyphen)){ 1510 if (ret == null){ 1511 ret = st.toString(); 1512 }else{ 1513 ret = st.toString()+TBaseType.newline + ret; 1514 } 1515 continue; 1516 } 1517 else if (st.getAstext().startsWith("--") || st.getAstext().startsWith("/*")){ 1518 if (ret == null){ 1519 ret = st.toString(); 1520 }else{ 1521 ret = st.toString()+TBaseType.newline + ret; 1522 } 1523 continue; 1524 } 1525 break; 1526 } 1527 } 1528 1529 return ret; 1530 } 1531 1532 public String getCommentAfterNode(){ 1533 String ret = null; 1534 TSourceToken lcEndToken; 1535 1536 if (!doubleLinkedTokenListToString) return ret; 1537 1538 lcEndToken = getEndToken(); 1539 if (lcEndToken == null) return null; 1540 1541 TSourceToken lcCurrentToken = lcEndToken; 1542 while (lcCurrentToken != null){ 1543 if ((lcCurrentToken.tokentype == ETokenType.ttreturn)||(lcCurrentToken.tokentype == ETokenType.ttwhitespace)) 1544 { 1545 if (ret != null){ 1546 ret = lcCurrentToken.toString() + ret ; 1547 } 1548 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1549 continue; 1550 } 1551 else if (lcCurrentToken.getAstext().trim().length() == 0) { 1552 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1553 continue; 1554 } 1555 if ((lcCurrentToken.tokencode == TBaseType.cmtslashstar) ||(lcCurrentToken.tokencode == TBaseType.cmtdoublehyphen) 1556 || lcCurrentToken.getAstext().startsWith("--") || lcCurrentToken.getAstext().startsWith("/*") 1557 ){ 1558 if (ret == null){ 1559 ret = lcCurrentToken.toString(); 1560 }else{ 1561 ret = lcCurrentToken.toString() +ret ; 1562 } 1563 1564 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1565 continue; 1566 } 1567 1568 break; 1569 } 1570 1571 return ret.trim(); // 移调第一个space or return token if any 1572 1573 } 1574 1575 /** 1576 * String representation of this parse tree node. 1577 * <p> 1578 * This string was generated by collecting text from the start token of this node to the end token of this node. 1579 * If this node was modified, then use {@link #toScript()} to get string representation of this node. 1580 * 1581 * @return string representation of this parse tree node 1582 */ 1583 public String toString2(){ 1584 String ret = null; 1585 TSourceToken lcStartToken = getStartToken(); 1586 if (lcStartToken == null) return null; 1587 1588 TSourceToken lcEndToken = getEndToken(); 1589 if (lcEndToken == null) return null; 1590 1591 TSourceTokenList stList = lcStartToken.container; 1592 if (stList == null) return null; 1593 1594 int b = lcStartToken.posinlist; 1595 int e = lcEndToken.posinlist; 1596 1597 StringBuffer sb = new StringBuffer(""); 1598 1599 for(int i= b ; i<=e;i++){ 1600 if(!includingComment && ((stList.get(i).tokencode == TBaseType.cmtslashstar) ||(stList.get(i).tokencode == TBaseType.cmtdoublehyphen))){ 1601 continue; 1602 } 1603 sb.append(stList.get(i).toString()); 1604 } 1605 1606 return sb.toString(); 1607 } 1608 1609 /** 1610 * <p> 1611 * 将一个语法树节点({@code TParseTreeNode})转换回它对应的原始SQL字符串。 1612 * 它通过遍历一个由 {@code startToken}(起始词法单元)和 {@code endToken}(结束词法单元)界定的双向链表来实现这一功能。 1613 * </p> 1614 * <p> 1615 * 这个方法的设计采用了“两遍扫描”(Two-Pass)的策略,也就是使用了两个 {@code while} 循环。 1616 * 第一个循环是预处理阶段,用于清理格式;第二个循环则负责构建最终的字符串。 1617 * </p> 1618 * <h3>第一个 {@code while} 循环</h3> 1619 * <p> 1620 * 这个循环的主要目标是识别并“软删除”多余的换行符(newline tokens)。这种情况尤其在SQL被程序动态修改后容易出现。 1621 * 这个循环本身不构建字符串,只负责分析和更新词法单元的状态。 1622 * 其工作原理是: 1623 * <ol> 1624 * <li><b>遍历:</b> 它从 {@code startToken} 开始,通过 {@code getNextTokenInChain()} 方法遍历到 {@code endToken}。</li> 1625 * <li><b>检测链表变化:</b> 它通过比较当前和前一个词法单元的 {@code posinlist}(词法单元在原始完整列表中的位置索引)来判断链表是否“自然”。 1626 * 如果不连续,意味着词法单元被插入或重排了,此时 {@code isChainModified} 标志位会被设为 {@code true}。</li> 1627 * <li><b>标记换行符:</b> 当遇到第一个换行符时,它会设置一个标志。如果紧接着又遇到了另一个换行符,并且此时链表已经被修改过, 1628 * 那么它会将这个多余的换行符标记为待删除状态。这个逻辑能有效地将代码被修改后可能产生的多个连续换行压缩成一个。</li> 1629 * </ol> 1630 * 简而言之,第一个循环是一个格式清理过程,它为后续生成整洁的字符串做准备。 1631 * </p> 1632 * <h3>第二个 {@code while} 循环</h3> 1633 * <p> 1634 * 这个循环负责从(经过第一步清理后的)词法单元流中实际地构建最终的输出字符串。 1635 * 其工作原理是: 1636 * <ol> 1637 * <li><b>遍历:</b> 它再次遍历完全相同的词法单元序列。</li> 1638 * <li><b>构建字符串:</b> 在将每个词法单元追加到 {@code StringBuffer} 之前,它会进行检查。</li> 1639 * <li><b>跳过特定单元:</b> 如果一个词法单元在第一个循环中被标记为待删除,或者它是一个注释(且配置为不包含注释),那么它将被跳过。</li> 1640 * <li><b>拼接字符串:</b> 对于所有未被跳过的词法单元,它会将其字符串值追加到 {@code StringBuffer} 中。</li> 1641 * </ol> 1642 * 当这个循环结束后,{@code StringBuffer} 就包含了该语法树节点的最终、重构后的SQL文本。 1643 * </p> 1644 * @return 节点对应的SQL字符串。 1645 */ 1646 public String toString(){ 1647 if (doubleLinkedTokenListToString){ 1648 TSourceToken startToken = getStartToken(); 1649 TSourceToken endToken = getEndToken(); 1650 1651 // Return null if boundary tokens are missing 1652 if (startToken == null || endToken == null) { 1653 return null; 1654 } 1655 1656 // First pass: Mark redundant newlines for deletion 1657 // This cleans up formatting issues caused by programmatic SQL modifications 1658 TSourceToken currentToken = startToken; 1659 TSourceToken previousToken = null; 1660 boolean shouldIgnoreNextNewline = false; 1661 boolean tokenChainWasModified = false; 1662 1663 while (currentToken != null) { 1664 // Detect if the token chain has been modified by checking position continuity 1665 if (!tokenChainWasModified && previousToken != null) { 1666 tokenChainWasModified = (currentToken.posinlist - previousToken.posinlist != 1); 1667 } 1668 1669 // Handle newline tokens - mark redundant ones for deletion 1670 if (currentToken.tokentype == ETokenType.ttreturn) { 1671 boolean isRedundantNewline = shouldIgnoreNextNewline && tokenChainWasModified; 1672 1673 if (isRedundantNewline) { 1674 currentToken.tokenstatus = ETokenStatus.tsdeleted; 1675 } else { 1676 shouldIgnoreNextNewline = true; 1677 } 1678 } else if (currentToken.tokentype != ETokenType.ttwhitespace) { 1679 // Reset newline flag when encountering non-whitespace tokens 1680 shouldIgnoreNextNewline = false; 1681 } 1682 1683 previousToken = currentToken; 1684 1685 // Move to next token or break if we've reached the end 1686 if (currentToken.equals(endToken)) { 1687 break; 1688 } 1689 currentToken = currentToken.getNextTokenInChain(); 1690 } 1691 1692 // Second pass: Build the final string by concatenating valid tokens 1693 StringBuffer result = new StringBuffer(); 1694 currentToken = startToken; 1695 1696 while (currentToken != null) { 1697 boolean shouldSkipToken = currentToken.tokenstatus == ETokenStatus.tsdeleted || 1698 (!includingComment && (currentToken.tokencode == TBaseType.cmtslashstar || 1699 currentToken.tokencode == TBaseType.cmtdoublehyphen) 1700 ); 1701 1702 if (!shouldSkipToken) { 1703 result.append(currentToken.toString()); 1704 } 1705 1706 // Move to next token or break if we've reached the end 1707 if (currentToken.equals(endToken)) { 1708 break; 1709 } 1710 currentToken = currentToken.getNextTokenInChain(); 1711 } 1712 1713 return result.toString(); 1714 } else { 1715 return toString2(); 1716 } 1717 } 1718 1719 1720 void setText(String nodeText, EDbVendor dbVendor){ 1721 this.dbvendor = dbVendor; 1722 setText(nodeText); 1723 } 1724 1725 private boolean tokensInChain = true; 1726 1727 public boolean isTokensInChain() { 1728 return tokensInChain; 1729 } 1730 1731 void setText(TSourceToken startToken, TSourceToken endToken){ 1732 TSourceToken newStartToken = startToken; 1733 TSourceToken newEndToken = endToken; 1734 1735 TSourceToken oldStartToken = getStartToken(); 1736 TSourceToken oldEndToken = getEndToken(); 1737 tokensInChain = false; 1738 if ((oldStartToken == null)||(oldEndToken == null)){ 1739 setStartToken(newStartToken); 1740 setEndToken(newEndToken); 1741 nodeStatus = ENodeStatus.nsFlattened; 1742 return; 1743 } 1744 1745 refreshAllNodesTokenCount(); 1746 1747 updateStatusOfNodeShareSameTokens(nodeActionUpdateText); 1748 1749 updateNodeWithTheSameStartToken(nodeActionUpdateText,newStartToken); 1750 updateMeNodeWithTheSameEndToken(nodeActionUpdateText,newEndToken); 1751 1752 if (oldStartToken.getPrevTokenInChain() != null){ 1753 oldStartToken.getPrevTokenInChain().setNextTokenInChain(newStartToken); 1754 newStartToken.setPrevTokenInChain(oldStartToken.getPrevTokenInChain()); 1755 } 1756 1757 if (oldEndToken.getNextTokenInChain() != null){ 1758 oldEndToken.getNextTokenInChain().setPrevTokenInChain(newEndToken); 1759 newEndToken.setNextTokenInChain(oldEndToken.getNextTokenInChain()); 1760 } 1761 1762 tokensInChain = true; 1763 } 1764 /** 1765 * set text of this node, if the original token of this node 1766 * is in a token chain, then merge the new token into the chain as well. 1767 * 1768 * use double linked list 1769 * 1770 * @param nodeText node text 1771 */ 1772 void setText(String nodeText){ 1773 if ((this.getNodeStatus() == ENodeStatus.nsRemoved)||(nodeText.length() == 0)) return; 1774 // if ((nodeText.length() == 0)) return; 1775 1776// TGSqlParser sqlParser = new TGSqlParser(TGSqlParser.currentDBVendor); 1777// sqlParser.sqltext = nodeText; 1778// sqlParser.tokenizeSqltext(); 1779// TSourceToken newStartToken = sqlParser.getSourcetokenlist().get(0); 1780// TSourceToken newEndToken = sqlParser.getSourcetokenlist().get(sqlParser.getSourcetokenlist().size() - 1); 1781// setText(newStartToken, newEndToken); 1782 1783 TSingletonParser singletonParser = TSingletonParser.getInstance(); 1784 1785 TSourceTokenList stList; 1786 if (this.dbvendor == EDbVendor.dbvgeneric){ 1787 stList = singletonParser.getTokens(TGSqlParser.currentDBVendor,nodeText); 1788 }else{ 1789 stList = singletonParser.getTokens(this.dbvendor,nodeText); 1790 } 1791 1792 1793 TSourceToken newStartToken = stList.get(0); 1794 TSourceToken newEndToken = stList.get(stList.size() - 1); 1795 setText(newStartToken, newEndToken); 1796 1797 } 1798 1799 1800 /** 1801 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1802 * Inserts tokens(from start token to end token ) of this parse tree node at the specified position in this 1803 * list. Shifts the element currently at that position (if any) and 1804 * any subsequent elements to the right . 1805 1806 * @param targetList the new list of source tokens 1807 * @param index the position insert the targetList 1808 * @return length of the source token list after insert targetList 1809 */ 1810 public int addAllMyTokensToTokenList(TSourceTokenList targetList, int index){ 1811 int ret = -1; 1812 1813 TSourceToken lcStartToken = getStartToken(); 1814 if (lcStartToken == null) return ret; 1815 1816 TSourceToken lcEndToken = getEndToken(); 1817 if (lcEndToken == null) return ret; 1818 1819 TSourceTokenList stList = lcStartToken.container; 1820 if (stList == null) return ret; 1821 1822 for(int i=lcEndToken.posinlist;i>=lcStartToken.posinlist;i--){ 1823 targetList.add(index,stList.get(i)); 1824 stList.get(i).container = targetList; 1825 } 1826 1827 // reindex posinlist after remove token from container 1828 for(int i=index;i<targetList.size();i++){ 1829 targetList.get(i).posinlist = i; 1830 } 1831 1832 return lcEndToken.posinlist - lcStartToken.posinlist + 1; 1833 } 1834 1835 /** 1836 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1837 * 1838 * @param additionalToken usually was comma before or after this parse tree node that also need to be deleted 1839 * @return length of the source token list after remove tokens 1840 */ 1841 protected int removeAllMyTokensFromTokenList(TSourceToken additionalToken){ 1842 1843 int ret = -1; 1844 TSourceToken lcStartToken = getStartToken(); 1845 if (lcStartToken == null) return ret; 1846 int lcStartTokenPos = lcStartToken.posinlist; 1847 1848 1849 TSourceToken lcEndToken = getEndToken(); 1850 if (lcEndToken == null) return ret; 1851 int lcEndTokenPos = lcEndToken.posinlist; 1852 1853 1854 TSourceTokenList stList = lcStartToken.container; 1855 if (stList == null) return ret; 1856 1857 if ((additionalToken != null)&&(additionalToken.tokentype != ETokenType.ttRemoved)){ 1858 if (additionalToken.posinlist < lcStartTokenPos){ 1859 lcStartTokenPos = additionalToken.posinlist; 1860 additionalToken.tokentype = ETokenType.ttRemoved; 1861 }else if(additionalToken.posinlist > lcEndTokenPos){ 1862 lcEndTokenPos = additionalToken.posinlist; 1863 additionalToken.tokentype = ETokenType.ttRemoved; 1864 } 1865 } 1866 1867 //reset start and end token of those parse tree node 1868 resetStartAndEndTokenBeforeRemoveTokens(stList, lcStartTokenPos, lcEndTokenPos,false,null,null); 1869 1870 // remove token from list 1871 for(int i=lcEndTokenPos;i>=lcStartTokenPos;i--){ 1872 if ((i>stList.size()-1) ||(i<0)) break; 1873 stList.remove(i); 1874 } 1875 1876 // reindex posinlist after remove token from container 1877 for(int i=lcStartTokenPos;i<stList.size();i++){ 1878 if ((i>stList.size()-1) ||(i<0)) break; 1879 stList.get(i).posinlist = i; 1880 } 1881 1882 return lcStartTokenPos - lcEndTokenPos +1; 1883 } 1884 1885 /** 1886 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1887 * parse string to tokens, then add those tokens to the end of this parse tree node 1888 * 1889 * @param sqlSegment 1890 */ 1891 1892 1893 /** 1894 * Accept a visitor 1895 * 1896 * @param v visitor is a descendant class of {@link TParseTreeVisitor} 1897 */ 1898 public void accept(TParseTreeVisitor v) 1899 { 1900 1901 } 1902 1903 /** 1904 * Accept a visitor to iterate this class and sub-nodes of this class 1905 * 1906 * @param v visitor is a descendant class of {@link TParseTreeVisitor} 1907 */ 1908 public void acceptChildren(TParseTreeVisitor v) 1909 { 1910 1911 } 1912 1913 private TScriptGenerator scriptGenerator = null; 1914 1915 /** 1916 * Return the text string of this node, the return value is the same as {@link #toString()} if this node is not modified manually 1917 * after created by parser. 1918 * <br> 1919 * If this node is modified, then use this method to get string representation instead of the {@link #toString()} method. 1920 * 1921 * @return text string of this node 1922 */ 1923 public String toScript(){ 1924 if (scriptGenerator == null){ 1925 scriptGenerator = new TScriptGenerator(); 1926 } 1927 return scriptGenerator.generateScript(this); 1928 } 1929 1930 public void setChanged(){ 1931 // set the changed status to the first token of this node 1932 if (getStartToken() == null) return; 1933 getStartToken().setTokenstatus(ETokenStatus.tschanged); 1934 } 1935 1936 /** 1937 * Detect wether this node is modified by checking all tokens included in this node. 1938 * @return true if this node is modified. 1939 */ 1940 public boolean isChanged(){ 1941 boolean ret = false; 1942 TSourceToken lcStartToken = getStartToken(); 1943 if (lcStartToken == null) return ret; 1944 1945 TSourceToken lcEndToken = getEndToken(); 1946 if (lcEndToken == null) return ret; 1947 1948 TSourceTokenList stList = lcStartToken.container; 1949 if (stList == null) return ret; 1950 1951 int b = lcStartToken.posinlist; 1952 int e = lcEndToken.posinlist; 1953 1954 for(int i= b ; i<=e;i++){ 1955 if (stList.get(i).getTokenstatus() == ETokenStatus.tschanged){ 1956 ret = true; 1957 break; 1958 } 1959 } 1960 return ret; 1961 } 1962 1963 protected void doAppendNewNode( TParseTreeNode newNode, TParseTreeNode anchorNode,boolean needCommaBefore){ 1964 if (anchorNode != null){ 1965 anchorNode.appendNewNode(newNode,needCommaBefore); 1966 }else{ 1967 if (!newNode.isTokensInChain()){ 1968 this.appendNewNode(newNode,false); 1969 this.setEndToken(newNode.getEndToken()); 1970 } 1971 } 1972 } 1973 1974 private TParseTreeNode anchorNode = null; 1975 1976 public void setAnchorNode(TParseTreeNode anchorNode) { 1977 this.anchorNode = anchorNode; 1978 } 1979 1980 public TParseTreeNode getAnchorNode() { 1981 return anchorNode; 1982 } 1983 1984 public void setNewSubNode( TParseTreeNode oldSubNode, TParseTreeNode newSubNode,TParseTreeNode anchorNode){ 1985 if (newSubNode == null){ 1986 //remove the old node 1987 if (oldSubNode != null){ 1988 oldSubNode.removeTokens(); 1989 } 1990 }else { 1991 if (oldSubNode == null){ 1992 // add a total new where clause, we need to find an anchor 1993 if (newSubNode.getNodeStatus()!=ENodeStatus.nsNormal){ 1994 doAppendNewNode(newSubNode,anchorNode,false); 1995 } 1996 1997 }else{ 1998 //replace old where clause 1999 if (newSubNode.getNodeStatus()!=ENodeStatus.nsNormal){ 2000 oldSubNode.replaceWithNewNode(newSubNode); 2001 } 2002 } 2003 } 2004 } 2005 2006 public static boolean subNodeInNode(TParseTreeNode subNode, TParseTreeNode wholeNode){ 2007 if (wholeNode == null) return false; 2008 TSourceToken startToken = wholeNode.getStartToken(); 2009 if (startToken == null) return false; 2010 TSourceToken endToken = wholeNode.getEndToken(); 2011 if (endToken == null) return false; 2012 2013 TSourceToken startTokenOfSubNode = subNode.getStartToken(); 2014 if (startTokenOfSubNode == null) return false; 2015 if (startTokenOfSubNode.lineNo < startToken.lineNo) return false; 2016 if ((startTokenOfSubNode.lineNo == startToken.lineNo) && (startTokenOfSubNode.columnNo < startToken.columnNo)) return false; 2017 2018 TSourceToken endTokenOfSubNode = subNode.getEndToken(); 2019 if (endTokenOfSubNode == null) return false; 2020 2021 if (endTokenOfSubNode.lineNo > endToken.lineNo) return false; 2022 if ((endTokenOfSubNode.lineNo == endToken.lineNo)&&(endTokenOfSubNode.columnNo > endToken.columnNo)) return false; 2023 2024 return true; 2025 } 2026 2027// public int setText(EDbVendor dbVendor, String sqlText){ 2028// TGSqlParser sqlParser = new TGSqlParser(dbvendor); 2029// sqlParser.sqltext = sqlText; 2030// sqlParser.tokenizeSqltext(); 2031// 2032// } 2033 2034} 2035