1 package io.jawk.jrt;
2
3 /*-
4 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5 * Jawk
6 * ჻჻჻჻჻჻
7 * Copyright (C) 2006 - 2026 MetricsHub
8 * ჻჻჻჻჻჻
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation, either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Lesser Public License for more details.
18 *
19 * You should have received a copy of the GNU General Lesser Public
20 * License along with this program. If not, see
21 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23 */
24
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.io.PrintStream;
28 import java.util.Locale;
29 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
30
31 /**
32 * Output target used by AWK {@code print} and {@code printf} statements.
33 * <p>
34 * Implementations decide how to represent AWK output, whether as text written
35 * to a stream, appended characters, or structured values collected by the
36 * embedding application. Numeric rendering uses the sink's immutable
37 * construction-time locale.
38 * </p>
39 */
40 public abstract class AwkSink {
41
42 private final Locale locale;
43
44 /**
45 * Creates a sink using the default {@link Locale#US} formatting rules.
46 */
47 protected AwkSink() {
48 this(Locale.US);
49 }
50
51 /**
52 * Creates a sink using the supplied locale for numeric formatting.
53 *
54 * @param localeParam locale to use for numeric formatting
55 */
56 protected AwkSink(Locale localeParam) {
57 this.locale = localeParam == null ? Locale.US : localeParam;
58 }
59
60 /**
61 * Returns the locale used by this sink when it renders numeric values.
62 *
63 * @return sink locale
64 */
65 public final Locale getLocale() {
66 return locale;
67 }
68
69 /**
70 * Writes one AWK {@code print} operation.
71 *
72 * @param ofs output field separator
73 * @param ors output record separator
74 * @param ofmt numeric output format used by plain {@code print}
75 * @param values values supplied to {@code print}
76 * @throws IOException if the sink cannot write the output
77 */
78 public abstract void print(String ofs, String ors, String ofmt, Object... values) throws IOException;
79
80 /**
81 * Writes one AWK {@code printf} operation.
82 *
83 * @param ofs output field separator
84 * @param ors output record separator
85 * @param ofmt numeric output format available to the sink
86 * @param convfmt number-to-string conversion format ({@code CONVFMT}),
87 * used by {@code %s} to convert numeric values the way AWK does
88 * @param format format string passed to {@code printf}
89 * @param values arguments supplied after the format string
90 * @throws IOException if the sink cannot write the output
91 */
92 public abstract void printf(String ofs, String ors, String ofmt, String convfmt, String format, Object... values)
93 throws IOException;
94
95 /**
96 * Flushes any buffered output held by this sink.
97 *
98 * @throws IOException if the sink cannot be flushed
99 */
100 public void flush() throws IOException {
101 // Most sinks do not buffer explicitly.
102 }
103
104 /**
105 * Returns a {@link PrintStream} view that receives raw process output written
106 * by spawned commands such as {@code system("...")}.
107 * <p>
108 * The default implementation returns a stream that silently discards all
109 * output. Override this method in sinks that need to capture process output.
110 * </p>
111 *
112 * @return print stream that should receive raw process output
113 */
114 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The shared discard stream is stateless and safe to expose.")
115 public PrintStream getPrintStream() {
116 return NULL_PRINT_STREAM;
117 }
118
119 /** Shared discard stream returned by the default {@link #getPrintStream()}. */
120 private static final PrintStream NULL_PRINT_STREAM = newNullPrintStream();
121
122 /**
123 * A shared no-op sink that silently discards all output.
124 * <p>
125 * This singleton is safe to share across all JRT/AVM instances because
126 * its {@link #print(String, String, String, Object...)},
127 * {@link #printf(String, String, String, String, String, Object...)}, and
128 * {@link #flush()} operations are all no-ops.
129 */
130 public static final AwkSink NOP_SINK = new NoOpAwkSink();
131
132 private static final class NoOpAwkSink extends AwkSink {
133
134 NoOpAwkSink() {
135 super();
136 }
137
138 @Override
139 public void print(String ofs, String ors, String ofmt, Object... values) {
140 // discard
141 }
142
143 @Override
144 public void printf(String ofs, String ors, String ofmt, String convfmt, String format, Object... values) {
145 // discard
146 }
147 }
148
149 private static PrintStream newNullPrintStream() {
150 try {
151 return new PrintStream(
152 new OutputStream() {
153 @Override
154 public void write(int b) {
155 // discard
156 }
157
158 @Override
159 public void write(byte[] b, int off, int len) {
160 // discard
161 }
162 },
163 false,
164 "UTF-8") {
165
166 @Override
167 public void close() {
168 // Prevent closing; this stream is a shared singleton.
169 }
170 };
171 } catch (java.io.UnsupportedEncodingException e) {
172 throw new IllegalStateException(e);
173 }
174 }
175
176 /**
177 * Creates a sink backed by an {@link OutputStream}.
178 *
179 * @param outputStream stream that should receive AWK output
180 * @return sink writing to {@code outputStream}
181 */
182 public static AwkSink from(OutputStream outputStream) {
183 return from(outputStream, Locale.US);
184 }
185
186 /**
187 * Creates a sink backed by an {@link OutputStream}.
188 *
189 * @param outputStream stream that should receive AWK output
190 * @param locale locale to use for numeric formatting
191 * @return sink writing to {@code outputStream}
192 */
193 public static AwkSink from(OutputStream outputStream, Locale locale) {
194 return new OutputStreamAwkSink(outputStream, locale);
195 }
196
197 /**
198 * Creates a sink backed by a {@link PrintStream}.
199 *
200 * @param printStream stream that should receive AWK output
201 * @return sink writing to {@code printStream}
202 */
203 public static AwkSink from(PrintStream printStream) {
204 return from(printStream, Locale.US);
205 }
206
207 /**
208 * Creates a sink backed by a {@link PrintStream}.
209 *
210 * @param printStream stream that should receive AWK output
211 * @param locale locale to use for numeric formatting
212 * @return sink writing to {@code printStream}
213 */
214 public static AwkSink from(PrintStream printStream, Locale locale) {
215 return new OutputStreamAwkSink(printStream, locale);
216 }
217
218 /**
219 * Creates a sink backed by an {@link Appendable}.
220 *
221 * @param appendable appendable that should receive AWK output
222 * @return sink writing to {@code appendable}
223 */
224 public static AwkSink from(Appendable appendable) {
225 return from(appendable, Locale.US);
226 }
227
228 /**
229 * Creates a sink backed by an {@link Appendable}.
230 *
231 * @param appendable appendable that should receive AWK output
232 * @param locale locale to use for numeric formatting
233 * @return sink writing to {@code appendable}
234 */
235 public static AwkSink from(Appendable appendable, Locale locale) {
236 return new AppendableAwkSink(appendable, locale);
237 }
238
239 /**
240 * Formats one operand of a plain AWK {@code print} statement.
241 * <p>
242 * Numeric values are rendered with {@code OFMT} (or as integers when they
243 * hold an integral value). String values — including numeric strings that
244 * originate from input — are printed verbatim, as required by POSIX.
245 * </p>
246 *
247 * @param value operand to format
248 * @param ofmt numeric output format
249 * @return the textual representation AWK would print for this operand
250 */
251 protected final String formatPrintArgument(Object value, String ofmt) {
252 return AwkPrintf.toAwkString(value, ofmt, locale);
253 }
254
255 /**
256 * Formats a string in the same way as AWK's {@code sprintf()} built-in,
257 * converting numeric {@code %s} operands with the supplied {@code CONVFMT}
258 * value.
259 * <p>
260 * Subclasses may override this method to customize formatting. The default
261 * implementation delegates to
262 * {@link AwkPrintf#sprintf(Locale, String, String, Object...)}. The
263 * built-in sinks render {@code printf} output through this method, so
264 * overriding it keeps {@code printf} and {@code sprintf} consistent.
265 * </p>
266 *
267 * @param convfmt number-to-string conversion format ({@code CONVFMT})
268 * @param format format string
269 * @param values arguments supplied after the format string
270 * @return formatted text
271 */
272 public String sprintf(String convfmt, String format, Object... values) {
273 Object[] safeValues = values == null ? new Object[0] : values;
274 return AwkPrintf.sprintf(locale, convfmt, format, safeValues);
275 }
276 }