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.ArrayList;
 29 import java.util.Collections;
 30 import java.util.List;
 31 import java.util.Objects;
 32 import java.util.Set;
 33 
 34 /**
 35  * JsonArray implementation class
 36  */
 37 final class JsonArrayImpl implements JsonArray, JsonValueImpl {
 38 
 39     private final JsonDocumentInfo docInfo;
 40     private final int endIndex;
 41     private final int startIndex;
 42     private final int startOffset;
 43     private List<JsonValue> theValues;
 44 
 45     // Via of factory
 46     JsonArrayImpl(List<? extends JsonValue> from) {
 47         theValues = Collections.unmodifiableList(from);
 48         this.endIndex = 0;
 49         this.startIndex = 0;
 50         this.startOffset = 0;
 51         docInfo = null;
 52     }
 53 
 54     // Via untyped
 55     JsonArrayImpl(List<?> from, Set<Object> untypedObjs, int depth) {
 56         List<JsonValue> l = new ArrayList<>(from.size());
 57         for (Object o : from) {
 58             l.add(JsonGenerator.fromUntyped(o, untypedObjs, depth));
 59         }
 60         theValues = Collections.unmodifiableList(l);
 61         this.endIndex = 0;
 62         this.startIndex = 0;
 63         this.startOffset = 0;
 64         docInfo = null;
 65     }
 66 
 67     JsonArrayImpl(JsonDocumentInfo doc, int offset, int index) {
 68         docInfo = doc;
 69         startOffset = offset;
 70         startIndex = index;
 71         endIndex = startIndex == 0 ? docInfo.getIndexCount() - 1 // For root
 72                 : docInfo.nextIndex(index, '[', ']');
 73     }
 74 
 75     @Override
 76     public List<JsonValue> values() {
 77         if (theValues == null) {
 78             theValues = inflate();
 79         }
 80         return theValues;
 81     }
 82 
 83     // Inflate the JsonArray using the tokens array.
 84     private List<JsonValue> inflate() {
 85         if (docInfo.charAt(JsonParser.skipWhitespaces(docInfo, startOffset + 1)) == ']') {
 86             return Collections.emptyList();
 87         }
 88         var v = new ArrayList<JsonValue>();
 89         var index = startIndex;
 90         while (index < endIndex) { // start on comma or opening bracket
 91             // Get Val
 92             int offset = docInfo.getOffset(index) + 1;
 93             if (docInfo.shouldWalkToken(docInfo.charAtIndex(index + 1))) {
 94                 index++;
 95             }
 96             var value = JsonGenerator.createValue(docInfo, offset, index);
 97             v.add(value);
 98             index = ((JsonValueImpl)value).getEndIndex(); // Move to comma or closing
 99         }
100         return Collections.unmodifiableList(v);
101     }
102 
103     @Override
104     public int getEndIndex() {
105         return endIndex + 1;  // We are always interested in the index after ']'
106     }
107 
108     @Override
109     public boolean equals(Object o) {
110         return this == o ||
111             o instanceof JsonArrayImpl ojai &&
112             Objects.equals(values(), ojai.values());
113     }
114 
115     @Override
116     public int hashCode() {
117         return Objects.hash(values());
118     }
119 
120     @Override
121     public List<Object> toUntyped() {
122         return values().stream()
123                 .map(Json::toUntyped)
124                 .toList();
125     }
126 
127     @Override
128     public String toString() {
129         var s = new StringBuilder("[");
130         for (JsonValue v: values()) {
131             s.append(v.toString()).append(",");
132         }
133         if (!values().isEmpty()) {
134             s.setLength(s.length() - 1); // trim final comma
135         }
136         return s.append("]").toString();
137     }
138 
139     @Override
140     public String toDisplayString(int indent, boolean isField) {
141         var prefix = " ".repeat(indent);
142         var s = new StringBuilder(isField ? " " : prefix);
143         if (values().isEmpty()) {
144             s.append("[]");
145         } else {
146             s.append("[\n");
147             for (JsonValue v: values()) {
148                 if (v instanceof JsonValueImpl impl) {
149                     s.append(impl.toDisplayString(indent + INDENT, false)).append(",\n");
150                 } else {
151                     throw new InternalError("type mismatch");
152                 }
153             }
154             s.setLength(s.length() - 2); // trim final comma/newline
155             s.append("\n").append(prefix).append("]");
156         }
157         return s.toString();
158     }
159 }