StreamForEachConverter.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.corext.fix.helper;

import java.util.Set;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.text.edits.TextEditGroup;
import org.sandbox.jdt.internal.common.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.UseFunctionalCallFixCore;

/**
 * Registers stream-to-loop rewrites using the common ULR and imperative renderers.
 * Parsing and semantic metadata belong to JdtStreamExtractor; this class owns
 * only integration with the cleanup lifecycle.
 */
public final class StreamForEachConverter {
	private StreamForEachConverter() {
	}

	public static void find(UseFunctionalCallFixCore fix, CompilationUnit unit,
			Set<CompilationUnitRewriteOperation> operations, Set<ASTNode> processed) {
		LoopConversionService.scanRoot(unit).accept(new ASTVisitor() {
			@Override
			public boolean visit(MethodInvocation node) {
				if (ExpressionHelper.overlapsProcessedNode(node, processed)) {
					return false;
				}
				JdtStreamExtractor.ExtractedStream extracted = JdtStreamExtractor.extract(node);
				if (extracted == null) {
					return true;
				}
				if (fix == UseFunctionalCallFixCore.STREAM_TO_FOR
						&& extracted.model().getSource().type() == org.sandbox.functional.core.model.SourceDescriptor.SourceType.STREAM) {
					// A Stream has no enhanced-for protocol; preserve lazy factories via an iterator target.
					return true;
				}
				ReferenceHolder<ASTNode, Object> data = new ReferenceHolder<>();
				data.put(node, extracted);
				operations.add(fix.rewrite(node, data));
				processed.add(node);
				// One owner per replaced statement, including nested calls in its lambdas.
				return false;
			}
		});
	}

	public static void rewrite(ASTNode node, CompilationUnitRewrite cuRewrite, TextEditGroup group,
			ReferenceHolder<ASTNode, Object> data, boolean iteratorTarget) {
		if (data.get(node) instanceof JdtStreamExtractor.ExtractedStream extracted) {
			if (iteratorTarget) {
				new ASTIteratorWhileRenderer(cuRewrite.getAST(), cuRewrite.getASTRewrite())
						.renderPipeline(extracted.model(), extracted.context(), cuRewrite.getImportRewrite(), group);
			} else {
				new ASTEnhancedForRenderer(cuRewrite.getAST(), cuRewrite.getASTRewrite())
						.renderPipeline(extracted.model(), extracted.context(), cuRewrite.getImportRewrite(), group);
			}
		}
	}
}