UseFunctionalCallCleanUpCore.java

/*******************************************************************************
 * Copyright (c) 2021 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
 *
 * Contributors:
 *     Carsten Hammer
 *******************************************************************************/
package org.sandbox.jdt.internal.ui.fix;

import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.LOOP_CONVERSION_ENABLED;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.LOOP_CONVERSION_FROM_ENHANCED_FOR;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.LOOP_CONVERSION_FROM_ITERATOR_WHILE;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.LOOP_CONVERSION_FROM_STREAM;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.LOOP_CONVERSION_TARGET_FORMAT;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.USEFUNCTIONALLOOP_CLEANUP;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.USEFUNCTIONALLOOP_CLEANUP_V2;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.USEFUNCTIONALLOOP_FORMAT_FOR;
import static org.sandbox.jdt.internal.corext.fix2.MYCleanUpConstants.USEFUNCTIONALLOOP_FORMAT_WHILE;
import static org.sandbox.jdt.internal.ui.fix.MultiFixMessages.FunctionalCallCleanUpFix_refactor;
import static org.sandbox.jdt.internal.ui.fix.MultiFixMessages.FunctionalCallCleanUp_description;
import static org.sandbox.jdt.internal.ui.preferences.cleanup.CleanUpMessages.LoopConversion_Description;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.ui.fix.AbstractCleanUp;
import org.eclipse.jdt.internal.ui.fix.MapCleanUpOptions;
import org.eclipse.jdt.ui.cleanup.CleanUpContext;
import org.eclipse.jdt.ui.cleanup.CleanUpOptions;
import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
import org.sandbox.jdt.internal.corext.fix.UseFunctionalCallFixCore;

public class UseFunctionalCallCleanUpCore extends AbstractCleanUp {
	
	private Map<String, String> optionsMap;
	
	public UseFunctionalCallCleanUpCore(final Map<String, String> options) {
		super(options);
		this.optionsMap = options;
	}

	public UseFunctionalCallCleanUpCore() {
	}

	@Override
	public void setOptions(CleanUpOptions options) {
		super.setOptions(options);
		if (options instanceof MapCleanUpOptions mapOptions) {
			this.optionsMap = mapOptions.getMap();
		}
	}

	@Override
	public CleanUpRequirements getRequirements() {
		return new CleanUpRequirements(requireAST(), false, false, null);
	}

	public boolean requireAST() {
		return isEnabled(USEFUNCTIONALLOOP_CLEANUP) || isEnabled(USEFUNCTIONALLOOP_CLEANUP_V2) || isEnabled(LOOP_CONVERSION_ENABLED);
	}

