LoopConversionInspection.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer and others.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which 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.ArrayList;

import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.BuildContext;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CompilationParticipant;
import org.eclipse.jdt.core.compiler.ReconcileContext;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.sandbox.jdt.internal.corext.fix.UseFunctionalCallFixCore.LoopConversionOperation;
import org.sandbox.jdt.internal.ui.fix.LoopConversionQuickAssistProcessor;

/** Optional, managed JDT problems; disabled by default, also cleared on reconcile. */
public final class LoopConversionInspection extends CompilationParticipant {
	public static final String PLUGIN_ID = "sandbox_functional_converter"; //$NON-NLS-1$
	public static final String MARKER_TYPE = PLUGIN_ID + ".loopConversionProblem"; //$NON-NLS-1$
	public static final int PROBLEM_ID = 12001;
	public static final String SEVERITY = "loopConversion.inspectionSeverity"; //$NON-NLS-1$
	public static final String TARGET = "loopConversion.inspectionTarget"; //$NON-NLS-1$

	public static boolean isEnabled() {
		return !"off".equals(preference(SEVERITY, "off")); //$NON-NLS-1$ //$NON-NLS-2$
	}

	public static LoopTargetFormat target() {
		return LoopTargetFormat.fromId(preference(TARGET, "stream")); //$NON-NLS-1$
	}

	private static String preference(String key, String fallback) {
		return Platform.getPreferencesService().getString(PLUGIN_ID, key, fallback, null);
	}

	@Override
	public boolean isActive(org.eclipse.jdt.core.IJavaProject project) {
		// Remain active when disabled so JDT can remove old managed problems.
		return true;
	}

	@Override
	public void buildStarting(BuildContext[] files, boolean batch) {
		for (BuildContext file : files) {
			if (!isEnabled()) {
				file.recordNewProblems(new CategorizedProblem[0]);
				continue;
			}
			var unit = JavaCore.createCompilationUnitFrom(file.getFile());
			if (unit != null) {
				ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
				parser.setSource(unit);
				parser.setResolveBindings(true);
				file.recordNewProblems(problems((CompilationUnit) parser.createAST(null)));
			}
		}
	}

	@Override
	public void reconcile(ReconcileContext context) {
		try {
			context.putProblems(MARKER_TYPE, isEnabled() ? problems(context.getAST(AST.getJLSLatest())) : new CategorizedProblem[0]);
		} catch (JavaModelException unavailable) {
			context.putProblems(MARKER_TYPE, new CategorizedProblem[0]);
		}
	}

	public static CategorizedProblem[] problems(CompilationUnit unit) {
		if (!isEnabled() || unit == null) {
			return new CategorizedProblem[0];
		}
		LoopTargetFormat target = target();
		var problems = new ArrayList<CategorizedProblem>();
		for (var operation : LoopConversionService.analyze(unit, LoopConversionService.handlers(target)).operations()) {
			if (operation instanceof LoopConversionOperation loop) {
				ASTNode anchor = loop.getAnchor();
				int severity = "warning".equals(preference(SEVERITY, "off")) ? ProblemSeverities.Warning : ProblemSeverities.Info; //$NON-NLS-1$ //$NON-NLS-2$
				char[] fileName = unit.getJavaElement() == null ? new char[0] : unit.getJavaElement().getPath().toString().toCharArray();
				problems.add(new DefaultProblem(fileName, LoopConversionQuickAssistProcessor.label(target), PROBLEM_ID,
						new String[] { target.getId() }, severity, anchor.getStartPosition(),
						anchor.getStartPosition() + anchor.getLength() - 1, unit.getLineNumber(anchor.getStartPosition()), 0) {
					@Override
					public String getMarkerType() {
						return MARKER_TYPE;
					}
					@Override
					public int getCategoryID() {
						return CAT_CODE_STYLE;
					}
				});
			}
		}
		return problems.toArray(CategorizedProblem[]::new);
	}
}