001/*
002 * ============================================================================
003 *  Copyright © 2002-2026 by Thomas Thrien.
004 *  All Rights Reserved.
005 * ============================================================================
006 *  Licensed to the public under the agreements of the GNU Lesser General Public
007 *  License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *       http://www.gnu.org/licenses/lgpl.html
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 *  License for the specific language governing permissions and limitations
015 *  under the License.
016 */
017
018package org.tquadrat.foundation.jsonbuilder.internal;
019
020import org.apiguardian.api.API;
021import org.tquadrat.foundation.annotation.ClassVersion;
022import org.tquadrat.foundation.jsonbuilder.JSONArray;
023import org.tquadrat.foundation.jsonbuilder.JSONBuilder;
024import org.tquadrat.foundation.jsonbuilder.JSONObject;
025import org.tquadrat.foundation.jsonbuilder.JSONValue;
026import org.tquadrat.foundation.lang.value.Dimension;
027import org.tquadrat.foundation.lang.value.DimensionedValue;
028
029import java.io.IOException;
030import java.io.UncheckedIOException;
031import java.math.BigDecimal;
032import java.math.BigInteger;
033import java.util.Formatter;
034import java.util.Iterator;
035import java.util.LinkedHashMap;
036import java.util.Optional;
037import java.util.SequencedCollection;
038import java.util.SequencedMap;
039import java.util.StringJoiner;
040
041import static java.lang.Integer.max;
042import static java.util.Collections.unmodifiableSequencedCollection;
043import static org.apiguardian.api.API.Status.INTERNAL;
044import static org.tquadrat.foundation.jsonbuilder.JSONLiteral.NULL;
045import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING;
046import static org.tquadrat.foundation.lang.Objects.hash;
047import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
048import static org.tquadrat.foundation.lang.Objects.requireNotBlankArgument;
049
050/**
051 *  <p>{@summary The implementation for the interface
052 *  {@link JSONObject}.}</p>
053 *
054 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
055 *  @version $Id: JSONObjectImpl.java 1195 2026-04-15 21:33:40Z tquadrat $
056 *  @since 0.25.0
057 *
058 *  @UMLGraph.link
059 */
060@ClassVersion( sourceVersion = "$Id: JSONObjectImpl.java 1195 2026-04-15 21:33:40Z tquadrat $" )
061@API( status = INTERNAL, since = "0.25.0" )
062public final class JSONObjectImpl implements JSONObject
063{
064        /*------------*\
065    ====** Attributes **=======================================================
066        \*------------*/
067    /**
068     *  The reference to the
069     *  {@link JSONBuilder}
070     *  that was used to create this {@code JSONObject}, and that is used to
071     *  create the members.
072     */
073    private final JSONBuilderImpl m_Builder;
074
075    /**
076     *  The members of this {@code JSONObject}.
077     */
078    private final SequencedMap<String,JSONValue> m_Members = new LinkedHashMap<>();
079
080        /*--------------*\
081    ====** Constructors **=====================================================
082        \*--------------*/
083    /**
084     *  Creates a new instance of {@code JSONObjectImpl}.
085     *
086     *  @param  builder The reference to the
087     *      {@link JSONBuilder}.
088     */
089    public JSONObjectImpl( final JSONBuilderImpl builder )
090    {
091        m_Builder = requireNonNullArgument( builder, "builder" );
092    }   //  JSONObjectImpl()
093
094    /**
095     *  <p>{summary Creates a new instance of {@code JSONObjectImpl} from the
096     *  given other object.}</p>
097     *  <p>The new object is a deep copy of the given instance.</p>
098     *
099     *  @param  other   The other JSON object.
100     */
101    public JSONObjectImpl( final JSONObjectImpl other )
102    {
103        m_Builder = requireNonNullArgument( other, "other" ).m_Builder;
104        for( final var entry : other.m_Members.entrySet() )
105        {
106            final var name = entry.getKey();
107            final var value = entry.getValue();
108            m_Members.put( name, switch( value )
109            {
110               case null -> NULL;
111               case JSONArrayImpl array -> new JSONArrayImpl( array );
112               case JSONObjectImpl object -> new JSONObjectImpl( object );
113               default -> value;
114            });
115        }
116    }   //  JSONObjectImpl()
117
118        /*---------*\
119    ====** Methods **==========================================================
120        \*---------*/
121    /**
122     *  {@inheritDoc}
123     */
124    @Override
125    public final boolean contains( final String name ) { return m_Members.containsKey( name ); }
126
127    /**
128     *  {@inheritDoc}
129     */
130    @Override
131    public final boolean equals( final Object o )
132    {
133        var retValue = this == o;
134        if( !retValue && o instanceof final JSONObjectImpl other )
135        {
136            retValue = m_Builder.equals( other.m_Builder )
137                && m_Members.equals( other.m_Members );
138        }
139
140        //---* Done *----------------------------------------------------------
141        return retValue;
142    }   //  equals()
143
144    /**
145     * {@inheritDoc}
146     */
147    @Override
148    public void formatTo( final Formatter formatter, final int flags, final int width, final int precision )
149    {
150        final var indentation1 = "\n" + (width <= 0 ? EMPTY_STRING : " ".repeat( width ));
151        final var newWidth = max( 0, width ) + m_Builder.getIndentation();
152        final var indentation2 = "\n" + " ".repeat( newWidth );
153        final var appendable = formatter.out();
154        try
155        {
156            switch( m_Members.size() )
157            {
158                case 0 -> appendable.append( "{}" );
159                case 1 ->
160                {
161                    final var entry = m_Members.firstEntry();
162                    appendable.append( "{ " )
163                        .append( '"' )
164                        .append( entry.getKey() )
165                        .append( '"' )
166                        .append( " : " );
167                    entry.getValue().formatTo( formatter, flags, newWidth, precision );
168                    appendable.append( " }" );
169                }
170                default ->
171                {
172                    var isFirst = true;
173                    appendable.append( "{" );
174                    for( final var entry : m_Members.entrySet() )
175                    {
176                        if( isFirst )
177                        {
178                            isFirst = false;
179                        }
180                        else
181                        {
182                            appendable.append( ',' );
183                        }
184                        appendable.append( indentation2 )
185                            .append( '"' )
186                            .append( entry.getKey() )
187                            .append( '"' )
188                            .append( " : " );
189                        entry.getValue().formatTo( formatter, flags, newWidth, precision );
190                    }
191                    appendable.append( indentation1 )
192                        .append( "}" );
193                }
194            }
195        }
196        catch( final IOException e )
197        {
198            throw new UncheckedIOException( e.getMessage(), e );
199        }
200    }   //  formatTo()
201
202    /**
203     *  {@inheritDoc}
204     */
205    @Override
206    public final Optional<JSONValue> get( final String name )
207    {
208        final var retValue = Optional.ofNullable( m_Members.get( name ) );
209
210        //---* Done *----------------------------------------------------------
211        return retValue;
212    }   //  get()
213
214    /**
215     *  {@inheritDoc}
216     */
217    @Override
218    public final int hashCode() { return hash( m_Builder, m_Members ); }
219
220    /**
221     *  {@inheritDoc}
222     */
223    @Override
224    public final boolean isEmpty() { return m_Members.isEmpty(); }
225
226    /**
227     *  {@inheritDoc}
228     */
229    @Override
230    public final Iterator<JSONValue> iterator() { return m_Members.sequencedValues().iterator(); }
231
232    /**
233     *  {@inheritDoc}
234     */
235    @Override
236    public JSONObject merge( final JSONObject object )
237    {
238        m_Members.putAll( ((JSONObjectImpl) requireNonNullArgument( object, "object" )).m_Members );
239
240        //---* Done *----------------------------------------------------------
241        return this;
242    }   //  merge()
243
244    /**
245     *  {@inheritDoc}
246     */
247    @Override
248    public final SequencedCollection<String> names()
249    {
250        final var retValue = unmodifiableSequencedCollection( m_Members.sequencedKeySet() );
251
252        //---* Done *----------------------------------------------------------
253        return retValue;
254    }   //  names()
255
256    /**
257     *  {@inheritDoc}
258     */
259    @Override
260    public final JSONObject remove( final String name )
261    {
262        m_Members.remove( name );
263
264        //---* Done *----------------------------------------------------------
265        return this;
266    }   //  remove()
267
268    /**
269     *  {@inheritDoc}
270     */
271    @Override
272    public final JSONObject set( final String name, final BigDecimal value )
273    {
274        return set( name, m_Builder.valueOf( value ) );
275    }   //  set
276
277    /**
278     *  {@inheritDoc}
279     */
280    @Override
281    public final JSONObject set( final String name, final BigInteger value )
282    {
283        return set( name, m_Builder.valueOf( value ) );
284    }   //  set
285
286    /**
287     *  {@inheritDoc}
288     */
289    @Override
290    public <T extends Dimension> JSONObject set( final String name, final DimensionedValue<T> value, final T targetUnit )
291    {
292        return set( name, m_Builder.valueOf( value, targetUnit ) );
293    }   //  set()
294
295    /**
296     *  {@inheritDoc}
297     */
298    @Override
299    public final JSONObject set( final String name, final double value )
300    {
301        return set( name, m_Builder.valueOf( value ) );
302    }   //  set
303
304    /**
305     *  {@inheritDoc}
306     */
307    @Override
308    public final JSONObject set( final String name, final Double value )
309    {
310        return set( name, m_Builder.valueOf( value ) );
311    }   //  set
312
313    /**
314     *  {@inheritDoc}
315     */
316    @Override
317    public final JSONObject set( final String name, final float value )
318    {
319        return set( name, m_Builder.valueOf( value ) );
320    }   //  set
321
322    /**
323     *  {@inheritDoc}
324     */
325    @Override
326    public final JSONObject set( final String name, final Float value )
327    {
328        return set( name, m_Builder.valueOf( value ) );
329    }   //  set
330
331    /**
332     *  {@inheritDoc}
333     */
334    @Override
335    public final JSONObject set( final String name, final int value )
336    {
337        return set( name, m_Builder.valueOf( value ) );
338    }   //  set
339
340    /**
341     *  {@inheritDoc}
342     */
343    @Override
344    public final JSONObject set( final String name, final Integer value )
345    {
346        return set( name, m_Builder.valueOf( value ) );
347    }   //  set
348
349    /**
350     *  {@inheritDoc}
351     */
352    @Override
353    public final JSONObject set( final String name, final JSONValue value )
354    {
355        m_Members.put( requireNotBlankArgument( name, "name" ), requireNonNullArgument( value, "value" ) );
356
357        //---* Done *----------------------------------------------------------
358        return this;
359    }   //  set
360
361    /**
362     *  {@inheritDoc}
363     */
364    @Override
365    public final JSONObject set( final String name, final long value )
366    {
367        return set( name, m_Builder.valueOf( value ) );
368    }   //  set
369
370    /**
371     *  {@inheritDoc}
372     */
373    @Override
374    public final JSONObject set( final String name, final Long value )
375    {
376        return set( name, m_Builder.valueOf( value ) );
377    }   //  set
378
379    /**
380     *  {@inheritDoc}
381     */
382    @Override
383    public final JSONObject set( final String name, final String value )
384    {
385        return set( name, m_Builder.valueOf( value ) );
386    }   //  set
387
388    /**
389     *  {@inheritDoc}
390     */
391    @Override
392    public final JSONArray setArray( final String name )
393    {
394        final var retValue = m_Builder.createArray();
395        set( name, retValue );
396
397        //---* Done *----------------------------------------------------------
398        return retValue;
399    }   //  setArray()
400
401    /**
402     *  {@inheritDoc}
403     */
404    @Override
405    public final JSONObject setObject( final String name )
406    {
407        final var retValue = m_Builder.createObject();
408        set( name, retValue );
409
410        //---* Done *----------------------------------------------------------
411        return retValue;
412    }   //  setArray()
413
414    /**
415     *  {@inheritDoc}
416     */
417    @Override
418    public final int size() { return m_Members.size(); }
419
420    /**
421     *  {@inheritDoc}
422     */
423    @Override
424    public final String toString()
425    {
426        final var buffer = new StringJoiner( ",", "{", "}" );
427        for( final var entry : m_Members.entrySet() )
428        {
429            buffer.add( "%s:%s".formatted( new JSONStringImpl( entry.getKey() ).toString(), entry.getValue().toString() ) );
430        }
431        final var retValue = buffer.toString();
432
433        //---* Done *----------------------------------------------------------
434        return retValue;
435    }   //  toString()
436}
437//  class JSONObjectImpl
438
439/*
440 *  End of File
441 */