All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jsonvalues.JsArray Maven / Gradle / Ivy

package jsonvalues;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
import io.vavr.collection.Vector;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.function.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static com.dslplatform.json.MyDslJson.INSTANCE;
import static com.fasterxml.jackson.core.JsonToken.START_ARRAY;
import static java.util.Objects.requireNonNull;
import static java.util.stream.IntStream.range;
import static jsonvalues.JsArray.TYPE.LIST;
import static jsonvalues.JsArray.TYPE.MULTISET;
import static jsonvalues.JsBool.FALSE;
import static jsonvalues.JsBool.TRUE;
import static jsonvalues.JsNothing.NOTHING;
import static jsonvalues.JsNull.NULL;
import static jsonvalues.JsObj.streamOfObj;
import static jsonvalues.MatchExp.ifJsonElse;
import static jsonvalues.MatchExp.ifNothingElse;
import static jsonvalues.Trampoline.done;
import static jsonvalues.Trampoline.more;

/**
 Represents a json array, which is an ordered list of elements.
 */
public class JsArray implements Json, Iterable {
    public static final int ID = 4;
    /**
     lenses defined for a Json array
     */
    public static final JsOptics.JsArrayLenses lens = JsOptics.array.lens;
    /**
     optionals defined for a Json array
     */
    public static final JsOptics.JsArrayOptionals optional = JsOptics.array.optional;

    public static JsArray EMPTY = new JsArray(Vector.empty());
    /**
     prism between the sum type JsValue and JsArray
     */
    public static Prism prism =
            new Prism<>(
                    s -> s.isArray() ? Optional.of(s.toJsArray()) : Optional.empty(),
                    a -> a
            );
    private final Vector seq;
    private volatile int hashcode;
    //squid:S3077: doesn't make any sense, volatile is perfectly valid here an as a matter of fact
    //is a recommendation from Effective Java to apply the idiom single check for lazy initialization
    @SuppressWarnings("squid:S3077")

    private volatile String str;


    JsArray(Vector seq) {
        this.seq = seq;
    }

    public JsArray(){
        this.seq = Vector.empty();
    }

    public static JsArray empty() {
        return EMPTY;
    }

    /**
     Returns an immutable array from one or more pairs.

     @param pair   a pair
     @param others more optional pairs
     @return an immutable JsArray
     @throws UserError if an elem of a pair is mutable
     */
    public static JsArray of(final JsPair pair,
                             final JsPair... others
                            ) {
        JsArray arr = JsArray.EMPTY.set(pair.path,
                                        pair.value
                                       );
        for (JsPair p : others) {

            arr = arr.set(p.path,
                          p.value
                         );
        }
        return arr;

    }

    /**
     Returns an immutable array.

     @param e    a JsValue
     @param e1   a JsValue
     @param e2   a JsValue
     @param e3   a JsValue
     @param e4   a JsValue
     @param rest more optional JsValue
     @return an immutable JsArray
     @throws UserError if an elem is a mutable Json
     */
    // squid:S00107: static factory methods usually have more than 4 parameters, that's one their advantages precisely
    @SuppressWarnings("squid:S00107")
    public static JsArray of(final JsValue e,
                             final JsValue e1,
                             final JsValue e2,
                             final JsValue e3,
                             final JsValue e4,
                             final JsValue... rest
                            ) {
        JsArray result = of(e,
                            e1,
                            e2,
                            e3,
                            e4
                           );
        for (JsValue other : requireNonNull(rest)) {
            result = result.append(other);
        }
        return result;


    }

    /**
     Returns an immutable five-element array.

     @param e  a JsValue
     @param e1 a JsValue
     @param e2 a JsValue
     @param e3 a JsValue
     @param e4 a JsValue
     @return an immutable five-element JsArray
     @throws UserError if an elem is a mutable Json
     */
    //squid:S00107: static factory methods usually have more than 4 parameters, that's one their advantages precisely
    @SuppressWarnings("squid:S00107")
    public static JsArray of(final JsValue e,
                             final JsValue e1,
                             final JsValue e2,
                             final JsValue e3,
                             final JsValue e4
                            ) {

        return of(e,
                  e1,
                  e2,
                  e3
                 ).append(e4);
    }

