IndependentAstProcessorBuilder.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
package org.sandbox.jdt.internal.common;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;

import org.eclipse.jdt.core.dom.ASTNode;

/**
 * Collects independent AST visitors that each inspect the complete root supplied to
 * {@link #build(ASTNode)}.
 *
 * <p>Registration order is deterministic, but one visitor never narrows the scope of
 * another. Use {@link AstProcessing#scoped(ReferenceHolder)} when later stages are
 * semantically dependent on an earlier match.</p>
 *
 * @param <V> reference-holder key type
 * @param <T> reference-holder value type
 */
public final class IndependentAstProcessorBuilder<V, T> {

	private final ReferenceHolder<V, T> holder;
	private final List<Stage<V, T>> stages= new ArrayList<>();
	private Set<ASTNode> excludedNodes= Set.of();

	IndependentAstProcessorBuilder(ReferenceHolder<V, T> holder) {
		this.holder= holder;
	}

	/**
	 * Registers one typed visitor that will inspect the complete build root.
	 *
	 * @param <N> AST node type
	 * @param nodeType node class to visit
	 * @param handler processing callback; its result controls child traversal
	 * @return this builder
	 */
	public <N extends ASTNode> IndependentAstProcessorBuilder<V, T> on(
			Class<N> nodeType,
			BiPredicate<? super N, ReferenceHolder<V, T>> handler) {
		Objects.requireNonNull(nodeType, "nodeType"); //$NON-NLS-1$
		Objects.requireNonNull(handler, "handler"); //$NON-NLS-1$
		VisitorEnum visitorType= AstProcessing.visitorType(nodeType);
		stages.add(new Stage<>(visitorType,
				(node, data) -> handler.test(nodeType.cast(node), data)));
		return this;
	}

	/**
	 * Registers a typed action and always visits descendants of matching nodes.
	 * Use {@link #on(Class, BiPredicate)} when child traversal must be conditional
	 * or callbacks need access to the shared reference holder.
	 *
	 * @param <N> AST node type
	 * @param nodeType node class to visit
	 * @param action action performed for each matching node
	 * @return this builder
	 */
	public <N extends ASTNode> IndependentAstProcessorBuilder<V, T> visit(
			Class<N> nodeType, Consumer<? super N> action) {
		Objects.requireNonNull(action, "action"); //$NON-NLS-1$
		return on(nodeType, (node, data) -> {
			action.accept(node);
			return true;
		});
	}

	/** Skips callbacks for these nodes; their descendants are still visited. */
	public IndependentAstProcessorBuilder<V, T> excluding(Set<? extends ASTNode> nodes) {
		excludedNodes= Set.copyOf(Objects.requireNonNull(nodes, "nodes")); //$NON-NLS-1$
		return this;
	}

	/** Executes every registered visitor independently against {@code root}. */
	public void build(ASTNode root) {
		Objects.requireNonNull(root, "root"); //$NON-NLS-1$
		for (Stage<V, T> stage : stages) {
			HelperVisitor<ReferenceHolder<V, T>, V, T> visitor=
					new HelperVisitor<>(new HashSet<>(excludedNodes), holder);
			visitor.add(stage.visitorType(), (node, data) ->
					excludedNodes.contains(node) || stage.handler().test(node, data));
			visitor.build(root);
		}
	}

	private record Stage<V, T>(
			VisitorEnum visitorType,
			BiPredicate<ASTNode, ReferenceHolder<V, T>> handler) {
	}
}