HintContext.java

/*******************************************************************************
 * Copyright (c) 2025 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 - initial API and implementation
 *******************************************************************************/
package org.sandbox.jdt.triggerpattern.eclipse;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.sandbox.jdt.triggerpattern.api.Match;

/**
 * Context information provided to hint implementations.
 * 
 * <p>This class provides access to the compilation unit, the matched pattern result,
 * and tools for making code changes (ASTRewrite, ImportRewrite).</p>
 * 
 * @since 1.2.2
 */
public final class HintContext {
	private final CompilationUnit cu;
	private final ICompilationUnit icu;
	private final Match match;
	private final ASTRewrite rewrite;
	private ImportRewrite importRewrite;
	private final AtomicBoolean cancel;
	
	/**
	 * Creates a new hint context.
	 * 
	 * @param cu the parsed compilation unit
	 * @param icu the ICompilationUnit (workspace model)
	 * @param match the pattern match result
	 * @param rewrite the AST rewrite for making changes
	 */
	public HintContext(CompilationUnit cu, ICompilationUnit icu, Match match, ASTRewrite rewrite) {
		this(cu, icu, match, rewrite, new AtomicBoolean(false));
	}
	
	/**
	 * Creates a new hint context with cancellation support.
	 * 
	 * @param cu the parsed compilation unit
	 * @param icu the ICompilationUnit (workspace model)
	 * @param match the pattern match result
	 * @param rewrite the AST rewrite for making changes
	 * @param cancel atomic boolean for cancellation signaling
	 */
	public HintContext(CompilationUnit cu, ICompilationUnit icu, Match match, ASTRewrite rewrite, AtomicBoolean cancel) {
		this.cu = cu;
		this.icu = icu;
		this.match = match;
		this.rewrite = rewrite;
		this.cancel = cancel != null ? cancel : new AtomicBoolean(false);
	}
	
	/**
	 * Returns the parsed compilation unit.
	 * 
	 * @return the compilation unit
	 */
	public CompilationUnit getCompilationUnit() {
		return cu;
	}
	
	/**
	 * Returns the ICompilationUnit (workspace model).
	 * 
	 * @return the ICompilationUnit
	 */
	public ICompilationUnit getICompilationUnit() {
		return icu;
	}
	
	/**
	 * Returns the pattern match.
	 * 
	 * @return the match
	 */
	public Match getMatch() {
		return match;
	}
	
	/**
	 * Returns the AST rewrite for making code changes.
	 * 
	 * @return the AST rewrite
	 */
	public ASTRewrite getASTRewrite() {
		return rewrite;
	}
	
	/**
	 * Returns the import rewrite for managing imports.
	 * Creates one lazily if needed.
	 * 
	 * @return the import rewrite
	 */
	public ImportRewrite getImportRewrite() {
		if (importRewrite == null) {
			try {
				importRewrite = ImportRewrite.create(cu, true);
			} catch (Exception e) {
				// Return null if creation fails
				return null;
			}
		}
		return importRewrite;
	}
	
	/**
	 * Sets the import rewrite.
	 * 
	 * @param importRewrite the import rewrite to use
	 */
	public void setImportRewrite(ImportRewrite importRewrite) {
		this.importRewrite = importRewrite;
	}
	
	/**
	 * Checks if the operation has been canceled.
	 * 
	 * @return true if canceled
	 */
	public boolean isCanceled() {
		return cancel.get();
	}
	
	/**
	 * Returns single-placeholder bindings only.
	 * 
	 * <p>Filters out multi-placeholder bindings (those with values that are lists).</p>
	 * 
	 * @return map of placeholder names to their bound AST nodes
	 */
	public Map<String, ASTNode> getVariables() {
		Map<String, ASTNode> result = new HashMap<>();
		for (Map.Entry<String, Object> entry : match.getBindings().entrySet()) {
			if (entry.getValue() instanceof ASTNode) {
				result.put(entry.getKey(), (ASTNode) entry.getValue());
			}
		}
		return result;
	}
	
	/**
	 * Returns multi-placeholder bindings only.
	 * 
	 * <p>Filters out single-placeholder bindings (those with ASTNode values).</p>
	 * 
	 * @return map of placeholder names to their lists of bound AST nodes
	 */
	@SuppressWarnings("unchecked")
	public Map<String, List<ASTNode>> getMultiVariables() {
		Map<String, List<ASTNode>> result = new HashMap<>();
		for (Map.Entry<String, Object> entry : match.getBindings().entrySet()) {
			if (entry.getValue() instanceof List<?>) {
				result.put(entry.getKey(), (List<ASTNode>) entry.getValue());
			}
		}
		return result;
	}
	
	/**
	 * Returns a map of placeholder names to their source text representations.
	 * 
	 * <p>For single placeholders, returns the toString() of the bound node.
	 * For multi-placeholders, returns a comma-separated string of all nodes.</p>
	 * 
	 * @return map of placeholder names to source text
	 */
	public Map<String, String> getVariableNames() {
		Map<String, String> result = new HashMap<>();
		for (Map.Entry<String, Object> entry : match.getBindings().entrySet()) {
			String name = entry.getKey();
			Object value = entry.getValue();
			
			if (value instanceof ASTNode) {
				result.put(name, value.toString());
			} else if (value instanceof List<?>) {
				@SuppressWarnings("unchecked")
				List<ASTNode> nodes = (List<ASTNode>) value;
				String text = nodes.stream()
					.map(Object::toString)
					.collect(Collectors.joining(", ")); //$NON-NLS-1$
				result.put(name, text);
			}
		}
		return result;
	}
}