    /**
     Adds one or more elements, starting from the first, to the back of this array.

     @param e      the JsValue to be added to the back.
     @param others more optional JsValue to be added to the back
     @return a new JsArray
     */
    public final JsArray append(final JsValue e,
                                final JsValue... others
                               ) {
        Vector acc = this.seq.append(requireNonNull(e));
        for (JsValue other : requireNonNull(others)) acc = acc.append(requireNonNull(other));
        return new JsArray(acc);
    }

    /**
     Returns an immutable four-element array.

     @param e  a JsValue
     @param e1 a JsValue
     @param e2 a JsValue
     @param e3 a JsValue
     @return an immutable four-element JsArray
     @throws UserError if an elem is a mutable Json
     */
    public static JsArray of(final JsValue e,
                             final JsValue e1,
                             final JsValue e2,
                             final JsValue e3
                            ) {
        return of(e,
                  e1,
                  e2
                 ).append(e3);
    }

    /**
     Returns an immutable three-element array.

     @param e  a JsValue
     @param e1 a JsValue
     @param e2 a JsValue
     @return an immutable three-element JsArray
     @throws UserError if an elem is a mutable Json
     */
    public static JsArray of(final JsValue e,
                             final JsValue e1,
                             final JsValue e2
                            ) {

        return of(e,
                  e1
                 ).append(e2);
    }

    /**
     Returns an immutable two-element array.

     @param e  a JsValue
     @param e1 a JsValue
     @return an immutable two-element JsArray
     @throws UserError if an elem is a mutable Json
     */
    public static JsArray of(final JsValue e,
                             final JsValue e1
                            ) {
        return of(e).append(e1);
    }

    public static JsArray of(JsValue e) {

        return JsArray.EMPTY.append(e);
    }

    /**
     Returns an immutable array from one or more strings.

     @param str    a string
     @param others more optional strings
     @return an immutable JsArray
     */
    public static JsArray of(String str,
                             String... others
                            ) {

        Vector vector = Vector.empty().append(JsStr.of(str));
        for (String a : others) {
            vector = vector.append(JsStr.of(a));
        }
        return new JsArray(vector
        );
    }

    /**
     Returns an immutable array from one or more integers.

     @param number an integer
     @param others more optional integers
     @return an immutable JsArray
     */
    public static JsArray of(int number,
                             int... others
                            ) {

        Vector vector = Vector.empty().append(JsInt.of(number));
        for (int a : others) {
            vector = vector.append(JsInt.of(a));
        }
        return new JsArray(vector
        );
    }

    /**
     Returns an immutable array from one or more booleans.

     @param bool   an boolean
     @param others more optional booleans
     @return an immutable JsArray
     */
    public static JsArray of(final boolean bool,
                             final boolean... others
                            ) {
        Vector vector = Vector.empty().append(JsBool.of(bool));
        for (boolean a : others) {
            vector = vector.append(JsBool.of(a));
        }
        return new JsArray(vector
        );
    }

    /**
     Returns an immutable array from one or more longs.

     @param number a long
     @param others more optional longs
     @return an immutable JsArray
     */
    public static JsArray of(final long number,
                             final long... others
                            ) {

        Vector vector = Vector.empty().append(JsLong.of(number));
        for (long a : others) {
            vector = vector.append(JsLong.of(a));
        }
        return new JsArray(vector

        );
    }

    /**
     Returns an immutable array from one or more big integers.

     @param number a big integer
     @param others more optional big integers
     @return an immutable JsArray
     */
    public static JsArray of(final BigInteger number,
                             final BigInteger... others
                            ) {

        Vector vector = Vector.empty().append(JsBigInt.of(number));
        for (BigInteger a : others) {
            vector = vector.append(JsBigInt.of(a));
        }
        return new JsArray(vector);
    }

    /**
     Returns an immutable array from one or more doubles.

     @param number a double
     @param others more optional doubles
     @return an immutable JsArray
     */
    public static JsArray of(final double number,
                             final double... others
                            ) {

        Vector vector = Vector.empty().append(JsDouble.of(number));
        for (double a : others) {
            vector = vector.append(JsDouble.of(a));
        }
        return new JsArray(vector
        );
    }

    /**
     returns an immutable json array from an iterable of json elements

     @param iterable the iterable of json elements
     @return an immutable json array
     */
    public static JsArray ofIterable(Iterable iterable) {
        Vector vector = Vector.empty();
        for (JsValue e : requireNonNull(iterable)) {
            vector = vector.append(e);
        }
        return new JsArray(vector

        );
    }

