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 }