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&lt;TextSegment&gt; 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>/* *&#47;</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>/* *&#47;</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