    /**
     Tries to parse the string into an immutable json array.

     @param str the string to be parsed
     @return a JsArray
     @throws MalformedJson if the string doesnt represent a json array
     */
    public static JsArray parse(final String str) throws MalformedJson {

        try (JsonParser parser = JacksonFactory.INSTANCE.createParser(requireNonNull(str))) {
            final JsonToken keyEvent = parser.nextToken();
            if (START_ARRAY != keyEvent) throw MalformedJson.expectedArray(str);
            return new JsArray(parse(parser
                                    ));
        } catch (IOException e) {

            throw new MalformedJson(e.getMessage());
        }

    }

    static Vector parse(final JsonParser parser
                                ) throws IOException {
        Vector root = Vector.empty();
        while (true) {
            JsonToken token = parser.nextToken();
            JsValue   elem;
            switch (token.id()) {
                case JsonTokenId.ID_END_ARRAY:
                    return root;
                case JsonTokenId.ID_START_OBJECT:
                    elem = new JsObj(JsObj.parse(parser)
                    );
                    break;
                case JsonTokenId.ID_START_ARRAY:
                    elem = new JsArray(parse(parser
                                            )
                    );
                    break;
                case JsonTokenId.ID_STRING:
                    elem = JsStr.of(parser.getValueAsString());
                    break;
                case JsonTokenId.ID_NUMBER_INT:
                    elem = JsNumber.of(parser);
                    break;
                case JsonTokenId.ID_NUMBER_FLOAT:
                    elem = JsBigDec.of(parser.getDecimalValue());
                    break;
                case JsonTokenId.ID_TRUE:
                    elem = TRUE;
                    break;
                case JsonTokenId.ID_FALSE:
                    elem = FALSE;
                    break;
                case JsonTokenId.ID_NULL:
                    elem = NULL;
                    break;
                default:
                    throw InternalError.tokenNotExpected(token.name());
            }
            root = root.append(elem);
        }
    }

    static Stream streamOfArr(final JsArray array,
                                      final JsPath path
                                     ) {


        requireNonNull(path);
        return requireNonNull(array).ifEmptyElse(() -> Stream.of(JsPair.of(path,
                                                                           array
                                                                          )),
                                                 () -> range(0,
                                                             array.size()
                                                            ).mapToObj(pair -> JsPair.of(path.index(pair),
                                                                                         array.get(Index.of(pair))
                                                                                        ))
                                                             .flatMap(pair -> MatchExp.ifJsonElse(o -> streamOfObj(o,
                                                                                                                   pair.path
                                                                                                                  ),
                                                                                                  a -> streamOfArr(a,
                                                                                                                   pair.path
                                                                                                                  ),
                                                                                                  e -> Stream.of(pair)
                                                                                                 )
                                                                                      .apply(pair.value)
                                                                     )
                                                );


    }

    @SuppressWarnings("squid:S00117") //  ARRAY_AS is a perfectly fine name
    private static Trampoline intersection(JsArray a,
                                                    JsArray b,
                                                    JsArray.TYPE ARRAY_AS
                                                   ) {
        switch (ARRAY_AS) {
            case SET:
                return intersectionAsSet(a,
                                         b
                                        );
            case LIST:
                return intersectionAsList(a,
                                          b
                                         );
            case MULTISET:
                return intersectionAsMultiSet(a,
                                              b
                                             );
            default:
                throw InternalError.arrayOptionNotImplemented(ARRAY_AS.name());
        }

    }

    private static Trampoline intersectionAsList(JsArray a,
                                                          JsArray b
                                                         ) {

        if (a.isEmpty()) return done(a);
        if (b.isEmpty()) return done(b);

        final JsValue head = a.head();
        final JsArray tail = a.tail();

        final JsValue otherHead = b.head();
        final JsArray otherTail = b.tail();

        final Trampoline> tailCall = () -> intersectionAsList(tail,
                                                                                  otherTail
                                                                                 );

        if (head.equals(otherHead)) return more(tailCall).map(it -> it.prepend(head));

        return more(tailCall);


    }

    private static Trampoline intersectionAsMultiSet(JsArray a,
                                                              JsArray b
                                                             ) {

        if (a.isEmpty()) return done(a);
        if (b.isEmpty()) return done(b);

        final JsValue head = a.head();
        final JsArray tail = a.tail();

        final Trampoline> tailCall = () -> intersectionAsMultiSet(tail,
                                                                                      b
                                                                                     );

        if (b.containsValue(head)) return more(tailCall).map(it -> it.prepend(head));

        return more(tailCall);
    }

