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 }