StringRenderer.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.renderer;

import java.util.List;
import org.sandbox.functional.core.model.*;
import org.sandbox.functional.core.terminal.*;

/**
 * Renderer that generates Java code strings.
 * 
 * <p>This implementation is used for testing in the core module.
 * For production use with JDT, see ASTRenderer in sandbox_functional_converter.</p>
 */
public class StringRenderer implements StreamPipelineRenderer<String> {
    
    @Override
    public String renderSource(SourceDescriptor source) {
        if (source == null) return "";
        String expr = source.expression();
        
        return switch (source.type()) {
            case COLLECTION -> expr + ".stream()";
            case ARRAY -> "Arrays.stream(" + expr + ")";
            case ITERABLE -> "StreamSupport.stream(" + expr + ".spliterator(), false)";
            case STREAM -> expr;
            case INT_RANGE -> "IntStream.range(0, " + expr + ")";
            case EXPLICIT_RANGE -> {
                // Parse start and end from expression (format: "start,end")
                String[] parts = expr.split(",");
                if (parts.length != 2 || parts[0].trim().isEmpty() || parts[1].trim().isEmpty()) {
                    throw new IllegalArgumentException("Invalid EXPLICIT_RANGE expression: '" + expr
                            + "'. Expected format 'start,end' with non-empty expressions.");
                }
                yield "IntStream.range(" + parts[0].trim() + ", " + parts[1].trim() + ")";
            }
            default -> expr + ".stream()";
        };
    }
    
    @Override
    public String renderFilter(String pipeline, String expression, String variableName) {
        return pipeline + ".filter(" + variableName + " -> " + expression + ")";
    }
    
    @Override
    public String renderMap(String pipeline, String expression, String variableName, String targetType) {
        return pipeline + ".map(" + variableName + " -> " + expression + ")";
    }
    
    @Override
    public String renderFlatMap(String pipeline, String expression, String variableName) {
        return pipeline + ".flatMap(" + variableName + " -> " + expression + ")";
    }
    
    @Override
    public String renderPeek(String pipeline, String expression, String variableName) {
        return pipeline + ".peek(" + variableName + " -> " + expression + ")";
    }
    
    @Override
    public String renderDistinct(String pipeline) {
        return pipeline + ".distinct()";
    }
    
    @Override
    public String renderSorted(String pipeline, String comparatorExpression) {
        if (comparatorExpression == null || comparatorExpression.isEmpty()) {
            return pipeline + ".sorted()";
        }
        return pipeline + ".sorted(" + comparatorExpression + ")";
    }
    
    @Override
    public String renderLimit(String pipeline, long maxSize) {
        return pipeline + ".limit(" + maxSize + ")";
    }
    
    @Override
    public String renderSkip(String pipeline, long count) {
        return pipeline + ".skip(" + count + ")";
    }
    
    @Override
    public String renderForEach(String pipeline, List<String> bodyStatements, 
                                 String variableName, boolean ordered) {
        String body = String.join("; ", bodyStatements);
        String method = ordered ? ".forEachOrdered" : ".forEach";
        return pipeline + method + "(" + variableName + " -> " + body + ")";
    }
    
    @Override
    public String renderCollect(String pipeline, CollectTerminal terminal, String variableName) {
        // Use modern Java 16+ .toList() for conciseness (project targets Java 21)
        // For other collectors, use Collectors API
        String collector = switch (terminal.collectorType()) {
            case TO_LIST -> ".toList()";  // Java 16+ - more concise than Collectors.toList()
            case TO_SET -> ".collect(Collectors.toSet())";
            case TO_MAP -> ".collect(Collectors.toMap(...))";
            case JOINING -> ".collect(Collectors.joining())";
            case GROUPING_BY -> ".collect(Collectors.groupingBy(...))";
            case CUSTOM -> ".collect(...)";
        };
        
        return pipeline + collector;
    }
    
    @Override
    public String renderReduce(String pipeline, ReduceTerminal terminal, String variableName) {
        return pipeline + ".reduce(" + terminal.identity() + ", " + terminal.accumulator() + ")";
    }
    
    @Override
    public String renderCount(String pipeline) {
        return pipeline + ".count()";
    }
    
    @Override
    public String renderFind(String pipeline, boolean findFirst) {
        return pipeline + (findFirst ? ".findFirst()" : ".findAny()");
    }
    
    @Override
    public String renderMatch(String pipeline, MatchTerminal terminal, String variableName) {
        return pipeline + "." + terminal.operationType() + "(" + variableName + " -> " + terminal.predicate() + ")";
    }
}