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.ByteArrayOutputStream;
26  import java.io.Flushable;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  import java.io.PrintStream;
30  import java.nio.charset.StandardCharsets;
31  import java.util.Locale;
32  import java.util.Objects;
33  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
34  
35  /**
36   * Text {@link AwkSink} backed by an {@link Appendable}.
37   * <p>
38   * All writes to the underlying {@link Appendable} are synchronized on a shared
39   * lock so that concurrent access from the main AWK thread ({@code print}/
40   * {@code printf}) and background {@link io.jawk.jrt.DataPump} threads
41   * ({@link #getPrintStream()}) cannot corrupt the output.
42   * </p>
43   */
44  public final class AppendableAwkSink extends AwkSink {
45  
46  	private final Appendable appendable;
47  	private final Object lock = new Object();
48  	private final PrintStream printStream;
49  
50  	/**
51  	 * Creates a sink backed by an {@link Appendable}.
52  	 *
53  	 * @param appendableParam appendable that should receive AWK output
54  	 */
55  	public AppendableAwkSink(Appendable appendableParam) {
56  		this(appendableParam, Locale.US);
57  	}
58  
59  	/**
60  	 * Creates a sink backed by an {@link Appendable}.
61  	 *
62  	 * @param appendableParam appendable that should receive AWK output
63  	 * @param locale locale used for numeric formatting
64  	 */
65  	public AppendableAwkSink(Appendable appendableParam, Locale locale) {
66  		super(locale);
67  		this.appendable = Objects.requireNonNull(appendableParam, "appendable");
68  		try {
69  			this.printStream = new PrintStream(
70  					new AppendableOutputStream(appendable, lock),
71  					false,
72  					StandardCharsets.UTF_8.name());
73  		} catch (java.io.UnsupportedEncodingException e) {
74  			throw new IllegalStateException(e);
75  		}
76  	}
77  
78  	@Override
79  	public void print(String ofs, String ors, String ofmt, Object... values) throws IOException {
80  		synchronized (lock) {
81  			for (int i = 0; i < values.length; i++) {
82  				appendable.append(formatPrintArgument(values[i], ofmt));
83  				if (i < values.length - 1) {
84  					appendable.append(ofs);
85  				}
86  			}
87  			appendable.append(ors);
88  		}
89  	}
90  
91  	@Override
92  	public void printf(String ofs, String ors, String ofmt, String format, Object... values)
93  			throws IOException {
94  		synchronized (lock) {
95  			appendable.append(formatPrintfResult(format, values));
96  		}
97  	}
98  
99  	@Override
100 	public void flush() throws IOException {
101 		printStream.flush();
102 		if (appendable instanceof Flushable) {
103 			synchronized (lock) {
104 				((Flushable) appendable).flush();
105 			}
106 		}
107 	}
108 
109 	@Override
110 	@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Callers need the live PrintStream used for process output pumping.")
111 	public PrintStream getPrintStream() {
112 		return printStream;
113 	}
114 
115 	private static final class AppendableOutputStream extends OutputStream {
116 
117 		private final Appendable appendable;
118 		private final Object lock;
119 		private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
120 
121 		private AppendableOutputStream(Appendable appendableParam, Object lockParam) {
122 			this.appendable = appendableParam;
123 			this.lock = lockParam;
124 		}
125 
126 		@Override
127 		public void write(int value) {
128 			synchronized (lock) {
129 				buffer.write(value);
130 			}
131 		}
132 
133 		@Override
134 		public void write(byte[] bytes, int off, int len) {
135 			synchronized (lock) {
136 				buffer.write(bytes, off, len);
137 			}
138 		}
139 
140 		@Override
141 		public void flush() throws IOException {
142 			synchronized (lock) {
143 				if (buffer.size() > 0) {
144 					appendable.append(buffer.toString(StandardCharsets.UTF_8.name()));
145 					buffer.reset();
146 				}
147 			}
148 		}
149 
150 		@Override
151 		public void close() throws IOException {
152 			flush();
153 		}
154 	}
155 }