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(java.util.Locale.ROOT)); 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.getCurrentDBVendor(),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 return getCommentBeforeNode(false); 1461 } 1462 1463 /** 1464 * The comment block that precedes this node, verbatim, including the {@code --} or 1465 * <code>/* */</code> markers. 1466 * 1467 * @param stopAtBlankLine when {@code true}, stop at a blank line, so only the comment block 1468 * ADJACENT to this node is returned. A blank line is how a file says a comment 1469 * introduces the section rather than the next declaration: in 1470 * {@code CREATE PACKAGE BODY p AS /}<code>** header **</code>{@code /}, blank line, 1471 * {@code /}<code>** 主方法调用 **</code>{@code / PROCEDURE m}, only the second block is 1472 * the procedure's. Unbounded ({@code false}) returns both concatenated, which is what 1473 * {@link #getCommentBeforeNode()} has always done and what its callers still get. 1474 */ 1475 public String getCommentBeforeNode(boolean stopAtBlankLine){ 1476 return getCommentBeforeNode(stopAtBlankLine, null); 1477 } 1478 1479 /** 1480 * As {@link #getCommentBeforeNode(boolean)}, but the walk also stops when it reaches 1481 * {@code stopBefore}. 1482 * 1483 * @param stopBefore a token the walk must not pass — the {@code AS} that opens the enclosing 1484 * package body, or the first token of the statement a call sits in. Whatever was 1485 * collected before reaching it is kept; deciding whether that text belongs to this node 1486 * or to the thing the bound opens is the caller's, which knows both. {@code null} 1487 * imposes no bound. 1488 */ 1489 public String getCommentBeforeNode(boolean stopAtBlankLine, TSourceToken stopBefore){ 1490 String ret = null; 1491 TSourceToken lcStartToken; 1492 1493 if (doubleLinkedTokenListToString){ 1494 lcStartToken = getStartToken(); 1495 if (lcStartToken == null) return null; 1496 1497 TSourceToken lcCurrentToken = lcStartToken.getPrevTokenInChain(); 1498 int consecutiveLineBreaks = 0; 1499 while (lcCurrentToken != null){ 1500 // Positional, not identity: when the node IS the first token of its statement, 1501 // the walk starts already outside the bound and must stop at once. 1502 if (stopBefore != null && lcCurrentToken.posinlist <= stopBefore.posinlist) break; 1503 if ((lcCurrentToken.tokentype == ETokenType.ttreturn)||(lcCurrentToken.tokentype == ETokenType.ttwhitespace)) 1504 { 1505 // Count line-break CHARACTERS: one whitespace token can carry "\n\n", so 1506 // counting tokens would never see a blank line. 1507 consecutiveLineBreaks += countLineBreaks(lcCurrentToken.toString()); 1508 // Also when nothing has been collected yet: a blank line means the comment 1509 // above it introduces something else, so this node simply has none. 1510 if (stopAtBlankLine && consecutiveLineBreaks > 1) break; 1511 if (ret != null){ 1512 ret = lcCurrentToken.toString() + ret ; 1513 } 1514 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1515 continue; 1516 } 1517 else if (lcCurrentToken.toString().trim().length() == 0) { 1518 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1519 continue; 1520 } 1521 // Real text: any run of line breaks before it is over. 1522 consecutiveLineBreaks = 0; 1523 if ((lcCurrentToken.tokencode == TBaseType.cmtslashstar) ||(lcCurrentToken.tokencode == TBaseType.cmtdoublehyphen) 1524 || lcCurrentToken.toString().startsWith("--") || lcCurrentToken.toString().startsWith("/*") 1525 ){ 1526 if (stopAtBlankLine && trailsFinishedStatement(lcCurrentToken)) break; 1527 if (ret == null){ 1528 ret = lcCurrentToken.toString(); 1529 }else{ 1530 ret = lcCurrentToken.toString()+ ret; 1531 } 1532 1533 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1534 continue; 1535 } 1536 1537 break; 1538 } 1539 }else{ 1540 lcStartToken = getStartToken(); 1541 if (lcStartToken == null) return null; 1542 1543 TSourceTokenList stList = lcStartToken.container; 1544 if (stList == null) return null; 1545 1546 int b = lcStartToken.posinlist; 1547 1548 int consecutiveLineBreaks = 0; 1549 for(int i = b - 1 ; i>=0;i--){ 1550 TSourceToken st = stList.get(i); 1551 if (stopBefore != null && st.posinlist <= stopBefore.posinlist) break; 1552 if ((st.tokencode == TBaseType.lexspace)|| (st.tokencode == TBaseType.lexnewline) 1553 || (st.getAstext().trim().length() == 0)) { 1554 consecutiveLineBreaks += countLineBreaks(st.getAstext()); 1555 // Same bound as the double-linked walk above: this branch is what runs when 1556 // TParseTreeNode.doubleLinkedTokenListToString is off, and the bounded API 1557 // must mean the same thing in both. 1558 if (stopAtBlankLine && consecutiveLineBreaks > 1) break; 1559 continue; 1560 } 1561 consecutiveLineBreaks = 0; 1562 if ((st.tokencode == TBaseType.cmtslashstar) ||(st.tokencode == TBaseType.cmtdoublehyphen)){ 1563 if (stopAtBlankLine && trailsFinishedStatementInList(stList, i)) break; 1564 if (ret == null){ 1565 ret = st.toString(); 1566 }else{ 1567 ret = st.toString()+TBaseType.newline + ret; 1568 } 1569 continue; 1570 } 1571 else if (st.getAstext().startsWith("--") || st.getAstext().startsWith("/*")){ 1572 if (ret == null){ 1573 ret = st.toString(); 1574 }else{ 1575 ret = st.toString()+TBaseType.newline + ret; 1576 } 1577 continue; 1578 } 1579 break; 1580 } 1581 } 1582 1583 if (stopAtBlankLine && ret != null){ 1584 ret = ret.trim(); 1585 if (ret.isEmpty()) ret = null; 1586 } 1587 return ret; 1588 } 1589 1590 /** As {@link #trailsFinishedStatement}, for the indexed token-list walk. */ 1591 private static boolean trailsFinishedStatementInList(TSourceTokenList list, int commentIndex){ 1592 for (int i = commentIndex - 1; i >= 0; i--){ 1593 TSourceToken st = list.get(i); 1594 String text = st.getAstext(); 1595 if (countLineBreaks(text) > 0) return false; 1596 if (isCommentToken(st)) continue; 1597 if (text.trim().length() > 0) return closesAStatementOrOpensABody(text); 1598 } 1599 return false; 1600 } 1601 1602 private static boolean isCommentToken(TSourceToken token){ 1603 String text = token.toString(); 1604 return (token.tokencode == TBaseType.cmtslashstar) || (token.tokencode == TBaseType.cmtdoublehyphen) 1605 || text.startsWith("--") || text.startsWith("/*"); 1606 } 1607 1608 /** 1609 * Whether {@code commentToken} follows a FINISHED statement on its own line, which makes it 1610 * that statement's trailing note rather than an introduction to whatever comes next. 1611 * 1612 * <p>The discriminator is the terminator: in {@code g_counter NUMBER; -- variable only} the 1613 * comment describes the declaration above it, while in {@code x := f1() + /* second *}{@code / f2()} 1614 * the code before the comment is the same unfinished statement and the comment introduces the 1615 * call that follows. Looking for a statement boundary rather than for any code is what tells 1616 * those apart, on one line or across several. 1617 */ 1618 private static boolean trailsFinishedStatement(TSourceToken commentToken){ 1619 TSourceToken current = commentToken.getPrevTokenInChain(); 1620 while (current != null){ 1621 String text = current.toString(); 1622 if (countLineBreaks(text) > 0) return false; // reached the start of the line 1623 if (isCommentToken(current)){ // `/* a */ /* b */` is one block 1624 current = current.getPrevTokenInChain(); 1625 continue; 1626 } 1627 if (text.trim().length() > 0) return closesAStatementOrOpensABody(text); 1628 current = current.getPrevTokenInChain(); 1629 } 1630 return false; 1631 } 1632 1633 /** 1634 * Whether {@code text} is the end of a statement, or the keyword that opens a body. 1635 * 1636 * <p>Both mean a comment after it on the same line describes what came before rather than 1637 * what follows: {@code g_counter NUMBER; -- variable only} labels the declaration, and 1638 * {@code IF 1=1 THEN -- only when enabled} labels the branch, not the first call inside it. 1639 */ 1640 private static boolean closesAStatementOrOpensABody(String text){ 1641 String token = text.trim(); 1642 if (token.endsWith(";")) return true; 1643 return "THEN".equalsIgnoreCase(token) || "ELSE".equalsIgnoreCase(token) 1644 || "LOOP".equalsIgnoreCase(token) || "BEGIN".equalsIgnoreCase(token) 1645 || "DECLARE".equalsIgnoreCase(token) || "DO".equalsIgnoreCase(token) 1646 || "AS".equalsIgnoreCase(token) || "IS".equalsIgnoreCase(token); 1647 } 1648 1649 private static int countLineBreaks(String text){ 1650 if (text == null) return 0; 1651 int n = 0; 1652 for (int i = 0; i < text.length(); i++){ 1653 char c = text.charAt(i); 1654 // CRLF counts once; a lone CR is a line break too, as in CR-only files. 1655 if (c == '\n') n++; 1656 else if (c == '\r' && (i + 1 >= text.length() || text.charAt(i + 1) != '\n')) n++; 1657 } 1658 return n; 1659 } 1660 1661 /** 1662 * The comment block that FOLLOWS {@code token}, up to a blank line or the first real token. 1663 * 1664 * <p>Used for a header that sits under a declaration rather than above it — a package body 1665 * writes its description after {@code AS}, where no node starts. 1666 */ 1667 /** 1668 * The last token of the comment block {@link #commentBlockAfter} would return, or {@code null} 1669 * when there is none. Callers bound a later walk with it, so ownership is decided by the 1670 * tokens themselves rather than by comparing rendered text — two objects may legitimately 1671 * carry identical comments. 1672 */ 1673 public static TSourceToken commentBlockEndAfter(TSourceToken token){ 1674 if (token == null) return null; 1675 TSourceToken last = null; 1676 int consecutiveLineBreaks = 0; 1677 TSourceToken current = token.getNextTokenInChain(); 1678 while (current != null){ 1679 if ((current.tokentype == ETokenType.ttreturn) || (current.tokentype == ETokenType.ttwhitespace) 1680 || (current.toString().trim().length() == 0)){ 1681 consecutiveLineBreaks += countLineBreaks(current.toString()); 1682 if (consecutiveLineBreaks > 1) break; 1683 current = current.getNextTokenInChain(); 1684 continue; 1685 } 1686 if (isCommentToken(current)){ 1687 consecutiveLineBreaks = 0; 1688 last = current; 1689 current = current.getNextTokenInChain(); 1690 continue; 1691 } 1692 break; 1693 } 1694 return last; 1695 } 1696 1697 public static String commentBlockAfter(TSourceToken token){ 1698 if (token == null) return null; 1699 String ret = null; 1700 String pending = ""; 1701 int consecutiveLineBreaks = 0; 1702 TSourceToken current = token.getNextTokenInChain(); 1703 while (current != null){ 1704 if ((current.tokentype == ETokenType.ttreturn) || (current.tokentype == ETokenType.ttwhitespace) 1705 || (current.toString().trim().length() == 0)){ 1706 consecutiveLineBreaks += countLineBreaks(current.toString()); 1707 // Unconditional: a blank line right after the keyword means the block below it 1708 // introduces whatever follows, not this node. 1709 if (consecutiveLineBreaks > 1) break; 1710 pending = pending + current.toString(); 1711 current = current.getNextTokenInChain(); 1712 continue; 1713 } 1714 if ((current.tokencode == TBaseType.cmtslashstar) || (current.tokencode == TBaseType.cmtdoublehyphen) 1715 || current.toString().startsWith("--") || current.toString().startsWith("/*")){ 1716 consecutiveLineBreaks = 0; 1717 // Keep the whitespace BETWEEN comments: two line comments run together would 1718 // otherwise read as one line, `-- one-- two`. 1719 ret = (ret == null) ? current.toString() : ret + pending + current.toString(); 1720 pending = ""; 1721 current = current.getNextTokenInChain(); 1722 continue; 1723 } 1724 break; 1725 } 1726 if (ret != null){ 1727 ret = ret.trim(); 1728 if (ret.isEmpty()) ret = null; 1729 } 1730 return ret; 1731 } 1732 1733 /** 1734 * The comment that trails this node on the SAME line, verbatim, including its 1735 * {@code --} or <code>/* */</code> markers. 1736 * 1737 * <p>Returns {@code null} when there is none — the common case, since most statements end at a 1738 * semicolon with nothing after it. It never throws. 1739 * 1740 * <p>Bounded to the line the node ends on, because a comment on the NEXT line introduces 1741 * whatever follows it; claiming it here would hand one comment to two nodes. See 1742 * {@link #getCommentBeforeNode(boolean)} for the leading side of the same idea. 1743 */ 1744 public String getCommentAfterNode(){ 1745 if (!doubleLinkedTokenListToString) return null; 1746 1747 TSourceToken lcEndToken = getEndToken(); 1748 if (lcEndToken == null) return null; 1749 1750 String ret = null; 1751 // Forward, not backward: a TRAILING comment sits after the node's last token. The walk 1752 // used to start at that token and follow getPrevTokenInChain(), which reads the LEADING 1753 // comment and then dereferenced a null result for every node that had none. 1754 TSourceToken lcCurrentToken = lcEndToken.getNextTokenInChain(); 1755 boolean terminatorSkipped = false; 1756 while (lcCurrentToken != null){ 1757 if (lcCurrentToken.tokentype == ETokenType.ttreturn) break; // end of this line 1758 if (!terminatorSkipped && ret == null && ";".equals(lcCurrentToken.toString().trim())){ 1759 // `NULL; --部门港口`: the node ends at NULL, so the comment sits past the 1760 // terminator. Step over it once, never over anything else. 1761 terminatorSkipped = true; 1762 lcCurrentToken = lcCurrentToken.getNextTokenInChain(); 1763 continue; 1764 } 1765 if ((lcCurrentToken.tokentype == ETokenType.ttwhitespace) 1766 || (lcCurrentToken.toString().trim().length() == 0)){ 1767 lcCurrentToken = lcCurrentToken.getNextTokenInChain(); 1768 continue; 1769 } 1770 if ((lcCurrentToken.tokencode == TBaseType.cmtslashstar) 1771 || (lcCurrentToken.tokencode == TBaseType.cmtdoublehyphen) 1772 || lcCurrentToken.toString().startsWith("--") 1773 || lcCurrentToken.toString().startsWith("/*")){ 1774 ret = (ret == null) ? lcCurrentToken.toString() : ret + lcCurrentToken.toString(); 1775 lcCurrentToken = lcCurrentToken.getNextTokenInChain(); 1776 continue; 1777 } 1778 break; // real code follows: nothing here belongs to this node 1779 } 1780 1781 if (ret == null){ 1782 // A statement's span can INCLUDE its trailing comment: for a semicolon-less T-SQL 1783 // `INSERT ... -- note`, the end token is the line break AFTER the note, so walking 1784 // forward starts at the next statement. Look back over the span's tail instead, 1785 // stopping at this statement's own last code token so the leading comment — which 1786 // lies before it — can never be reached. 1787 TSourceToken tail = lcEndToken; 1788 while (tail != null){ 1789 String text = tail.toString(); 1790 if (isCommentToken(tail)){ 1791 ret = (ret == null) ? text : text + ret; 1792 } else if (text.trim().length() > 0){ 1793 break; 1794 } else if (countLineBreaks(text) > 0 && ret == null){ 1795 break; // a comment on a later line introduces what follows, it does not trail 1796 } 1797 tail = tail.getPrevTokenInChain(); 1798 } 1799 } 1800 return ret == null ? null : ret.trim(); 1801 } 1802 1803 /** 1804 * String representation of this parse tree node. 1805 * <p> 1806 * This string was generated by collecting text from the start token of this node to the end token of this node. 1807 * If this node was modified, then use {@link #toScript()} to get string representation of this node. 1808 * 1809 * @return string representation of this parse tree node 1810 */ 1811 public String toString2(){ 1812 String ret = null; 1813 TSourceToken lcStartToken = getStartToken(); 1814 if (lcStartToken == null) return null; 1815 1816 TSourceToken lcEndToken = getEndToken(); 1817 if (lcEndToken == null) return null; 1818 1819 TSourceTokenList stList = lcStartToken.container; 1820 if (stList == null) return null; 1821 1822 int b = lcStartToken.posinlist; 1823 int e = lcEndToken.posinlist; 1824 1825 StringBuffer sb = new StringBuffer(""); 1826 1827 for(int i= b ; i<=e;i++){ 1828 if(!includingComment && ((stList.get(i).tokencode == TBaseType.cmtslashstar) ||(stList.get(i).tokencode == TBaseType.cmtdoublehyphen))){ 1829 continue; 1830 } 1831 sb.append(stList.get(i).toString()); 1832 } 1833 1834 return sb.toString(); 1835 } 1836 1837 /** 1838 * <p> 1839 * 将一个语法树节点({@code TParseTreeNode})转换回它对应的原始SQL字符串。 1840 * 它通过遍历一个由 {@code startToken}(起始词法单元)和 {@code endToken}(结束词法单元)界定的双向链表来实现这一功能。 1841 * </p> 1842 * <p> 1843 * 这个方法的设计采用了“两遍扫描”(Two-Pass)的策略,也就是使用了两个 {@code while} 循环。 1844 * 第一个循环是预处理阶段,用于清理格式;第二个循环则负责构建最终的字符串。 1845 * </p> 1846 * <h3>第一个 {@code while} 循环</h3> 1847 * <p> 1848 * 这个循环的主要目标是识别并“软删除”多余的换行符(newline tokens)。这种情况尤其在SQL被程序动态修改后容易出现。 1849 * 这个循环本身不构建字符串,只负责分析和更新词法单元的状态。 1850 * 其工作原理是: 1851 * <ol> 1852 * <li><b>遍历:</b> 它从 {@code startToken} 开始,通过 {@code getNextTokenInChain()} 方法遍历到 {@code endToken}。</li> 1853 * <li><b>检测链表变化:</b> 它通过比较当前和前一个词法单元的 {@code posinlist}(词法单元在原始完整列表中的位置索引)来判断链表是否“自然”。 1854 * 如果不连续,意味着词法单元被插入或重排了,此时 {@code isChainModified} 标志位会被设为 {@code true}。</li> 1855 * <li><b>标记换行符:</b> 当遇到第一个换行符时,它会设置一个标志。如果紧接着又遇到了另一个换行符,并且此时链表已经被修改过, 1856 * 那么它会将这个多余的换行符标记为待删除状态。这个逻辑能有效地将代码被修改后可能产生的多个连续换行压缩成一个。</li> 1857 * </ol> 1858 * 简而言之,第一个循环是一个格式清理过程,它为后续生成整洁的字符串做准备。 1859 * </p> 1860 * <h3>第二个 {@code while} 循环</h3> 1861 * <p> 1862 * 这个循环负责从(经过第一步清理后的)词法单元流中实际地构建最终的输出字符串。 1863 * 其工作原理是: 1864 * <ol> 1865 * <li><b>遍历:</b> 它再次遍历完全相同的词法单元序列。</li> 1866 * <li><b>构建字符串:</b> 在将每个词法单元追加到 {@code StringBuffer} 之前,它会进行检查。</li> 1867 * <li><b>跳过特定单元:</b> 如果一个词法单元在第一个循环中被标记为待删除,或者它是一个注释(且配置为不包含注释),那么它将被跳过。</li> 1868 * <li><b>拼接字符串:</b> 对于所有未被跳过的词法单元,它会将其字符串值追加到 {@code StringBuffer} 中。</li> 1869 * </ol> 1870 * 当这个循环结束后,{@code StringBuffer} 就包含了该语法树节点的最终、重构后的SQL文本。 1871 * </p> 1872 * @return 节点对应的SQL字符串。 1873 */ 1874 public String toString(){ 1875 if (doubleLinkedTokenListToString){ 1876 TSourceToken startToken = getStartToken(); 1877 TSourceToken endToken = getEndToken(); 1878 1879 // Return null if boundary tokens are missing 1880 if (startToken == null || endToken == null) { 1881 return null; 1882 } 1883 1884 // First pass: Mark redundant newlines for deletion 1885 // This cleans up formatting issues caused by programmatic SQL modifications 1886 TSourceToken currentToken = startToken; 1887 TSourceToken previousToken = null; 1888 boolean shouldIgnoreNextNewline = false; 1889 boolean tokenChainWasModified = false; 1890 1891 while (currentToken != null) { 1892 // Detect if the token chain has been modified by checking position continuity 1893 if (!tokenChainWasModified && previousToken != null) { 1894 tokenChainWasModified = (currentToken.posinlist - previousToken.posinlist != 1); 1895 } 1896 1897 // Handle newline tokens - mark redundant ones for deletion 1898 if (currentToken.tokentype == ETokenType.ttreturn) { 1899 boolean isRedundantNewline = shouldIgnoreNextNewline && tokenChainWasModified; 1900 1901 if (isRedundantNewline) { 1902 currentToken.tokenstatus = ETokenStatus.tsdeleted; 1903 } else { 1904 shouldIgnoreNextNewline = true; 1905 } 1906 } else if (currentToken.tokentype != ETokenType.ttwhitespace) { 1907 // Reset newline flag when encountering non-whitespace tokens 1908 shouldIgnoreNextNewline = false; 1909 } 1910 1911 previousToken = currentToken; 1912 1913 // Move to next token or break if we've reached the end 1914 if (currentToken.equals(endToken)) { 1915 break; 1916 } 1917 currentToken = currentToken.getNextTokenInChain(); 1918 } 1919 1920 // Second pass: Build the final string by concatenating valid tokens 1921 StringBuffer result = new StringBuffer(); 1922 currentToken = startToken; 1923 1924 while (currentToken != null) { 1925 boolean shouldSkipToken = currentToken.tokenstatus == ETokenStatus.tsdeleted || 1926 (!includingComment && (currentToken.tokencode == TBaseType.cmtslashstar || 1927 currentToken.tokencode == TBaseType.cmtdoublehyphen) 1928 ); 1929 1930 if (!shouldSkipToken) { 1931 result.append(currentToken.toString()); 1932 } 1933 1934 // Move to next token or break if we've reached the end 1935 if (currentToken.equals(endToken)) { 1936 break; 1937 } 1938 currentToken = currentToken.getNextTokenInChain(); 1939 } 1940 1941 return result.toString(); 1942 } else { 1943 return toString2(); 1944 } 1945 } 1946 1947 1948 void setText(String nodeText, EDbVendor dbVendor){ 1949 this.dbvendor = dbVendor; 1950 setText(nodeText); 1951 } 1952 1953 private boolean tokensInChain = true; 1954 1955 public boolean isTokensInChain() { 1956 return tokensInChain; 1957 } 1958 1959 void setText(TSourceToken startToken, TSourceToken endToken){ 1960 TSourceToken newStartToken = startToken; 1961 TSourceToken newEndToken = endToken; 1962 1963 TSourceToken oldStartToken = getStartToken(); 1964 TSourceToken oldEndToken = getEndToken(); 1965 tokensInChain = false; 1966 if ((oldStartToken == null)||(oldEndToken == null)){ 1967 setStartToken(newStartToken); 1968 setEndToken(newEndToken); 1969 nodeStatus = ENodeStatus.nsFlattened; 1970 return; 1971 } 1972 1973 refreshAllNodesTokenCount(); 1974 1975 updateStatusOfNodeShareSameTokens(nodeActionUpdateText); 1976 1977 updateNodeWithTheSameStartToken(nodeActionUpdateText,newStartToken); 1978 updateMeNodeWithTheSameEndToken(nodeActionUpdateText,newEndToken); 1979 1980 if (oldStartToken.getPrevTokenInChain() != null){ 1981 oldStartToken.getPrevTokenInChain().setNextTokenInChain(newStartToken); 1982 newStartToken.setPrevTokenInChain(oldStartToken.getPrevTokenInChain()); 1983 } 1984 1985 if (oldEndToken.getNextTokenInChain() != null){ 1986 oldEndToken.getNextTokenInChain().setPrevTokenInChain(newEndToken); 1987 newEndToken.setNextTokenInChain(oldEndToken.getNextTokenInChain()); 1988 } 1989 1990 tokensInChain = true; 1991 } 1992 /** 1993 * set text of this node, if the original token of this node 1994 * is in a token chain, then merge the new token into the chain as well. 1995 * 1996 * use double linked list 1997 * 1998 * @param nodeText node text 1999 */ 2000 void setText(String nodeText){ 2001 if ((this.getNodeStatus() == ENodeStatus.nsRemoved)||(nodeText.length() == 0)) return; 2002 // if ((nodeText.length() == 0)) return; 2003 2004// TGSqlParser sqlParser = new TGSqlParser(TGSqlParser.getCurrentDBVendor()); 2005// sqlParser.sqltext = nodeText; 2006// sqlParser.tokenizeSqltext(); 2007// TSourceToken newStartToken = sqlParser.getSourcetokenlist().get(0); 2008// TSourceToken newEndToken = sqlParser.getSourcetokenlist().get(sqlParser.getSourcetokenlist().size() - 1); 2009// setText(newStartToken, newEndToken); 2010 2011 TSingletonParser singletonParser = TSingletonParser.getInstance(); 2012 2013 TSourceTokenList stList; 2014 if (this.dbvendor == EDbVendor.dbvgeneric){ 2015 stList = singletonParser.getTokens(TGSqlParser.getCurrentDBVendor(),nodeText); 2016 }else{ 2017 stList = singletonParser.getTokens(this.dbvendor,nodeText); 2018 } 2019 2020 2021 // nodeText may not be tokenizable (e.g. a fragment of an expression such as 2022 // an unterminated string literal); leave the node unchanged in that case 2023 if (stList.size() == 0) return; 2024 2025 TSourceToken newStartToken = stList.get(0); 2026 TSourceToken newEndToken = stList.get(stList.size() - 1); 2027 setText(newStartToken, newEndToken); 2028 2029 } 2030 2031 2032 /** 2033 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 2034 * Inserts tokens(from start token to end token ) of this parse tree node at the specified position in this 2035 * list. Shifts the element currently at that position (if any) and 2036 * any subsequent elements to the right . 2037 2038 * @param targetList the new list of source tokens 2039 * @param index the position insert the targetList 2040 * @return length of the source token list after insert targetList 2041 */ 2042 public int addAllMyTokensToTokenList(TSourceTokenList targetList, int index){ 2043 int ret = -1; 2044 2045 TSourceToken lcStartToken = getStartToken(); 2046 if (lcStartToken == null) return ret; 2047 2048 TSourceToken lcEndToken = getEndToken(); 2049 if (lcEndToken == null) return ret; 2050 2051 TSourceTokenList stList = lcStartToken.container; 2052 if (stList == null) return ret; 2053 2054 for(int i=lcEndToken.posinlist;i>=lcStartToken.posinlist;i--){ 2055 targetList.add(index,stList.get(i)); 2056 stList.get(i).container = targetList; 2057 } 2058 2059 // reindex posinlist after remove token from container 2060 for(int i=index;i<targetList.size();i++){ 2061 targetList.get(i).posinlist = i; 2062 } 2063 2064 return lcEndToken.posinlist - lcStartToken.posinlist + 1; 2065 } 2066 2067 /** 2068 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 2069 * 2070 * @param additionalToken usually was comma before or after this parse tree node that also need to be deleted 2071 * @return length of the source token list after remove tokens 2072 */ 2073 protected int removeAllMyTokensFromTokenList(TSourceToken additionalToken){ 2074 2075 int ret = -1; 2076 TSourceToken lcStartToken = getStartToken(); 2077 if (lcStartToken == null) return ret; 2078 int lcStartTokenPos = lcStartToken.posinlist; 2079 2080 2081 TSourceToken lcEndToken = getEndToken(); 2082 if (lcEndToken == null) return ret; 2083 int lcEndTokenPos = lcEndToken.posinlist; 2084 2085 2086 TSourceTokenList stList = lcStartToken.container; 2087 if (stList == null) return ret; 2088 2089 if ((additionalToken != null)&&(additionalToken.tokentype != ETokenType.ttRemoved)){ 2090 if (additionalToken.posinlist < lcStartTokenPos){ 2091 lcStartTokenPos = additionalToken.posinlist; 2092 additionalToken.tokentype = ETokenType.ttRemoved; 2093 }else if(additionalToken.posinlist > lcEndTokenPos){ 2094 lcEndTokenPos = additionalToken.posinlist; 2095 additionalToken.tokentype = ETokenType.ttRemoved; 2096 } 2097 } 2098 2099 //reset start and end token of those parse tree node 2100 resetStartAndEndTokenBeforeRemoveTokens(stList, lcStartTokenPos, lcEndTokenPos,false,null,null); 2101 2102 // remove token from list 2103 for(int i=lcEndTokenPos;i>=lcStartTokenPos;i--){ 2104 if ((i>stList.size()-1) ||(i<0)) break; 2105 stList.remove(i); 2106 } 2107 2108 // reindex posinlist after remove token from container 2109 for(int i=lcStartTokenPos;i<stList.size();i++){ 2110 if ((i>stList.size()-1) ||(i<0)) break; 2111 stList.get(i).posinlist = i; 2112 } 2113 2114 return lcStartTokenPos - lcEndTokenPos +1; 2115 } 2116 2117 /** 2118 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 2119 * parse string to tokens, then add those tokens to the end of this parse tree node 2120 * 2121 * @param sqlSegment 2122 */ 2123 2124 2125 /** 2126 * Accept a visitor 2127 * 2128 * @param v visitor is a descendant class of {@link TParseTreeVisitor} 2129 */ 2130 public void accept(TParseTreeVisitor v) 2131 { 2132 2133 } 2134 2135 /** 2136 * Accept a visitor to iterate this class and sub-nodes of this class 2137 * 2138 * @param v visitor is a descendant class of {@link TParseTreeVisitor} 2139 */ 2140 public void acceptChildren(TParseTreeVisitor v) 2141 { 2142 2143 } 2144 2145 private TScriptGenerator scriptGenerator = null; 2146 2147 /** 2148 * Return the text string of this node, the return value is the same as {@link #toString()} if this node is not modified manually 2149 * after created by parser. 2150 * <br> 2151 * If this node is modified, then use this method to get string representation instead of the {@link #toString()} method. 2152 * 2153 * @return text string of this node 2154 */ 2155 public String toScript(){ 2156 if (scriptGenerator == null){ 2157 scriptGenerator = new TScriptGenerator(); 2158 } 2159 return scriptGenerator.generateScript(this); 2160 } 2161 2162 public void setChanged(){ 2163 // set the changed status to the first token of this node 2164 if (getStartToken() == null) return; 2165 getStartToken().setTokenstatus(ETokenStatus.tschanged); 2166 } 2167 2168 /** 2169 * Detect wether this node is modified by checking all tokens included in this node. 2170 * @return true if this node is modified. 2171 */ 2172 public boolean isChanged(){ 2173 boolean ret = false; 2174 TSourceToken lcStartToken = getStartToken(); 2175 if (lcStartToken == null) return ret; 2176 2177 TSourceToken lcEndToken = getEndToken(); 2178 if (lcEndToken == null) return ret; 2179 2180 TSourceTokenList stList = lcStartToken.container; 2181 if (stList == null) return ret; 2182 2183 int b = lcStartToken.posinlist; 2184 int e = lcEndToken.posinlist; 2185 2186 for(int i= b ; i<=e;i++){ 2187 if (stList.get(i).getTokenstatus() == ETokenStatus.tschanged){ 2188 ret = true; 2189 break; 2190 } 2191 } 2192 return ret; 2193 } 2194 2195 protected void doAppendNewNode( TParseTreeNode newNode, TParseTreeNode anchorNode,boolean needCommaBefore){ 2196 if (anchorNode != null){ 2197 anchorNode.appendNewNode(newNode,needCommaBefore); 2198 }else{ 2199 if (!newNode.isTokensInChain()){ 2200 this.appendNewNode(newNode,false); 2201 this.setEndToken(newNode.getEndToken()); 2202 } 2203 } 2204 } 2205 2206 private TParseTreeNode anchorNode = null; 2207 2208 public void setAnchorNode(TParseTreeNode anchorNode) { 2209 this.anchorNode = anchorNode; 2210 } 2211 2212 public TParseTreeNode getAnchorNode() { 2213 return anchorNode; 2214 } 2215 2216 public void setNewSubNode( TParseTreeNode oldSubNode, TParseTreeNode newSubNode,TParseTreeNode anchorNode){ 2217 if (newSubNode == null){ 2218 //remove the old node 2219 if (oldSubNode != null){ 2220 oldSubNode.removeTokens(); 2221 } 2222 }else { 2223 if (oldSubNode == null){ 2224 // add a total new where clause, we need to find an anchor 2225 if (newSubNode.getNodeStatus()!=ENodeStatus.nsNormal){ 2226 doAppendNewNode(newSubNode,anchorNode,false); 2227 } 2228 2229 }else{ 2230 //replace old where clause 2231 if (newSubNode.getNodeStatus()!=ENodeStatus.nsNormal){ 2232 oldSubNode.replaceWithNewNode(newSubNode); 2233 } 2234 } 2235 } 2236 } 2237 2238 public static boolean subNodeInNode(TParseTreeNode subNode, TParseTreeNode wholeNode){ 2239 if (wholeNode == null) return false; 2240 TSourceToken startToken = wholeNode.getStartToken(); 2241 if (startToken == null) return false; 2242 TSourceToken endToken = wholeNode.getEndToken(); 2243 if (endToken == null) return false; 2244 2245 TSourceToken startTokenOfSubNode = subNode.getStartToken(); 2246 if (startTokenOfSubNode == null) return false; 2247 if (startTokenOfSubNode.lineNo < startToken.lineNo) return false; 2248 if ((startTokenOfSubNode.lineNo == startToken.lineNo) && (startTokenOfSubNode.columnNo < startToken.columnNo)) return false; 2249 2250 TSourceToken endTokenOfSubNode = subNode.getEndToken(); 2251 if (endTokenOfSubNode == null) return false; 2252 2253 if (endTokenOfSubNode.lineNo > endToken.lineNo) return false; 2254 if ((endTokenOfSubNode.lineNo == endToken.lineNo)&&(endTokenOfSubNode.columnNo > endToken.columnNo)) return false; 2255 2256 return true; 2257 } 2258 2259// public int setText(EDbVendor dbVendor, String sqlText){ 2260// TGSqlParser sqlParser = new TGSqlParser(dbvendor); 2261// sqlParser.sqltext = sqlText; 2262// sqlParser.tokenizeSqltext(); 2263// 2264// } 2265 2266} 2267