1 package io.jawk.util;
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.util.HashMap;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.concurrent.atomic.AtomicLong;
30 import io.jawk.Awk;
31
32 /**
33 * Reusable behavioral configuration for the Jawk engine.
34 * <p>
35 * Instances hold settings that control how the AWK interpreter behaves
36 * (field separator, locale, sorted keys, initial variables, and default
37 * record separator) but do <em>not</em> carry per-execution state such as
38 * input sources, filename arguments, or output destinations. This
39 * separation allows a single {@code AwkSettings} object to be shared
40 * across many invocations of {@link io.jawk.Awk#eval},
41 * {@link io.jawk.Awk#script}, or {@link io.jawk.Awk#createAvm()}.
42 * </p>
43 * <p>
44 * Output is configured on the execution builder returned by
45 * {@link io.jawk.Awk#script(String)} or {@link io.jawk.Awk#script(io.jawk.AwkProgram)}
46 * and is therefore always per-execution.
47 * </p>
48 *
49 * @author Danny Daglas
50 */
51 public class AwkSettings {
52
53 /**
54 * Shared immutable settings instance representing the default configuration.
55 */
56 public static final AwkSettings DEFAULT_SETTINGS = new ImmutableAwkSettings();
57
58 /**
59 * Contains variable assignments which are applied prior to
60 * executing the script (-v assignments).
61 * The values may be of type <code>Integer</code>,
62 * <code>Double</code>, <code>String</code>,
63 * {@link io.jawk.jrt.AssocArray} (for array variables),
64 * any {@link java.util.Map} that Jawk exposes directly to the script,
65 * or any {@link java.util.List} that Jawk materializes as an array with
66 * zero-based {@link java.lang.Long} keys.
67 * <p>
68 * When a {@link java.util.Map} is provided, the Jawk runtime may mutate it
69 * during execution. Callers must therefore supply a mutable map
70 * implementation. Numeric indices written by the runtime into such maps and
71 * indices derived from {@link java.util.List} values use
72 * {@link java.lang.Long} keys (for example, <code>0L</code>,
73 * <code>1L</code>, ...).
74 * </p>
75 */
76 private final Map<String, Object> variables = new HashMap<String, Object>();
77
78 /**
79 * Initial Field Separator (FS) value.
80 * <code>null</code> means the default FS value.
81 */
82 private volatile String fieldSeparator = null;
83
84 /**
85 * Whether to maintain array keys in sorted order;
86 * <code>false</code> by default.
87 */
88 private volatile boolean useSortedArrayKeys = false;
89
90 /**
91 * Whether to accept gawk-style arrays of arrays syntax such as {@code a[i][j]}
92 * and subarray operands in array-only positions such as {@code split(..., a[i])}.
93 * <code>true</code> by default.
94 */
95 private volatile boolean allowArraysOfArrays = true;
96
97 /**
98 * Locale for the output of numbers
99 * <code>US-English</code> by default.
100 */
101 private volatile Locale locale = Locale.US;
102
103 /**
104 * Default value for RS, when not set specifically by the AWK script.
105 * Defaults to {@link Awk#DEFAULT_RS} per POSIX. Platform-specific
106 * end-of-line handling is the responsibility of the input source.
107 */
108 private volatile String defaultRS = Awk.DEFAULT_RS;
109
110 /**
111 * Monotonically increasing counter incremented whenever the settings change.
112 * It allows callers that cache derived runtime state to detect when a new
113 * snapshot must be built.
114 */
115 private final AtomicLong modificationCount = new AtomicLong();
116
117 /**
118 * <p>
119 * toDescriptionString.
120 * </p>
121 *
122 * @return a human readable representation of the parameters values.
123 */
124 public String toDescriptionString() {
125 StringBuilder desc = new StringBuilder();
126
127 final char newLine = '\n';
128
129 desc.append("variables = ").append(getVariables()).append(newLine);
130 desc.append("fieldSeparator = ").append(getFieldSeparator()).append(newLine);
131 desc.append("useSortedArrayKeys = ").append(isUseSortedArrayKeys()).append(newLine);
132 desc.append("allowArraysOfArrays = ").append(isAllowArraysOfArrays()).append(newLine);
133 return desc.toString();
134 }
135
136 /**
137 * Provides a description of extensions that are enabled/disabled.
138 * The default compiler implementation uses this method
139 * to describe extensions which are compiled into the script.
140 * The description is then provided to the user within the usage.
141 *
142 * @return A description of the extensions which are enabled/disabled.
143 */
144 public String toExtensionDescription() {
145 StringBuilder extensions = new StringBuilder();
146
147 if (isUseSortedArrayKeys()) {
148 extensions.append(", associative array keys are sorted");
149 }
150 if (isAllowArraysOfArrays()) {
151 extensions.append(", arrays of arrays");
152 }
153 if (extensions.length() > 0) {
154 return "{extensions: " + extensions.substring(2) + "}";
155 } else {
156 return "{no compiled extensions utilized}";
157 }
158 }
159
160 @SuppressWarnings("unused")
161 private void addInitialVariable(String keyValue) {
162 int equalsIdx = keyValue.indexOf('=');
163 String name = keyValue.substring(0, equalsIdx);
164 String valueString = keyValue.substring(equalsIdx + 1);
165 // note: can overwrite previously defined variables
166 putVariable(name, valueString);
167 }
168
169 /**
170 * Contains variable assignments which are applied prior to
171 * executing the script (-v assignments).
172 * The values may be of type <code>Integer</code>,
173 * <code>Double</code>, <code>String</code>,
174 * {@link io.jawk.jrt.AssocArray} (for array variables),
175 * any {@link java.util.Map} that Jawk exposes directly to the script,
176 * or any {@link java.util.List} that Jawk materializes as an array with
177 * zero-based {@link java.lang.Long} keys.
178 *
179 * @return the variables
180 */
181 public Map<String, Object> getVariables() {
182 synchronized (variables) {
183 return new HashMap<String, Object>(variables);
184 }
185 }
186
187 /**
188 * Returns the number of explicit mutations applied to this settings
189 * instance.
190 * <p>
191 * The value is intended for cache invalidation only; it has no behavioral
192 * meaning other than changing whenever one of the configuration mutators is
193 * called.
194 * </p>
195 *
196 * @return the current modification counter
197 */
198 public long getModificationCount() {
199 return modificationCount.get();
200 }
201
202 /**
203 * Contains variable assignments which are applied prior to
204 * executing the script (-v assignments).
205 * The values may be of type <code>Integer</code>,
206 * <code>Double</code>, <code>String</code>,
207 * {@link io.jawk.jrt.AssocArray} (for array variables),
208 * any {@link java.util.Map} that Jawk exposes directly to the script,
209 * or any {@link java.util.List} that Jawk materializes as an array with
210 * zero-based {@link java.lang.Long} keys.
211 *
212 * @param variables the variables to set
213 */
214 public void setVariables(Map<String, Object> variables) {
215 synchronized (this.variables) {
216 this.variables.clear();
217 this.variables.putAll(variables);
218 }
219 markModified();
220 }
221
222 /**
223 * Put or replace a variable entry.
224 *
225 * @param name Variable name
226 * @param value Variable value
227 */
228 public void putVariable(String name, Object value) {
229 synchronized (variables) {
230 variables.put(name, value);
231 }
232 markModified();
233 }
234
235 /**
236 * Initial Field Separator (FS) value.
237 * <code>null</code> means the default FS value.
238 *
239 * @return the fieldSeparator
240 */
241 public String getFieldSeparator() {
242 return fieldSeparator;
243 }
244
245 /**
246 * Initial Field Separator (FS) value.
247 * <code>null</code> means the default FS value.
248 *
249 * @param fieldSeparator the fieldSeparator to set
250 */
251 public void setFieldSeparator(String fieldSeparator) {
252 this.fieldSeparator = fieldSeparator;
253 markModified();
254 }
255
256 /**
257 * Whether to maintain array keys in sorted order;
258 * <code>false</code> by default.
259 *
260 * @return the useSortedArrayKeys
261 */
262 public boolean isUseSortedArrayKeys() {
263 return useSortedArrayKeys;
264 }
265
266 /**
267 * Whether to maintain array keys in sorted order;
268 * <code>false</code> by default.
269 *
270 * @param useSortedArrayKeys the useSortedArrayKeys to set
271 */
272 public void setUseSortedArrayKeys(boolean useSortedArrayKeys) {
273 this.useSortedArrayKeys = useSortedArrayKeys;
274 markModified();
275 }
276
277 /**
278 * Whether to accept gawk-style arrays of arrays syntax such as {@code a[i][j]}
279 * and subarray operands in array-only positions such as {@code split(..., a[i])}.
280 *
281 * @return {@code true} when arrays of arrays are enabled at compile time
282 */
283 public boolean isAllowArraysOfArrays() {
284 return allowArraysOfArrays;
285 }
286
287 /**
288 * Enables or disables gawk-style arrays of arrays syntax such as
289 * {@code a[i][j]} and subarray operands in array-only positions such as
290 * {@code split(..., a[i])} or {@code for (k in a[i])}.
291 *
292 * @param allowArraysOfArrays {@code true} to accept arrays-of-arrays features
293 */
294 public void setAllowArraysOfArrays(boolean allowArraysOfArrays) {
295 this.allowArraysOfArrays = allowArraysOfArrays;
296 markModified();
297 }
298
299 /**
300 * <p>
301 * Getter for the field <code>locale</code>.
302 * </p>
303 *
304 * @return the Locale that will be used for outputting numbers
305 */
306 public Locale getLocale() {
307 return locale;
308 }
309
310 /**
311 * Sets the Locale for outputting numbers.
312 *
313 * @param pLocale The locale to be used (e.g.: <code>Locale.US</code>)
314 */
315 public void setLocale(Locale pLocale) {
316 locale = pLocale == null ? Locale.US : pLocale;
317 markModified();
318 }
319
320 /**
321 * <p>
322 * Getter for the field <code>defaultRS</code>.
323 * </p>
324 *
325 * @return the default RS, when not set by the AWK script
326 */
327 public String getDefaultRS() {
328 return defaultRS;
329 }
330
331 /**
332 * Sets the default RS, when not set by the AWK script
333 *
334 * @param rs The regular expression that separates records
335 */
336 public void setDefaultRS(String rs) {
337 defaultRS = Objects.requireNonNull(rs, "defaultRS");
338 markModified();
339 }
340
341 protected final void markModified() {
342 modificationCount.incrementAndGet();
343 }
344
345 private static final class ImmutableAwkSettings extends AwkSettings {
346
347 private ImmutableAwkSettings() {
348 super();
349 }
350
351 @Override
352 public void setVariables(Map<String, Object> variables) {
353 throw unsupported();
354 }
355
356 @Override
357 public void putVariable(String name, Object value) {
358 throw unsupported();
359 }
360
361 @Override
362 public void setFieldSeparator(String fieldSeparator) {
363 throw unsupported();
364 }
365
366 @Override
367 public void setUseSortedArrayKeys(boolean useSortedArrayKeys) {
368 throw unsupported();
369 }
370
371 @Override
372 public void setAllowArraysOfArrays(boolean allowArraysOfArrays) {
373 throw unsupported();
374 }
375
376 @Override
377 public void setLocale(Locale pLocale) {
378 throw unsupported();
379 }
380
381 @Override
382 public void setDefaultRS(String rs) {
383 throw unsupported();
384 }
385
386 private UnsupportedOperationException unsupported() {
387 return new UnsupportedOperationException("DEFAULT_SETTINGS is immutable");
388 }
389 }
390 }