    private static Trampoline intersectionAsSet(JsArray a,
                                                         JsArray b
                                                        ) {
        if (a.isEmpty()) return done(a);
        if (b.isEmpty()) return done(b);

        final JsValue head = a.head();
        final JsArray tail = a.tail();

        final Trampoline> tailCall = () -> intersectionAsSet(tail,
                                                                                 b
                                                                                );

        if (b.containsValue(head) && !tail.containsValue(head))
            return more(tailCall).map(it -> it.prepend(head));

        return more(tailCall);
    }

    private static Trampoline unionAsList(final JsArray a,
                                                   final JsArray b
                                                  ) {
        if (b.isEmpty()) return done(a);
        if (a.isEmpty()) return done(b);
        final Trampoline tailCall = unionAsList(a.tail(),
                                                         b.tail()
                                                        );
        return more(() -> tailCall).map(it -> it.prepend(a.head()));
    }

    private static Trampoline unionAsMultiSet(final JsArray a,
                                                       final JsArray b
                                                      ) {
        if (b.isEmpty()) return done(a);
        if (a.isEmpty()) return done(b);
        return more(() -> () -> a.appendAll(b));
    }

    private static Trampoline unionAsSet(final JsArray a,
                                                  final JsArray b
                                                 ) {
        if (b.isEmpty()) return done(a);
        if (a.isEmpty()) return done(b);
        JsValue last = b.last();
        final Trampoline initCall = unionAsSet(a,
                                                        b.init()
                                                       );
        if (!a.containsValue(last)) return more(() -> initCall).map(it -> it.append(last));
        return more(() -> initCall);
    }

    /**
     Adds all the elements of the given array, starting from the head, to the back of this array.

     @param array the JsArray of elements to be added to the back
     @return a new JsArray
     */
    public final JsArray appendAll(final JsArray array) {
        return appendAllBackTrampoline(this,
                                       requireNonNull(array)
                                      ).get();


    }

    private Trampoline appendAllBackTrampoline(final JsArray arr1,
                                                        final JsArray arr2
                                                       ) {
        assert arr1 != null;
        assert arr2 != null;
        if (arr2.isEmpty()) return Trampoline.done(arr1);
        if (arr1.isEmpty()) return Trampoline.done(arr2);
        return Trampoline.more(() -> appendAllBackTrampoline(arr1.append(arr2.head()),
                                                             arr2.tail()
                                                            ));
    }

    private Trampoline appendAllFrontTrampoline(final JsArray arr1,
                                                         final JsArray arr2
                                                        ) {
        assert arr1 != null;
        assert arr2 != null;
        if (arr2.isEmpty()) return Trampoline.done(arr1);
        if (arr1.isEmpty()) return Trampoline.done(arr2);
        return Trampoline.more(() -> appendAllFrontTrampoline(arr1.prepend(arr2.last()),
                                                              arr2.init()
                                                             ));
    }

    /**
     Returns true if this array is equal to the given as a parameter. In the case of ARRAY_AS=LIST,
     this method is equivalent to JsArray.equals(Object).

     @param array    the given array
     @param ARRAY_AS option to define if arrays are considered SETS, LISTS OR MULTISET
     @return true if both arrays are equals according to ARRAY_AS parameter
     */
    @SuppressWarnings("squid:S00117") //  ARRAY_AS is a perfectly fine name
    public boolean equals(final JsArray array,
                          final TYPE ARRAY_AS
                         ) {
        if (ARRAY_AS == LIST) return this.equals(array);
        if (isEmpty()) return array.isEmpty();
        if (array.isEmpty()) return false;
        return IntStream.range(0,
                               size()
                              )
                        .mapToObj(i -> get(Index.of(i)))
                        .allMatch(elem ->
                                  {
                                      if (!array.containsValue(elem)) return false;
                                      if (ARRAY_AS == MULTISET) return times(elem) == array.times(elem);
                                      return true;
                                  }) && IntStream.range(0,
                                                        array.size()
                                                       )
                                                 .mapToObj(i -> array.get(Index.of(i)))
                                                 .allMatch(this::containsValue);
    }

    private JsValue get(final Position pos) {
        return requireNonNull(pos).match(key -> JsNothing.NOTHING,
                                         index ->
                                         {
                                             if (index == -1 && !this.seq.isEmpty()) return this.seq.last();
                                             return (this.seq.isEmpty() || index < 0 || index > this.seq.size() - 1) ?
                                                    JsNothing.NOTHING : this.seq.get(index);
                                         }
                                        );
    }

