View Javadoc
1   package org.metricshub.jawk.util;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2025 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.InputStream;
26  import java.io.PrintStream;
27  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Objects;
34  
35  /**
36   * A simple container for the parameters of a single AWK invocation.
37   * These values have defaults.
38   * These defaults may be changed through command line arguments,
39   * or when invoking Jawk programmatically, from within Java code.
40   *
41   * @author Danny Daglas
42   */
43  public class AwkSettings {
44  
45  	/**
46  	 * Shared immutable settings instance representing the default configuration.
47  	 */
48  	public static final AwkSettings DEFAULT_SETTINGS = new ImmutableAwkSettings();
49  
50  	/**
51  	 * Where input is read from.
52  	 * By default, this is {@link System#in}.
53  	 */
54  	private InputStream input = System.in;
55  
56  	/**
57  	 * Contains variable assignments which are applied prior to
58  	 * executing the script (-v assignments).
59  	 * The values may be of type <code>Integer</code>,
60  	 * <code>Double</code> or <code>String</code>.
61  	 */
62  	private Map<String, Object> variables = new HashMap<String, Object>();
63  
64  	/**
65  	 * Contains name=value or filename entries.
66  	 * Order is important, which is why name=value and filenames
67  	 * are listed in the same List container.
68  	 */
69  	private List<String> nameValueOrFileNames = new ArrayList<String>();
70  
71  	/**
72  	 * Initial Field Separator (FS) value.
73  	 * <code>null</code> means the default FS value.
74  	 */
75  	private String fieldSeparator = null;
76  
77  	/**
78  	 * Whether to maintain array keys in sorted order;
79  	 * <code>false</code> by default.
80  	 */
81  	private boolean useSortedArrayKeys = false;
82  
83  	/**
84  	 * Whether to trap <code>IllegalFormatExceptions</code>
85  	 * for <code>[s]printf</code>;
86  	 * <code>true</code> by default.
87  	 */
88  	private boolean catchIllegalFormatExceptions = true;
89  
90  	/**
91  	 * Output stream;
92  	 * <code>System.out</code> by default,
93  	 * which means we will print to stdout by default
94  	 */
95  	private PrintStream outputStream = System.out;
96  
97  	/**
98  	 * Locale for the output of numbers
99  	 * <code>US-English</code> by default.
100 	 */
101 	private Locale locale = Locale.US;
102 
103 	/**
104 	 * Default value for RS, when not set specifically by the AWK script
105 	 */
106 	private String defaultRS = System.getProperty("line.separator", "\n");
107 
108 	/**
109 	 * Default value for ORS, when not set specifically by the AWK script
110 	 */
111 	private String defaultORS = System.getProperty("line.separator", "\n");
112 
113 	/**
114 	 * <p>
115 	 * toDescriptionString.
116 	 * </p>
117 	 *
118 	 * @return a human readable representation of the parameters values.
119 	 */
120 	public String toDescriptionString() {
121 		StringBuilder desc = new StringBuilder();
122 
123 		final char newLine = '\n';
124 
125 		desc.append("variables = ").append(getVariables()).append(newLine);
126 		desc.append("nameValueOrFileNames = ").append(getNameValueOrFileNames()).append(newLine);
127 		desc.append("fieldSeparator = ").append(getFieldSeparator()).append(newLine);
128 		desc.append("useSortedArrayKeys = ").append(isUseSortedArrayKeys()).append(newLine);
129 		desc.append("catchIllegalFormatExceptions = ").append(isCatchIllegalFormatExceptions()).append(newLine);
130 
131 		return desc.toString();
132 	}
133 
134 	/**
135 	 * Provides a description of extensions that are enabled/disabled.
136 	 * The default compiler implementation uses this method
137 	 * to describe extensions which are compiled into the script.
138 	 * The description is then provided to the user within the usage.
139 	 *
140 	 * @return A description of the extensions which are enabled/disabled.
141 	 */
142 	public String toExtensionDescription() {
143 		StringBuilder extensions = new StringBuilder();
144 
145 		if (isUseSortedArrayKeys()) {
146 			extensions.append(", associative array keys are sorted");
147 		}
148 		if (isCatchIllegalFormatExceptions()) {
149 			extensions.append(", IllegalFormatExceptions NOT trapped");
150 		}
151 		if (extensions.length() > 0) {
152 			return "{extensions: " + extensions.substring(2) + "}";
153 		} else {
154 			return "{no compiled extensions utilized}";
155 		}
156 	}
157 
158 	@SuppressWarnings("unused")
159 	private void addInitialVariable(String keyValue) {
160 		int equalsIdx = keyValue.indexOf('=');
161 		assert equalsIdx >= 0;
162 		String name = keyValue.substring(0, equalsIdx);
163 		String valueString = keyValue.substring(equalsIdx + 1);
164 		Object value;
165 		// deduce type
166 		try {
167 			value = Integer.parseInt(valueString);
168 		} catch (NumberFormatException nfe) {
169 			try {
170 				value = Double.parseDouble(valueString);
171 			} catch (NumberFormatException nfe2) {
172 				value = valueString;
173 			}
174 		}
175 		// note: can overwrite previously defined variables
176 		putVariable(name, value);
177 	}
178 
179 	/**
180 	 * Where input is read from.
181 	 * By default, this is {@link java.lang.System#in}.
182 	 *
183 	 * @return the input
184 	 */
185 	public InputStream getInput() {
186 		return input;
187 	}
188 
189 	/**
190 	 * Where input is read from.
191 	 * By default, this is {@link java.lang.System#in}.
192 	 *
193 	 * @param input the input to set
194 	 */
195 	public void setInput(InputStream input) {
196 		this.input = Objects.requireNonNull(input, "input");
197 	}
198 
199 	/**
200 	 * Contains variable assignments which are applied prior to
201 	 * executing the script (-v assignments).
202 	 * The values may be of type <code>Integer</code>,
203 	 * <code>Double</code> or <code>String</code>.
204 	 *
205 	 * @return the variables
206 	 */
207 	public Map<String, Object> getVariables() {
208 		return new HashMap<String, Object>(variables);
209 	}
210 
211 	/**
212 	 * Contains variable assignments which are applied prior to
213 	 * executing the script (-v assignments).
214 	 * The values may be of type <code>Integer</code>,
215 	 * <code>Double</code> or <code>String</code>.
216 	 *
217 	 * @param variables the variables to set
218 	 */
219 	public void setVariables(Map<String, Object> variables) {
220 		this.variables = new HashMap<String, Object>(variables);
221 	}
222 
223 	/**
224 	 * Put or replace a variable entry.
225 	 *
226 	 * @param name Variable name
227 	 * @param value Variable value
228 	 */
229 	public void putVariable(String name, Object value) {
230 		variables.put(name, value);
231 	}
232 
233 	/**
234 	 * Contains name=value or filename entries.
235 	 * Order is important, which is why name=value and filenames
236 	 * are listed in the same List container.
237 	 *
238 	 * @return the nameValueOrFileNames
239 	 */
240 	public List<String> getNameValueOrFileNames() {
241 		return new ArrayList<String>(nameValueOrFileNames);
242 	}
243 
244 	/**
245 	 * Add a name=value or filename entry.
246 	 *
247 	 * @param entry entry to add
248 	 */
249 	public void addNameValueOrFileName(String entry) {
250 		nameValueOrFileNames.add(entry);
251 	}
252 
253 	/**
254 	 * Initial Field Separator (FS) value.
255 	 * <code>null</code> means the default FS value.
256 	 *
257 	 * @return the fieldSeparator
258 	 */
259 	public String getFieldSeparator() {
260 		return fieldSeparator;
261 	}
262 
263 	/**
264 	 * Initial Field Separator (FS) value.
265 	 * <code>null</code> means the default FS value.
266 	 *
267 	 * @param fieldSeparator the fieldSeparator to set
268 	 */
269 	public void setFieldSeparator(String fieldSeparator) {
270 		this.fieldSeparator = fieldSeparator;
271 	}
272 
273 	/**
274 	 * Whether to maintain array keys in sorted order;
275 	 * <code>false</code> by default.
276 	 *
277 	 * @return the useSortedArrayKeys
278 	 */
279 	public boolean isUseSortedArrayKeys() {
280 		return useSortedArrayKeys;
281 	}
282 
283 	/**
284 	 * Whether to maintain array keys in sorted order;
285 	 * <code>false</code> by default.
286 	 *
287 	 * @param useSortedArrayKeys the useSortedArrayKeys to set
288 	 */
289 	public void setUseSortedArrayKeys(boolean useSortedArrayKeys) {
290 		this.useSortedArrayKeys = useSortedArrayKeys;
291 	}
292 
293 	/**
294 	 * Output stream;
295 	 * <code>System.out</code> by default,
296 	 * which means we will print to stdout by default
297 	 *
298 	 * @return the output stream
299 	 */
300 	@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "OutputStream reference is intentionally shared so callers can control output.")
301 	public PrintStream getOutputStream() {
302 		return outputStream;
303 	}
304 
305 	/**
306 	 * Sets the OutputStream to print to (instead of System.out by default)
307 	 *
308 	 * @param pOutputStream OutputStream to use for print statements
309 	 */
310 	public void setOutputStream(PrintStream pOutputStream) {
311 		outputStream = Objects.requireNonNull(pOutputStream, "outputStream");
312 	}
313 
314 	/**
315 	 * Whether to trap <code>IllegalFormatExceptions</code>
316 	 * for <code>[s]printf</code>;
317 	 * <code>true</code> by default.
318 	 *
319 	 * @return the catchIllegalFormatExceptions
320 	 */
321 	public boolean isCatchIllegalFormatExceptions() {
322 		return catchIllegalFormatExceptions;
323 	}
324 
325 	/**
326 	 * Whether to trap <code>IllegalFormatExceptions</code>
327 	 * for <code>[s]printf</code>;
328 	 * <code>true</code> by default.
329 	 *
330 	 * @param catchIllegalFormatExceptions the catchIllegalFormatExceptions to set
331 	 */
332 	public void setCatchIllegalFormatExceptions(boolean catchIllegalFormatExceptions) {
333 		this.catchIllegalFormatExceptions = catchIllegalFormatExceptions;
334 	}
335 
336 	/**
337 	 * <p>
338 	 * Getter for the field <code>locale</code>.
339 	 * </p>
340 	 *
341 	 * @return the Locale that will be used for outputting numbers
342 	 */
343 	public Locale getLocale() {
344 		return locale;
345 	}
346 
347 	/**
348 	 * Sets the Locale for outputting numbers
349 	 *
350 	 * @param pLocale The locale to be used (e.g.: <code>Locale.US</code>)
351 	 */
352 	public void setLocale(Locale pLocale) {
353 		locale = pLocale;
354 	}
355 
356 	/**
357 	 * <p>
358 	 * Getter for the field <code>defaultRS</code>.
359 	 * </p>
360 	 *
361 	 * @return the default RS, when not set by the AWK script
362 	 */
363 	public String getDefaultRS() {
364 		return defaultRS;
365 	}
366 
367 	/**
368 	 * Sets the default RS, when not set by the AWK script
369 	 *
370 	 * @param rs The regular expression that separates records
371 	 */
372 	public void setDefaultRS(String rs) {
373 		defaultRS = Objects.requireNonNull(rs, "defaultRS");
374 	}
375 
376 	/**
377 	 * <p>
378 	 * Getter for the field <code>defaultORS</code>.
379 	 * </p>
380 	 *
381 	 * @return the default ORS, when not set by the AWK script
382 	 */
383 	public String getDefaultORS() {
384 		return defaultORS;
385 	}
386 
387 	/**
388 	 * Sets the default ORS, when not set by the AWK script
389 	 *
390 	 * @param ors The string that separates output records (with the print statement)
391 	 */
392 	public void setDefaultORS(String ors) {
393 		defaultORS = Objects.requireNonNull(ors, "defaultORS");
394 	}
395 
396 	private static final class ImmutableAwkSettings extends AwkSettings {
397 
398 		private ImmutableAwkSettings() {
399 			super();
400 		}
401 
402 		@Override
403 		public void setInput(InputStream input) {
404 			throw unsupported();
405 		}
406 
407 		@Override
408 		public void setVariables(Map<String, Object> variables) {
409 			throw unsupported();
410 		}
411 
412 		@Override
413 		public void putVariable(String name, Object value) {
414 			throw unsupported();
415 		}
416 
417 		@Override
418 		public void addNameValueOrFileName(String entry) {
419 			throw unsupported();
420 		}
421 
422 		@Override
423 		public void setFieldSeparator(String fieldSeparator) {
424 			throw unsupported();
425 		}
426 
427 		@Override
428 		public void setUseSortedArrayKeys(boolean useSortedArrayKeys) {
429 			throw unsupported();
430 		}
431 
432 		@Override
433 		public void setOutputStream(PrintStream pOutputStream) {
434 			throw unsupported();
435 		}
436 
437 		@Override
438 		public void setCatchIllegalFormatExceptions(boolean catchIllegalFormatExceptions) {
439 			throw unsupported();
440 		}
441 
442 		@Override
443 		public void setLocale(Locale pLocale) {
444 			throw unsupported();
445 		}
446 
447 		@Override
448 		public void setDefaultRS(String rs) {
449 			throw unsupported();
450 		}
451 
452 		@Override
453 		public void setDefaultORS(String ors) {
454 			throw unsupported();
455 		}
456 
457 		private UnsupportedOperationException unsupported() {
458 			return new UnsupportedOperationException("DEFAULT_SETTINGS is immutable");
459 		}
460 	}
461 }