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; 27 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Set; 31 32 // Responsible for creating "lazy" state JsonValue(s) using the tokens array 33 final class JsonGenerator { 34 35 static JsonValue createValue(JsonDocumentInfo docInfo, int offset, int index) { 36 offset = JsonParser.skipWhitespaces(docInfo, offset); 37 return switch (docInfo.charAt(offset)) { 38 case '{' -> createObject(docInfo, index); 39 case '[' -> createArray(docInfo, offset, index); 40 case '"' -> createString(docInfo, offset, index); 41 case 't', 'f' -> createBoolean(docInfo, offset, index); 42 case 'n' -> createNull(docInfo, index); 43 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' 44 -> createNumber(docInfo, offset, index); 45 default -> throw new InternalError(); 46 }; 47 } 48 49 static JsonObject createObject(JsonDocumentInfo docInfo, int index) { 50 return new JsonObjectImpl(docInfo, index); 51 } 52 53 static JsonArray createArray(JsonDocumentInfo docInfo, int offset, int index) { 54 return new JsonArrayImpl(docInfo, offset, index); 55 } 56 57 static JsonString createString(JsonDocumentInfo docInfo, int offset, int index) { 58 return new JsonStringImpl(docInfo, offset, index); 59 } 60 61 static JsonBoolean createBoolean(JsonDocumentInfo docInfo, int offset, int index) { 62 return new JsonBooleanImpl(docInfo, offset, index); 63 } 64 65 static JsonNull createNull(JsonDocumentInfo docInfo, int index) { 66 return new JsonNullImpl(docInfo, index); 67 } 68 69 static JsonNumber createNumber(JsonDocumentInfo docInfo, int offset, int index) { 70 return new JsonNumberImpl(docInfo, offset, index); 71 } 72 73 // untypedObjs is an identity hash set that serves to identify if a circular 74 // reference exists 75 static JsonValue fromUntyped(Object src, Set<Object> untypedObjs, int depth) { 76 return switch (src) { 77 // Structural JSON: Object, Array 78 case Map<?, ?> map -> { 79 if (!untypedObjs.add(map)) { 80 throw new IllegalArgumentException("Circular reference detected"); 81 } 82 if (depth + 1 > Json.MAX_DEPTH) { 83 throw new IllegalArgumentException("Max depth exceeded"); 84 } 85 yield new JsonObjectImpl(map, untypedObjs, depth + 1); 86 } 87 case List<?> list-> { 88 if (!untypedObjs.add(list)) { 89 throw new IllegalArgumentException("Circular reference detected"); 90 } 91 if (depth + 1 > Json.MAX_DEPTH) { 92 throw new IllegalArgumentException("Max depth exceeded"); 93 } 94 yield new JsonArrayImpl(list, untypedObjs, depth + 1); 95 } 96 // JsonPrimitives 97 case String str -> new JsonStringImpl(str); 98 case Boolean bool -> new JsonBooleanImpl(bool); 99 case null -> JsonNull.of(); 100 case Float f -> JsonNumber.of(f); // promote Float to Double 101 case Integer i -> new JsonNumberImpl(i); // preserve Integer via ctr 102 case Double db -> JsonNumber.of(db); 103 case Long lg -> JsonNumber.of(lg); 104 // JsonValue 105 case JsonValue jv -> { 106 checkDepth(jv, depth + 1); 107 yield jv; 108 } 109 default -> throw new IllegalArgumentException("Type not recognized."); 110 }; 111 } 112 113 static void checkDepth(JsonValue val, int depth) { 114 if (depth > Json.MAX_DEPTH) { 115 throw new IllegalArgumentException("Max depth exceeded"); 116 } 117 switch (val) { 118 case JsonObject jo -> jo.keys().forEach((_, jV) -> checkDepth(jV, depth + 1)); 119 case JsonArray ja -> ja.values().forEach(jV -> checkDepth(jV, depth + 1)); 120 default -> {} // Primitive JSON can not nest 121 } 122 } 123 124 // no instantiation of this generator 125 private JsonGenerator(){} 126 }