    public final boolean containsValue(final JsValue el) {
        return seq.contains(requireNonNull(el));
    }

    @Override
    public JsValue get(final JsPath path) {
        if (path.isEmpty()) return this;
        final JsValue e    = get(path.head());
        final JsPath  tail = path.tail();
        if (tail.isEmpty()) return e;
        if (e.isNotJson()) return NOTHING;
        return e.toJson()
                .get(tail);
    }

    public final JsArray filterValues(final Predicate filter) {
        return new OpFilterArrElems(this).filter(JsPath.empty()
                                                       .index(-1),
                                                 requireNonNull(filter)
                                                )

                                         .get();
    }

    public final JsArray filterAllValues(final Predicate filter) {
        return new OpFilterArrElems(this).filterAll(JsPath.empty()
                                                          .index(-1),
                                                    requireNonNull(filter)
                                                   )

                                         .get();
    }

    public final JsArray filterKeys(final Predicate filter) {
        return this;
    }

    public final JsArray filterAllKeys(final Predicate filter) {
        return new OpFilterArrKeys(this).filterAll(JsPath.empty()
                                                         .index(-1),
                                                   filter
                                                  )
                                        .get();
    }

    public final JsArray filterObjs(final BiPredicate filter) {
        return new OpFilterArrObjs(this).filter(JsPath.empty()
                                                      .index(-1),
                                                requireNonNull(filter)
                                               )

                                        .get();
    }

    public final JsArray filterAllObjs(final BiPredicate filter) {
        return new OpFilterArrObjs(this).filterAll(JsPath.empty()
                                                         .index(-1),
                                                   requireNonNull(filter)
                                                  )
                                        .get();
    }

    public final boolean isEmpty() {
        return seq.isEmpty();
    }

    public final JsArray mapValues(final Function fn) {
        return new OpMapArrElems(this).map(requireNonNull(fn),
                                           JsPath.empty()
                                                 .index(-1)
                                          )
                                      .get();

    }

    public JsArray mapAllValues(final Function fn) {
        return new OpMapArrElems(this).mapAll(requireNonNull(fn),
                                              JsPath.empty()
                                                    .index(-1)
                                             )
                                      .get();
    }

    public final JsArray mapKeys(final Function fn) {
        return this;
    }

    public final JsArray mapAllKeys(final Function fn) {
        return new OpMapArrKeys(this).mapAll(requireNonNull(fn),
                                             JsPath.empty()
                                                   .index(-1)
                                            )
                                     .get();
    }

    public final JsArray mapObjs(final BiFunction fn) {
        return new OpMapArrObjs(this).map(requireNonNull(fn),
                                          JsPath.empty()
                                                .index(-1)
                                         )
                                     .get();
    }

    public final JsArray mapAllObjs(final BiFunction fn) {
        return new OpMapArrObjs(this).mapAll(requireNonNull(fn),
                                             JsPath.empty()
                                                   .index(-1)
                                            )
                                     .get();
    }

    @Override
    public JsArray set(final JsPath path,
                       final JsValue element) {
        return set(path, element,
                   NULL);
    }

    public final JsArray set(final JsPath path,
                             final JsValue value,
                             final JsValue padElement
                            ) {

        requireNonNull(value);
        if (requireNonNull(path).isEmpty()) return this;
        return path.head()
                   .match(head -> this,
                          index ->
                          {
                              final JsPath tail = path.tail();

                              return tail.isEmpty() ? ifNothingElse(() -> this,
                                                                    elem -> new JsArray(nullPadding(index,
                                                                                                    seq,
                                                                                                    elem,
                                                                                                    padElement
                                                                                                   ))
                                                                   )
                                      .apply(value) :
                                     putEmptyJson(seq).test(index,
                                                            tail
                                                           ) ?
                                     new JsArray(nullPadding(index,
                                                             seq,
                                                             tail.head()
                                                                 .match(key -> JsObj.EMPTY
                                                                                .set(tail,
                                                                                     value,
                                                                                     padElement

                                                                                    ),
                                                                        i -> JsArray.EMPTY
                                                                                .set(tail,
                                                                                     value,
                                                                                     padElement
                                                                                    )
                                                                       ),
                                                             padElement
                                                            )) :

                                     new JsArray(seq.update(index,
                                                            seq.get(index)
                                                               .toJson()
                                                               .set(tail,
                                                                    value,
                                                                    padElement
                                                                   )
                                                           ));

                          }

                         );

    }

