001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.NotThreadSafe;
023import javax.annotation.concurrent.ThreadSafe;
024import java.math.BigDecimal;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.List;
028
029import static java.util.Objects.requireNonNull;
030
031/**
032 * An immutable JSON array.
033 *
034 * @author <a href="https://www.revetkn.com">Mark Allen</a>
035 */
036@ThreadSafe
037public final class McpJsonArray implements McpJsonValue {
038        @NonNull
039        private static final McpJsonArray EMPTY = new McpJsonArray(List.of());
040        @NonNull
041        private final List<@NonNull McpJsonValue> elements;
042
043        /**
044         * Returns the shared empty array.
045         *
046         * @return empty JSON array
047         */
048        @NonNull
049        public static McpJsonArray emptyInstance() {
050                return EMPTY;
051        }
052
053        /**
054         * Vends a mutable builder.
055         *
056         * @return JSON array builder
057         */
058        @NonNull
059        public static Builder builder() {
060                return new Builder();
061        }
062
063        /**
064         * Creates an array from the supplied elements.
065         *
066         * @param elements array elements
067         * @return immutable JSON array
068         */
069        @NonNull
070        public static McpJsonArray fromElements(
071                        @NonNull Collection<? extends @NonNull McpJsonValue> elements) {
072                requireNonNull(elements);
073                if (elements.isEmpty())
074                        return emptyInstance();
075                return new McpJsonArray(elements);
076        }
077
078        private McpJsonArray(
079                        @NonNull Collection<? extends @NonNull McpJsonValue> elements) {
080                this.elements = List.copyOf(elements);
081        }
082
083        /**
084         * Returns the immutable array elements.
085         *
086         * @return array elements
087         */
088        @NonNull
089        public List<@NonNull McpJsonValue> getElements() {
090                return this.elements;
091        }
092
093        /** @return whether this array has structurally equal elements in the same order */
094        @Override
095        public boolean equals(@Nullable Object other) {
096                if (this == other)
097                        return true;
098                if (!(other instanceof McpJsonArray array))
099                        return false;
100                return this.elements.equals(array.elements);
101        }
102
103        /** @return order-sensitive structural element hash code */
104        @Override
105        public int hashCode() {
106                return this.elements.hashCode();
107        }
108
109        /**
110         * Mutable builder for {@link McpJsonArray}.
111         *
112         * @author <a href="https://www.revetkn.com">Mark Allen</a>
113         */
114        @NotThreadSafe
115        public static final class Builder {
116                @NonNull
117                private final List<@NonNull McpJsonValue> elements = new ArrayList<>();
118
119                private Builder() {
120                }
121
122                /**
123                 * Appends a JSON value.
124                 *
125                 * @param value the value to append
126                 * @return this builder
127                 */
128                @NonNull
129                public Builder add(@NonNull McpJsonValue value) {
130                        this.elements.add(requireNonNull(value));
131                        return this;
132                }
133
134                /**
135                 * Appends a JSON string.
136                 *
137                 * @param value the string value to append
138                 * @return this builder
139                 */
140                @NonNull
141                public Builder add(@NonNull String value) {
142                        return add(McpJsonString.fromValue(value));
143                }
144
145                /**
146                 * Appends an exactly represented JSON number.
147                 *
148                 * @param value the number to append
149                 * @return this builder
150                 */
151                @NonNull
152                public Builder add(@NonNull BigDecimal value) {
153                        return add(McpJsonNumber.fromValue(value));
154                }
155
156                /**
157                 * Appends an integral JSON number.
158                 *
159                 * @param value the integer value to append
160                 * @return this builder
161                 * @throws NullPointerException if {@code value} is null
162                 */
163                @NonNull
164                public Builder add(@NonNull Integer value) {
165                        return add(BigDecimal.valueOf(requireNonNull(value)));
166                }
167
168                /**
169                 * Appends an integral JSON number.
170                 *
171                 * @param value the long value to append
172                 * @return this builder
173                 * @throws NullPointerException if {@code value} is null
174                 */
175                @NonNull
176                public Builder add(@NonNull Long value) {
177                        return add(BigDecimal.valueOf(requireNonNull(value)));
178                }
179
180                /**
181                 * Appends a finite JSON number.
182                 *
183                 * @param value the finite double value to append
184                 * @return this builder
185                 * @throws NullPointerException if {@code value} is null
186                 * @throws IllegalArgumentException if {@code value} is not finite
187                 */
188                @NonNull
189                public Builder add(@NonNull Double value) {
190                        requireNonNull(value);
191                        if (!Double.isFinite(value))
192                                throw new IllegalArgumentException("JSON numbers must be finite.");
193                        return add(BigDecimal.valueOf(value));
194                }
195
196                /**
197                 * Appends a JSON boolean.
198                 *
199                 * @param value the boolean to append
200                 * @return this builder
201                 * @throws NullPointerException if {@code value} is null
202                 */
203                @NonNull
204                public Builder add(@NonNull Boolean value) {
205                        return add(McpJsonBoolean.fromValue(requireNonNull(value)));
206                }
207
208                /**
209                 * Appends the JSON {@code null} value.
210                 *
211                 * @return this builder
212                 */
213                @NonNull
214                public Builder addNull() {
215                        return add(McpJsonNull.INSTANCE);
216                }
217
218                /**
219                 * Builds an immutable JSON array in append order.
220                 *
221                 * @return the JSON array
222                 */
223                @NonNull
224                public McpJsonArray build() {
225                        return McpJsonArray.fromElements(this.elements);
226                }
227        }
228}