StreamOperationDetector.java
/*******************************************************************************
* Copyright (c) 2026 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 org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodInvocation;
/**
* Shared utility for detecting chained intermediate stream operations.
*
* <p>Used by {@link StreamToEnhancedFor} and {@link StreamToIteratorWhile}
* to ensure reverse conversions only apply to simple forEach patterns
* (collection.forEach or collection.stream().forEach) and not to
* pipelines with intermediate operations that would be lost.</p>
*
* @since 1.0.0
*/
public final class StreamOperationDetector {
private StreamOperationDetector() {
// utility class
}
/**
* Checks if a forEach call has chained intermediate stream operations.
*
* <p>Patterns like {@code list.stream().filter(...).forEach(...)} or
* {@code list.stream().map(...).forEach(...)} cannot be safely converted
* to a simple loop because the intermediate operations (filter, map,
* flatMap, sorted, distinct, limit, skip, peek) would be lost.</p>
*
* <p>Only {@code collection.forEach(...)} and
* {@code collection.stream().forEach(...)} (with no intermediate
* operations) are convertible.</p>
*
* @param forEach the forEach method invocation to check
* @return {@code true} if there are chained stream operations that block conversion
*/
public static boolean hasChainedStreamOperations(MethodInvocation forEach) {
Expression receiver = forEach.getExpression();
if (!(receiver instanceof MethodInvocation chainedCall)) {
return false; // collection.forEach() — no chain
}
String methodName = chainedCall.getName().getIdentifier();
// collection.stream().forEach() is OK — no intermediate ops
if (StreamConstants.STREAM_METHOD.equals(methodName) || StreamConstants.PARALLEL_STREAM_METHOD.equals(methodName)) {
return false;
}
// Any other chained method (filter, map, flatMap, sorted, distinct,
// limit, skip, peek, etc.) means there are intermediate operations
return true;
}
}