    public final  Optional reduce(final BinaryOperator op,
                                        final Function map,
                                        final Predicate predicate
                                       ) {
        return new OpMapReduce<>(requireNonNull(predicate),
                                 map,
                                 op
        ).reduce(this);


    }

    public final  Optional reduceAll(final BinaryOperator op,
                                           final Function map,
                                           final Predicate predicate
                                          ) {
        return new OpMapReduce<>(requireNonNull(predicate),
                                 map,
                                 op
        ).reduceAll(this);

    }

    public final JsArray delete(final JsPath path) {
        if (requireNonNull(path).isEmpty()) return this;
        return path.head()
                   .match(head -> this,
                          index ->
                          {
                              final int maxIndex = seq.size() - 1;
                              if (index < -1 || index > maxIndex) return this;
                              final JsPath tail = path.tail();
                              return tail.isEmpty() ? new JsArray(index == -1 ? seq.removeAt(maxIndex) : seq.removeAt(index)) :
                                     ifJsonElse(json -> new JsArray(seq.update(index,
                                                                               json.delete(tail)
                                                                              )),
                                                e -> this
                                               )
                                             .apply(seq.get(index));
                          }

                         );


    }

    public final int size() {
        return seq.size();
    }

    public Stream streamAll() {
        return streamOfArr(this,
                           JsPath.empty()
                          );
    }

    public Stream stream() {
        return IntStream.range(0,
                               size()
                              )
                        .mapToObj(i ->
                                  {
                                      final JsPath path = JsPath.fromIndex(i);
                                      return JsPair.of(path,
                                                       get(path)
                                                      );
                                  });

    }

    private boolean yContainsX(final Vector x,
                               final Vector y
                              ) {
        for (int i = 0; i < x.size(); i++) {
            if (!Objects.equals(x.get(i),
                                y.get(i)
                               ))
                return false;

        }
        return true;

    }

    /**
     returns the element located at the specified index or JsNothing if it doesn't exist. It never throws
     an exception, it's a total function.

     @param i the index
     @return a JsValue
     */
    public JsValue get(final int i) {
        try {
            return seq.get(i);
        } catch (IndexOutOfBoundsException e) {
            return NOTHING;
        }
    }

    /**
     equals method is inherited, so it's implemented. The purpose of this method is to cache
     the hashcode once calculated. the object is immutable and it won't change
     Single-check idiom  Item 83 from Effective Java
     */
    @SuppressWarnings("squid:S1206")

    public final int hashCode() {
        int result = hashcode;
        if (result == 0)
            hashcode = result = seq.hashCode();
        return result;
    }

    public final boolean equals(final Object that) {
        if (!(that instanceof JsArray)) return false;
        if (this == that) return true;
        final Vector thatSeq   = ((JsArray) that).seq;
        final boolean         thatEmpty = thatSeq.isEmpty();
        final boolean         thisEmpty = isEmpty();
        if (thatEmpty && thisEmpty) return true;
        if (this.size() != thatSeq.size()) return false;
        return yContainsX(seq,
                          thatSeq
                         ) && yContainsX(thatSeq,
                                         seq
                                        );

    }

    /**
     // Single-check idiom  Item 83 from effective java
     */
    public final String toString() {
        String result = str;
        if (result == null)
            str = result = new String(INSTANCE.serialize(this));

        return result;
    }

     Trampoline ifEmptyElse(final Trampoline empty,
                                  final BiFunction> fn
                                 ) {
        if (this.isEmpty()) return empty;
        final JsValue head = this.head();
        final JsArray tail = this.tail();
        return fn.apply(head,
                        tail
                       );
    }

    /**
     Returns the first element of this array.

     @return the first JsValue of this JsArray
     @throws UserError if this JsArray is empty
     */
    public final JsValue head() {
        return seq.head();
    }

    /**
     Returns a json array consisting of all elements of this array except the first one.

     @return a JsArray consisting of all the elements of this JsArray except the head
     @throws UserError if this JsArray is empty.
     */
    public final JsArray tail() {
        return new JsArray(seq.tail());
    }

    /**
     Returns all the elements of this array except the last one.

     @return JsArray with all the JsValue except the last one
     @throws UserError if this JsArray is empty
     */
    public final JsArray init() {
        return new JsArray(seq.init());
    }

