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

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperationWithSourceRange;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.text.edits.TextEditGroup;
import org.osgi.framework.Bundle;
import org.sandbox.jdt.internal.common.AstProcessorBuilder;
import org.sandbox.jdt.internal.common.LibStandardNames;
import org.sandbox.jdt.internal.common.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.JfaceCleanUpFixCore;

/**
 * Cleanup transformation for migrating from deprecated {@code SubProgressMonitor} to {@link SubMonitor}.
 * 
 * <p>This helper transforms progress monitor usage patterns in Eclipse JDT code:</p>
 * <ul>
 * <li>Converts {@code IProgressMonitor.beginTask()} to {@code SubMonitor.convert()}</li>
 * <li>Replaces {@code new SubProgressMonitor(monitor, work)} with {@code subMonitor.split(work)}</li>
 * <li>Handles both 2-argument and 3-argument SubProgressMonitor constructors</li>
 * <li>Generates unique variable names to avoid collisions in scope</li>
 * </ul>
 * 
 * <p><b>Migration Pattern:</b></p>
 * <pre>
 * // Before:
 * monitor.beginTask("Main Task", 100);
 * IProgressMonitor subMon = new SubProgressMonitor(monitor, 60);
 * 
 * // After:
 * SubMonitor subMonitor = SubMonitor.convert(monitor, "Main Task", 100);
 * IProgressMonitor subMon = subMonitor.split(60);
 * </pre>
 * 
 * @see SubProgressMonitor
 * @see SubMonitor
 */
