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