001/* 002 * Copyright 2025 devteam@scivicslab.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, 011 * software distributed under the License is distributed on an 012 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the 014 * specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package com.scivicslab.actoriac.accumulator; 019 020import java.io.PrintStream; 021import java.util.concurrent.atomic.AtomicInteger; 022 023import com.scivicslab.pojoactor.core.accumulator.Accumulator; 024 025/** 026 * Accumulator that outputs to the console (System.out/System.err). 027 * 028 * <p>This accumulator writes output directly to the console as it arrives. 029 * It supports different output types:</p> 030 * <ul> 031 * <li>{@code cowsay} - Rendered cowsay ASCII art (output as-is)</li> 032 * <li>{@code stdout} - Command stdout (output as-is)</li> 033 * <li>{@code stderr} - Command stderr (output to System.err)</li> 034 * </ul> 035 * 036 * <h2>Usage</h2> 037 * <pre>{@code 038 * ConsoleAccumulator console = new ConsoleAccumulator(); 039 * console.add("node-1", "stdout", "command output line"); 040 * console.add("workflow", "cowsay", renderedCowsayArt); 041 * }</pre> 042 * 043 * @author devteam@scivicslab.com 044 * @since 2.12.0 045 */ 046public class ConsoleAccumulator implements Accumulator { 047 048 private final PrintStream stdout; 049 private final PrintStream stderr; 050 private final AtomicInteger count = new AtomicInteger(0); 051 private volatile boolean quiet = false; 052 053 /** 054 * Constructs a ConsoleAccumulator with default System.out and System.err. 055 */ 056 public ConsoleAccumulator() { 057 this(System.out, System.err); 058 } 059 060 /** 061 * Constructs a ConsoleAccumulator with custom output streams. 062 * 063 * @param stdout the stream for stdout output 064 * @param stderr the stream for stderr output 065 */ 066 public ConsoleAccumulator(PrintStream stdout, PrintStream stderr) { 067 this.stdout = stdout; 068 this.stderr = stderr; 069 } 070 071 /** 072 * Sets quiet mode. When quiet, no output is written. 073 * 074 * @param quiet true to suppress output, false to enable output 075 */ 076 public void setQuiet(boolean quiet) { 077 this.quiet = quiet; 078 } 079 080 /** 081 * Returns whether quiet mode is enabled. 082 * 083 * @return true if quiet mode is enabled 084 */ 085 public boolean isQuiet() { 086 return quiet; 087 } 088 089 @Override 090 public void add(String source, String type, String data) { 091 if (quiet) { 092 count.incrementAndGet(); 093 return; 094 } 095 096 if (data == null || data.isEmpty()) { 097 count.incrementAndGet(); 098 return; 099 } 100 101 // Format output with fixed-width source prefix on each line 102 String output = formatOutput(source, data); 103 104 switch (type) { 105 case "stderr": 106 stderr.print(output); 107 break; 108 case "cowsay": 109 case "stdout": 110 default: 111 stdout.print(output); 112 break; 113 } 114 count.incrementAndGet(); 115 } 116 117 /** 118 * Formats the output with a fixed-width source prefix on each line. 119 * 120 * <p>Every line of output is prefixed with {@code [source]} where source 121 * is left-justified in a fixed-width field. This allows multi-line output 122 * (such as cowsay ASCII art) to remain properly aligned while still being 123 * identifiable by source.</p> 124 * 125 * <p>Example output:</p> 126 * <pre> 127 * [node-web-01 ] command output here 128 * [workflow ] _______________________ 129 * [workflow ] < Starting workflow... > 130 * [workflow ] ----------------------- 131 * </pre> 132 * 133 * @param source the source identifier (e.g., "node-web-01", "cli") 134 * @param data the output data (may contain multiple lines) 135 * @return the formatted output string with prefix on each line 136 */ 137 private String formatOutput(String source, String data) { 138 String prefix = formatPrefix(source); 139 StringBuilder sb = new StringBuilder(); 140 141 String[] lines = data.split("\n", -1); 142 for (int i = 0; i < lines.length; i++) { 143 sb.append(prefix).append(lines[i]); 144 if (i < lines.length - 1) { 145 sb.append("\n"); 146 } 147 } 148 sb.append("\n"); 149 150 return sb.toString(); 151 } 152 153 /** 154 * Creates a prefix from the source name. 155 * 156 * @param source the source identifier 157 * @return formatted prefix like "[node-web-01] " 158 */ 159 private String formatPrefix(String source) { 160 String src = (source != null) ? source : ""; 161 return "[" + src + "] "; 162 } 163 164 @Override 165 public String getSummary() { 166 return "ConsoleAccumulator: " + count.get() + " entries written to console"; 167 } 168 169 @Override 170 public int getCount() { 171 return count.get(); 172 } 173 174 @Override 175 public void clear() { 176 count.set(0); 177 } 178}