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 oracle.code.json.impl;
 27 
 28 import oracle.code.json.JsonArray;
 29 import oracle.code.json.JsonObject;
 30 import oracle.code.json.JsonValue;
 31 
 32 import java.util.List;
 33 import java.util.Map;
 34 
 35 /**
 36  * Shared utilities for Json classes.
 37  */
 38 public class Utils {
 39 
 40     // Non instantiable
 41     private Utils() {}
 42 
 43     // Equivalent to JsonObject/Array.of() factories without the need for defensive copy
 44     // and other input validation
 45     public static JsonArray arrayOf(List<JsonValue> list) {
 46         return new JsonArrayImpl(list);
 47     }
 48 
 49     public static JsonObject objectOf(Map<String, JsonValue> map) {
 50         return new JsonObjectImpl(map);
 51     }
 52 
 53     // Used for escaping String values, applicable to JSON Strings and member names
 54     public static String unescape(char[] doc, int startOffset, int endOffset) {
 55         StringBuilder sb = null; // Only use if required
 56         var escape = false;
 57         int offset = startOffset;
 58         boolean useBldr = false;
 59         for (; offset < endOffset; offset++) {
 60             var c = doc[offset];
 61             if (escape) {
 62                 var length = 0;
 63                 switch (c) {
 64                     case '"', '\\', '/' -> {}
 65                     case 'b' -> c = '\b';
 66                     case 'f' -> c = '\f';
 67                     case 'n' -> c = '\n';
 68                     case 'r' -> c = '\r';
 69                     case 't' -> c = '\t';
 70                     case 'u' -> {
 71                         if (offset + 4 < endOffset) {
 72                             c = codeUnit(doc, offset + 1);
 73                             length = 4;
 74                         } else {
 75                             throw new IllegalArgumentException("Illegal Unicode escape sequence");
 76                         }
 77                     }
 78                     default -> throw new IllegalArgumentException("Illegal escape sequence");
 79                 }
 80                 if (!useBldr) {
 81                     useBldr = true;
 82                     // At best, we know the size of the first escaped value
 83                     sb = new StringBuilder(endOffset - startOffset - length - 1)
 84                             .append(doc, startOffset, offset - 1 - startOffset);
 85                 }
 86                 offset+=length;
 87                 escape = false;
 88             } else if (c == '\\') {
 89                 escape = true;
 90                 continue;
 91             }
 92             if (useBldr) {
 93                 sb.append(c);
 94             }
 95         }
 96         if (useBldr) {
 97             return sb.toString();
 98         } else {
 99             return new String(doc, startOffset, endOffset - startOffset);
100         }
101     }
102 
103     // Validate and construct corresponding value of Unicode escape sequence
104     // This method does not increment offset
105     static char codeUnit(char[] doc, int o) {
106         char val = 0;
107         for (int index = 0; index < 4; index ++) {
108             char c = doc[o + index];
109             val <<= 4;
110             val += (char) (
111                     switch (c) {
112                         case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> c - '0';
113                         case 'a', 'b', 'c', 'd', 'e', 'f' -> c - 'a' + 10;
114                         case 'A', 'B', 'C', 'D', 'E', 'F' -> c - 'A' + 10;
115                         default -> throw new IllegalArgumentException("Illegal Unicode escape sequence");
116                     });
117         }
118         return val;
119     }
120 }