View Javadoc
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 }