1 /*
2 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package hat.tools.json.impl;
27
28
29 import hat.tools.json.JsonArray;
30 import hat.tools.json.JsonObject;
31 import hat.tools.json.JsonValue;
32
33 import java.util.List;
34 import java.util.Map;
35
36 /**
37 * Shared utilities for Json classes.
38 */
39 public class Utils {
40
41 // Non instantiable
42 private Utils() {}
43
44 // Equivalent to JsonObject/Array.of() factories without the need for defensive copy
45 // and other input validation
46 public static JsonArray arrayOf(List<JsonValue> list) {
47 return new JsonArrayImpl(list);
48 }
49
50 public static JsonObject objectOf(Map<String, JsonValue> map) {
51 return new JsonObjectImpl(map);
52 }
53
54 // Used for escaping String values, applicable to JSON Strings and member names
55 public static String unescape(char[] doc, int startOffset, int endOffset) {
56 StringBuilder sb = null; // Only use if required
57 var escape = false;
58 int offset = startOffset;
59 boolean useBldr = false;
60 for (; offset < endOffset; offset++) {
61 var c = doc[offset];
62 if (escape) {
63 var length = 0;
64 switch (c) {
65 case '"', '\\', '/' -> {}
66 case 'b' -> c = '\b';
67 case 'f' -> c = '\f';
68 case 'n' -> c = '\n';
69 case 'r' -> c = '\r';
70 case 't' -> c = '\t';
71 case 'u' -> {
72 if (offset + 4 < endOffset) {
73 c = codeUnit(doc, offset + 1);
74 length = 4;
75 } else {
76 throw new IllegalArgumentException("Illegal Unicode escape sequence");
77 }
78 }
79 default -> throw new IllegalArgumentException("Illegal escape sequence");
80 }
81 if (!useBldr) {
82 useBldr = true;
83 // At best, we know the size of the first escaped value
84 sb = new StringBuilder(endOffset - startOffset - length - 1)
85 .append(doc, startOffset, offset - 1 - startOffset);
86 }
87 offset+=length;
88 escape = false;
89 } else if (c == '\\') {
90 escape = true;
91 continue;
92 }
93 if (useBldr) {
94 sb.append(c);
95 }
96 }
97 if (useBldr) {
98 return sb.toString();
99 } else {
100 return new String(doc, startOffset, endOffset - startOffset);
101 }
102 }
103
104 // Validate and construct corresponding value of Unicode escape sequence
105 // This method does not increment offset
106 static char codeUnit(char[] doc, int o) {
107 char val = 0;
108 for (int index = 0; index < 4; index ++) {
109 char c = doc[o + index];
110 val <<= 4;
111 val += (char) (
112 switch (c) {
113 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> c - '0';
114 case 'a', 'b', 'c', 'd', 'e', 'f' -> c - 'a' + 10;
115 case 'A', 'B', 'C', 'D', 'E', 'F' -> c - 'A' + 10;
116 default -> throw new IllegalArgumentException("Illegal Unicode escape sequence");
117 });
118 }
119 return val;
120 }
121 }