SideEffectChecker.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 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.List;

import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;

/**
 * Checks if a statement is a safe side-effect that can be included in a stream pipeline.
 * 
 * <p>A side-effect is considered safe if it doesn't modify variables that would
 * violate stream semantics (e.g., loop variables, accumulator variables, or
 * external variables).</p>
 * 
 * <p><b>Safe side-effects:</b></p>
 * <ul>
 * <li>Method calls (System.out.println, logging, etc.)</li>
 * <li>Array element assignments</li>
 * <li>Field assignments</li>
 * </ul>
 * 
 * <p><b>Unsafe side-effects:</b></p>
 * <ul>
 * <li>Assignments to the current pipeline variable</li>
 * <li>Assignments to accumulator variables</li>
 * <li>Assignments to external simple variables</li>
 * </ul>
 * 
 * @see LoopBodyParser
 * @see ProspectiveOperation
 */
public class SideEffectChecker {

	/**
	 * Checks if a statement is a safe side-effect.
	 * 
	 * @param stmt           the statement to check
	 * @param currentVarName the current variable name in the pipeline
	 * @param operations     the list of operations (to check for accumulators)
	 * @return true if safe, false otherwise
	 */
	public boolean isSafeSideEffect(Statement stmt, String currentVarName, 
			List<ProspectiveOperation> operations) {
		
		if (stmt == null) {
			return false;
		}

		if (!(stmt instanceof ExpressionStatement)) {
			return false;
		}

		ExpressionStatement exprStmt = (ExpressionStatement) stmt;
		Expression expr = exprStmt.getExpression();

		if (expr == null) {
			return false;
		}

		// Check for assignments
		if (expr instanceof Assignment) {
			return isAssignmentSafe((Assignment) expr, currentVarName, operations);
		}
		
		// Check for postfix increment/decrement (counter++, counter--)
		// These modify external state and are unsafe
		if (expr instanceof PostfixExpression) {
			PostfixExpression postfix = (PostfixExpression) expr;
			PostfixExpression.Operator op = postfix.getOperator();
			if (op == PostfixExpression.Operator.INCREMENT || op == PostfixExpression.Operator.DECREMENT) {
				// Modifying a variable is a side effect
				return false;
			}
		}
		
		// Check for prefix increment/decrement (++counter, --counter)
		// These modify external state and are unsafe
		if (expr instanceof PrefixExpression) {
			PrefixExpression prefix = (PrefixExpression) expr;
			PrefixExpression.Operator op = prefix.getOperator();
			if (op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT) {
				// Modifying a variable is a side effect
				return false;
			}
		}

		// Method calls and other expressions are generally safe
		return true;
	}

	/**
	 * Checks if an assignment is safe for stream conversion.
	 */
	private boolean isAssignmentSafe(Assignment assignment, String currentVarName,
			List<ProspectiveOperation> operations) {
		
		Expression lhs = assignment.getLeftHandSide();

		// Only validate SimpleName assignments (simple variables)
		// Array access and field access are conservatively allowed
		if (lhs instanceof SimpleName) {
			String varName = ((SimpleName) lhs).getIdentifier();

			// Assignment to current pipeline variable is unsafe
			if (varName.equals(currentVarName)) {
				return false;
			}

			// Assignment to accumulator variables is handled by REDUCE
			if (isAccumulatorVariable(varName, operations)) {
				return false;
			}

			// Other assignments to external variables are unsafe
			return false;
		}

		// Non-SimpleName assignments (array access, field access) are allowed
		return true;
	}

	/**
	 * Checks if a variable is an accumulator variable in any REDUCE operation.
	 */
	private boolean isAccumulatorVariable(String varName, List<ProspectiveOperation> operations) {
		for (ProspectiveOperation op : operations) {
				if (op.getOperationType() == OperationType.REDUCE) {
				if (varName.equals(op.getAccumulatorVariableName())) {
					return true;
				}
			}
		}
		return false;
	}
}