    /**
     {@code this.intersection(that, SET)} returns an array with the elements that exist in both {@code this}
     and {@code that}.
     {@code this.intersection(that, MULTISET)} returns an array with the elements that exist in both
     {@code this} and {@code that}, being duplicates allowed.
     {@code this.intersection(that, LIST)} returns an array with the elements that exist in both {@code this}
     and {@code that} and are located at the same position.

     @param that     the other array
     @param ARRAY_AS option to define if arrays are considered SETS, LISTS OR MULTISET
     @return a new JsArray of the same type as the inputs (mutable or immutable)
     */
    @SuppressWarnings("squid:S00117") //  ARRAY_AS is a perfectly fine name
    public final JsArray intersection(final JsArray that,
                                      final TYPE ARRAY_AS
                                     ) {
        return intersection(this,
                            requireNonNull(that),
                            requireNonNull(ARRAY_AS)
                           ).get();
    }

    /**
     {@code this.intersectionAll(that)} behaves as {@code this.intersection(that, LIST)}, but for those
     elements that are containers of the same type and are located at the same position, the result
     is their intersection. So this operation is kind of a recursive intersection

     @param that the other array
     @return a JsArray of the same type as the inputs (mutable or immutable)
     */
    public JsArray intersectionAll(final JsArray that) {
        return intersectionAll(this,
                               requireNonNull(that)
                              ).get();
    }

    @Override
    public int id() {
        return ID;
    }

    public boolean isArray() {
        return true;
    }

    public final Iterator iterator() {
        return seq.iterator();
    }

    /**
     Returns the last element of this array.

     @return the last JsValue of this JsArray
     @throws UserError if this JsArray is empty
     */
    public final JsValue last() {

        return seq.last();
    }

    /**
     Adds one or more elements, starting from the last, to the front of this array.

     @param e      the JsValue to be added to the front.
     @param others more optional JsValue to be added to the front
     @return a new JsArray
     */
    public final JsArray prepend(final JsValue e,
                                 final JsValue... others
                                ) {
        Vector acc = seq;
        for (int i = 0, othersLength = requireNonNull(others).length; i < othersLength; i++) {
            final JsValue other = others[othersLength - 1 - i];
            acc = acc.prepend(requireNonNull(other));
        }
        return new JsArray(acc.prepend(requireNonNull(e)));
    }

    /**
     Adds all the elements of the array, starting from the last, to the front of this array.

     @param array the JsArray of elements to be added to the front
     @return a new JsArray
     */
    public final JsArray prependAll(final JsArray array) {
        return appendAllFrontTrampoline(this,
                                        requireNonNull(array)
                                       ).get();

    }

    @SuppressWarnings("squid:S1602")
    // curly braces makes IntelliJ to format the code in a more legible way
    private BiPredicate putEmptyJson(final Vector pseq) {
        return (index, tail) ->
        {
            return index > pseq.size() - 1 || pseq.isEmpty() || pseq.get(index)
                                                                    .isNotJson()
                    ||
                    (tail.head()
                         .isKey() && pseq.get(index)
                                         .isArray()
                    )
                    ||
                    (tail.head()
                         .isIndex() && pseq.get(index)
                                           .isObj()
                    );
        };
    }

    public final JsArray delete(final int index) {
        if (index < -1) throw new IllegalArgumentException("index must be >= -1");
        return delete(JsPath.fromIndex(index));
    }

    /**
     {@code this.union(that, SET)} returns {@code this} plus those elements from {@code that} that
     don't exist in {@code this}.
     {@code this.union(that, MULTISET)} returns {@code this} plus those elements from {@code that}
     appended to the back.
     {@code this.union(that, LIST)} returns {@code this} plus those elements from {@code that} which
     position is {@code >= this.size()}.

     @param that     the other array
     @param ARRAY_AS option to define if arrays are considered SETS, LISTS OR MULTISET
     @return a new json array of the same type as the inputs (mutable or immutable)
     */
    @SuppressWarnings("squid:S00117") //  ARRAY_AS is a perfectly fine name
    public final JsArray union(final JsArray that,
                               final JsArray.TYPE ARRAY_AS
                              ) {
        return union(this,
                     requireNonNull(that),
                     requireNonNull(ARRAY_AS)
                    ).get();
    }

