TextElementList.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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* Default mutable implementation of {@link TextElementSequence}.
*
* <p>This class wraps an existing {@code List<TextElement>} without copying it.
* All mutations directly affect the underlying list.
*
* <p>The implementation provides optimized search operations and clear semantics
* for index-based manipulations required by lexical preservation operations.
*
* @since 3.28.0
*/
public class TextElementList implements TextElementSequence {
private final List<TextElement> elements;
/**
* Creates a wrapper around the given list.
* The list is NOT copied, mutations affect the original.
*
* @param elements the list to wrap
* @throws NullPointerException if elements is null
*/
public TextElementList(List<TextElement> elements) {
this.elements = Objects.requireNonNull(elements, "elements cannot be null");
}
/**
* Creates a new TextElementList containing the given elements.
*
* @param elements varargs of elements
* @return a new list
*/
public static TextElementList of(TextElement... elements) {
return new TextElementList(new ArrayList<>(Arrays.asList(elements)));
}
/**
* Creates a new TextElementList wrapping the given list.
*
* <p><b>IMPORTANT:</b> This method wraps the list directly without copying.
* Modifications to the TextElementList will affect the original list.
* Use {@link #copyOf(List)} if you need an independent copy.
*
* <p>This method is useful for chaining operations:
* <pre>{@code
* List<TextElement> result = TextElementList.of(list.subList(0, 10))
* .takeWhile(TextElement::isSpaceOrTab);
* }</pre>
*
* @param elements the list to wrap (not copied)
* @return a new TextElementList wrapping the given list
* @throws NullPointerException if elements is null
*/
public static TextElementList of(List<TextElement> elements) {
return new TextElementList(elements);
}
/**
* Creates an empty mutable TextElementList.
*
* @return an empty list
*/
public static TextElementList empty() {
return new TextElementList(new ArrayList<>());
}
/**
* Creates a new TextElementList with a copy of the given list.
*
* @param elements the list to copy
* @return a new list with copied elements
* @throws NullPointerException if elements is null
*/
public static TextElementList copyOf(List<TextElement> elements) {
return new TextElementList(new ArrayList<>(elements));
}
// === SEARCH BY PREDICATE ===
@Override
public int findFirst(Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
for (int i = 0; i < elements.size(); i++) {
if (predicate.test(elements.get(i))) {
return i;
}
}
return -1;
}
@Override
public int findLast(Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
for (int i = elements.size() - 1; i >= 0; i--) {
if (predicate.test(elements.get(i))) {
return i;
}
}
return -1;
}
@Override
public int findNext(int fromIndex, Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
if (!isValidIndex(fromIndex)) {
return -1;
}
for (int i = fromIndex; i < elements.size(); i++) {
if (predicate.test(elements.get(i))) {
return i;
}
}
return -1;
}
@Override
public int findPrevious(int fromIndex, Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
if (fromIndex < 0 || fromIndex >= elements.size()) {
return -1;
}
for (int i = fromIndex; i >= 0; i--) {
if (predicate.test(elements.get(i))) {
return i;
}
}
return -1;
}
// === FILTERING ===
@Override
public List<TextElement> takeWhile(Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
List<TextElement> result = new ArrayList<>();
for (TextElement element : elements) {
if (!predicate.test(element)) {
break;
}
result.add(element);
}
return result;
}
@Override
public List<TextElement> subList(int fromIndex, int toIndex) {
return elements.subList(fromIndex, toIndex);
}
// === MUTATION ===
@Override
public void insert(int index, TextElement element) {
Objects.requireNonNull(element, "element cannot be null");
elements.add(index, element);
}
@Override
public void insertAll(int index, List<TextElement> elementsToInsert) {
Objects.requireNonNull(elementsToInsert, "elements cannot be null");
elements.addAll(index, elementsToInsert);
}
@Override
public void remove(int index) {
elements.remove(index);
}
@Override
public void removeRange(int fromIndex, int toIndex) {
if (!isValidIndex(fromIndex) || !isValidIndex(toIndex) || fromIndex > toIndex) {
throw new IndexOutOfBoundsException(
"Invalid range: [" + fromIndex + ", " + toIndex + "] for size " + elements.size());
}
// toIndex is inclusive, subList.clear() expects exclusive upper bound
elements.subList(fromIndex, toIndex + 1).clear();
}
// === ACCESS ===
@Override
public TextElement get(int index) {
return elements.get(index);
}
@Override
public boolean isValidIndex(int index) {
return index >= 0 && index < elements.size();
}
@Override
public int size() {
return elements.size();
}
@Override
public boolean isEmpty() {
return elements.isEmpty();
}
@Override
public List<TextElement> toList() {
return Collections.unmodifiableList(elements);
}
@Override
public List<TextElement> toMutableList() {
return elements;
}
// === MATCHING (TERMINAL OPERATIONS) ===
@Override
public boolean anyMatch(Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
for (TextElement element : elements) {
if (predicate.test(element)) {
return true;
}
}
return false;
}
@Override
public boolean allMatch(Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
for (TextElement element : elements) {
if (!predicate.test(element)) {
return false;
}
}
return true;
}
@Override
public boolean noneMatch(Predicate<TextElement> predicate) {
Objects.requireNonNull(predicate, "predicate cannot be null");
return !anyMatch(predicate);
}
// === ITERATION ===
@Override
public TextElementIterator iterator(int fromIndex) {
return new TextElementIterator(elements, fromIndex);
}
@Override
public String toString() {
return "TextElementList{size=" + elements.size() + "}";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof TextElementList)) return false;
TextElementList other = (TextElementList) obj;
return elements.equals(other.elements);
}
@Override
public int hashCode() {
return elements.hashCode();
}
}