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 package oracle.code.json;
 26 
 27 import java.util.Arrays;
 28 import java.util.Collections;
 29 import java.util.IdentityHashMap;
 30 import java.util.Objects;
 31 
 32 /**
 33  * This class provides static methods for producing and manipulating a {@link JsonValue}.
 34  * <p>
 35  * {@link #parse(String)} and {@link #parse(char[])} produce a {@code JsonValue}
 36  * by parsing data adhering to the JSON syntax defined in RFC 8259.
 37  * <p>
 38  * {@link #toDisplayString(JsonValue)} is a formatter that produces a
 39  * representation of the JSON value suitable for display.
 40  * <p>
 41  * {@link #fromUntyped(Object)} and {@link #toUntyped(JsonValue)} provide a conversion
 42  * between {@code JsonValue} and an untyped object.
 43  *
 44  * <table id="mapping-table" class="striped">
 45  * <caption>Mapping Table</caption>
 46  * <thead>
 47  *    <tr>
 48  *       <th scope="col" class="TableHeadingColor">JsonValue</th>
 49  *       <th scope="col" class="TableHeadingColor">Untyped Object</th>
 50  *    </tr>
 51  * </thead>
 52  * <tbody>
 53      * <tr>
 54      *     <th>{@code List<Object>}</th>
 55      *     <th> {@code JsonArray}</th>
 56      * </tr>
 57      * <tr>
 58      *     <th>{@code Boolean}</th>
 59      *     <th>{@code JsonBoolean}</th>
 60      * </tr>
 61      * <tr>
 62      *     <th>{@code `null`}</th>
 63      *     <th> {@code JsonNull}</th>
 64      * </tr>
 65      * <tr>
 66      *     <th>{@code Number}</th>
 67      *     <th>{@code JsonNumber}</th>
 68      * </tr>
 69      * <tr>
 70      *     <th>{@code Map<String, Object>}</th>
 71      *     <th> {@code JsonObject}</th>
 72      * </tr>
 73      * <tr>
 74      *     <th>{@code String}</th>
 75      *     <th>{@code JsonString}</th>
 76      * </tr>
 77  * </tbody>
 78  * </table>
 79  *
 80  * @implSpec The reference implementation defines a {@code JsonValue} nesting
 81  * depth limit of 32. Attempting to construct a {@code JsonValue} that exceeds this limit
 82  * will throw an {@code IllegalArgumentException}.
 83  *
 84  * @spec https://datatracker.ietf.org/doc/html/rfc8259 RFC 8259: The JavaScript
 85  *          Object Notation (JSON) Data Interchange Format
 86  */
 87 //@PreviewFeature(feature = PreviewFeature.Feature.JSON)
 88 public final class Json {
 89 
 90     // Depth limit used by Parser and Generator
 91     static final int MAX_DEPTH = 32;
 92 
 93     /**
 94      * Parses and creates the top level {@code JsonValue} in this JSON
 95      * document. If the document contains any JSON Object that has
 96      * duplicate keys, a {@code JsonParseException} is thrown.
 97      *
 98      * @param in the input JSON document as {@code String}. Non-null.
 99      * @throws JsonParseException if the input JSON document does not conform
100      *      to the JSON document format, a JSON object containing
101      *      duplicate keys is encountered, or a nest limit is exceeded.
102      * @return the top level {@code JsonValue}
103      */
104     public static JsonValue parse(String in) {
105         Objects.requireNonNull(in);
106         return JsonGenerator.createValue(JsonParser.parseRoot(
107                 new JsonDocumentInfo(in.toCharArray())), 0, 0);
108     }
109 
110     /**
111      * Parses and creates the top level {@code JsonValue} in this JSON
112      * document. If the document contains any JSON Object that has
113      * duplicate keys, a {@code JsonParseException} is thrown.
114      *
115      * @param in the input JSON document as {@code char[]}. Non-null.
116      * @throws JsonParseException if the input JSON document does not conform
117      *      to the JSON document format, a JSON object containing
118      *      duplicate keys is encountered, or a nest limit is exceeded.
119      * @return the top level {@code JsonValue}
120      */
121     public static JsonValue parse(char[] in) {
122         Objects.requireNonNull(in);
123         return JsonGenerator.createValue(JsonParser.parseRoot(
124                 new JsonDocumentInfo(Arrays.copyOf(in, in.length))), 0, 0);
125     }
126 
127     /**
128      * {@return a {@code JsonValue} corresponding to {@code src}}
129      * See the {@link ##mapping-table Mapping Table} for conversion details.
130      *
131      * <p>If {@code src} contains a circular reference, {@code IllegalArgumentException}
132      * will be thrown. For example, the following code throws an exception,
133      * {@snippet lang=java:
134      *     var map = new HashMap<String, Object>();
135      *     map.put("foo", false);
136      *     map.put("bar", map);
137      *     Json.fromUntyped(map);
138      * }
139      *
140      * @param src the data to produce the {@code JsonValue} from. May be null.
141      * @throws IllegalArgumentException if {@code src} cannot be converted
142      *      to {@code JsonValue}, contains a circular reference, or exceeds a nesting limit.
143      * @see ##mapping-table Mapping Table
144      * @see #toUntyped(JsonValue)
145      */
146     public static JsonValue fromUntyped(Object src) {
147         if (src instanceof JsonValue jv) {
148             return jv; // If root is JV, no need to check depth
149         } else {
150             return JsonGenerator.fromUntyped(
151                     src, Collections.newSetFromMap(new IdentityHashMap<>()), 0);
152         }
153     }
154 
155     /**
156      * {@return an {@code Object} corresponding to {@code src}}
157      * See the {@link ##mapping-table Mapping Table} for conversion details.
158      *
159      * @param src the {@code JsonValue} to convert to untyped. Non-null.
160      * @see ##mapping-table Mapping Table
161      * @see #fromUntyped(Object)
162      */
163     public static Object toUntyped(JsonValue src) {
164         Objects.requireNonNull(src);
165         return ((JsonValueImpl)src).toUntyped();
166     }
167 
168     /**
169      * {@return the String representation of the given {@code JsonValue} that conforms
170      * to the JSON syntax} As opposed to {@link JsonValue#toString()}, this method returns
171      * a JSON string that is suitable for display.
172      *
173      * @param value the {@code JsonValue} to create the display string from. Non-null.
174      */
175     public static String toDisplayString(JsonValue value) {
176         Objects.requireNonNull(value);
177         return ((JsonValueImpl)value).toDisplayString();
178     }
179 
180     // no instantiation is allowed for this class
181     private Json() {}
182 }