content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed 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 com.alibaba.nacos.common.utils;
  
  import java.lang.reflect.Array;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.Set;
  
  /**
   * Copy from {@link org.apache.commons.collections}.
   *
   * @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
   */
  public final class CollectionUtils {
      
      /**
       * Constant to avoid repeated object creation.
       */
      private static final Integer INTEGER_ONE = 1;
  
      private CollectionUtils() {
      }
      
      /**
       * Returns the <code>index</code>-th value in <code>object</code>, throwing
       * <code>IndexOutOfBoundsException</code> if there is no such element or
       * <code>IllegalArgumentException</code> if <code>object</code> is not an
       * instance of one of the supported types.
       *
       * <p>The supported types, and associated semantics are:
       * <ul>
       * <li> Map -- the value returned is the <code>Map.Entry</code> in position
       *      <code>index</code> in the map's <code>entrySet</code> iterator,
       *      if there is such an entry.</li>
       * <li> List -- this method is equivalent to the list's get method.</li>
       * <li> Array -- the <code>index</code>-th array entry is returned,
       *      if there is such an entry; otherwise an <code>IndexOutOfBoundsException</code>
       *      is thrown.</li>
       * <li> Collection -- the value returned is the <code>index</code>-th object
       *      returned by the collection's default iterator, if there is such an element.</li>
       * <li> Iterator or Enumeration -- the value returned is the
       *      <code>index</code>-th object in the Iterator/Enumeration, if there
       *      is such an element.  The Iterator/Enumeration is advanced to
       *      <code>index</code> (or to the end, if <code>index</code> exceeds the
       *      number of entries) as a side effect of this method.</li>
       * </ul>
       *
       * @param object the object to get a value from
       * @param index  the index to get
       * @return the object at the specified index
       * @throws IndexOutOfBoundsException if the index is invalid
       * @throws IllegalArgumentException  if the object type is invalid
       */
      public static Object get(Object object, int index) {
          if (index < 0) {
              throw new IndexOutOfBoundsException("Index cannot be negative: " + index);
          }
          if (object instanceof Map) {
              Map map = (Map) object;
              Iterator iterator = map.entrySet().iterator();
              return get(iterator, index);
          } else if (object instanceof List) {
              return ((List) object).get(index);
          } else if (object instanceof Object[]) {
              return ((Object[]) object)[index];
          } else if (object instanceof Iterator) {
              Iterator it = (Iterator) object;
              while (it.hasNext()) {
                  index--;
                  if (index == -1) {
                      return it.next();
                  } else {
                      it.next();
                  }
              }
              throw new IndexOutOfBoundsException("Entry does not exist: " + index);
          } else if (object instanceof Collection) {
              Iterator iterator = ((Collection) object).iterator();
              return get(iterator, index);
          } else if (object instanceof Enumeration) {
              Enumeration it = (Enumeration) object;
              while (it.hasMoreElements()) {
                  index--;
                  if (index == -1) {
                      return it.nextElement();
                  } else {
                      it.nextElement();
                  }
              }
              throw new IndexOutOfBoundsException("Entry does not exist: " + index);
          } else if (object == null) {
              throw new IllegalArgumentException("Unsupported object type: null");
          } else {
              try {
                  return Array.get(object, index);
              } catch (IllegalArgumentException ex) {
                  throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
              }
          }
      }
      
      /**
       * Gets the size of the collection/iterator specified.
       *
       * <p>This method can handles objects as follows
       * <ul>
       * <li>Collection - the collection size
       * <li>Map - the map size
       * <li>Array - the array size
       * <li>Iterator - the number of elements remaining in the iterator
       * <li>Enumeration - the number of elements remaining in the enumeration
       * </ul>
       *
       * @param object the object to get the size of
       * @return the size of the specified collection
       * @throws IllegalArgumentException thrown if object is not recognised or null
       * @since Commons Collections 3.1
       */
      public static int size(Object object) {
          int total = 0;
          if (object instanceof Map) {
              total = ((Map) object).size();
          } else if (object instanceof Collection) {
              total = ((Collection) object).size();
          } else if (object instanceof Object[]) {
              total = ((Object[]) object).length;
          } else if (object instanceof Iterator) {
              Iterator it = (Iterator) object;
              while (it.hasNext()) {
                  total++;
                  it.next();
              }
          } else if (object instanceof Enumeration) {
              Enumeration it = (Enumeration) object;
              while (it.hasMoreElements()) {
                  total++;
                  it.nextElement();
              }
          } else if (object == null) {
              throw new IllegalArgumentException("Unsupported object type: null");
          } else {
              try {
                  total = Array.getLength(object);
              } catch (IllegalArgumentException ex) {
                  throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
              }
          }
          return total;
      }
      
      /**
       * Judge whether object is empty.
       *
       * @param object object
       * @return true if object is empty, otherwise false
       * @throws IllegalArgumentException if object has no length or size
       */
      public static boolean sizeIsEmpty(Object object) {
          if (object instanceof Collection) {
              return ((Collection) object).isEmpty();
          } else if (object instanceof Map) {
              return ((Map) object).isEmpty();
          } else if (object instanceof Object[]) {
              return ((Object[]) object).length == 0;
          } else if (object instanceof Iterator) {
              return ((Iterator) object).hasNext() == false;
          } else if (object instanceof Enumeration) {
              return ((Enumeration) object).hasMoreElements() == false;
          } else if (object == null) {
              throw new IllegalArgumentException("Unsupported object type: null");
          } else {
              try {
                  return Array.getLength(object) == 0;
              } catch (IllegalArgumentException ex) {
                  throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
              }
          }
      }
      
      /**
       * Whether contain item in collection.
       *
       * @param coll   collection
       * @param target target value
       * @param <T>    General Type
       * @return true if contain, otherwise false
       */
      public static <T> boolean contains(Collection<T> coll, T target) {
          if (isEmpty(coll)) {
              return false;
          }
          return coll.contains(target);
      }
      
      /**
       * Null-safe check if the specified collection is empty.
       *
       * <p>Null returns true.
       *
       * @param coll the collection to check, may be null
       * @return true if empty or null
       * @since Commons Collections 3.2
       */
      public static boolean isEmpty(Collection coll) {
          return (coll == null || coll.isEmpty());
      }
      
      /**
       * Null-safe check if the specified collection is not empty.
       *
       * <p>Null returns false.
       *
       * @param coll the collection to check, may be null
       * @return true if non-null and non-empty
       * @since Commons Collections 3.2
       */
      public static boolean isNotEmpty(Collection coll) {
          return !CollectionUtils.isEmpty(coll);
      }
      
      /**
       * Returns the value to which the specified index , or {@code defaultValue} if this collection contains no value for
       * the index.
       *
       * @param obj          the object to get a value from
       * @param index        the index to get
       * @param defaultValue default value
       * @param <T>          General Type
       * @return the value to which the specified index , or {@code defaultValue} if this collection contains no value for
       * the index.
       */
      public static <T> T getOrDefault(Object obj, int index, T defaultValue) {
          try {
              return (T) get(obj, index);
          } catch (IndexOutOfBoundsException e) {
              return defaultValue;
          }
      }
      
      /**
       * return an arraylist containing all input parameters.
       *
       * @param elements element array
       * @return arraylist containing all input parameters
       * @author zzq
       */
      public static <T> List<T> list(T... elements) {
          if (elements == null) {
              throw new IllegalArgumentException("Expected an array of elements (or empty array) but received a null.");
          }
          ArrayList<T> list = new ArrayList<>(elements.length);
          Collections.addAll(list, elements);
          return list;
      }
      
      /**
       * Return a set containing all input parameters.
       *
       * @param elements elements element array
       * @return set containing all input parameters
       */
      public static <T> Set<T> set(T... elements) {
          if (elements == null) {
              throw new IllegalArgumentException("Expected an array of elements (or empty array) but received a null.");
          } else {
              return new LinkedHashSet<>(Arrays.asList(elements));
          }
      }
      
      /**
       * return the first element, if the iterator contains multiple elements, will throw {@code
       * IllegalArgumentException}.
       *
       * @throws NoSuchElementException   if the iterator is empty
       * @throws IllegalArgumentException if the iterator contains multiple elements. The state of the iterator is
       *                                  unspecified.
       */
      public static <T> T getOnlyElement(Iterable<T> iterable) {
          if (iterable == null) {
              throw new IllegalArgumentException("iterable cannot be null.");
          }
          Iterator<T> iterator = iterable.iterator();
          T first = iterator.next();
          if (!iterator.hasNext()) {
              return first;
          }
          throw new IllegalArgumentException(buildExceptionMessage(iterator, first));
      }
      
      @SuppressWarnings("PMD.UndefineMagicConstantRule")
      private static <T> String buildExceptionMessage(Iterator<T> iterator, T first) {
          StringBuilder msg = new StringBuilder();
          msg.append("expected one element but was: <");
          msg.append(first);
          for (int i = 0; i < 4 && iterator.hasNext(); i++) {
              msg.append(", ");
              msg.append(iterator.next());
          }
          if (iterator.hasNext()) {
              msg.append(", ...");
          }
          msg.append('>');
          return msg.toString();
      }
      
      /**
       * Return {@code true} if the supplied Map is {@code null} or empty. Otherwise, return {@code false}.
       *
       * @param map the Map to check
       * @return whether the given Map is empty
       */
      public static boolean isMapEmpty(Map<?, ?> map) {
          return (map == null || map.isEmpty());
      }
      
      /**
       * Returns a {@link Map} mapping each unique element in the given {@link Collection} to an {@link Integer}
       * representing the number of occurrences of that element in the {@link Collection}.
       *
       * <p>Only those elements present in the collection will appear as keys in the map.
       *
       * @param coll the collection to get the cardinality map for, must not be null
       * @return the populated cardinality map
       */
      public static Map getCardinalityMap(final Collection coll) {
          Map count = new HashMap(coll.size());
          for (Iterator it = coll.iterator(); it.hasNext(); ) {
              Object obj = it.next();
              Integer c = (Integer) (count.get(obj));
              if (c == null) {
                  count.put(obj, INTEGER_ONE);
              } else {
                  count.put(obj, c + 1);
              }
          }
          return count;
      }
      
      /**
       * Returns <tt>true</tt> iff the given {@link Collection}s contain exactly the same elements with exactly the same
       * cardinalities.
       *
       * <p>That is, iff the cardinality of <i>e</i> in <i>a</i> is equal to the cardinality of <i>e</i> in <i>b</i>, for
       * each element <i>e</i> in <i>a</i> or <i>b</i>.
       *
       * @param a the first collection, must not be null
       * @param b the second collection, must not be null
       * @return <code>true</code> iff the collections contain the same elements with the same cardinalities.
       */
      public static boolean isEqualCollection(final Collection a, final Collection b) {
          if (a.size() != b.size()) {
              return false;
          } else {
              Map mapa = getCardinalityMap(a);
              Map mapb = getCardinalityMap(b);
              if (mapa.size() != mapb.size()) {
                  return false;
              } else {
                  Iterator it = mapa.keySet().iterator();
                  while (it.hasNext()) {
                      Object obj = it.next();
                      if (getFreq(obj, mapa) != getFreq(obj, mapb)) {
                          return false;
                      }
                  }
                  return true;
              }
          }
      }
      
      private static int getFreq(final Object obj, final Map freqMap) {
          Integer count = (Integer) freqMap.get(obj);
          if (count != null) {
              return count.intValue();
          }
          return 0;
      }
  }
  