View Javadoc
1   
2   package org.sentrysoftware.jawk.ext;
3   
4   /*-
5    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
6    * Jawk
7    * ჻჻჻჻჻჻
8    * Copyright (C) 2006 - 2023 Sentry Software
9    * ჻჻჻჻჻჻
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as
12   * published by the Free Software Foundation, either version 3 of the
13   * License, or (at your option) any later version.
14   *
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   *
20   * You should have received a copy of the GNU General Lesser Public
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
23   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
24   */
25  
26  import java.io.BufferedReader;
27  import java.io.IOException;
28  import java.io.InputStreamReader;
29  import java.util.concurrent.BlockingQueue;
30  import java.util.concurrent.LinkedBlockingQueue;
31  
32  import org.sentrysoftware.jawk.NotImplementedError;
33  import org.sentrysoftware.jawk.jrt.BlockObject;
34  import org.sentrysoftware.jawk.jrt.JRT;
35  import org.sentrysoftware.jawk.jrt.VariableManager;
36  import org.sentrysoftware.jawk.util.AwkLogger;
37  import org.sentrysoftware.jawk.util.AwkSettings;
38  import org.slf4j.Logger;
39  
40  /**
41   * Enable stdin processing in Jawk, to be used in conjunction with the -ni parameter.
42   * Since normal input processing is turned off via -ni, this is provided to enable a way
43   * to read input from stdin.
44   *
45   * <p>
46   * To use:
47   * <blockquote><pre>
48   * StdinGetline() == 1 { print "--&gt; " $0 }
49   * </pre></blockquote>
50   *
51   *
52   * <p>
53   * The extension functions are as follows:
54   * <ul>
55   * <li>
56   *   <p>
57   *   <strong><em>StdinHasInput</em></strong> -<br/>
58   *   Returns 1 when StdinGetline() does not block (i.e., when input is available
59   *   or upon an EOF), 0 otherwise.<br/>
60   *   <strong>Parameters:</strong>
61   *   <ul>
62   *   <li>none
63   *   </ul>
64   *   <strong>Returns:</strong>
65   *   <ul>
66   *   <li>1 when StdinGetline() does not block, 0 otherwise.
67   *   </ul>
68   *
69   * <li>
70   *   <p>
71   *   <strong><em>StdinGetline</em></strong> -<br/>
72   *   Retrieve a line of input from stdin. The operation
73   *   will block until input is available, EOF, or an IO error.<br/>
74   *   <strong>Parameters:</strong>
75   *   <ul>
76   *   <li>none
77   *   </ul>
78   *   <strong>Returns:</strong>
79   *   <ul>
80   *   <li>1 upon successful read of a line of input from stdin,
81   *     0 upon an EOF, and -1 when an IO error occurs.
82   *   </ul>
83   *
84   * <li>
85   *   <p>
86   *   <strong><em>StdinBlock</em></strong> -<br/>
87   *   Block until a call to StdinGetline() would not block.<br/>
88   *   <strong>Parameters:</strong>
89   *   <ul>
90   *   <li>chained block function - optional
91   *   </ul>
92   *   <strong>Returns:</strong>
93   *   <ul>
94   *   <li>"Stdin" if this block object is triggered
95   *   </ul>
96   *
97   * </ul>
98   *
99   * @author Danny Daglas
100  */
101 public class StdinExtension extends AbstractExtension implements JawkExtension {
102 
103 	private static final Logger LOG = AwkLogger.getLogger(StdinExtension.class);
104 
105 	private static final Object DONE = new Object();
106 
107 	private final BlockingQueue<Object> getLineInput = new LinkedBlockingQueue<Object>();
108 
109 	private final BlockObject blocker = new BlockObject() {
110 
111 		@Override
112 		public String getNotifierTag() {
113 			return "Stdin";
114 		}
115 
116 		@Override
117 		public final void block()
118 				throws InterruptedException
119 		{
120 			synchronized (blocker) {
121 				if (stdInHasInput() == 0) {
122 					blocker.wait();
123 				}
124 			}
125 		}
126 	};
127 
128 	private boolean isEof = false;
129 
130 	/** {@inheritDoc} */
131 	@Override
132 	public void init(VariableManager vm, JRT jrt, final AwkSettings settings) {
133 		super.init(vm, jrt, settings);
134 
135 		Thread getLineInputThread = new Thread("getLineInputThread") {
136 			@Override
137 			public final void run() {
138 				try {
139 					BufferedReader br = new BufferedReader(
140 							new InputStreamReader(settings.getInput()));
141 					String line;
142 					while ((line = br.readLine()) != null) {
143 						getLineInput.put(line);
144 						synchronized (blocker) {
145 							blocker.notify();
146 						}
147 					}
148 				} catch (InterruptedException ie) {
149 					LOG.error("", ie);
150 					// do nothing ... the thread death will signal an issue
151 				} catch (IOException ioe) {
152 					LOG.error("", ioe);
153 					// do nothing ... the thread death will signal an issue
154 				}
155 				try {
156 					getLineInput.put(DONE);
157 				} catch (InterruptedException ie) {
158 					LOG.error("Should never be interrupted.", ie);
159 					System.exit(1);
160 				}
161 				synchronized (blocker) {
162 					blocker.notify();
163 				}
164 			}
165 		};
166 		getLineInputThread.setDaemon(true);
167 		getLineInputThread.start();
168 	}
169 
170 	/** {@inheritDoc} */
171 	@Override
172 	public String getExtensionName() {
173 		return "Stdin Support";
174 	}
175 
176 	/** {@inheritDoc} */
177 	@Override
178 	public String[] extensionKeywords() {
179 		return new String[] {
180 					// keyboard stuff
181 					"StdinHasInput", // i.e. b = StdinHasInput()
182 					"StdinGetline", // i.e. retcode = StdinGetline() # $0 = the input
183 					"StdinBlock", // i.e. StdinBlock(...)
184 				};
185 	}
186 
187 	/** {@inheritDoc} */
188 	@Override
189 	public Object invoke(String keyword, Object[] args) {
190 		if        (keyword.equals("StdinHasInput")) {
191 			checkNumArgs(args, 0);
192 			return stdInHasInput();
193 		} else if (keyword.equals("StdinGetline")) {
194 			checkNumArgs(args, 0);
195 			return stdInGetLine();
196 		} else if (keyword.equals("StdinBlock")) {
197 			if (args.length == 0) {
198 				return stdInBlock();
199 			} else if (args.length == 1) {
200 				return stdInBlock((BlockObject) args[0]);
201 			} else {
202 				throw new IllegalArgumentException("StdinBlock accepts 0 or 1 args.");
203 			}
204 		} else {
205 			throw new NotImplementedError(keyword);
206 		}
207 	}
208 
209 	private int stdInHasInput() {
210 		if (isEof) {
211 			// upon eof, always "don't block" !
212 			return 1;
213 		} else if (getLineInput.size() == 0) {
214 			// nothing in the queue
215 			return 0;
216 		} else if (getLineInput.size() == 1 && getLineInput.peek() == DONE) {
217 			// DONE indicator in the queue
218 			return 0;
219 		} else {
220 			// otherwise, something to read
221 			return 1;
222 		}
223 	}
224 
225 	/**
226 	 * @return 1 upon successful read,
227 	 *   0 upon EOF, and -1 if an IO error occurs
228 	 */
229 	private Object stdInGetLine() {
230 		try {
231 			if (isEof) {
232 				return 0;
233 			}
234 			Object lineObj = getLineInput.take();
235 			if (lineObj == DONE) {
236 				isEof = true;
237 				return 0;
238 			}
239 			getJrt().setInputLine((String) lineObj);
240 			getJrt().jrtParseFields();
241 			return 1;
242 		} catch (InterruptedException ie) {
243 			LOG.warn("", ie);
244 			return -1;
245 		}
246 	}
247 
248 	private BlockObject stdInBlock() {
249 		blocker.clearNextBlockObject();
250 		return blocker;
251 	}
252 
253 	private BlockObject stdInBlock(BlockObject bo) {
254 		assert bo != null;
255 		blocker.setNextBlockObject(bo);
256 		return blocker;
257 	}
258 }