content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *       http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.causeway.commons.internal.base;
  
  import java.lang.reflect.Array;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.EnumSet;
  import java.util.Enumeration;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Set;
  import java.util.Spliterator;
  import java.util.Spliterators;
  import java.util.function.Consumer;
  import java.util.stream.IntStream;
  import java.util.stream.Stream;
  import java.util.stream.StreamSupport;
  
  import org.jspecify.annotations.Nullable;
  
  import org.apache.causeway.commons.collections.Can;
  import org.apache.causeway.commons.collections.ImmutableCollection;
  import org.apache.causeway.commons.collections.ImmutableEnumSet;
  
  /**
   * <h1>- internal use only -</h1>
   * <p>
   *  Provides convenient null check / null safe methods primarily
   * to shortcut null-check idioms.
   * </p>
   * <p>
   * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
   * These may be changed or removed without notice!
   * </p>
   *
   * @since 2.0
   *
   */
  public final class _NullSafe {
  
      private _NullSafe(){}
  
      // -- STREAM CREATION
  
      /**
       * If {@code array} is {@code null} returns the empty stream,
       * otherwise returns a stream of the array's elements.
       * @param array
       * @return non-null stream object
       */
      public static <T> Stream<T> stream(final @Nullable  T[] array) {
          return array!=null
                  ? Stream.of(array)
                  : Stream.empty();
      }
  
      /**
       * If {@code nullable} is {@code null} returns the empty stream,
       * otherwise returns a Stream containing the single element {@code nullable}.
       *
       * @param nullable
       * @return non-null stream object
       */
      public static <T> Stream<T> streamNullable(final @Nullable T nullable) {
          return nullable != null
                  ? Stream.of(nullable)
                  : Stream.empty();
      }
  
      /**
       * If {@code can} is {@code null} returns the empty stream,
       * otherwise returns a stream of the can's elements.
       * @param can
       * @return non-null stream object
       */
      public static <T> Stream<T> stream(final @Nullable Can<T> can){
          return can!=null
                  ? can.stream()
                  : Stream.empty();
      }
  
      /**
       * If {@code collection} is {@code null} returns the empty stream,
       * otherwise returns a stream of the collection's elements.
       * @param coll
       * @return non-null stream object
       */
      public static <T> Stream<T> stream(final @Nullable Collection<T> coll){
          return coll!=null
                  ? coll.stream()
                  : Stream.empty();
      }
  
      /**
       * If {@code iterable} is {@code null} returns the empty stream,
       * otherwise returns a stream of the iterable's elements.
       * @param iterable
       * @return non-null stream object
       */
      public static <T> Stream<T> stream(final @Nullable Iterable<T> iterable){
          if(iterable instanceof Collection) {
              return ((Collection<T>) iterable).stream();
          }
          if(iterable instanceof Can) {
              return ((Can<T>) iterable).stream();
          }
          return iterable!=null
                  ? stream(iterable.iterator())
                  : Stream.empty();
      }
  
      /**
       * If {@code iterator} is {@code null} returns the empty stream,
       * otherwise returns a stream of the iterator's elements.
       * @param iterator
       * @return non-null stream object
       */
      public static <T> Stream<T> stream(final @Nullable Iterator<T> iterator){
          return iterator!=null
                  ? StreamSupport.stream(
                          Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED),
                          false) //not parallel
                  : Stream.empty();
      }
  
      /**
       * If {@code stream} is {@code null} returns the empty stream,
       * otherwise returns the stream argument.
       * @param stream
       * @return non-null stream object
       */
      public static <T> Stream<T> stream(final @Nullable Stream<T> stream) {
          return stream!=null
                  ? stream
                  : Stream.empty();
      }
  
      /**
       * If {@code enumeration} is {@code null} returns the empty stream,
       * otherwise returns a stream of the enumeration's elements.
       * @param enumeration
       * @return non-null stream object
       */
      public static <T> Stream<T> stream(final @Nullable Enumeration<T> enumeration){
          return enumeration!=null
                  ? StreamSupport.stream(toSpliterator(enumeration), /*parallel*/false)
                  : Stream.empty();
      }
  
      // not public, used internally for stream(Enumeration) only
      private static <T> Spliterator<T> toSpliterator(final Enumeration<T> e){
          return new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) {
              @Override
              public boolean tryAdvance(final Consumer<? super T> action) {
                  if(e.hasMoreElements()) {
                      action.accept(e.nextElement());
                      return true;
                  }
                  return false;
              }
              @Override
              public void forEachRemaining(final Consumer<? super T> action) {
                  while(e.hasMoreElements()) {
                      action.accept(e.nextElement());
                  }
              }
          };
      }
  
      /**
       * Returns a stream of elements contained by given pojo.
       * Supports {@link Collection}, arrays, {@link Map}, etc.
       * <p>
       * In case of a {@link Map} traverses the map's values.
       */
      public static Stream<?> streamAutodetect(final @Nullable Object pojo) {
          if(pojo==null) {
              return Stream.empty();
          }
          if(pojo instanceof Collection) {
              return ((Collection<?>)pojo).stream();
          }
          if(pojo instanceof ImmutableCollection) {
              return ((ImmutableCollection<?>)pojo).stream();
          }
          if(pojo.getClass().isArray()) {
              if(Array.getLength(pojo)==0) return Stream.empty();
              if(pojo instanceof Object[]) return Stream.of((Object[]) pojo);
              if(pojo instanceof boolean[]) return primitiveStream((boolean[]) pojo);
              if(pojo instanceof byte[]) return primitiveStream((byte[]) pojo);
              if(pojo instanceof char[]) return primitiveStream((char[]) pojo);
              if(pojo instanceof double[]) return primitiveStream((double[]) pojo);
              if(pojo instanceof float[]) return primitiveStream((float[]) pojo);
              if(pojo instanceof int[]) return primitiveStream((int[]) pojo);
              if(pojo instanceof long[]) return primitiveStream((long[]) pojo);
              if(pojo instanceof short[]) return primitiveStream((short[]) pojo);
          }
          if(pojo instanceof Map) {
              return ((Map<?, ?>)pojo).values().stream();
          }
          if(pojo instanceof Iterable) {
              return stream((Iterable<?>)pojo);
          }
          if(pojo instanceof Enumeration) {
              return stream((Enumeration<?>)pojo);
          }
          return Stream.of(pojo);
      }
  
      // not null-safe, but for performance reasons not checked (private anyway) ...
  
      private static Stream<Boolean> primitiveStream(final boolean[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      private static Stream<Byte> primitiveStream(final byte[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      private static Stream<Character> primitiveStream(final char[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      private static Stream<Float> primitiveStream(final float[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      private static Stream<Double> primitiveStream(final double[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      private static Stream<Short> primitiveStream(final short[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      private static Stream<Integer> primitiveStream(final int[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      private static Stream<Long> primitiveStream(final long[] array) {
          return IntStream.range(0, array.length).mapToObj(s -> array[s]);
      }
  
      // -- ABSENCE/PRESENCE PREDICATES
  
      /**
       * Equivalent to {@link java.util.Objects#nonNull(Object)}.
       * @param x
       * @return whether {@code x} is not null (present).
       *
       * @apiNote we keep this, arguably provides better code readability than {@code Objects#nonNull}
       */
      public static boolean isPresent(final @Nullable Object x) {
          return x!=null;
      }
  
      /**
       * Equivalent to {@link java.util.Objects#isNull(Object)}.
       * @param x
       * @return whether {@code x} is null (absent).
       *
       * @apiNote we keep this, arguably provides better code readability than {@code Objects#isNull}
       */
      public static boolean isAbsent(final @Nullable Object x) {
          return x==null;
      }
  
      // -- EMTPY CHECKS
  
      public static boolean isEmpty(final @Nullable String x) { return x==null || x.length() == 0; }
      public static boolean isEmpty(final @Nullable Can<?> x) { return x==null || x.size() == 0; }
      public static boolean isEmpty(final @Nullable Collection<?> x) { return x==null || x.size() == 0; }
      public static boolean isEmpty(final @Nullable Map<?,?> x) { return x==null || x.size() == 0; }
      public static boolean isEmpty(final @Nullable boolean[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable byte[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable char[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable double[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable float[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable int[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable long[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable short[] array){ return array==null || array.length == 0;}
      public static <T> boolean isEmpty(final @Nullable T[] array){ return array==null || array.length == 0;}
      public static boolean isEmpty(final @Nullable EnumSet<?> enumSet){ return enumSet==null || enumSet.size() == 0;}
      public static boolean isEmpty(final @Nullable ImmutableEnumSet<?> enumSet){ return enumSet==null || enumSet.size() == 0;}
  
      // -- SIZE/LENGTH CHECKS
  
      public static int size(final @Nullable String x){ return x!=null ? x.length() : 0; }
      public static int size(final @Nullable Collection<?> x){ return x!=null ? x.size() : 0; }
      public static int size(final @Nullable Map<?,?> x){ return x!=null ? x.size() : 0; }
      public static int size(final @Nullable boolean[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable byte[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable char[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable double[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable float[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable int[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable long[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable short[] array){ return array!=null ? array.length : 0; }
      public static <T> int size(final @Nullable T[] array){ return array!=null ? array.length : 0; }
      public static int size(final @Nullable EnumSet<?> enumSet){ return enumSet!=null ? enumSet.size() : 0; }
      public static int size(final @Nullable ImmutableEnumSet<?> enumSet){ return enumSet!=null ? enumSet.size() : 0; }
      public static int sizeAutodetect(final @Nullable Object pojo) {
          if(pojo==null) {
              return 0;
          }
          if(pojo instanceof Collection) {
              return ((Collection<?>)pojo).size();
          }
          if(pojo instanceof ImmutableCollection) {
              return ((ImmutableCollection<?>)pojo).size();
          }
          if(pojo.getClass().isArray()) {
              return Array.getLength(pojo);
          }
          if(pojo instanceof Map) {
              return ((Map<?, ?>)pojo).size();
          }
          return Math.toIntExact(streamAutodetect(pojo).count());
      }
  
      // -- NON-NULL VARIANTS
  
      public static String toNonNull(final @Nullable String x){ return x!=null ? x : ""; }
      public static boolean[] toNonNull(final @Nullable boolean[] array){ return array!=null ? array : new boolean[0]; }
      public static byte[] toNonNull(final @Nullable byte[] array){ return array!=null ? array : new byte[0]; }
      public static char[] toNonNull(final @Nullable char[] array){ return array!=null ? array : new char[0]; }
      public static double[] toNonNull(final @Nullable double[] array){ return array!=null ? array : new double[0]; }
      public static float[] toNonNull(final @Nullable float[] array){ return array!=null ? array : new float[0]; }
      public static int[] toNonNull(final @Nullable int[] array){ return array!=null ? array : new int[0]; }
      public static long[] toNonNull(final @Nullable long[] array){ return array!=null ? array : new long[0]; }
      public static short[] toNonNull(final @Nullable short[] array){ return array!=null ? array : new short[0]; }
      public static <T> T[] toNonNull(final @Nullable T[] array){ return array!=null ? array : _Casts.uncheckedCast(new Object[0]); }
  
      // -- TO STRING
  
      public static String toString(final @Nullable Object obj) {
          return obj!=null
                  ? obj.toString()
                  : null;
      }
  
      // -- MAP
  
      /**
       * Null-safe variant of {@link java.util.Map#getOrDefault(Object, Object)}
       * @param map
       * @param key
       * @param defaultValue - (null-able)
       * @return (null-able)
       */
      @Nullable
      public static final <K,V> V getOrDefault(
              final @Nullable Map<K, V> map,
              final @Nullable K key,
              final @Nullable V defaultValue) {
  
          if(map==null || key==null) {
              return defaultValue;
          }
          return map.getOrDefault(key, defaultValue);
      }
  
      /**
       * Null-safe variant of {@link java.util.Map#entrySet()}
       * @param <K>
       * @param <V>
       * @param map
       */
      public static <K, V> Set<Map.Entry<K, V>> entrySet(final @Nullable Map<K, V> map) {
          return map==null
                  ? Collections.emptySet()
                  : map.entrySet();
      }
  
  }
  