View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util;
6   
7   import java.math.BigInteger;
8   import java.net.InetAddress;
9   import java.net.UnknownHostException;
10  import java.nio.ByteBuffer;
11  import java.nio.ByteOrder;
12  import java.nio.charset.StandardCharsets;
13  import java.time.LocalTime;
14  import java.time.OffsetDateTime;
15  import java.time.format.DateTimeFormatter;
16  import java.time.format.DateTimeParseException;
17  import java.util.ArrayList;
18  import java.util.Arrays;
19  import java.util.EnumMap;
20  import java.util.EnumSet;
21  import java.util.HashMap;
22  import java.util.LinkedHashMap;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.TimeZone;
27  import java.util.regex.Matcher;
28  import java.util.regex.Pattern;
29  
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  import oshi.annotation.SuppressForbidden;
34  import oshi.annotation.concurrent.ThreadSafe;
35  import oshi.util.tuples.Pair;
36  import oshi.util.tuples.Triplet;
37  
38  /**
39   * String parsing utility.
40   */
41  @ThreadSafe
42  @SuppressForbidden(reason = "Require parse methods to parse in utility class")
43  public final class ParseUtil {
44  
45      private static final Logger LOG = LoggerFactory.getLogger(ParseUtil.class);
46  
47      private static final String DEFAULT_LOG_MSG = "{} didn't parse. Returning default. {}";
48  
49      /*
50       * Used for matching
51       */
52      private static final Pattern HERTZ_PATTERN = Pattern.compile("(\\d+(.\\d+)?) ?([kKMGT]?Hz).*");
53      private static final Pattern BYTES_PATTERN = Pattern.compile("(\\d+) ?([kKMGT]?B?).*");
54      private static final Pattern UNITS_PATTERN = Pattern.compile("(\\d+(.\\d+)?)[\\s]?([kKMGT])?");
55  
56      /*
57       * Used to check validity of a hexadecimal string
58       */
59      private static final Pattern VALID_HEX = Pattern.compile("[0-9a-fA-F]+");
60  
61      /*
62       * Pattern for [dd-[hh:[mm:[ss[.sss]]]]]
63       */
64      private static final Pattern DHMS = Pattern.compile("(?:(\\d+)-)?(?:(\\d+):)??(?:(\\d+):)?(\\d+)(?:\\.(\\d+))?");
65  
66      /*
67       * Pattern for a UUID
68       */
69      private static final Pattern UUID_PATTERN = Pattern
70              .compile(".*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*");
71  
72      /*
73       * Pattern for Windows DeviceID vendor and product ID and serial
74       */
75      private static final Pattern VENDOR_PRODUCT_ID_SERIAL = Pattern
76              .compile(".*(?:VID|VEN)_(\\p{XDigit}{4})&(?:PID|DEV)_(\\p{XDigit}{4})(.*)\\\\(.*)");
77  
78      /*
79       * Pattern for Linux lspci machine readable
80       */
81      private static final Pattern LSPCI_MACHINE_READABLE = Pattern.compile("(.+)\\s\\[(.*?)\\]");
82  
83      /*
84       * Pattern for Linux lspci memory
85       */
86      private static final Pattern LSPCI_MEMORY_SIZE = Pattern.compile(".+\\s\\[size=(\\d+)([kKMGT])\\]");
87  
88      /*
89       * Hertz related variables.
90       */
91      private static final String HZ = "Hz";
92      private static final String KHZ = "kHz";
93      private static final String MHZ = "MHz";
94      private static final String GHZ = "GHz";
95      private static final String THZ = "THz";
96      private static final String PHZ = "PHz";
97  
98      private static final Map<String, Long> multipliers;
99  
100     // PDH timestamps are 1601 epoch, local time
101     // Constants to convert to UTC millis
102     private static final long EPOCH_DIFF = 11_644_473_600_000L;
103     private static final int TZ_OFFSET = TimeZone.getDefault().getOffset(System.currentTimeMillis());
104 
105     /** Constant <code>whitespacesColonWhitespace</code> */
106     public static final Pattern whitespacesColonWhitespace = Pattern.compile("\\s+:\\s");
107 
108     /** Constant <code>whitespaces</code> */
109     public static final Pattern whitespaces = Pattern.compile("\\s+");
110 
111     /** Constant <code>notDigits</code> */
112     public static final Pattern notDigits = Pattern.compile("[^0-9]+");
113 
114     /** Constant <code>startWithNotDigits</code> */
115     public static final Pattern startWithNotDigits = Pattern.compile("^[^0-9]*");
116 
117     /** Constant <code>forwardSlash</code> */
118     public static final Pattern slash = Pattern.compile("\\/");
119 
120     static {
121         multipliers = new HashMap<>();
122         multipliers.put(HZ, 1L);
123         multipliers.put(KHZ, 1_000L);
124         multipliers.put(MHZ, 1_000_000L);
125         multipliers.put(GHZ, 1_000_000_000L);
126         multipliers.put(THZ, 1_000_000_000_000L);
127         multipliers.put(PHZ, 1_000_000_000_000_000L);
128     }
129 
130     // Fast decimal exponentiation: pow(10,y) --> POWERS_OF_10[y]
131     private static final long[] POWERS_OF_TEN = { 1L, 10L, 100L, 1_000L, 10_000L, 100_000L, 1_000_000L, 10_000_000L,
132             100_000_000L, 1_000_000_000L, 10_000_000_000L, 100_000_000_000L, 1_000_000_000_000L, 10_000_000_000_000L,
133             100_000_000_000_000L, 1_000_000_000_000_000L, 10_000_000_000_000_000L, 100_000_000_000_000_000L,
134             1_000_000_000_000_000_000L };
135 
136     // Format returned by WMI for DateTime
137     private static final DateTimeFormatter CIM_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss.SSSSSSZZZZZ",
138             Locale.US);
139 
140     private ParseUtil() {
141     }
142 
143     /**
144      * Parse hertz from a string, eg. "2.00MHz" is 2000000L.
145      *
146      * @param hertz Hertz size.
147      * @return {@link java.lang.Long} Hertz value or -1 if not parseable.
148      */
149     public static long parseHertz(String hertz) {
150         Matcher matcher = HERTZ_PATTERN.matcher(hertz.trim());
151         if (matcher.find() && matcher.groupCount() == 3) {
152             // Regexp enforces #(.#) format so no test for NFE required
153             double value = Double.valueOf(matcher.group(1)) * multipliers.getOrDefault(matcher.group(3), -1L);
154             if (value >= 0d) {
155                 return (long) value;
156             }
157         }
158         return -1L;
159     }
160 
161     /**
162      * Parse the last element of a space-delimited string to a value
163      *
164      * @param s The string to parse
165      * @param i Default integer if not parsable
166      * @return value or the given default if not parsable
167      */
168     public static int parseLastInt(String s, int i) {
169         try {
170             String ls = parseLastString(s);
171             if (ls.toLowerCase(Locale.ROOT).startsWith("0x")) {
172                 return Integer.decode(ls);
173             } else {
174                 return Integer.parseInt(ls);
175             }
176         } catch (NumberFormatException e) {
177             LOG.trace(DEFAULT_LOG_MSG, s, e);
178             return i;
179         }
180     }
181 
182     /**
183      * Parse the last element of a space-delimited string to a value
184      *
185      * @param s  The string to parse
186      * @param li Default long integer if not parsable
187      * @return value or the given default if not parsable
188      */
189     public static long parseLastLong(String s, long li) {
190         try {
191             String ls = parseLastString(s);
192             if (ls.toLowerCase(Locale.ROOT).startsWith("0x")) {
193                 return Long.decode(ls);
194             } else {
195                 return Long.parseLong(ls);
196             }
197         } catch (NumberFormatException e) {
198             LOG.trace(DEFAULT_LOG_MSG, s, e);
199             return li;
200         }
201     }
202 
203     /**
204      * Parse the last element of a space-delimited string to a value
205      *
206      * @param s The string to parse
207      * @param d Default double if not parsable
208      * @return value or the given default if not parsable
209      */
210     public static double parseLastDouble(String s, double d) {
211         try {
212             return Double.parseDouble(parseLastString(s));
213         } catch (NumberFormatException e) {
214             LOG.trace(DEFAULT_LOG_MSG, s, e);
215             return d;
216         }
217     }
218 
219     /**
220      * Parse the last element of a space-delimited string to a string
221      *
222      * @param s The string to parse
223      * @return last space-delimited element
224      */
225     public static String parseLastString(String s) {
226         String[] ss = whitespaces.split(s);
227         // guaranteed at least one element
228         return ss[ss.length - 1];
229     }
230 
231     /**
232      * Parse a byte array into a string of hexadecimal digits including all array bytes as digits
233      *
234      * @param bytes The byte array to represent
235      * @return A string of hex characters corresponding to the bytes. The string is upper case.
236      */
237     public static String byteArrayToHexString(byte[] bytes) {
238         StringBuilder sb = new StringBuilder(bytes.length * 2);
239         for (byte b : bytes) {
240             sb.append(Character.forDigit((b & 0xf0) >>> 4, 16));
241             sb.append(Character.forDigit(b & 0x0f, 16));
242         }
243         return sb.toString().toUpperCase(Locale.ROOT);
244     }
245 
246     /**
247      * Parse a string of hexadecimal digits into a byte array
248      *
249      * @param digits The string to be parsed
250      * @return a byte array with each pair of characters converted to a byte, or empty array if the string is not valid
251      *         hex
252      */
253     public static byte[] hexStringToByteArray(String digits) {
254         int len = digits.length();
255         // Check if string is valid hex
256         if (!VALID_HEX.matcher(digits).matches() || (len & 0x1) != 0) {
257             LOG.warn("Invalid hexadecimal string: {}", digits);
258             return new byte[0];
259         }
260         byte[] data = new byte[len / 2];
261         for (int i = 0; i < len; i += 2) {
262             data[i / 2] = (byte) (Character.digit(digits.charAt(i), 16) << 4
263                     | Character.digit(digits.charAt(i + 1), 16));
264         }
265         return data;
266     }
267 
268     /**
269      * Parse a human readable ASCII string into a byte array, truncating or padding with zeros (if necessary) so the
270      * array has the specified length.
271      *
272      * @param text   The string to be parsed
273      * @param length Length of the returned array.
274      * @return A byte array of specified length, with each of the first length characters converted to a byte. If length
275      *         is longer than the provided string length, will be filled with zeroes.
276      */
277     public static byte[] asciiStringToByteArray(String text, int length) {
278         return Arrays.copyOf(text.getBytes(StandardCharsets.US_ASCII), length);
279     }
280 
281     /**
282      * Convert a long value to a byte array using Big Endian, truncating or padding with zeros (if necessary) so the
283      * array has the specified length.
284      *
285      * @param value     The value to be converted
286      * @param valueSize Number of bytes representing the value
287      * @param length    Number of bytes to return
288      * @return A byte array of specified length representing the long in the first valueSize bytes
289      */
290     public static byte[] longToByteArray(long value, int valueSize, int length) {
291         long val = value;
292         // Convert the long to 8-byte BE representation
293         byte[] b = new byte[8];
294         for (int i = 7; i >= 0 && val != 0L; i--) {
295             b[i] = (byte) val;
296             val >>>= 8;
297         }
298         // Then copy the rightmost valueSize bytes
299         // e.g., for an integer we want rightmost 4 bytes
300         return Arrays.copyOfRange(b, 8 - valueSize, 8 + length - valueSize);
301     }
302 
303     /**
304      * Convert a string to an integer representation.
305      *
306      * @param str  A human readable ASCII string
307      * @param size Number of characters to convert to the long. May not exceed 8.
308      * @return An integer representing the string where each character is treated as a byte
309      */
310     public static long strToLong(String str, int size) {
311         return byteArrayToLong(str.getBytes(StandardCharsets.US_ASCII), size);
312     }
313 
314     /**
315      * Convert a byte array to its (long) integer representation assuming big endian ordering.
316      *
317      * @param bytes An array of bytes no smaller than the size to be converted
318      * @param size  Number of bytes to convert to the long. May not exceed 8.
319      * @return A long integer representing the byte array
320      */
321     public static long byteArrayToLong(byte[] bytes, int size) {
322         return byteArrayToLong(bytes, size, true);
323     }
324 
325     /**
326      * Convert a byte array to its (long) integer representation in the specified endianness.
327      *
328      * @param bytes     An array of bytes no smaller than the size to be converted
329      * @param size      Number of bytes to convert to the long. May not exceed 8.
330      * @param bigEndian True to parse big-endian, false to parse little-endian
331      * @return An long integer representing the byte array
332      */
333     public static long byteArrayToLong(byte[] bytes, int size, boolean bigEndian) {
334         if (size > 8) {
335             throw new IllegalArgumentException("Can't convert more than 8 bytes.");
336         }
337         if (size > bytes.length) {
338             throw new IllegalArgumentException("Size can't be larger than array length.");
339         }
340         long total = 0L;
341         for (int i = 0; i < size; i++) {
342             if (bigEndian) {
343                 total = total << 8 | bytes[i] & 0xff;
344             } else {
345                 total = total << 8 | bytes[size - i - 1] & 0xff;
346             }
347         }
348         return total;
349     }
350 
351     /**
352      * Convert a byte array to its floating point representation.
353      *
354      * @param bytes  An array of bytes no smaller than the size to be converted
355      * @param size   Number of bytes to convert to the float. May not exceed 8.
356      * @param fpBits Number of bits representing the decimal
357      * @return A float; the integer portion representing the byte array as an integer shifted by the bits specified in
358      *         fpBits; with the remaining bits used as a decimal
359      */
360     public static float byteArrayToFloat(byte[] bytes, int size, int fpBits) {
361         return byteArrayToLong(bytes, size) / (float) (1 << fpBits);
362     }
363 
364     /**
365      * Convert an unsigned integer to a long value. The method assumes that all bits in the specified integer value are
366      * 'data' bits, including the most-significant bit which Java normally considers a sign bit. The method must be used
367      * only when it is certain that the integer value represents an unsigned integer, for example when the integer is
368      * returned by JNA library in a structure which holds unsigned integers.
369      *
370      * @param unsignedValue The unsigned integer value to convert.
371      * @return The unsigned integer value widened to a long.
372      */
373     public static long unsignedIntToLong(int unsignedValue) {
374         // use standard Java widening conversion to long which does
375         // sign-extension,
376         // then drop any copies of the sign bit, to prevent the value being
377         // considered a negative one by Java if it is set
378         long longValue = unsignedValue;
379         return longValue & 0xffff_ffffL;
380     }
381 
382     /**
383      * Convert an unsigned long to a signed long value by stripping the sign bit. This method "rolls over" long values
384      * greater than the max value but ensures the result is never negative.
385      *
386      * @param unsignedValue The unsigned long value to convert.
387      * @return The signed long value.
388      */
389     public static long unsignedLongToSignedLong(long unsignedValue) {
390         return unsignedValue & 0x7fff_ffff_ffff_ffffL;
391     }
392 
393     /**
394      * Parses a string of hex digits to a string where each pair of hex digits represents an ASCII character
395      *
396      * @param hexString A sequence of hex digits
397      * @return The corresponding string if valid hex; otherwise the original hexString
398      */
399     public static String hexStringToString(String hexString) {
400         // Odd length strings won't parse, return
401         if (hexString.length() % 2 > 0) {
402             return hexString;
403         }
404         int charAsInt;
405         StringBuilder sb = new StringBuilder();
406         try {
407             for (int pos = 0; pos < hexString.length(); pos += 2) {
408                 charAsInt = Integer.parseInt(hexString.substring(pos, pos + 2), 16);
409                 if (charAsInt < 32 || charAsInt > 127) {
410                     return hexString;
411                 }
412                 sb.append((char) charAsInt);
413             }
414         } catch (NumberFormatException e) {
415             LOG.trace(DEFAULT_LOG_MSG, hexString, e);
416             // Hex failed to parse, just return the existing string
417             return hexString;
418         }
419         return sb.toString();
420     }
421 
422     /**
423      * Attempts to parse a string to an int. If it fails, returns the default
424      *
425      * @param s          The string to parse
426      * @param defaultInt The value to return if parsing fails
427      * @return The parsed int, or the default if parsing failed
428      */
429     public static int parseIntOrDefault(String s, int defaultInt) {
430         try {
431             return Integer.parseInt(s);
432         } catch (NumberFormatException e) {
433             LOG.trace(DEFAULT_LOG_MSG, s, e);
434             return defaultInt;
435         }
436     }
437 
438     /**
439      * Attempts to parse a string to a long. If it fails, returns the default
440      *
441      * @param s           The string to parse
442      * @param defaultLong The value to return if parsing fails
443      * @return The parsed long, or the default if parsing failed
444      */
445     public static long parseLongOrDefault(String s, long defaultLong) {
446         try {
447             return Long.parseLong(s);
448         } catch (NumberFormatException e) {
449             LOG.trace(DEFAULT_LOG_MSG, s, e);
450             return defaultLong;
451         }
452     }
453 
454     /**
455      * Attempts to parse a string to an "unsigned" long. If it fails, returns the default
456      *
457      * @param s           The string to parse
458      * @param defaultLong The value to return if parsing fails
459      * @return The parsed long containing the same 64 bits that an unsigned long would contain (which may produce a
460      *         negative value)
461      */
462     public static long parseUnsignedLongOrDefault(String s, long defaultLong) {
463         try {
464             return new BigInteger(s).longValue();
465         } catch (NumberFormatException e) {
466             LOG.trace(DEFAULT_LOG_MSG, s, e);
467             return defaultLong;
468         }
469     }
470 
471     /**
472      * Attempts to parse a string to a double. If it fails, returns the default
473      *
474      * @param s             The string to parse
475      * @param defaultDouble The value to return if parsing fails
476      * @return The parsed double, or the default if parsing failed
477      */
478     public static double parseDoubleOrDefault(String s, double defaultDouble) {
479         try {
480             return Double.parseDouble(s);
481         } catch (NumberFormatException e) {
482             LOG.trace(DEFAULT_LOG_MSG, s, e);
483             return defaultDouble;
484         }
485     }
486 
487     /**
488      * Attempts to parse a string of the form [DD-[hh:]]mm:ss[.ddd] to a number of milliseconds. If it fails, returns
489      * the default.
490      *
491      * @param s           The string to parse
492      * @param defaultLong The value to return if parsing fails
493      * @return The parsed number of seconds, or the default if parsing fails
494      */
495     public static long parseDHMSOrDefault(String s, long defaultLong) {
496         Matcher m = DHMS.matcher(s);
497         if (m.matches()) {
498             long milliseconds = 0L;
499             if (m.group(1) != null) {
500                 milliseconds += parseLongOrDefault(m.group(1), 0L) * 86_400_000L;
501             }
502             if (m.group(2) != null) {
503                 milliseconds += parseLongOrDefault(m.group(2), 0L) * 3_600_000L;
504             }
505             if (m.group(3) != null) {
506                 milliseconds += parseLongOrDefault(m.group(3), 0L) * 60_000L;
507             }
508             milliseconds += parseLongOrDefault(m.group(4), 0L) * 1000L;
509             if (m.group(5) != null) {
510                 milliseconds += (long) (1000 * parseDoubleOrDefault("0." + m.group(5), 0d));
511             }
512             return milliseconds;
513         }
514         return defaultLong;
515     }
516 
517     /**
518      * Attempts to parse a UUID. If it fails, returns the default.
519      *
520      * @param s          The string to parse
521      * @param defaultStr The value to return if parsing fails
522      * @return The parsed UUID, or the default if parsing fails
523      */
524     public static String parseUuidOrDefault(String s, String defaultStr) {
525         Matcher m = UUID_PATTERN.matcher(s.toLowerCase(Locale.ROOT));
526         if (m.matches()) {
527             return m.group(1);
528         }
529         return defaultStr;
530     }
531 
532     /**
533      * Parses a string key = 'value' (string)
534      *
535      * @param line The entire string
536      * @return the value contained between single tick marks
537      */
538     public static String getSingleQuoteStringValue(String line) {
539         return getStringBetween(line, '\'');
540     }
541 
542     /**
543      * Parse a string key = "value" (string)
544      *
545      * @param line the entire string
546      * @return the value contained between double tick marks
547      */
548     public static String getDoubleQuoteStringValue(String line) {
549         return getStringBetween(line, '"');
550     }
551 
552     /**
553      * Gets a value between two characters having multiple same characters between them. <b>Examples : </b>
554      * <ul>
555      * <li>"name = 'James Gosling's Java'" returns "James Gosling's Java"</li>
556      * <li>"pci.name = 'Realtek AC'97 Audio Device'" returns "Realtek AC'97 Audio Device"</li>
557      * </ul>
558      *
559      * @param line The "key-value" pair line.
560      * @param c    The Trailing And Leading characters of the string line
561      * @return : The value having the characters between them.
562      */
563     public static String getStringBetween(String line, char c) {
564         int firstOcc = line.indexOf(c);
565         if (firstOcc < 0) {
566             return "";
567         }
568         return line.substring(firstOcc + 1, line.lastIndexOf(c)).trim();
569     }
570 
571     /**
572      * Parses a string such as "10.12.2" or "key = 1 (0x1) (int)" to find the integer value of the first set of one or
573      * more consecutive digits
574      *
575      * @param line The entire string
576      * @return the value of first integer if any; 0 otherwise
577      */
578     public static int getFirstIntValue(String line) {
579         return getNthIntValue(line, 1);
580     }
581 
582     /**
583      * Parses a string such as "10.12.2" or "key = 1 (0x1) (int)" to find the integer value of the nth set of one or
584      * more consecutive digits
585      *
586      * @param line The entire string
587      * @param n    Which set of integers to return
588      * @return the value of nth integer if any; 0 otherwise
589      */
590     public static int getNthIntValue(String line, int n) {
591         // Split the string by non-digits,
592         String[] split = notDigits.split(startWithNotDigits.matcher(line).replaceFirst(""));
593         if (split.length >= n) {
594             return parseIntOrDefault(split[n - 1], 0);
595         }
596         return 0;
597     }
598 
599     /**
600      * Removes all matching sub strings from the string. More efficient than regexp.
601      *
602      * @param original source String to remove from
603      * @param toRemove the sub string to be removed
604      * @return The string with all matching substrings removed
605      */
606     public static String removeMatchingString(final String original, final String toRemove) {
607         if (original == null || original.isEmpty() || toRemove == null || toRemove.isEmpty()) {
608             return original;
609         }
610 
611         int matchIndex = original.indexOf(toRemove, 0);
612         if (matchIndex == -1) {
613             return original;
614         }
615 
616         StringBuilder buffer = new StringBuilder(original.length() - toRemove.length());
617         int currIndex = 0;
618         do {
619             buffer.append(original.substring(currIndex, matchIndex));
620             currIndex = matchIndex + toRemove.length();
621             matchIndex = original.indexOf(toRemove, currIndex);
622         } while (matchIndex != -1);
623 
624         buffer.append(original.substring(currIndex));
625         return buffer.toString();
626     }
627 
628     /**
629      * Parses a delimited string to an array of longs. Optimized for processing predictable-length arrays such as
630      * outputs of reliably formatted Linux proc or sys filesystem, minimizing new object creation. Users should perform
631      * other sanity checks of data.
632      *
633      * As a special case, non-numeric fields (such as UUIDs in OpenVZ) at the end of the list are ignored. Values
634      * greater than the max long value return the max long value.
635      *
636      * The indices parameters are referenced assuming the length as specified, and leading characters are ignored. For
637      * example, if the string is "foo 12 34 5" and the length is 3, then index 0 is 12, index 1 is 34, and index 2 is 5.
638      *
639      * @param s         The string to parse
640      * @param indices   An array indicating which indexes should be populated in the final array; other values will be
641      *                  skipped. This idex is zero-referenced assuming the rightmost delimited fields of the string
642      *                  contain the array.
643      * @param length    The total number of elements in the string array. It is permissible for the string to have more
644      *                  elements than this; leading elements will be ignored. This should be calculated once per text
645      *                  format by {@link #countStringToLongArray}.
646      * @param delimiter The character to delimit by.
647      * @return If successful, an array of parsed longs. If parsing errors occurred, will be an array of zeros.
648      */
649     public static long[] parseStringToLongArray(String s, int[] indices, int length, char delimiter) {
650         // Ensure that the last character is a number
651         s = s.trim();
652 
653         long[] parsed = new long[indices.length];
654         // Iterate from right-to-left of String
655         // Fill right to left of result array using index array
656         int charIndex = s.length();
657         int parsedIndex = indices.length - 1;
658         int stringIndex = length - 1;
659 
660         int power = 0;
661         int c;
662         boolean delimCurrent = false;
663         boolean numeric = true;
664         boolean numberFound = false; // ignore nonnumeric at end
665         boolean dashSeen = false; // to flag uuids as nonnumeric
666         while (--charIndex >= 0 && parsedIndex >= 0) {
667             c = s.charAt(charIndex);
668             if (c == delimiter) {
669                 // first parseable number?
670                 if (!numberFound && numeric) {
671                     numberFound = true;
672                 }
673                 if (!delimCurrent) {
674                     if (numberFound && indices[parsedIndex] == stringIndex--) {
675                         parsedIndex--;
676                     }
677                     delimCurrent = true;
678                     power = 0;
679                     dashSeen = false;
680                     numeric = true;
681                 }
682             } else if (indices[parsedIndex] != stringIndex || c == '+' || !numeric) {
683                 // Doesn't impact parsing, ignore
684                 delimCurrent = false;
685             } else if (c >= '0' && c <= '9' && !dashSeen) {
686                 if (power > 18 || power == 17 && c == '9' && parsed[parsedIndex] > 223_372_036_854_775_807L) {
687                     parsed[parsedIndex] = Long.MAX_VALUE;
688                 } else {
689                     parsed[parsedIndex] += (c - '0') * ParseUtil.POWERS_OF_TEN[power++];
690                 }
691                 delimCurrent = false;
692             } else if (c == '-') {
693                 parsed[parsedIndex] *= -1L;
694                 delimCurrent = false;
695                 dashSeen = true;
696             } else {
697                 // Flag as nonnumeric and continue unless we've seen a numeric
698                 // error on everything else
699                 if (numberFound) {
700                     if (!noLog(s)) {
701                         LOG.error("Illegal character parsing string '{}' to long array: {}", s, s.charAt(charIndex));
702                     }
703                     return new long[indices.length];
704                 }
705                 parsed[parsedIndex] = 0;
706                 numeric = false;
707             }
708         }
709         if (parsedIndex > 0) {
710             if (!noLog(s)) {
711                 LOG.error("Not enough fields in string '{}' parsing to long array: {}", s,
712                         indices.length - parsedIndex);
713             }
714             return new long[indices.length];
715         }
716         return parsed;
717     }
718 
719     /**
720      * Test whether to log this message
721      *
722      * @param s The string to log
723      * @return True if the string begins with {@code NOLOG}
724      */
725     private static boolean noLog(String s) {
726         return s.startsWith("NOLOG: ");
727     }
728 
729     /**
730      * Parses a delimited string to count elements of an array of longs. Intended to be called once to calculate the
731      * {@code length} field for {@link #parseStringToLongArray}.
732      *
733      * As a special case, non-numeric fields (such as UUIDs in OpenVZ) at the end of the list are ignored.
734      *
735      * @param s         The string to parse
736      * @param delimiter The character to delimit by
737      * @return The number of parsable long values which follow the last unparsable value.
738      */
739     public static int countStringToLongArray(String s, char delimiter) {
740         // Ensure that the last character is a number
741         s = s.trim();
742 
743         // Iterate from right-to-left of String
744         // Fill right to left of result array using index array
745         int charIndex = s.length();
746         int numbers = 0;
747 
748         int c;
749         boolean delimCurrent = false;
750         boolean numeric = true;
751         boolean dashSeen = false; // to flag uuids as nonnumeric
752         while (--charIndex >= 0) {
753             c = s.charAt(charIndex);
754             if (c == delimiter) {
755                 if (!delimCurrent) {
756                     if (numeric) {
757                         numbers++;
758                     }
759                     delimCurrent = true;
760                     dashSeen = false;
761                     numeric = true;
762                 }
763             } else if (c == '+' || !numeric) {
764                 // Doesn't impact parsing, ignore
765                 delimCurrent = false;
766             } else if (c >= '0' && c <= '9' && !dashSeen) {
767                 delimCurrent = false;
768             } else if (c == '-') {
769                 delimCurrent = false;
770                 dashSeen = true;
771             } else {
772                 // we found non-digit or delimiter. If not last field, exit
773                 if (numbers > 0) {
774                     return numbers;
775                 }
776                 // Else flag as nonnumeric and continue
777                 numeric = false;
778             }
779         }
780         // We got to beginning of string with only numbers, count start as a delimiter
781         // and exit
782         return numbers + 1;
783     }
784 
785     /**
786      * Get a String in a line of text between two marker strings
787      *
788      * @param text   Text to search for match
789      * @param before Start matching after this text
790      * @param after  End matching before this text
791      * @return Text between the strings before and after, or empty string if either marker does not exist
792      */
793     public static String getTextBetweenStrings(String text, String before, String after) {
794 
795         String result = "";
796 
797         if (text.indexOf(before) >= 0 && text.indexOf(after) >= 0) {
798             result = text.substring(text.indexOf(before) + before.length(), text.length());
799             result = result.substring(0, result.indexOf(after));
800         }
801         return result;
802     }
803 
804     /**
805      * Convert a long representing filetime (100-ns since 1601 epoch) to ms since 1970 epoch
806      *
807      * @param filetime A 64-bit value equivalent to FILETIME
808      * @param local    True if converting from a local filetime (PDH counter); false if already UTC (WMI PerfRawData
809      *                 classes)
810      * @return Equivalent milliseconds since the epoch
811      */
812     public static long filetimeToUtcMs(long filetime, boolean local) {
813         return filetime / 10_000L - EPOCH_DIFF - (local ? TZ_OFFSET : 0L);
814     }
815 
816     /**
817      * Parse a date in MM-DD-YYYY or MM/DD/YYYY to YYYY-MM-DD
818      *
819      * @param dateString The date in MM DD YYYY format
820      * @return The date in ISO YYYY-MM-DD format if parseable, or the original string
821      */
822     public static String parseMmDdYyyyToYyyyMmDD(String dateString) {
823         try {
824             // Date is MM-DD-YYYY, convert to YYYY-MM-DD
825             return String.format(Locale.ROOT, "%s-%s-%s", dateString.substring(6, 10), dateString.substring(0, 2),
826                     dateString.substring(3, 5));
827         } catch (StringIndexOutOfBoundsException e) {
828             return dateString;
829         }
830     }
831 
832     /**
833      * Converts a string in CIM Date Format, as returned by WMI for DateTime types, into a
834      * {@link java.time.OffsetDateTime}.
835      *
836      * @param cimDateTime A non-null DateTime String in CIM date format, e.g., <code>20160513072950.782000-420</code>
837      * @return The parsed {@link java.time.OffsetDateTime} if the string is parsable, otherwise
838      *         {@link oshi.util.Constants#UNIX_EPOCH}.
839      */
840     public static OffsetDateTime parseCimDateTimeToOffset(String cimDateTime) {
841         // Keep first 22 characters: digits, decimal, and + or - sign
842         // But alter last 3 characters from a minute offset to hh:mm
843         try {
844             // From WMI as 20160513072950.782000-420,
845             int tzInMinutes = Integer.parseInt(cimDateTime.substring(22));
846             // modified to 20160513072950.782000-07:00 which can be parsed
847             LocalTime offsetAsLocalTime = LocalTime.MIDNIGHT.plusMinutes(tzInMinutes);
848             return OffsetDateTime.parse(
849                     cimDateTime.substring(0, 22) + offsetAsLocalTime.format(DateTimeFormatter.ISO_LOCAL_TIME),
850                     ParseUtil.CIM_FORMAT);
851         } catch (IndexOutOfBoundsException // if cimDate not 22+ chars
852                 | NumberFormatException // if TZ minutes doesn't parse
853                 | DateTimeParseException e) {
854             LOG.trace("Unable to parse {} to CIM DateTime.", cimDateTime);
855             return Constants.UNIX_EPOCH;
856         }
857     }
858 
859     /**
860      * Checks if a file path equals or starts with an prefix in the given list
861      *
862      * @param prefixList A list of path prefixes
863      * @param path       a string path to check
864      * @return true if the path exactly equals, or starts with one of the strings in prefixList
865      */
866     public static boolean filePathStartsWith(List<String> prefixList, String path) {
867         for (String match : prefixList) {
868             if (path.equals(match) || path.startsWith(match + "/")) {
869                 return true;
870             }
871         }
872         return false;
873     }
874 
875     /**
876      * Parses a string like "53G" or "54.904 M" to its long value.
877      *
878      * @param count A count with a multiplyer like "4096 M"
879      * @return the count parsed to a long
880      */
881     public static long parseMultipliedToLongs(String count) {
882         Matcher matcher = UNITS_PATTERN.matcher(count.trim());
883         String[] mem;
884         if (matcher.find() && matcher.groupCount() == 3) {
885             mem = new String[2];
886             mem[0] = matcher.group(1);
887             mem[1] = matcher.group(3);
888         } else {
889             mem = new String[] { count };
890         }
891 
892         double number = ParseUtil.parseDoubleOrDefault(mem[0], 0L);
893         if (mem.length == 2 && mem[1] != null && mem[1].length() >= 1) {
894             switch (mem[1].charAt(0)) {
895             case 'T':
896                 number *= 1_000_000_000_000L;
897                 break;
898             case 'G':
899                 number *= 1_000_000_000L;
900                 break;
901             case 'M':
902                 number *= 1_000_000L;
903                 break;
904             case 'K':
905             case 'k':
906                 number *= 1_000L;
907                 break;
908             default:
909             }
910         }
911         return (long) number;
912     }
913 
914     /**
915      * Parses a string such as "4096 MB" to its long. Used to parse macOS and *nix memory chip sizes. Although the units
916      * given are decimal they must parse to binary units.
917      *
918      * @param size A string of memory sizes like "4096 MB"
919      * @return the size parsed to a long
920      */
921     public static long parseDecimalMemorySizeToBinary(String size) {
922         String[] mem = ParseUtil.whitespaces.split(size);
923         if (mem.length < 2) {
924             // If no spaces, use regexp
925             Matcher matcher = BYTES_PATTERN.matcher(size.trim());
926             if (matcher.find() && matcher.groupCount() == 2) {
927                 mem = new String[2];
928                 mem[0] = matcher.group(1);
929                 mem[1] = matcher.group(2);
930             }
931         }
932         long capacity = ParseUtil.parseLongOrDefault(mem[0], 0L);
933         if (mem.length == 2 && mem[1].length() > 1) {
934             switch (mem[1].charAt(0)) {
935             case 'T':
936                 capacity <<= 40;
937                 break;
938             case 'G':
939                 capacity <<= 30;
940                 break;
941             case 'M':
942                 capacity <<= 20;
943                 break;
944             case 'K':
945             case 'k':
946                 capacity <<= 10;
947                 break;
948             default:
949                 break;
950             }
951         }
952         return capacity;
953     }
954 
955     /**
956      * Parse a Windows DeviceID to get the vendor ID, product ID, and Serial Number
957      *
958      * @param deviceId The DeviceID
959      * @return A {@link Triplet} where the first element is the vendor ID, the second element is the product ID, and the
960      *         third element is either a serial number or empty string if parsing was successful, or {@code null}
961      *         otherwise
962      */
963     public static Triplet<String, String, String> parseDeviceIdToVendorProductSerial(String deviceId) {
964         Matcher m = VENDOR_PRODUCT_ID_SERIAL.matcher(deviceId);
965         if (m.matches()) {
966             String vendorId = "0x" + m.group(1).toLowerCase(Locale.ROOT);
967             String productId = "0x" + m.group(2).toLowerCase(Locale.ROOT);
968             String serial = m.group(4);
969             return new Triplet<>(vendorId, productId, !m.group(3).isEmpty() || serial.contains("&") ? "" : serial);
970         }
971         return null;
972     }
973 
974     /**
975      * Parse a Linux lshw resources string to calculate the memory size
976      *
977      * @param resources A string containing one or more elements of the form {@code memory:b00000000-bffffffff}
978      * @return The number of bytes consumed by the memory in the {@code resources} string
979      */
980     public static long parseLshwResourceString(String resources) {
981         long bytes = 0L;
982         // First split by whitespace
983         String[] resourceArray = whitespaces.split(resources);
984         for (String r : resourceArray) {
985             // Remove prefix
986             if (r.startsWith("memory:")) {
987                 // Split to low and high
988                 String[] mem = r.substring(7).split("-");
989                 if (mem.length == 2) {
990                     try {
991                         // Parse the hex strings
992                         bytes += Long.parseLong(mem[1], 16) - Long.parseLong(mem[0], 16) + 1;
993                     } catch (NumberFormatException e) {
994                         LOG.trace(DEFAULT_LOG_MSG, r, e);
995                     }
996                 }
997             }
998         }
999         return bytes;
1000     }
1001 
1002     /**
1003      * Parse a Linux lspci machine readble line to its name and id
1004      *
1005      * @param line A string in the form Foo [bar]
1006      * @return A pair separating the String before the square brackets and within them if found, null otherwise
1007      */
1008     public static Pair<String, String> parseLspciMachineReadable(String line) {
1009         Matcher matcher = LSPCI_MACHINE_READABLE.matcher(line);
1010         if (matcher.matches()) {
1011             return new Pair<>(matcher.group(1), matcher.group(2));
1012         }
1013         return null;
1014     }
1015 
1016     /**
1017      * Parse a Linux lspci line containing memory size
1018      *
1019      * @param line A string in the form Foo [size=256M]
1020      * @return A the memory size in bytes
1021      */
1022     public static long parseLspciMemorySize(String line) {
1023         Matcher matcher = LSPCI_MEMORY_SIZE.matcher(line);
1024         if (matcher.matches()) {
1025             return parseDecimalMemorySizeToBinary(matcher.group(1) + " " + matcher.group(2) + "B");
1026         }
1027         return 0;
1028     }
1029 
1030     /**
1031      * Parse a space-delimited list of integers which include hyphenated ranges to a list of just the integers. For
1032      * example, 0 1 4-7 parses to a list containing 0, 1, 4, 5, 6, and 7. Also support comma separated entries like 0,
1033      * 2-5, 7-8, 9 to a list containing 0, 2, 3, 4, 5, 7, 8, 9.
1034      *
1035      * @param str A string containing space-delimited integers or ranges of integers with a hyphen
1036      * @return A list of integers representing the provided range(s).
1037      */
1038     public static List<Integer> parseHyphenatedIntList(String str) {
1039         List<Integer> result = new ArrayList<>();
1040         String[] csvTokens = str.split(",");
1041         for (String csvToken : csvTokens) {
1042             csvToken = csvToken.trim();
1043             for (String s : whitespaces.split(csvToken)) {
1044                 if (s.contains("-")) {
1045                     int first = getFirstIntValue(s);
1046                     int last = getNthIntValue(s, 2);
1047                     for (int i = first; i <= last; i++) {
1048                         result.add(i);
1049                     }
1050                 } else {
1051                     int only = ParseUtil.parseIntOrDefault(s, -1);
1052                     if (only >= 0) {
1053                         result.add(only);
1054                     }
1055                 }
1056             }
1057         }
1058         return result;
1059     }
1060 
1061     /**
1062      * Parse an integer in big endian IP format to its component bytes representing an IPv4 address
1063      *
1064      * @param ip The address as an integer
1065      * @return The address as an array of four bytes
1066      */
1067     public static byte[] parseIntToIP(int ip) {
1068         return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ip).array();
1069     }
1070 
1071     /**
1072      * Parse an integer array in big endian IP format to its component bytes representing an IPv6 address
1073      *
1074      * @param ip6 The address as an integer array
1075      * @return The address as an array of sizteen bytes
1076      */
1077     public static byte[] parseIntArrayToIP(int[] ip6) {
1078         ByteBuffer bb = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN);
1079         for (int i : ip6) {
1080             bb.putInt(i);
1081         }
1082         return bb.array();
1083     }
1084 
1085     /**
1086      * TCP network addresses and ports are in big endian format by definition. The order of the two bytes in the 16-bit
1087      * unsigned short port value must be reversed
1088      *
1089      * @param port The port number in big endian order
1090      * @return The port number
1091      * @see <a href= "https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-ntohs">ntohs</a>
1092      */
1093     public static int bigEndian16ToLittleEndian(int port) {
1094         // 20480 = 0x5000 should be 0x0050 = 80
1095         // 47873 = 0xBB01 should be 0x01BB = 443
1096         return port >> 8 & 0xff | port << 8 & 0xff00;
1097     }
1098 
1099     /**
1100      * Parse an integer array to an IPv4 or IPv6 as appropriate.
1101      * <p>
1102      * Intended for use on Utmp structures's {@code ut_addr_v6} element.
1103      *
1104      * @param utAddrV6 An array of 4 integers representing an IPv6 address. IPv4 address uses just utAddrV6[0]
1105      * @return A string representation of the IP address.
1106      */
1107     public static String parseUtAddrV6toIP(int[] utAddrV6) {
1108         if (utAddrV6.length != 4) {
1109             throw new IllegalArgumentException("ut_addr_v6 must have exactly 4 elements");
1110         }
1111         // IPv4 has only first element
1112         if (utAddrV6[1] == 0 && utAddrV6[2] == 0 && utAddrV6[3] == 0) {
1113             // Special case for all 0's
1114             if (utAddrV6[0] == 0) {
1115                 return "::";
1116             }
1117             // Parse using InetAddress
1118             byte[] ipv4 = ByteBuffer.allocate(4).putInt(utAddrV6[0]).array();
1119             try {
1120                 return InetAddress.getByAddress(ipv4).getHostAddress();
1121             } catch (UnknownHostException e) {
1122                 // Shouldn't happen with length 4 or 16
1123                 return Constants.UNKNOWN;
1124             }
1125         }
1126         // Parse all 16 bytes
1127         byte[] ipv6 = ByteBuffer.allocate(16).putInt(utAddrV6[0]).putInt(utAddrV6[1]).putInt(utAddrV6[2])
1128                 .putInt(utAddrV6[3]).array();
1129         try {
1130             return InetAddress.getByAddress(ipv6).getHostAddress()
1131                     .replaceAll("((?:(?:^|:)0+\\b){2,8}):?(?!\\S*\\b\\1:0+\\b)(\\S*)", "::$2");
1132         } catch (UnknownHostException e) {
1133             // Shouldn't happen with length 4 or 16
1134             return Constants.UNKNOWN;
1135         }
1136     }
1137 
1138     /**
1139      * Parses a string of hex digits to an int value.
1140      *
1141      * @param hexString    A sequence of hex digits
1142      * @param defaultValue default value to return if parsefails
1143      * @return The corresponding int value
1144      */
1145     public static int hexStringToInt(String hexString, int defaultValue) {
1146         if (hexString != null) {
1147             try {
1148                 if (hexString.startsWith("0x")) {
1149                     return new BigInteger(hexString.substring(2), 16).intValue();
1150                 } else {
1151                     return new BigInteger(hexString, 16).intValue();
1152                 }
1153             } catch (NumberFormatException e) {
1154                 LOG.trace(DEFAULT_LOG_MSG, hexString, e);
1155             }
1156         }
1157         // Hex failed to parse, just return the default long
1158         return defaultValue;
1159     }
1160 
1161     /**
1162      * Parses a string of hex digits to a long value.
1163      *
1164      * @param hexString    A sequence of hex digits
1165      * @param defaultValue default value to return if parsefails
1166      * @return The corresponding long value
1167      */
1168     public static long hexStringToLong(String hexString, long defaultValue) {
1169         if (hexString != null) {
1170             try {
1171                 if (hexString.startsWith("0x")) {
1172                     return new BigInteger(hexString.substring(2), 16).longValue();
1173                 } else {
1174                     return new BigInteger(hexString, 16).longValue();
1175                 }
1176             } catch (NumberFormatException e) {
1177                 LOG.trace(DEFAULT_LOG_MSG, hexString, e);
1178             }
1179         }
1180         // Hex failed to parse, just return the default long
1181         return defaultValue;
1182     }
1183 
1184     /**
1185      * Parses a String "....foo" to "foo"
1186      *
1187      * @param dotPrefixedStr A string with possibly leading dots
1188      * @return The string without the dots
1189      */
1190     public static String removeLeadingDots(String dotPrefixedStr) {
1191         int pos = 0;
1192         while (pos < dotPrefixedStr.length() && dotPrefixedStr.charAt(pos) == '.') {
1193             pos++;
1194         }
1195         return pos < dotPrefixedStr.length() ? dotPrefixedStr.substring(pos) : "";
1196     }
1197 
1198     /**
1199      * Parse a null-delimited byte array to a list of strings.
1200      *
1201      * @param bytes A byte array containing Strings delimited by null characters. Two consecutive null characters mark
1202      *              the end of the list.
1203      * @return A list of Strings between the nulls.
1204      */
1205     public static List<String> parseByteArrayToStrings(byte[] bytes) {
1206         List<String> strList = new ArrayList<>();
1207         int start = 0;
1208         int end = 0;
1209         // Iterate characters
1210         do {
1211             // If we've reached a delimiter or the end of the array, add to list
1212             if (end == bytes.length || bytes[end] == 0) {
1213                 // Zero length string means two nulls, we're done
1214                 if (start == end) {
1215                     break;
1216                 }
1217                 // Otherwise add string and reset start
1218                 // Intentionally using platform default charset
1219                 strList.add(new String(bytes, start, end - start, StandardCharsets.UTF_8));
1220                 start = end + 1;
1221             }
1222         } while (end++ < bytes.length);
1223         return strList;
1224     }
1225 
1226     /**
1227      * Parse a null-delimited byte array to a map of string keys and values.
1228      *
1229      * @param bytes A byte array containing String key-value pairs with keys and values delimited by {@code =} and pairs
1230      *              delimited by null characters. Two consecutive null characters mark the end of the map.
1231      * @return A map of String key-value pairs between the nulls.
1232      */
1233     public static Map<String, String> parseByteArrayToStringMap(byte[] bytes) {
1234         // API does not specify any particular order of entries, but it is reasonable to
1235         // maintain whatever order the OS provided to the end user
1236         Map<String, String> strMap = new LinkedHashMap<>();
1237         int start = 0;
1238         int end = 0;
1239         String key = null;
1240         // Iterate characters
1241         do {
1242             // If we've reached a delimiter or the end of the array, add to list
1243             if (end == bytes.length || bytes[end] == 0) {
1244                 // Zero length string with no key, we're done
1245                 if (start == end && key == null) {
1246                     break;
1247                 }
1248                 // Otherwise add string (possibly empty) and reset start
1249                 // Intentionally using platform default charset
1250                 strMap.put(key, new String(bytes, start, end - start, StandardCharsets.UTF_8));
1251                 key = null;
1252                 start = end + 1;
1253             } else if (bytes[end] == '=' && key == null) {
1254                 key = new String(bytes, start, end - start, StandardCharsets.UTF_8);
1255                 start = end + 1;
1256             }
1257         } while (end++ < bytes.length);
1258         return strMap;
1259     }
1260 
1261     /**
1262      * Parse a null-delimited char array to a map of string keys and values.
1263      *
1264      * @param chars A char array containing String key-value pairs with keys and values delimited by {@code =} and pairs
1265      *              delimited by null characters. Two consecutive null characters mark the end of the map.
1266      * @return A map of String key-value pairs between the nulls.
1267      */
1268     public static Map<String, String> parseCharArrayToStringMap(char[] chars) {
1269         // API does not specify any particular order of entries, but it is reasonable to
1270         // maintain whatever order the OS provided to the end user
1271         Map<String, String> strMap = new LinkedHashMap<>();
1272         int start = 0;
1273         int end = 0;
1274         String key = null;
1275         // Iterate characters
1276         do {
1277             // If we've reached a delimiter or the end of the array, add to list
1278             if (end == chars.length || chars[end] == 0) {
1279                 // Zero length string with no key, we're done
1280                 if (start == end && key == null) {
1281                     break;
1282                 }
1283                 // Otherwise add string (possibly empty) and reset start
1284                 // Intentionally using platform default charset
1285                 strMap.put(key, new String(chars, start, end - start));
1286                 key = null;
1287                 start = end + 1;
1288             } else if (chars[end] == '=' && key == null) {
1289                 key = new String(chars, start, end - start);
1290                 start = end + 1;
1291             }
1292         } while (end++ < chars.length);
1293         return strMap;
1294     }
1295 
1296     /**
1297      * Parses a delimited String into an enum map. Multiple consecutive delimiters are treated as one.
1298      *
1299      * @param <K>    a type extending Enum
1300      * @param clazz  The enum class
1301      * @param values A delimited String to be parsed into the map
1302      * @param delim  the delimiter to use
1303      * @return An EnumMap populated in order using the delimited String values. If there are fewer String values than
1304      *         enum values, the later enum values are not mapped. The final enum value will contain the remainder of the
1305      *         String, including excess delimiters.
1306      */
1307     public static <K extends Enum<K>> Map<K, String> stringToEnumMap(Class<K> clazz, String values, char delim) {
1308         EnumMap<K, String> map = new EnumMap<>(clazz);
1309         int start = 0;
1310         int len = values.length();
1311         EnumSet<K> keys = EnumSet.allOf(clazz);
1312         int keySize = keys.size();
1313         for (K key : keys) {
1314             // If this is the last enum, put the index at the end of the string, otherwise
1315             // put at delimiter
1316             int idx = --keySize == 0 ? len : values.indexOf(delim, start);
1317             if (idx >= 0) {
1318                 map.put(key, values.substring(start, idx));
1319                 start = idx;
1320                 do {
1321                     start++;
1322                 } while (start < len && values.charAt(start) == delim);
1323             } else {
1324                 map.put(key, values.substring(start));
1325                 break;
1326             }
1327         }
1328         return map;
1329     }
1330 
1331     /**
1332      * Checks if value exists in map for the given key or not and returns value or unknown based on it
1333      *
1334      * @param map A map of String key-value pairs
1335      * @param key Fetch value for the given key
1336      * @return Returns the value for the key if it exists in the map else it returns unknown
1337      */
1338     public static String getValueOrUnknown(Map<String, String> map, String key) {
1339         String value = map.getOrDefault(key, "");
1340         return value.isEmpty() ? Constants.UNKNOWN : value;
1341     }
1342 }