    /**
     returns {@code this} plus those elements from {@code that} which position is  {@code >= this.size()},
     and, at the positions where a container of the same type exists in both {@code this} and {@code that},
     the result is their union. This operations doesn't make any sense if arrays are not considered lists,
     because there is no notion of order.

     @param that the other array
     @return a new JsArray of the same type as the inputs (mutable or immutable)
     */
    @SuppressWarnings("squid:S00100")
    public final JsArray unionAll(final JsArray that
                                 ) {
        return unionAll(this,
                        requireNonNull(that)
                       ).get();
    }

    private Trampoline intersectionAll(final JsArray a,
                                                final JsArray b
                                               ) {
        if (a.isEmpty()) return done(a);
        if (b.isEmpty()) return done(b);

        final JsValue head      = a.head();
        final JsValue otherHead = b.head();

        final Trampoline tailCall = intersectionAsList(a.tail(),
                                                                b.tail()
                                                               );

        if (head.isJson() && head.isSameType(otherHead)) {
            final Json obj  = head.toJson();
            final Json obj1 = otherHead.toJson();

            Trampoline> headCall = more(() -> () -> new OpIntersectionJsons().intersectionAll(obj,
                                                                                                                obj1,
                                                                                                                JsArray.TYPE.LIST
                                                                                                               ));

            return more(() -> tailCall).flatMap(tailResult -> headCall.map(tailResult::prepend));

        }
        else if (head.equals(otherHead))
            return more(() -> tailCall).map(it -> it.prepend(head));
        else return more(() -> tailCall);
    }

    private Vector nullPadding(final int index,
                                        final Vector arr,
                                        final JsValue e,
                                        final JsValue pad
                                       ) {
        assert arr != null;
        assert e != null;

        return nullPaddingTrampoline(index,
                                     arr,
                                     e,
                                    pad
                                    ).get();
    }

    private Trampoline> nullPaddingTrampoline(final int i,
                                                              final Vector arr,
                                                              final JsValue e,
                                                              final JsValue pad
                                                             ) {

        if (i == arr.size()) return Trampoline.done(arr.append(e));

        if (i == -1) return Trampoline.done(arr.update(seq.size() - 1,
                                                       e
                                                      ));

        if (i < arr.size()) return Trampoline.done(arr.update(i,
                                                              e
                                                             ));
        return Trampoline.more(() -> nullPaddingTrampoline(i,
                                                           arr.append(pad),
                                                           e,
                                                           pad
                                                          ));
    }

    @SuppressWarnings("squid:S00117") //  ARRAY_AS is a perfectly fine name
    private Trampoline union(JsArray a,
                                      JsArray b,
                                      JsArray.TYPE ARRAY_AS
                                     ) {
        switch (ARRAY_AS) {
            case SET:
                return unionAsSet(a,
                                  b
                                 );
            case LIST:
                return unionAsList(a,
                                   b
                                  );
            case MULTISET:
                return unionAsMultiSet(a,
                                       b
                                      );
            default:
                throw InternalError.arrayOptionNotImplemented(ARRAY_AS.name());
        }
    }

    private Trampoline unionAll(final JsArray a,
                                         final JsArray b
                                        ) {
        if (b.isEmpty()) return done(a);
        if (a.isEmpty()) return done(b);
        final JsValue head      = a.head();
        final JsValue otherHead = b.head();
        final Trampoline tailCall = unionAll(a.tail(),
                                                      b.tail()
                                                     );
        if (head.isJson() && head.isSameType(otherHead)) {
            final Json obj  = head.toJson();
            final Json obj1 = otherHead.toJson();
            Trampoline> headCall = more(() -> () -> new OpUnionJsons().unionAll(obj,
                                                                                                  obj1,
                                                                                                  JsArray.TYPE.LIST
                                                                                                 ));
            return more(() -> tailCall).flatMap(tailResult -> headCall.map(tailResult::prepend));

        }
        return more(() -> tailCall).map(it -> it.prepend(head));
    }


    /**
     Type of arrays: SET, MULTISET or LIST.
     */
    public enum TYPE {
        /**
         The order of data items does not matter (or is undefined) but duplicate data items are not
         permitted.
         */
        SET,
        /**
         The order of data matters and duplicate data items are permitted.
         */
        LIST,
        /**
         The order of data items does not matter, but in this
         case duplicate data items are permitted.
         */
        MULTISET
    }



}







© 2015 - 2025 Weber Informatics LLC | Privacy Policy