MapOp.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.functional.core.operation;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.sandbox.functional.core.model.FunctionalExpression;
/**
* Represents a map operation in a stream pipeline.
*/
public final class MapOp implements Operation {
/** Preserve reference/primitive stream transitions when a pipeline is re-rendered. */
public enum Kind {
MAP("map"), TO_INT("mapToInt"), TO_LONG("mapToLong"), TO_DOUBLE("mapToDouble"), TO_OBJECT("mapToObj");
private final String method;
Kind(String method) { this.method = method; }
public String method() { return method; }
public static Kind fromMethod(String method) {
for (Kind kind : values()) {
if (kind.method.equals(method)) return kind;
}
throw new IllegalArgumentException("Unsupported mapping operation: " + method);
}
}
private final String expression;
private final String targetType;
private final String outputVariableName;
private final boolean sideEffect;
private final FunctionalExpression function;
private final Kind kind;
private final List<String> associatedComments;
/**
* Creates a MapOp with all parameters.
* @param expression the mapping expression
* @param targetType the target type (can be null)
* @param outputVariableName the variable name for the output of this map (can be null)
* @param sideEffect if true, this is a side-effect map: map(var -> { stmt; return var; })
*/
public MapOp(String expression, String targetType, String outputVariableName, boolean sideEffect) {
this(expression, targetType, outputVariableName, sideEffect, null);
}
public MapOp(String expression, String targetType, String outputVariableName, boolean sideEffect,
FunctionalExpression function) {
this(expression, targetType, outputVariableName, sideEffect, function, Kind.MAP);
}
public MapOp(String expression, String targetType, String outputVariableName, boolean sideEffect,
FunctionalExpression function, Kind kind) {
this.expression = Objects.requireNonNull(expression, "expression must not be null");
this.targetType = targetType;
this.outputVariableName = outputVariableName;
this.sideEffect = sideEffect;
if (function != null && !Objects.equals(targetType, function.outputType())) {
throw new IllegalArgumentException("Map target type must match the functional result type");
}
this.function = function;
this.kind = Objects.requireNonNull(kind, "kind must not be null");
if (kind != Kind.MAP && (function == null || sideEffect)) {
throw new IllegalArgumentException("A stream type transition requires a complete mapping function");
}
String primitive = switch (kind) {
case TO_INT -> "int";
case TO_LONG -> "long";
case TO_DOUBLE -> "double";
default -> null;
};
if (primitive != null && !primitive.equals(targetType)) {
throw new IllegalArgumentException("Mapping result does not match " + kind.method());
}
this.associatedComments = new ArrayList<>();
}
/**
* Creates a MapOp with expression, target type, and output variable name.
* @param expression the mapping expression
* @param targetType the target type (can be null)
* @param outputVariableName the variable name for the output of this map (can be null)
*/
public MapOp(String expression, String targetType, String outputVariableName) {
this(expression, targetType, outputVariableName, false);
}
/**
* Creates a MapOp with expression and target type.
* @param expression the mapping expression
* @param targetType the target type (can be null)
*/
public MapOp(String expression, String targetType) {
this(expression, targetType, null, false);
}
/**
* Creates a MapOp with just an expression and no target type.
* @param expression the mapping expression
*/
public MapOp(String expression) {
this(expression, null, null, false);
}
@Override
public String expression() {
return expression;
}
public String targetType() {
return targetType;
}
public FunctionalExpression function() {
return function;
}
/**
* Returns the output variable name for this map operation.
* When chaining maps, the next operation should use this as its lambda parameter.
* @return the output variable name, or null if not specified
*/
public String outputVariableName() {
return outputVariableName;
}
/**
* Returns whether this is a side-effect map.
* Side-effect maps render as: {@code map(var -> { statements; return var; })}
* @return true if this is a side-effect map
*/
public boolean isSideEffect() {
return sideEffect;
}
@Override
public String operationType() {
return kind.method();
}
/**
* Adds a comment associated with this operation.
* @param comment the comment text
*/
public void addComment(String comment) {
if (comment != null && !comment.isBlank()) {
this.associatedComments.add(comment);
}
}
/**
* Adds multiple comments associated with this operation.
* @param comments the list of comment texts
*/
public void addComments(List<String> comments) {
if (comments != null) {
comments.forEach(this::addComment);
}
}
/**
* Returns all comments associated with this operation.
* @return unmodifiable view of the comments list
*/
public List<String> getComments() {
return List.copyOf(associatedComments);
}
/**
* Checks if this operation has any associated comments.
* @return true if there are comments, false otherwise
*/
public boolean hasComments() {
return !associatedComments.isEmpty();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MapOp)) return false;
MapOp mapOp = (MapOp) o;
return sideEffect == mapOp.sideEffect && kind == mapOp.kind &&
expression.equals(mapOp.expression) &&
Objects.equals(targetType, mapOp.targetType) &&
Objects.equals(outputVariableName, mapOp.outputVariableName) &&
Objects.equals(function, mapOp.function);
}
@Override
public int hashCode() {
return Objects.hash(expression, targetType, outputVariableName, sideEffect, function, kind);
}
@Override
public String toString() {
return "MapOp[expression=" + expression +
", targetType=" + targetType +
", kind=" + kind +
", function=" + function +
", comments=" + associatedComments.size() + "]";
}
}