	@Override
	public ICleanUpFix createFix(final CleanUpContext context) throws CoreException {
		CompilationUnit compilationUnit = context.getAST();
		if (compilationUnit == null) {
			return null;
		}
		EnumSet<UseFunctionalCallFixCore> computeFixSet = computeFixSet();
		if ((!isEnabled(USEFUNCTIONALLOOP_CLEANUP) && !isEnabled(USEFUNCTIONALLOOP_CLEANUP_V2) && !isEnabled(LOOP_CONVERSION_ENABLED)) || computeFixSet.isEmpty()) {
			return null;
		}
		
		Set<CompilationUnitRewriteOperation> operations = new LinkedHashSet<>();
		Set<ASTNode> nodesprocessed = new HashSet<>();
		computeFixSet.forEach(i -> i.findOperations(compilationUnit, operations, nodesprocessed));
		if (operations.isEmpty()) {
			return null;
		}
		return new CompilationUnitRewriteOperationsFixCore(FunctionalCallCleanUpFix_refactor, compilationUnit,
				operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[0]));
	}

	@Override
	public String[] getStepDescriptions() {
		List<String> result = new ArrayList<>();
		if (isEnabled(USEFUNCTIONALLOOP_CLEANUP) || isEnabled(USEFUNCTIONALLOOP_CLEANUP_V2)) {
			result.add(Messages.format(FunctionalCallCleanUp_description, new Object[] { String.join(",", //$NON-NLS-1$
					computeFixSet().stream().map(UseFunctionalCallFixCore::toString).collect(Collectors.toList())) }));
		}
		if (isEnabled(LOOP_CONVERSION_ENABLED)) {
			result.add(LoopConversion_Description);
		}
		return result.toArray(new String[0]);
	}

	@Override
	public String getPreview() {
		StringBuilder sb = new StringBuilder();
		EnumSet<UseFunctionalCallFixCore> computeFixSet = computeFixSet();
		// Always pad preview to max lines for stable preview height
		EnumSet<UseFunctionalCallFixCore> all = EnumSet.allOf(UseFunctionalCallFixCore.class);
		int maxLines = 0;
		String[] previews = new String[all.size()];
		int idx = 0;
		for (UseFunctionalCallFixCore e : all) {
			String preview = e.getPreview(computeFixSet.contains(e));
			previews[idx++] = preview;
			int lines = (int) preview.lines().count();
			if (lines > maxLines) maxLines = lines;
		}
		for (String preview : previews) {
			int lines = (int) preview.lines().count();
			sb.append(preview);
			for (int i = lines; i < maxLines; i++) {
				sb.append(System.lineSeparator());
			}
		}
		return sb.toString();
	}

	private EnumSet<UseFunctionalCallFixCore> computeFixSet() {
		EnumSet<UseFunctionalCallFixCore> fixSet = EnumSet.noneOf(UseFunctionalCallFixCore.class);

		// Functional loop cleanup (handles both V1 and V2 constants for backward compatibility)
		if (isEnabled(USEFUNCTIONALLOOP_CLEANUP) || isEnabled(USEFUNCTIONALLOOP_CLEANUP_V2)) {
			// Check if a non-stream target format is selected
			// If FOR or WHILE format is explicitly enabled, skip stream conversion
			boolean isForFormat = isEnabled(USEFUNCTIONALLOOP_FORMAT_FOR);
			boolean isWhileFormat = isEnabled(USEFUNCTIONALLOOP_FORMAT_WHILE);
			
			if (!isForFormat && !isWhileFormat) {
				// LOOP now uses the unified V2 implementation (ULR + Refactorer fallback)
				fixSet.add(UseFunctionalCallFixCore.LOOP);
				fixSet.add(UseFunctionalCallFixCore.ITERATOR_LOOP);
				fixSet.add(UseFunctionalCallFixCore.TRADITIONAL_FOR_LOOP);
			}
			// Note: FOR and WHILE format conversions are not yet implemented
			// When they are, add the appropriate converters here
		}
		
		// Bidirectional Loop Conversion (Phase 9)
		if (isEnabled(LOOP_CONVERSION_ENABLED)) {
			String targetFormat = getTargetFormat();
			addBidirectionalTransformers(fixSet, targetFormat);
		}
		
		return fixSet;
	}

	private String getTargetFormat() {
		if (optionsMap != null) {
			String value = optionsMap.get(LOOP_CONVERSION_TARGET_FORMAT);
			if (value != null) {
				return value;
			}
		}
		return "stream"; //$NON-NLS-1$
	}

	private void addBidirectionalTransformers(EnumSet<UseFunctionalCallFixCore> fixSet, String targetFormat) {
		switch (targetFormat) {
		case "enhanced_for": //$NON-NLS-1$
			if (isEnabled(LOOP_CONVERSION_FROM_STREAM)) {
				fixSet.add(UseFunctionalCallFixCore.STREAM_TO_FOR);
			}
			if (isEnabled(LOOP_CONVERSION_FROM_ITERATOR_WHILE)) {
				fixSet.add(UseFunctionalCallFixCore.ITERATOR_TO_FOR);
			}
			break;
		case "iterator_while": //$NON-NLS-1$
			if (isEnabled(LOOP_CONVERSION_FROM_ENHANCED_FOR)) {
				fixSet.add(UseFunctionalCallFixCore.FOR_TO_ITERATOR);
			}
			if (isEnabled(LOOP_CONVERSION_FROM_STREAM)) {
				fixSet.add(UseFunctionalCallFixCore.STREAM_TO_ITERATOR);
			}
			break;
		case "stream": //$NON-NLS-1$
		default:
			if (isEnabled(LOOP_CONVERSION_FROM_ENHANCED_FOR)) {
				fixSet.add(UseFunctionalCallFixCore.LOOP);
			}
			if (isEnabled(LOOP_CONVERSION_FROM_ITERATOR_WHILE)) {
				fixSet.add(UseFunctionalCallFixCore.ITERATOR_LOOP);
			}
			break;
		}
	}
}