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 oracle.code.json.impl.JsonObjectImpl;
 29 import oracle.code.json.impl.Utils;
 30 
 31 import java.util.LinkedHashMap;
 32 import java.util.Map;
 33 import java.util.Objects;
 34 
 35 /**
 36  * The interface that represents JSON object.
 37  * <p>
 38  * A {@code JsonObject} can be produced by a {@link Json#parse(String)}.
 39  * <p> Alternatively, {@link #of(Map)} can be used to obtain a {@code JsonObject}.
 40  * Implementations of {@code JsonObject} cannot be created from sources that
 41  * contain duplicate member names. If duplicate names appear during
 42  * a {@link Json#parse(String)}, a {@code JsonParseException} is thrown.
 43  *
 44  * @since 99
 45  */
 46 public non-sealed interface JsonObject extends JsonValue {
 47 
 48     /**
 49      * {@return an unmodifiable map of the {@code String} to {@code JsonValue}
 50      * members in this {@code JsonObject}}
 51      */
 52     Map<String, JsonValue> members();
 53 
 54     /**
 55      * {@return the {@code JsonObject} created from the given
 56      * map of {@code String} to {@code JsonValue}s}
 57      *
 58      * The {@code JsonObject}'s members occur in the same order as the given
 59      * map's entries.
 60      * <p>
 61      * If a key in the provided {@code map} contains escape characters, they are
 62      * unescaped before being added to the resulting {@code JsonObject}. If multiple
 63      * keys unescape to the same value, an {@code IllegalArgumentException} is thrown.
 64      *
 65      * @param map the map of {@code JsonValue}s. Non-null.
 66      * @throws IllegalArgumentException if {@code map} contains multiple keys
 67      *      that unescape to the same value
 68      * @throws NullPointerException if {@code map} is {@code null}, contains
 69      *      any keys that are {@code null}, or contains any values that are {@code null}
 70      */
 71     static JsonObject of(Map<String, ? extends JsonValue> map) {
 72         Map<String, JsonValue> ret = new LinkedHashMap<>(map.size()); // implicit NPE on map
 73         for (var e : map.entrySet()) {
 74             var key = e.getKey();
 75             // Implicit NPE on key
 76             var unescapedKey = Utils.unescape(key.toCharArray(), 0, key.length());
 77             var val = e.getValue();
 78             if (ret.containsKey(unescapedKey)) {
 79                 throw new IllegalArgumentException(
 80                         "Multiple keys unescape to the same value: '%s'".formatted(unescapedKey));
 81             } else {
 82                 ret.put(unescapedKey, Objects.requireNonNull(val));
 83             }
 84         }
 85         return new JsonObjectImpl(ret);
 86     }
 87 
 88     /**
 89      * {@return {@code true} if the given object is also a {@code JsonObject}
 90      * and the two {@code JsonObject}s represent the same mappings} Two
 91      * {@code JsonObject}s {@code jo1} and {@code jo2} represent the same
 92      * mappings if {@code jo1.members().equals(jo2.members())}.
 93      *
 94      * @see #members()
 95      */
 96     @Override
 97     boolean equals(Object obj);
 98 
 99     /**
100      * {@return the hash code value for this {@code JsonObject}} The hash code value
101      * of a {@code JsonObject} is defined to be the hash code of {@code JsonObject}'s
102      * {@link #members()} value. Thus, for two {@code JsonObject}s {@code jo1} and {@code jo2},
103      * {@code jo1.equals(jo2)} implies that {@code jo1.hashCode() == jo2.hashCode()}
104      * as required by the general contract of {@link Object#hashCode}.
105      *
106      * @see #members()
107      */
108     @Override
109     int hashCode();
110 }