NodeText.java
/*
* Copyright (C) 2007-2010 J��lio Vilmar Gesser.
* Copyright (C) 2011, 2013-2026 The JavaParser Team.
*
* This file is part of JavaParser.
*
* JavaParser can be used either under the terms of
* a) the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* b) the terms of the Apache License
*
* You should have received a copy of both licenses in LICENCE.LGPL and
* LICENCE.APACHE. Please refer to those files for details.
*
* JavaParser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/
package com.github.javaparser.printer.lexicalpreservation;
import com.github.javaparser.ast.Node;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* This contains the lexical information for a single node.
* It is basically a list of tokens and children.
*
* <p>This class has been refactored to use {@link TextElementList} internally
* for better code reuse and maintainability, while preserving the exact same
* external API and behavior.
*/
class NodeText {
// Changed from List<TextElement> to TextElementList for internal optimization
private final TextElementList elements;
public static final int NOT_FOUND = -1;
//
// Constructors
//
/**
* Creates a NodeText wrapping the given list of elements.
*
* @param elements the list to wrap (will be wrapped in TextElementList)
*/
NodeText(List<TextElement> elements) {
this.elements = new TextElementList(elements);
}
/**
* Initialize with an empty list of elements.
*/
NodeText() {
this(new LinkedList<>());
}
//
// Adding elements
//
/**
* Add an element at the end.
*/
void addElement(TextElement nodeTextElement) {
elements.insert(elements.size(), nodeTextElement);
}
/**
* Add an element at the given position.
*/
void addElement(int index, TextElement nodeTextElement) {
elements.insert(index, nodeTextElement);
}
void addChild(Node child) {
addElement(new ChildTextElement(child));
}
void addChild(int index, Node child) {
addElement(index, new ChildTextElement(child));
}
void addToken(int tokenKind, String text) {
addElement(new TokenTextElement(tokenKind, text));
}
void addToken(int index, int tokenKind, String text) {
addElement(index, new TokenTextElement(tokenKind, text));
}
//
// Finding elements
//
/**
* Finds the first element matching the given matcher.
*
* @param matcher the matcher to use
* @return the index of the first matching element
* @throws IllegalArgumentException if no matching element is found
*/
int findElement(TextElementMatcher matcher) {
return findElement(matcher, 0);
}
/**
* Finds the first element matching the given matcher, starting from the given index.
*
* @param matcher the matcher to use
* @param from the starting index (inclusive)
* @return the index of the first matching element
* @throws IllegalArgumentException if no matching element is found
*/
int findElement(TextElementMatcher matcher, int from) {
int res = tryToFindElement(matcher, from);
if (res == NOT_FOUND) {
throw new IllegalArgumentException(String.format(
"I could not find child '%s' from position %d. Elements: %s", matcher, from, elements.toList()));
}
return res;
}
/**
* Tries to find an element matching the given matcher, starting from the given index.
* Returns NOT_FOUND if no matching element is found.
*
* @param matcher the matcher to use
* @param from the starting index (inclusive)
* @return the index of the first matching element, or NOT_FOUND
*/
int tryToFindElement(TextElementMatcher matcher, int from) {
return elements.findNext(from, matcher::match);
}
/**
* Finds the first occurrence of the given child node.
*
* @param child the child to find
* @return the index of the child
* @throws IllegalArgumentException if child is not found
*/
int findChild(Node child) {
return findChild(child, 0);
}
/**
* Finds the first occurrence of the given child node, starting from the given index.
*
* @param child the child to find
* @param from the starting index (inclusive)
* @return the index of the child
* @throws IllegalArgumentException if child is not found
*/
int findChild(Node child, int from) {
return findElement(TextElementMatchers.byNode(child), from);
}
/**
* Tries to find the first occurrence of the given child node.
*
* @param child the child to find
* @return the index of the child, or NOT_FOUND
*/
int tryToFindChild(Node child) {
return tryToFindChild(child, 0);
}
/**
* Tries to find the first occurrence of the given child node, starting from the given index.
*
* @param child the child to find
* @param from the starting index (inclusive)
* @return the index of the child, or NOT_FOUND
*/
int tryToFindChild(Node child, int from) {
return tryToFindElement(TextElementMatchers.byNode(child), from);
}
//
// Removing single elements
//
/**
* Removes the first element matching the given matcher.
* Optionally removes following whitespace.
*
* @param matcher the matcher to use
* @param potentiallyFollowingWhitespace if true, removes following whitespace element
* @throws IllegalArgumentException if no matching element is found
* @throws UnsupportedOperationException if whitespace removal is requested but no element follows
*/
public void remove(TextElementMatcher matcher, boolean potentiallyFollowingWhitespace) {
// Find the matching element using our optimized search
int index = tryToFindElement(matcher, 0);
if (index == NOT_FOUND) {
throw new IllegalArgumentException("No matching element found");
}
// Remove the element
elements.remove(index);
// Optionally remove following whitespace
if (potentiallyFollowingWhitespace) {
if (index < elements.size()) {
if (elements.get(index).isWhiteSpace()) {
elements.remove(index);
}
} else {
throw new UnsupportedOperationException("There is no element to remove!");
}
}
}
//
// Removing sequences
//
/**
* Removes the element at the given index.
*
* @param index the index of the element to remove
*/
void removeElement(int index) {
elements.remove(index);
}
//
// Replacing elements
//
/**
* Replaces the element at the position matched by the given matcher
* with the given new element.
*
* @param position the matcher to find the element to replace
* @param newElement the new element
* @throws IllegalArgumentException if no matching element is found
*/
void replace(TextElementMatcher position, TextElement newElement) {
int index = findElement(position, 0);
elements.remove(index);
elements.insert(index, newElement);
}
/**
* Replaces the element at the position matched by the given matcher
* with the given collection of new elements.
*
* @param position the matcher to find the element to replace
* @param newElements the new elements
* @throws IllegalArgumentException if no matching element is found
*/
void replace(TextElementMatcher position, Collection<? extends TextElement> newElements) {
int index = findElement(position, 0);
elements.remove(index);
elements.insertAll(index, (List<TextElement>) newElements);
}
//
// Other methods
//
/**
* Generate the corresponding string by expanding all elements.
*
* @return the expanded string representation
*/
String expand() {
StringBuilder sb = new StringBuilder();
// Use the underlying list's forEach for efficiency
elements.toList().forEach(e -> sb.append(e.expand()));
return sb.toString();
}
/**
* Returns the number of elements.
* Visible for testing.
*
* @return the number of elements
*/
int numberOfElements() {
return elements.size();
}
/**
* Returns the element at the given index.
* Visible for testing.
*
* @param index the index
* @return the element at that index
*/
TextElement getTextElement(int index) {
return elements.get(index);
}
/**
* Returns the underlying list of elements.
* Visible for testing.
*
* <p><b>IMPORTANT:</b> This returns the internal mutable list.
* External modifications will affect this NodeText.
*
* @return the list of elements (mutable)
*/
List<TextElement> getElements() {
return elements.toMutableList();
}
@Override
public String toString() {
return "NodeText{" + elements.toList() + '}';
}
}