|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package com.github.javaparser.ast; |
|
|
|
import com.github.javaparser.HasParentNode; |
|
import com.github.javaparser.Position; |
|
import com.github.javaparser.Range; |
|
import com.github.javaparser.TokenRange; |
|
import com.github.javaparser.ast.comments.BlockComment; |
|
import com.github.javaparser.ast.comments.Comment; |
|
import com.github.javaparser.ast.comments.LineComment; |
|
import com.github.javaparser.ast.nodeTypes.NodeWithOptionalScope; |
|
import com.github.javaparser.ast.nodeTypes.NodeWithRange; |
|
import com.github.javaparser.ast.nodeTypes.NodeWithTokenRange; |
|
import com.github.javaparser.ast.observer.AstObserver; |
|
import com.github.javaparser.ast.observer.ObservableProperty; |
|
import com.github.javaparser.ast.observer.PropagatingAstObserver; |
|
import com.github.javaparser.ast.visitor.CloneVisitor; |
|
import com.github.javaparser.ast.visitor.EqualsVisitor; |
|
import com.github.javaparser.ast.visitor.HashCodeVisitor; |
|
import com.github.javaparser.ast.visitor.Visitable; |
|
import com.github.javaparser.metamodel.*; |
|
import com.github.javaparser.printer.DefaultPrettyPrinter; |
|
import com.github.javaparser.printer.Printer; |
|
import com.github.javaparser.printer.configuration.DefaultConfigurationOption; |
|
import com.github.javaparser.printer.configuration.DefaultPrinterConfiguration; |
|
import com.github.javaparser.printer.configuration.DefaultPrinterConfiguration.ConfigOption; |
|
import com.github.javaparser.printer.configuration.PrinterConfiguration; |
|
import com.github.javaparser.resolution.SymbolResolver; |
|
import com.github.javaparser.utils.LineSeparator; |
|
import java.util.*; |
|
import java.util.function.Consumer; |
|
import java.util.function.Function; |
|
import java.util.function.Predicate; |
|
import java.util.stream.Stream; |
|
import java.util.stream.StreamSupport; |
|
import static com.github.javaparser.ast.Node.Parsedness.PARSED; |
|
import static com.github.javaparser.ast.Node.TreeTraversal.PREORDER; |
|
import static java.util.Collections.emptySet; |
|
import static java.util.Collections.unmodifiableList; |
|
import static java.util.Spliterator.DISTINCT; |
|
import static java.util.Spliterator.NONNULL; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public abstract class Node implements Cloneable, HasParentNode<Node>, Visitable, NodeWithRange<Node>, NodeWithTokenRange<Node> { |
|
|
|
|
|
|
|
*/ |
|
public enum ObserverRegistrationMode { |
|
|
|
|
|
|
|
*/ |
|
JUST_THIS_NODE, |
|
|
|
|
|
|
|
*/ |
|
THIS_NODE_AND_EXISTING_DESCENDANTS, |
|
|
|
|
|
|
|
|
|
*/ |
|
SELF_PROPAGATING |
|
} |
|
|
|
public enum Parsedness { |
|
|
|
PARSED, UNPARSABLE |
|
} |
|
|
|
|
|
|
|
*/ |
|
public static Comparator<NodeWithRange<?>> NODE_BY_BEGIN_POSITION = (a, b) -> { |
|
if (a.hasRange() && b.hasRange()) { |
|
return a.getRange().get().begin.compareTo(b.getRange().get().begin); |
|
} |
|
if (a.hasRange() || b.hasRange()) { |
|
if (a.hasRange()) { |
|
return 1; |
|
} |
|
return -1; |
|
} |
|
return 0; |
|
}; |
|
|
|
|
|
private static final int LEVELS_TO_EXPLORE = 3; |
|
|
|
protected static final PrinterConfiguration prettyPrinterNoCommentsConfiguration = new DefaultPrinterConfiguration().removeOption(new DefaultConfigurationOption(ConfigOption.PRINT_COMMENTS)); |
|
|
|
@InternalProperty |
|
private Range range; |
|
|
|
@InternalProperty |
|
private TokenRange tokenRange; |
|
|
|
@InternalProperty |
|
private Node parentNode; |
|
|
|
@InternalProperty |
|
private ArrayList<Node> childNodes = new ArrayList<>(0); |
|
|
|
@InternalProperty |
|
private ArrayList<Comment> orphanComments = new ArrayList<>(0); |
|
|
|
@InternalProperty |
|
private IdentityHashMap<DataKey<?>, Object> data = null; |
|
|
|
@OptionalProperty |
|
private Comment comment; |
|
|
|
@InternalProperty |
|
private ArrayList<AstObserver> observers = new ArrayList<>(0); |
|
|
|
@InternalProperty |
|
private Parsedness parsed = PARSED; |
|
|
|
protected Node(TokenRange tokenRange) { |
|
setTokenRange(tokenRange); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected void customInitialization() { |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
protected Printer getPrinter() { |
|
return findCompilationUnit().map(c -> c.getPrinter()).orElse(createDefaultPrinter()); |
|
} |
|
|
|
|
|
|
|
*/ |
|
protected Printer getPrinter(PrinterConfiguration configuration) { |
|
return findCompilationUnit().map(c -> c.getPrinter(configuration)).orElse(createDefaultPrinter(configuration)); |
|
} |
|
|
|
protected Printer createDefaultPrinter() { |
|
return createDefaultPrinter(getDefaultPrinterConfiguration()); |
|
} |
|
|
|
protected Printer createDefaultPrinter(PrinterConfiguration configuration) { |
|
return new DefaultPrettyPrinter(configuration); |
|
} |
|
|
|
|
|
|
|
*/ |
|
protected PrinterConfiguration getDefaultPrinterConfiguration() { |
|
return new DefaultPrinterConfiguration(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Generated("com.github.javaparser.generator.core.node.PropertyGenerator") |
|
public Optional<Comment> getComment() { |
|
return Optional.ofNullable(comment); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Optional<Range> getRange() { |
|
return Optional.ofNullable(range); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Optional<TokenRange> getTokenRange() { |
|
return Optional.ofNullable(tokenRange); |
|
} |
|
|
|
public Node setTokenRange(TokenRange tokenRange) { |
|
this.tokenRange = tokenRange; |
|
if (tokenRange == null || !(tokenRange.getBegin().hasRange() && tokenRange.getEnd().hasRange())) { |
|
range = null; |
|
} else { |
|
range = new Range(tokenRange.getBegin().getRange().get().begin, tokenRange.getEnd().getRange().get().end); |
|
} |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public Node setRange(Range range) { |
|
if (this.range == range) { |
|
return this; |
|
} |
|
notifyPropertyChange(ObservableProperty.RANGE, this.range, range); |
|
this.range = range; |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Node setComment(final Comment comment) { |
|
if (this.comment == comment) { |
|
return this; |
|
} |
|
notifyPropertyChange(ObservableProperty.COMMENT, this.comment, comment); |
|
if (this.comment != null) { |
|
this.comment.setCommentedNode(null); |
|
} |
|
this.comment = comment; |
|
if (comment != null) { |
|
this.comment.setCommentedNode(this); |
|
} |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final Node setLineComment(String comment) { |
|
return setComment(new LineComment(comment)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final Node setBlockComment(String comment) { |
|
return setComment(new BlockComment(comment)); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public final String toString() { |
|
if (containsData(LINE_SEPARATOR_KEY)) { |
|
Printer printer = getPrinter(); |
|
LineSeparator lineSeparator = getLineEndingStyleOrDefault(LineSeparator.SYSTEM); |
|
PrinterConfiguration config = printer.getConfiguration(); |
|
config.addOption(new DefaultConfigurationOption(ConfigOption.END_OF_LINE_CHARACTER, lineSeparator.asRawString())); |
|
printer.setConfiguration(config); |
|
return printer.print(this); |
|
} |
|
return getPrinter().print(this); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public final String toString(PrinterConfiguration configuration) { |
|
return getPrinter(configuration).print(this); |
|
} |
|
|
|
@Override |
|
public final int hashCode() { |
|
return HashCodeVisitor.hashCode(this); |
|
} |
|
|
|
@Override |
|
public boolean equals(final Object obj) { |
|
if (!(obj instanceof Node)) { |
|
return false; |
|
} |
|
return EqualsVisitor.equals(this, (Node) obj); |
|
} |
|
|
|
@Override |
|
public Optional<Node> getParentNode() { |
|
return Optional.ofNullable(parentNode); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public List<Node> getChildNodes() { |
|
return unmodifiableList(childNodes); |
|
} |
|
|
|
public void addOrphanComment(Comment comment) { |
|
orphanComments.add(comment); |
|
comment.setParentNode(this); |
|
} |
|
|
|
public boolean removeOrphanComment(Comment comment) { |
|
boolean removed = orphanComments.remove(comment); |
|
if (removed) { |
|
notifyPropertyChange(ObservableProperty.COMMENT, comment, null); |
|
comment.setParentNode(null); |
|
orphanComments.trimToSize(); |
|
} |
|
return removed; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public List<Comment> getOrphanComments() { |
|
return unmodifiableList(orphanComments); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public List<Comment> getAllContainedComments() { |
|
List<Comment> comments = new LinkedList<>(orphanComments); |
|
for (Node child : getChildNodes()) { |
|
child.getComment().ifPresent(comments::add); |
|
comments.addAll(child.getAllContainedComments()); |
|
} |
|
return comments; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public Node setParentNode(Node newParentNode) { |
|
if (newParentNode == parentNode) { |
|
return this; |
|
} |
|
observers.forEach(o -> o.parentChange(this, parentNode, newParentNode)); |
|
|
|
if (parentNode != null) { |
|
final ArrayList<Node> parentChildNodes = parentNode.childNodes; |
|
for (int i = 0; i < parentChildNodes.size(); i++) { |
|
if (parentChildNodes.get(i) == this) { |
|
parentChildNodes.remove(i); |
|
} |
|
} |
|
parentChildNodes.trimToSize(); |
|
} |
|
parentNode = newParentNode; |
|
|
|
if (parentNode != null) { |
|
parentNode.childNodes.add(this); |
|
} |
|
return this; |
|
} |
|
|
|
protected void setAsParentNodeOf(Node childNode) { |
|
if (childNode != null) { |
|
childNode.setParentNode(getParentNodeForChildren()); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Deprecated |
|
public static final int ABSOLUTE_BEGIN_LINE = Position.ABSOLUTE_BEGIN_LINE; |
|
|
|
|
|
|
|
*/ |
|
@Deprecated |
|
public static final int ABSOLUTE_END_LINE = Position.ABSOLUTE_END_LINE; |
|
|
|
public void tryAddImportToParentCompilationUnit(Class<?> clazz) { |
|
findAncestor(CompilationUnit.class).ifPresent(p -> p.addImport(clazz)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Deprecated |
|
public <N extends Node> List<N> getChildNodesByType(Class<N> clazz) { |
|
List<N> nodes = new ArrayList<>(); |
|
for (Node child : getChildNodes()) { |
|
if (clazz.isInstance(child)) { |
|
nodes.add(clazz.cast(child)); |
|
} |
|
nodes.addAll(child.getChildNodesByType(clazz)); |
|
} |
|
return nodes; |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Deprecated |
|
public <N extends Node> List<N> getNodesByType(Class<N> clazz) { |
|
return getChildNodesByType(clazz); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
public <M> M getData(final DataKey<M> key) { |
|
if (data == null) { |
|
throw new IllegalStateException("No data of this type found. Use containsData to check for this first."); |
|
} |
|
M value = (M) data.get(key); |
|
if (value == null) { |
|
throw new IllegalStateException("No data of this type found. Use containsData to check for this first."); |
|
} |
|
return value; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Set<DataKey<?>> getDataKeys() { |
|
if (data == null) { |
|
return emptySet(); |
|
} |
|
return data.keySet(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public <M> void setData(DataKey<M> key, M object) { |
|
if (data == null) { |
|
data = new IdentityHashMap<>(); |
|
} |
|
data.put(key, object); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean containsData(DataKey<?> key) { |
|
if (data == null) { |
|
return false; |
|
} |
|
return data.containsKey(key); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void removeData(DataKey<?> key) { |
|
if (data != null) { |
|
data.remove(key); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean remove() { |
|
if (parentNode == null) { |
|
return false; |
|
} |
|
return parentNode.remove(this); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean replace(Node node) { |
|
if (parentNode == null) { |
|
return false; |
|
} |
|
return parentNode.replace(this, node); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void removeForced() { |
|
if (!remove()) { |
|
getParentNode().ifPresent(Node::remove); |
|
} |
|
} |
|
|
|
@Override |
|
public Node getParentNodeForChildren() { |
|
return this; |
|
} |
|
|
|
protected void setAsParentNodeOf(NodeList<? extends Node> list) { |
|
if (list != null) { |
|
list.setParentNode(getParentNodeForChildren()); |
|
} |
|
} |
|
|
|
public <P> void notifyPropertyChange(ObservableProperty property, P oldValue, P newValue) { |
|
this.observers.forEach(o -> o.propertyChange(this, property, oldValue, newValue)); |
|
} |
|
|
|
@Override |
|
public void unregister(AstObserver observer) { |
|
this.observers.remove(observer); |
|
this.observers.trimToSize(); |
|
} |
|
|
|
@Override |
|
public void register(AstObserver observer) { |
|
// Check if the observer is not registered yet. |
|
|
|
if (!this.observers.contains(observer)) { |
|
this.observers.add(observer); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public void register(AstObserver observer, ObserverRegistrationMode mode) { |
|
if (mode == null) { |
|
throw new IllegalArgumentException("Mode should be not null"); |
|
} |
|
switch(mode) { |
|
case JUST_THIS_NODE: |
|
register(observer); |
|
break; |
|
case THIS_NODE_AND_EXISTING_DESCENDANTS: |
|
registerForSubtree(observer); |
|
break; |
|
case SELF_PROPAGATING: |
|
registerForSubtree(PropagatingAstObserver.transformInPropagatingObserver(observer)); |
|
break; |
|
default: |
|
throw new UnsupportedOperationException("This mode is not supported: " + mode); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void registerForSubtree(AstObserver observer) { |
|
register(observer); |
|
this.getChildNodes().forEach(c -> c.registerForSubtree(observer)); |
|
for (PropertyMetaModel property : getMetaModel().getAllPropertyMetaModels()) { |
|
if (property.isNodeList()) { |
|
NodeList<?> nodeList = (NodeList<?>) property.getValue(this); |
|
if (nodeList != null) |
|
nodeList.register(observer); |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public boolean isRegistered(AstObserver observer) { |
|
return this.observers.contains(observer); |
|
} |
|
|
|
@Generated("com.github.javaparser.generator.core.node.RemoveMethodGenerator") |
|
public boolean remove(Node node) { |
|
if (node == null) |
|
return false; |
|
if (comment != null) { |
|
if (node == comment) { |
|
removeComment(); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
@Generated("com.github.javaparser.generator.core.node.RemoveMethodGenerator") |
|
public Node removeComment() { |
|
return setComment((Comment) null); |
|
} |
|
|
|
@Override |
|
@Generated("com.github.javaparser.generator.core.node.CloneGenerator") |
|
public Node clone() { |
|
return (Node) accept(new CloneVisitor(), null); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Generated("com.github.javaparser.generator.core.node.GetMetaModelGenerator") |
|
public NodeMetaModel getMetaModel() { |
|
return JavaParserMetaModel.nodeMetaModel; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public Parsedness getParsed() { |
|
return parsed; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Node setParsed(Parsedness parsed) { |
|
this.parsed = parsed; |
|
return this; |
|
} |
|
|
|
@Generated("com.github.javaparser.generator.core.node.ReplaceMethodGenerator") |
|
public boolean replace(Node node, Node replacementNode) { |
|
if (node == null) |
|
return false; |
|
if (comment != null) { |
|
if (node == comment) { |
|
setComment((Comment) replacementNode); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Node findRootNode() { |
|
Node n = this; |
|
while (n.getParentNode().isPresent()) { |
|
n = n.getParentNode().get(); |
|
} |
|
return n; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Optional<CompilationUnit> findCompilationUnit() { |
|
Node rootNode = findRootNode(); |
|
if (rootNode instanceof CompilationUnit) { |
|
return Optional.of((CompilationUnit) rootNode); |
|
} |
|
return Optional.empty(); |
|
} |
|
|
|
public LineSeparator getLineEndingStyleOrDefault(LineSeparator defaultLineSeparator) { |
|
if (getLineEndingStyle().isStandardEol()) { |
|
return getLineEndingStyle(); |
|
} |
|
return defaultLineSeparator; |
|
} |
|
|
|
public LineSeparator getLineEndingStyle() { |
|
Node current = this; |
|
|
|
if (current.containsData(Node.LINE_SEPARATOR_KEY)) { |
|
LineSeparator lineSeparator = current.getData(Node.LINE_SEPARATOR_KEY); |
|
return lineSeparator; |
|
} |
|
|
|
while (current.getParentNode().isPresent()) { |
|
current = current.getParentNode().get(); |
|
if (current.containsData(Node.LINE_SEPARATOR_KEY)) { |
|
return current.getData(Node.LINE_SEPARATOR_KEY); |
|
} |
|
} |
|
|
|
return LineSeparator.SYSTEM; |
|
} |
|
|
|
protected SymbolResolver getSymbolResolver() { |
|
return findCompilationUnit().map(cu -> { |
|
if (cu.containsData(SYMBOL_RESOLVER_KEY)) { |
|
return cu.getData(SYMBOL_RESOLVER_KEY); |
|
} else { |
|
throw new IllegalStateException("Symbol resolution not configured: to configure consider setting a SymbolResolver in the ParserConfiguration"); |
|
} |
|
}).orElseThrow(() -> new IllegalStateException("The node is not inserted in a CompilationUnit")); |
|
} |
|
|
|
|
|
public static final DataKey<SymbolResolver> SYMBOL_RESOLVER_KEY = new DataKey<SymbolResolver>() { |
|
}; |
|
|
|
public static final DataKey<LineSeparator> LINE_SEPARATOR_KEY = new DataKey<LineSeparator>() { |
|
}; |
|
|
|
protected static final DataKey<Printer> PRINTER_KEY = new DataKey<Printer>() { |
|
}; |
|
|
|
protected static final DataKey<Boolean> PHANTOM_KEY = new DataKey<Boolean>() { |
|
}; |
|
|
|
public enum TreeTraversal { |
|
|
|
PREORDER, BREADTHFIRST, POSTORDER, PARENTS, DIRECT_CHILDREN |
|
} |
|
|
|
private Iterator<Node> treeIterator(TreeTraversal traversal) { |
|
switch(traversal) { |
|
case BREADTHFIRST: |
|
return new BreadthFirstIterator(this); |
|
case POSTORDER: |
|
return new PostOrderIterator(this); |
|
case PREORDER: |
|
return new PreOrderIterator(this); |
|
case DIRECT_CHILDREN: |
|
return new DirectChildrenIterator(this); |
|
case PARENTS: |
|
return new ParentsVisitor(this); |
|
default: |
|
throw new IllegalArgumentException("Unknown traversal choice."); |
|
} |
|
} |
|
|
|
private Iterable<Node> treeIterable(TreeTraversal traversal) { |
|
return () -> treeIterator(traversal); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Stream<Node> stream(TreeTraversal traversal) { |
|
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(treeIterator(traversal), NONNULL | DISTINCT), false); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Stream<Node> stream() { |
|
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(treeIterator(PREORDER), NONNULL | DISTINCT), false); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public void walk(TreeTraversal traversal, Consumer<Node> consumer) { |
|
|
|
for (Node node : treeIterable(traversal)) { |
|
consumer.accept(node); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void walk(Consumer<Node> consumer) { |
|
walk(PREORDER, consumer); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public <T extends Node> void walk(Class<T> nodeType, Consumer<T> consumer) { |
|
walk(TreeTraversal.PREORDER, node -> { |
|
if (nodeType.isAssignableFrom(node.getClass())) { |
|
consumer.accept(nodeType.cast(node)); |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public <T extends Node> List<T> findAll(Class<T> nodeType) { |
|
final List<T> found = new ArrayList<>(); |
|
walk(nodeType, found::add); |
|
return found; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public <T extends Node> List<T> findAll(Class<T> nodeType, TreeTraversal traversal) { |
|
final List<T> found = new ArrayList<>(); |
|
walk(traversal, node -> { |
|
if (nodeType.isAssignableFrom(node.getClass())) { |
|
found.add(nodeType.cast(node)); |
|
} |
|
}); |
|
return found; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public <T extends Node> List<T> findAll(Class<T> nodeType, Predicate<T> predicate) { |
|
final List<T> found = new ArrayList<>(); |
|
walk(nodeType, n -> { |
|
if (predicate.test(n)) |
|
found.add(n); |
|
}); |
|
return found; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public <T> Optional<T> findFirst(TreeTraversal traversal, Function<Node, Optional<T>> consumer) { |
|
for (Node node : treeIterable(traversal)) { |
|
final Optional<T> result = consumer.apply(node); |
|
if (result.isPresent()) { |
|
return result; |
|
} |
|
} |
|
return Optional.empty(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public <N extends Node> Optional<N> findFirst(Class<N> nodeType) { |
|
return findFirst(TreeTraversal.PREORDER, node -> { |
|
if (nodeType.isAssignableFrom(node.getClass())) { |
|
return Optional.of(nodeType.cast(node)); |
|
} |
|
return Optional.empty(); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public <N extends Node> Optional<N> findFirst(Class<N> nodeType, Predicate<N> predicate) { |
|
return findFirst(TreeTraversal.PREORDER, node -> { |
|
if (nodeType.isAssignableFrom(node.getClass())) { |
|
final N castNode = nodeType.cast(node); |
|
if (predicate.test(castNode)) { |
|
return Optional.of(castNode); |
|
} |
|
} |
|
return Optional.empty(); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isAncestorOf(Node descendant) { |
|
return this != descendant && findFirst(Node.class, n -> n == descendant).isPresent(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static class BreadthFirstIterator implements Iterator<Node> { |
|
|
|
private final Queue<Node> queue = new LinkedList<>(); |
|
|
|
public BreadthFirstIterator(Node node) { |
|
queue.add(node); |
|
} |
|
|
|
@Override |
|
public boolean hasNext() { |
|
return !queue.isEmpty(); |
|
} |
|
|
|
@Override |
|
public Node next() { |
|
Node next = queue.remove(); |
|
queue.addAll(next.getChildNodes()); |
|
return next; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public static class DirectChildrenIterator implements Iterator<Node> { |
|
|
|
private final Iterator<Node> childrenIterator; |
|
|
|
public DirectChildrenIterator(Node node) { |
|
childrenIterator = node.getChildNodes().iterator(); |
|
} |
|
|
|
@Override |
|
public boolean hasNext() { |
|
return childrenIterator.hasNext(); |
|
} |
|
|
|
@Override |
|
public Node next() { |
|
return childrenIterator.next(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static class ParentsVisitor implements Iterator<Node> { |
|
|
|
private Node node; |
|
|
|
public ParentsVisitor(Node node) { |
|
this.node = node; |
|
} |
|
|
|
@Override |
|
public boolean hasNext() { |
|
return node.getParentNode().isPresent(); |
|
} |
|
|
|
@Override |
|
public Node next() { |
|
node = node.getParentNode().orElse(null); |
|
return node; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static class PreOrderIterator implements Iterator<Node> { |
|
|
|
private final Stack<Node> stack = new Stack<>(); |
|
|
|
public PreOrderIterator(Node node) { |
|
stack.add(node); |
|
} |
|
|
|
@Override |
|
public boolean hasNext() { |
|
return !stack.isEmpty(); |
|
} |
|
|
|
@Override |
|
public Node next() { |
|
Node next = stack.pop(); |
|
List<Node> children = next.getChildNodes(); |
|
for (int i = children.size() - 1; i >= 0; i--) { |
|
stack.add(children.get(i)); |
|
} |
|
return next; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static class PostOrderIterator implements Iterator<Node> { |
|
|
|
private final Stack<List<Node>> nodesStack = new Stack<>(); |
|
|
|
private final Stack<Integer> cursorStack = new Stack<>(); |
|
|
|
private final Node root; |
|
|
|
private boolean hasNext = true; |
|
|
|
public PostOrderIterator(Node root) { |
|
this.root = root; |
|
fillStackToLeaf(root); |
|
} |
|
|
|
private void fillStackToLeaf(Node node) { |
|
while (true) { |
|
List<Node> childNodes = node.getChildNodes(); |
|
if (childNodes.isEmpty()) { |
|
break; |
|
} |
|
nodesStack.push(childNodes); |
|
cursorStack.push(0); |
|
node = childNodes.get(0); |
|
} |
|
} |
|
|
|
@Override |
|
public boolean hasNext() { |
|
return hasNext; |
|
} |
|
|
|
@Override |
|
public Node next() { |
|
final List<Node> nodes = nodesStack.peek(); |
|
final int cursor = cursorStack.peek(); |
|
final boolean levelHasNext = cursor < nodes.size(); |
|
if (levelHasNext) { |
|
Node node = nodes.get(cursor); |
|
fillStackToLeaf(node); |
|
return nextFromLevel(); |
|
} else { |
|
nodesStack.pop(); |
|
cursorStack.pop(); |
|
hasNext = !nodesStack.empty(); |
|
if (hasNext) { |
|
return nextFromLevel(); |
|
} |
|
return root; |
|
} |
|
} |
|
|
|
private Node nextFromLevel() { |
|
final List<Node> nodes = nodesStack.peek(); |
|
final int cursor = cursorStack.pop(); |
|
cursorStack.push(cursor + 1); |
|
return nodes.get(cursor); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public boolean hasScope() { |
|
return NodeWithOptionalScope.class.isAssignableFrom(this.getClass()) && ((NodeWithOptionalScope) this).getScope().isPresent(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public boolean isPhantom() { |
|
return isPhantom(this); |
|
} |
|
|
|
private boolean isPhantom(Node node) { |
|
if (!node.containsData(PHANTOM_KEY)) { |
|
boolean res = (node.getParentNode().isPresent() && node.getParentNode().get().hasRange() && node.hasRange() && !node.getParentNode().get().getRange().get().contains(node.getRange().get()) || inPhantomNode(node, LEVELS_TO_EXPLORE)); |
|
node.setData(PHANTOM_KEY, res); |
|
} |
|
return node.getData(PHANTOM_KEY); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private boolean inPhantomNode(Node node, int levels) { |
|
return node.getParentNode().isPresent() && (isPhantom(node.getParentNode().get()) || inPhantomNode(node.getParentNode().get(), levels - 1)); |
|
} |
|
} |