public class JFacePlugin extends
AbstractTool<ReferenceHolder<Integer, JFacePlugin.MonitorHolder>> {

	public static final String CLASS_INSTANCE_CREATION = "ClassInstanceCreation"; //$NON-NLS-1$
	public static final String METHODINVOCATION = "MethodInvocation"; //$NON-NLS-1$

	/** Debug option key for enabling JFace plugin transformation logging */
	private static final String DEBUG_OPTION = "sandbox_jface_cleanup/debug/jfaceplugin"; //$NON-NLS-1$
	
	/** Bundle symbolic name for logging */
	private static final String BUNDLE_ID = "sandbox_jface_cleanup"; //$NON-NLS-1$

	/**
	 * Holder for monitor-related transformation data.
	 * Tracks beginTask invocations and associated SubProgressMonitor instances.
	 */
	public static class MonitorHolder {
		/** The beginTask method invocation to be converted */
		public MethodInvocation minv;
		/** The monitor variable name from beginTask expression */
		public String minvname;
		/** Set of SubProgressMonitor constructions to be converted to split() calls */
		public Set<ClassInstanceCreation> setofcic = new HashSet<>();
		/** Standalone SubProgressMonitor constructions (without associated beginTask) */
		public Set<ClassInstanceCreation> standaloneSubProgressMonitors = new HashSet<>();
		/** SubProgressMonitor on already-SubMonitor variables (use split() directly) */
		public Set<ClassInstanceCreation> subProgressMonitorOnSubMonitor = new HashSet<>();
		/** SubProgressMonitor type references to be replaced with IProgressMonitor */
		public Set<org.eclipse.jdt.core.dom.Type> typesToReplace = new HashSet<>();
		/** Nodes that have been processed to avoid duplicate transformations (references shared Set passed during construction) */
		public Set<ASTNode> nodesprocessed;
	}

	/**
	 * Checks if debug logging is enabled for JFace plugin transformations.
	 * 
	 * @return {@code true} if debug logging is enabled, {@code false} otherwise
	 */
	private static boolean isDebugEnabled() {
		return Platform.inDebugMode() && "true".equalsIgnoreCase(Platform.getDebugOption(DEBUG_OPTION)); //$NON-NLS-1$
	}

	/**
	 * Logs a debug message if debug mode is enabled.
	 * 
	 * @param message the message to log
	 */
	private static void logDebug(String message) {
		if (isDebugEnabled()) {
			try {
				Bundle bundle = Platform.getBundle(BUNDLE_ID);
				if (bundle != null) {
					ILog log = Platform.getLog(bundle);
					log.log(new Status(IStatus.INFO, BUNDLE_ID, "JFacePlugin: " + message)); //$NON-NLS-1$
				}
			} catch (Exception e) {
				System.err.println("Failed to log debug message: " + e.getMessage());
				e.printStackTrace(System.err);
			}
		}
	}

	/**
	 * Finds and identifies SubProgressMonitor usage patterns to be transformed.
	 * 
	 * <p>This method scans the compilation unit for:</p>
	 * <ul>
	 * <li>{@code beginTask} method invocations on IProgressMonitor instances</li>
	 * <li>{@code SubProgressMonitor} constructor invocations that reference the same monitor</li>
	 * </ul>
	 * 
	 * <p>When both patterns are found in the same scope, a cleanup operation is registered
	 * to transform them to the SubMonitor pattern.</p>
	 * 
	 * @param fixcore the cleanup fix core instance
	 * @param compilationUnit the compilation unit to analyze
	 * @param operations set to collect identified cleanup operations
	 * @param nodesprocessed set of nodes already processed to avoid duplicates
	 * @param createForOnlyIfVarUsed flag to control when operations are created (unused in this implementation)
	 */
	@Override
	public void find(JfaceCleanUpFixCore fixcore, CompilationUnit compilationUnit,
			Set<CompilationUnitRewriteOperationWithSourceRange> operations, Set<ASTNode> nodesprocessed,
			boolean createForOnlyIfVarUsed) {
		ReferenceHolder<Integer, MonitorHolder> dataholder = new ReferenceHolder<>();
		
		// Track which SubProgressMonitor nodes are associated with beginTask (prevents Pass 2 from re-processing them)
		Set<ASTNode> beginTaskAssociated = new HashSet<>();
		
		// Pass 1: Find beginTask + SubProgressMonitor patterns (chained visitors)
		AstProcessorBuilder.with(dataholder, nodesprocessed)
			.processor()
			.callMethodInvocationVisitor(IProgressMonitor.class, LibStandardNames.METHOD_BEGIN_TASK, (node, holder) -> {
				if (node.arguments().size() != 2) {
					return true;
				}
				logDebug("Found beginTask at position " + node.getStartPosition() + " (type: " + node.getClass().getSimpleName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
				
				// Check if parent is ExpressionStatement, otherwise skip
				if (!(node.getParent() instanceof ExpressionStatement)) {
					return true;
				}
				
				Expression expr = node.getExpression();
				if (expr == null) {
					return true;
				}
				SimpleName sn = ASTNodes.as(expr, SimpleName.class);
				if (sn != null) {
					IBinding ibinding = sn.resolveBinding();
					// Add null-check for binding
					if (ibinding == null) {
						return true;
					}
					String name = ibinding.getName();
					MonitorHolder mh = new MonitorHolder();
					mh.minv = node;
					mh.minvname = name;
					mh.nodesprocessed = nodesprocessed;
					holder.put(holder.size(), mh);
				}
				return true;
			}, s -> ASTNodes.getTypedAncestor(s, Block.class))
			.callClassInstanceCreationVisitor("org.eclipse.core.runtime.SubProgressMonitor", (node, holder) -> { //$NON-NLS-1$
				List<?> arguments = node.arguments();
				if (arguments.isEmpty()) {
					return true;
				}
				
				// Safe handling of first argument - extract identifier from expression
				Expression firstArg = (Expression) arguments.get(0);
				String firstArgName = null;
				
				// Try to extract SimpleName from the expression
				SimpleName sn = ASTNodes.as(firstArg, SimpleName.class);
				if (sn != null) {
					firstArgName = sn.getIdentifier();
				}
				
				// Check if this SubProgressMonitor is associated with a beginTask
				if (!holder.isEmpty() && firstArgName != null) {
					MonitorHolder mh = holder.get(holder.size() - 1);
					if (mh.minvname.equals(firstArgName)) {
						logDebug("Found SubProgressMonitor construction at position " + node.getStartPosition() + " for variable '" + firstArgName + "' with beginTask"); //$NON-NLS-1$ //$NON-NLS-2$
						mh.setofcic.add(node);
						beginTaskAssociated.add(node);
					}
				}
				
				return true;
			}, s -> ASTNodes.getTypedAncestor(s, Block.class))
			.build(compilationUnit);
		
		// Add operations for beginTask-associated monitors
		if (!dataholder.isEmpty()) {
			operations.add(fixcore.rewrite(dataholder));
		}
		
		// Pass 2: Find standalone SubProgressMonitor instances using AstProcessorBuilder
		ReferenceHolder<Integer, MonitorHolder> standaloneHolder = new ReferenceHolder<>();
		
		AstProcessorBuilder.with(standaloneHolder, nodesprocessed)
			.onClassInstanceCreation((node, holder) -> {
				// Check if this is a SubProgressMonitor construction (use simple name like Pass 1)
				ITypeBinding binding = node.resolveTypeBinding();
				if (binding == null || !"SubProgressMonitor".equals(binding.getName())) { //$NON-NLS-1$
					return true;
				}
				
				// Skip nodes already associated with beginTask from pass 1
				if (beginTaskAssociated.contains(node)) {
					return true;
				}
				
				List<?> arguments = node.arguments();
				if (arguments.isEmpty()) {
					return true;
				}
				
				// Safe handling of first argument - extract identifier from expression
				Expression firstArg = (Expression) arguments.get(0);
				String firstArgName = null;
				
				// Try to extract SimpleName from the expression
				SimpleName sn = ASTNodes.as(firstArg, SimpleName.class);
				if (sn != null) {
					firstArgName = sn.getIdentifier();
				}
				
				// Check if the variable is already a SubMonitor type (use simple name like Pass 1)
				boolean isSubMonitorType = false;
				if (sn != null) {
					IBinding snBinding = sn.resolveBinding();
					if (snBinding != null && snBinding.getKind() == IBinding.VARIABLE) {
						ITypeBinding typeBinding = 
							((org.eclipse.jdt.core.dom.IVariableBinding) snBinding).getType();
						if (typeBinding != null) {
							isSubMonitorType = "SubMonitor".equals(typeBinding.getName()); //$NON-NLS-1$
						}
					}
				}
				
				// If already SubMonitor type, handle separately (use split() directly)
				if (isSubMonitorType && firstArgName != null) {
					logDebug("Found SubProgressMonitor on already-SubMonitor variable at position " + node.getStartPosition() + " for variable '" + firstArgName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
					MonitorHolder mh = new MonitorHolder();
					mh.minvname = firstArgName;
					mh.nodesprocessed = nodesprocessed;
					mh.subProgressMonitorOnSubMonitor.add(node);
					holder.put(holder.size(), mh);
					return true;
				}
				
				// Standalone SubProgressMonitor (not associated with beginTask)
				String varName = firstArgName != null ? firstArgName : "monitor"; //$NON-NLS-1$
				logDebug("Found standalone SubProgressMonitor construction at position " + node.getStartPosition() + " for variable '" + varName + "' without beginTask"); //$NON-NLS-1$ //$NON-NLS-2$
				MonitorHolder mh = new MonitorHolder();
				mh.minvname = varName;
				mh.nodesprocessed = nodesprocessed;
				mh.standaloneSubProgressMonitors.add(node);
				holder.put(holder.size(), mh);
				
				return true;
			})
			.build(compilationUnit);
		
		// Add operations for standalone SubProgressMonitor
		if (!standaloneHolder.isEmpty()) {
			operations.add(fixcore.rewrite(standaloneHolder));
		}
		
		// Pass 3: Find SubProgressMonitor type references for type replacement
		// Each visitor type must be a separate AstProcessorBuilder call because
		// chaining multiple onXxx calls creates sequential/scoped visitors via ASTProcessor,
		// but these visitors need to run independently on the full compilation unit.
		ReferenceHolder<Integer, MonitorHolder> typeReplacementHolder = new ReferenceHolder<>();
		MonitorHolder typeHolder = new MonitorHolder();
		typeHolder.nodesprocessed = nodesprocessed;
		
		AstProcessorBuilder.with(typeReplacementHolder, nodesprocessed)
			.onFieldDeclaration((node, holder) -> {
				org.eclipse.jdt.core.dom.Type fieldType = node.getType();
				if (isSubProgressMonitorType(fieldType)) {
					logDebug("Found SubProgressMonitor field declaration at position " + node.getStartPosition()); //$NON-NLS-1$
					typeHolder.typesToReplace.add(fieldType);
				}
				return true;
			})
			.build(compilationUnit);
		
		AstProcessorBuilder.with(typeReplacementHolder, nodesprocessed)
			.onVariableDeclarationStatement((node, holder) -> {
				org.eclipse.jdt.core.dom.Type varType = node.getType();
				if (isSubProgressMonitorType(varType)) {
					logDebug("Found SubProgressMonitor variable declaration at position " + node.getStartPosition()); //$NON-NLS-1$
					typeHolder.typesToReplace.add(varType);
				}
				return true;
			})
			.build(compilationUnit);
		
		AstProcessorBuilder.with(typeReplacementHolder, nodesprocessed)
			.onMethodDeclaration((node, holder) -> {
				org.eclipse.jdt.core.dom.Type returnType = node.getReturnType2();
				if (isSubProgressMonitorType(returnType)) {
					logDebug("Found SubProgressMonitor return type at position " + node.getStartPosition()); //$NON-NLS-1$
					typeHolder.typesToReplace.add(returnType);
				}
				return true;
			})
			.build(compilationUnit);
		
		AstProcessorBuilder.with(typeReplacementHolder, nodesprocessed)
			.onSingleVariableDeclaration((node, holder) -> {
				org.eclipse.jdt.core.dom.Type paramType = node.getType();
				if (isSubProgressMonitorType(paramType)) {
					logDebug("Found SubProgressMonitor parameter type at position " + node.getStartPosition()); //$NON-NLS-1$
					typeHolder.typesToReplace.add(paramType);
				}
				return true;
			})
			.build(compilationUnit);
		
		AstProcessorBuilder.with(typeReplacementHolder, nodesprocessed)
			.onCastExpression((node, holder) -> {
				org.eclipse.jdt.core.dom.Type castType = node.getType();
				if (isSubProgressMonitorType(castType)) {
					logDebug("Found SubProgressMonitor cast at position " + node.getStartPosition()); //$NON-NLS-1$
					typeHolder.typesToReplace.add(castType);
				}
				return true;
			})
			.build(compilationUnit);
		
		// Add operations for type replacement if any types were found
		if (!typeHolder.typesToReplace.isEmpty()) {
			typeReplacementHolder.put(0, typeHolder);
			operations.add(fixcore.rewrite(typeReplacementHolder));
		}
	}

	/**
	 * Checks if a Type is SubProgressMonitor.
	 */
	private static boolean isSubProgressMonitorType(org.eclipse.jdt.core.dom.Type type) {
		if (type == null) {
			return false;
		}
		
		// First try binding (most reliable)
		ITypeBinding binding = type.resolveBinding();
		if (binding != null && !binding.isRecovered()) {
			return "org.eclipse.core.runtime.SubProgressMonitor".equals(binding.getQualifiedName()); //$NON-NLS-1$
		}
		
		// Fallback: check type name
		String typeName = getTypeName(type);
		return "SubProgressMonitor".equals(typeName) || //$NON-NLS-1$
			   "org.eclipse.core.runtime.SubProgressMonitor".equals(typeName); //$NON-NLS-1$
	}
	
	/**
	 * Extracts the type name from a Type node.
	 */
	private static String getTypeName(org.eclipse.jdt.core.dom.Type type) {
		if (type.isSimpleType()) {
			org.eclipse.jdt.core.dom.SimpleType simpleType = (org.eclipse.jdt.core.dom.SimpleType) type;
			return simpleType.getName().getFullyQualifiedName();
		}
		if (type.isQualifiedType()) {
			org.eclipse.jdt.core.dom.QualifiedType qualifiedType = (org.eclipse.jdt.core.dom.QualifiedType) type;
			return qualifiedType.getName().getFullyQualifiedName();
		}
		if (type.isNameQualifiedType()) {
			org.eclipse.jdt.core.dom.NameQualifiedType nameQualifiedType = (org.eclipse.jdt.core.dom.NameQualifiedType) type;
			return nameQualifiedType.getName().getFullyQualifiedName();
		}
		return type.toString();
	}

	/**
	 * Rewrites AST nodes to transform SubProgressMonitor patterns to SubMonitor.
	 * 
	 * <p>Performs two main transformations:</p>
	 * <ol>
	 * <li><b>beginTask → convert:</b> Transforms {@code monitor.beginTask(msg, work)} 
	 *     to {@code SubMonitor subMonitor = SubMonitor.convert(monitor, msg, work)}</li>
	 * <li><b>SubProgressMonitor → split:</b> Transforms constructor calls:
	 *     <ul>
	 *     <li>2-arg: {@code new SubProgressMonitor(monitor, work)} → {@code subMonitor.split(work)}</li>
	 *     <li>3-arg: {@code new SubProgressMonitor(monitor, work, flags)} → {@code subMonitor.split(work, flags)}</li>
	 *     </ul>
	 * </li>
	 * </ol>
	 * 
	 * <p>The transformation ensures:</p>
	 * <ul>
	 * <li>Unique variable names for SubMonitor to avoid collisions</li>
	 * <li>Preservation of flags parameter in 3-arg constructors</li>
	 * <li>Removal of SubProgressMonitor import</li>
	 * <li>Addition of SubMonitor import</li>
	 * </ul>
	 * 
	 * @param upp the cleanup fix core instance
	 * @param hit the holder containing identified monitor patterns to transform
	 * @param cuRewrite the compilation unit rewrite context
	 * @param group the text edit group for tracking changes
	 */
	@Override
	public void rewrite(JfaceCleanUpFixCore upp, final ReferenceHolder<Integer, MonitorHolder> hit,
			final CompilationUnitRewrite cuRewrite, TextEditGroup group) {
		ASTRewrite rewrite = cuRewrite.getASTRewrite();
		AST ast = cuRewrite.getRoot().getAST();
		ImportRewrite importRemover = cuRewrite.getImportRewrite();
		
		// Guard against empty holder
		if (hit.isEmpty()) {
			return;
		}
		
		// Track whether any flag was passed through unmapped (may still reference SubProgressMonitor)
		boolean hasUnmappedFlags = false;
		
		for (Entry<Integer, MonitorHolder> entry : hit.entrySet()) {

			MonitorHolder mh = entry.getValue();
			Set<ASTNode> nodesprocessed = mh.nodesprocessed;
			
			// Handle SubProgressMonitor on already-SubMonitor variables (use split() directly)
			if (!mh.subProgressMonitorOnSubMonitor.isEmpty()) {
				for (ClassInstanceCreation submon : mh.subProgressMonitorOnSubMonitor) {
					if (nodesprocessed.contains(submon)) {
						continue;
					}
					nodesprocessed.add(submon);
					
					List<?> arguments = submon.arguments();
					if (arguments.size() < 2) {
						continue;
					}
					
					logDebug("Rewriting SubProgressMonitor on SubMonitor variable at position " + submon.getStartPosition()); //$NON-NLS-1$
					
					// Create subMonitor.split(work [, flags]) call
					MethodInvocation splitCall = ast.newMethodInvocation();
					splitCall.setExpression(ASTNodes.createMoveTarget(rewrite, 
						ASTNodes.getUnparenthesedExpression((Expression) arguments.get(0))));
					splitCall.setName(ast.newSimpleName("split")); //$NON-NLS-1$
					List<ASTNode> splitArgs = splitCall.arguments();
					
					// Add work amount (second argument)
					splitArgs.add(ASTNodes.createMoveTarget(rewrite, 
						ASTNodes.getUnparenthesedExpression((Expression) arguments.get(1))));
					
					// Handle flags if present (3-arg constructor)
					if (arguments.size() >= 3) {
						Expression flagsArg = (Expression) arguments.get(2);
						FlagMappingResult flagResult = mapSubProgressMonitorFlags(flagsArg, ast, cuRewrite);
						
						if (flagResult.mappedExpression() != null) {
							splitArgs.add(flagResult.mappedExpression());
							if (flagResult.referencesSubMonitor()) {
								importRemover.addImport("org.eclipse.core.runtime.SubMonitor"); //$NON-NLS-1$
							}
						}
						if (flagResult.passedThrough()) {
							hasUnmappedFlags = true;
						}
					}
					
					ASTNodes.replaceButKeepComment(rewrite, submon, splitCall, group);
				}
				
				continue;
			}
			
			// Handle standalone SubProgressMonitor (without beginTask)
			if (!mh.standaloneSubProgressMonitors.isEmpty()) {
				for (ClassInstanceCreation submon : mh.standaloneSubProgressMonitors) {
					if (nodesprocessed.contains(submon)) {
						continue;
					}
					nodesprocessed.add(submon);
					
					List<?> arguments = submon.arguments();
					if (arguments.size() < 2) {
						continue;
					}
					
					logDebug("Rewriting standalone SubProgressMonitor at position " + submon.getStartPosition()); //$NON-NLS-1$
					
					// Create SubMonitor.convert(monitor) call
					MethodInvocation convertCall = ast.newMethodInvocation();
					convertCall.setExpression(addImport(SubMonitor.class.getCanonicalName(), cuRewrite, ast));
					convertCall.setName(ast.newSimpleName("convert")); //$NON-NLS-1$
					convertCall.arguments().add(
						ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression((Expression) arguments.get(0))));
					
					// Create .split(work [, flags]) call chained on convert
					MethodInvocation splitCall = ast.newMethodInvocation();
					splitCall.setExpression(convertCall);
					splitCall.setName(ast.newSimpleName("split")); //$NON-NLS-1$
					List<ASTNode> splitArgs = splitCall.arguments();
					
					// Add work amount (second argument)
					splitArgs.add(ASTNodes.createMoveTarget(rewrite, 
						ASTNodes.getUnparenthesedExpression((Expression) arguments.get(1))));
					
					// Handle flags if present (3-arg constructor)
					if (arguments.size() >= 3) {
						Expression flagsArg = (Expression) arguments.get(2);
						FlagMappingResult flagResult = mapSubProgressMonitorFlags(flagsArg, ast, cuRewrite);
						
						if (flagResult.mappedExpression() != null) {
							splitArgs.add(flagResult.mappedExpression());
							if (flagResult.referencesSubMonitor()) {
								importRemover.addImport("org.eclipse.core.runtime.SubMonitor"); //$NON-NLS-1$
							}
						}
						if (flagResult.passedThrough()) {
							hasUnmappedFlags = true;
						}
					}
					
					ASTNodes.replaceButKeepComment(rewrite, submon, splitCall, group);
				}
				
				continue;
			}
			
			// Handle beginTask + SubProgressMonitor pattern
			MethodInvocation minv = mh.minv;
			
			// Skip if no beginTask (already handled as standalone above)
			if (minv == null) {
				continue;
			}
			
			// Generate unique identifier name for SubMonitor variable
			String identifier = generateUniqueVariableName(minv, "subMonitor"); //$NON-NLS-1$
			
			if (!nodesprocessed.contains(minv)) {
				nodesprocessed.add(minv);
				logDebug("Rewriting beginTask at position " + minv.getStartPosition() + " (method: " + minv.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
				
				// Ensure parent is ExpressionStatement
				if (!(minv.getParent() instanceof ExpressionStatement)) {
					continue;
				}
				
				List<ASTNode> arguments = minv.arguments();

				/**
				 * Here we process the "beginTask" and change it to "SubMonitor.convert"
				 *
				 * monitor.beginTask(NewWizardMessages.NewSourceFolderWizardPage_operation, 3);
				 * SubMonitor subMonitor =
				 * SubMonitor.convert(monitor,NewWizardMessages.NewSourceFolderWizardPage_operation,
				 * 3);
				 *
				 */

				// Create the static call to SubMonitor.convert
				MethodInvocation staticCall = ast.newMethodInvocation();
				staticCall.setExpression(ASTNodeFactory.newName(ast, SubMonitor.class.getSimpleName()));
				staticCall.setName(ast.newSimpleName("convert")); //$NON-NLS-1$
				List<ASTNode> staticCallArguments = staticCall.arguments();
				staticCallArguments.add(
						ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(minv.getExpression())));
				staticCallArguments
				.add(ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(arguments.get(0))));
				staticCallArguments
				.add(ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(arguments.get(1))));

				// Create the variable declaration fragment (name + initializer)
				VariableDeclarationFragment fragment = ast.newVariableDeclarationFragment();
				fragment.setName(ast.newSimpleName(identifier));
				fragment.setInitializer(staticCall);

				// Create the variable declaration statement (type + fragment)
				VariableDeclarationStatement varDeclStmt = ast.newVariableDeclarationStatement(fragment);
				varDeclStmt.setType(ast.newSimpleType(addImport(SubMonitor.class.getCanonicalName(), cuRewrite, ast)));

				// Replace the entire ExpressionStatement (parent of beginTask), not just the MethodInvocation
				ASTNodes.replaceButKeepComment(rewrite, minv.getParent(), varDeclStmt, group);
				logDebug("Created SubMonitor.convert call: " + staticCall); //$NON-NLS-1$
			}
			
			for (ClassInstanceCreation submon : mh.setofcic) {
				if (nodesprocessed.contains(submon)) {
					continue;
				}
				nodesprocessed.add(submon);
				
				List<?> arguments = submon.arguments();
				if (arguments.size() < 2) {
					continue;
				}
				
				ASTNode origarg = (ASTNode) arguments.get(1);
				logDebug("Rewriting SubProgressMonitor at position " + submon.getStartPosition() + " (ClassInstanceCreation)"); //$NON-NLS-1$ //$NON-NLS-2$
				
				/**
				 * Handle both 2-arg and 3-arg SubProgressMonitor constructors:
				 * 
				 * 2-arg: new SubProgressMonitor(monitor, work)
				 *   -> subMonitor.split(work)
				 *   
				 * 3-arg: new SubProgressMonitor(monitor, work, flags)
				 *   -> subMonitor.split(work, mappedFlags)
				 *   
				 * Flag mapping:
				 *   - SUPPRESS_SUBTASK_LABEL -> SUPPRESS_SUBTASK
				 *   - PREPEND_MAIN_LABEL_TO_SUBTASK -> dropped (no equivalent)
				 */
				MethodInvocation newMethodInvocation2 = ast.newMethodInvocation();
				newMethodInvocation2.setName(ast.newSimpleName("split")); //$NON-NLS-1$
				newMethodInvocation2.setExpression(ASTNodeFactory.newName(ast, identifier));
				List<ASTNode> splitCallArguments = newMethodInvocation2.arguments();

				// Add the work amount (second argument)
				splitCallArguments
				.add(ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(origarg)));
				
				// Check for 3-arg constructor (with flags)
				if (arguments.size() >= 3) {
					Expression flagsArg = (Expression) arguments.get(2);
					FlagMappingResult flagResult = mapSubProgressMonitorFlags(flagsArg, ast, cuRewrite);
					
					// Only add the flag if it wasn't dropped (PREPEND_MAIN_LABEL_TO_SUBTASK is dropped)
					if (flagResult.mappedExpression() != null) {
						splitCallArguments.add(flagResult.mappedExpression());
						if (flagResult.referencesSubMonitor()) {
							importRemover.addImport("org.eclipse.core.runtime.SubMonitor"); //$NON-NLS-1$
						}
					}
					if (flagResult.passedThrough()) {
						hasUnmappedFlags = true;
					}
				}
				
				ASTNodes.replaceButKeepComment(rewrite, submon, newMethodInvocation2, group);
			}
		}
		
		// Handle type replacements (fields, parameters, return types, casts)
		for (Entry<Integer, MonitorHolder> entry : hit.entrySet()) {
			MonitorHolder mh = entry.getValue();
			
			if (!mh.typesToReplace.isEmpty()) {
				logDebug("Processing " + mh.typesToReplace.size() + " SubProgressMonitor type replacements"); //$NON-NLS-1$ //$NON-NLS-2$
				
				for (org.eclipse.jdt.core.dom.Type typeToReplace : mh.typesToReplace) {
					// Skip if already processed
					if (mh.nodesprocessed.contains(typeToReplace)) {
						continue;
					}
					mh.nodesprocessed.add(typeToReplace);
					
					logDebug("Replacing SubProgressMonitor type at position " + typeToReplace.getStartPosition()); //$NON-NLS-1$
					
					// Create replacement type: IProgressMonitor
					org.eclipse.jdt.core.dom.Type newType = ast.newSimpleType(
						addImport(IProgressMonitor.class.getCanonicalName(), cuRewrite, ast));
					
					// Replace the type in AST
					rewrite.replace(typeToReplace, newType, group);
				}
			}
		}
		
		// Only remove SubProgressMonitor import if no unmapped flags may still reference it
		if (!hasUnmappedFlags) {
			importRemover.removeImport("org.eclipse.core.runtime.SubProgressMonitor"); //$NON-NLS-1$
		}
	}
	
	/**
	 * Generates a unique variable name that doesn't collide with existing variables in scope.
	 * 
	 * <p>This method ensures the SubMonitor variable name doesn't conflict with other
	 * variables visible at the transformation point. If the base name is already in use,
	 * a numeric suffix is appended (e.g., "subMonitor2", "subMonitor3", etc.).</p>
	 * 
	 * @param node the AST node context for scope analysis
	 * @param baseName the base name to use (e.g., "subMonitor")
	 * @return a unique variable name that doesn't exist in the current scope
	 */
	private String generateUniqueVariableName(ASTNode node, String baseName) {
		Collection<String> usedNames = getUsedVariableNames(node);
		
		// If base name is not used, return it
		if (!usedNames.contains(baseName)) {
			return baseName;
		}
		
		// Otherwise, append a number until we find an unused name
		int counter = 2;
		String candidate = baseName + counter;
		while (usedNames.contains(candidate)) {
			counter++;
			candidate = baseName + counter;
		}
		return candidate;
	}

	/**
	 * Result of mapping SubProgressMonitor flags to SubMonitor flags.
	 */
	private record FlagMappingResult(
		/** The mapped expression, or null if the flag should be dropped */
		Expression mappedExpression,
		/** True if the mapped expression references SubMonitor (needs import) */
		boolean referencesSubMonitor,
		/** True if the flag was passed through unchanged (may still reference SubProgressMonitor) */
		boolean passedThrough
	) {}

	/**
	 * Maps SubProgressMonitor flags to SubMonitor flags.
	 * 
	 * <p>Flag mappings:</p>
	 * <ul>
	 * <li>{@code SubProgressMonitor.SUPPRESS_SUBTASK_LABEL} → {@code SubMonitor.SUPPRESS_SUBTASK}</li>
	 * <li>{@code SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK} → removed (no equivalent)</li>
	 * </ul>
	 * 
	 * <p><b>Limitations:</b> This method only handles single flag constants. Combined flag expressions
	 * using bitwise OR (e.g., {@code FLAG1 | FLAG2}) or numeric literals are not mapped and will be
	 * passed through unchanged. When flags are passed through, the SubProgressMonitor import is
	 * preserved to avoid breaking references in the unmapped expression.</p>
	 * 
	 * @param flagExpr the original flag expression from SubProgressMonitor constructor
	 * @param ast the AST to create new nodes
	 * @param cuRewrite the compilation unit rewrite context
	 * @return the flag mapping result containing the mapped expression and metadata
	 */
	private FlagMappingResult mapSubProgressMonitorFlags(Expression flagExpr, AST ast, CompilationUnitRewrite cuRewrite) {
		// Handle field access: SubProgressMonitor.SUPPRESS_SUBTASK_LABEL
		if (flagExpr instanceof QualifiedName) {
			QualifiedName qn = (QualifiedName) flagExpr;
			String fieldName = qn.getName().getIdentifier();
			
			if ("SUPPRESS_SUBTASK_LABEL".equals(fieldName)) { //$NON-NLS-1$
				// Map to SubMonitor.SUPPRESS_SUBTASK
				QualifiedName newFlag = ast.newQualifiedName(
					ast.newSimpleName(SubMonitor.class.getSimpleName()),
					ast.newSimpleName("SUPPRESS_SUBTASK")); //$NON-NLS-1$
				return new FlagMappingResult(newFlag, true, false);
			} else if ("PREPEND_MAIN_LABEL_TO_SUBTASK".equals(fieldName)) { //$NON-NLS-1$
				// Drop this flag - no equivalent in SubMonitor
				return new FlagMappingResult(null, false, false);
			}
		}
		
		// Handle FieldAccess syntax (e.g., expression.FIELD_NAME)
		if (flagExpr instanceof FieldAccess) {
			FieldAccess fa = (FieldAccess) flagExpr;
			String fieldName = fa.getName().getIdentifier();
			
			if ("SUPPRESS_SUBTASK_LABEL".equals(fieldName)) { //$NON-NLS-1$
				// Map to SubMonitor.SUPPRESS_SUBTASK
				FieldAccess newFlag = ast.newFieldAccess();
				newFlag.setExpression(ast.newSimpleName(SubMonitor.class.getSimpleName()));
				newFlag.setName(ast.newSimpleName("SUPPRESS_SUBTASK")); //$NON-NLS-1$
				return new FlagMappingResult(newFlag, true, false);
			} else if ("PREPEND_MAIN_LABEL_TO_SUBTASK".equals(fieldName)) { //$NON-NLS-1$
				// Drop this flag - no equivalent in SubMonitor
				return new FlagMappingResult(null, false, false);
			}
		}
		
		// For numeric literals, pass through unchanged but don't mark as passedThrough
		// since they don't reference SubProgressMonitor (safe to remove import)
		if (flagExpr instanceof NumberLiteral) {
			Expression passedExpr = ASTNodes.createMoveTarget(cuRewrite.getASTRewrite(), ASTNodes.getUnparenthesedExpression(flagExpr));
			return new FlagMappingResult(passedExpr, false, false);
		}
		
		// For other expressions (variables, bitwise OR), pass through unchanged
		// Mark as passedThrough so SubProgressMonitor import is preserved
		Expression passedExpr = ASTNodes.createMoveTarget(cuRewrite.getASTRewrite(), ASTNodes.getUnparenthesedExpression(flagExpr));
		return new FlagMappingResult(passedExpr, false, true);
	}

	@Override
	public String getPreview(boolean afterRefactoring) {
		if (!afterRefactoring) {
			return """
					monitor.beginTask(NewWizardMessages.NewSourceFolderWizardPage_operation, 3);
						IProgressMonitor subProgressMonitor= new SubProgressMonitor(monitor, 1);
						IProgressMonitor subProgressMonitor2= new SubProgressMonitor(monitor, 2);
				"""; //$NON-NLS-1$
		}
		return """
				SubMonitor subMonitor=SubMonitor.convert(monitor,NewWizardMessages.NewSourceFolderWizardPage_operation,3);
					IProgressMonitor subProgressMonitor= subMonitor.split(1);
					IProgressMonitor subProgressMonitor2= subMonitor.split(2);
			"""; //$NON-NLS-1$
	}
}