001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import static org.apache.juneau.internal.StringUtils.*;
016import static org.apache.juneau.internal.ThrowableUtils.*;
017
018import java.lang.reflect.*;
019import java.util.*;
020
021/**
022 * Quick and dirty utilities for working with arrays.
023 */
024public final class ArrayUtils {
025
026   /**
027    * Appends one or more elements to an array.
028    *
029    * @param <T> The element type.
030    * @param array The array to append to.
031    * @param newElements The new elements to append to the array.
032    * @return A new array with the specified elements appended.
033    */
034   @SuppressWarnings("unchecked")
035   public static <T> T[] append(T[] array, T...newElements) {
036      if (array == null)
037         return newElements;
038      if (newElements.length == 0)
039         return array;
040      T[] a = (T[])Array.newInstance(array.getClass().getComponentType(), array.length + newElements.length);
041      for (int i = 0; i < array.length; i++)
042         a[i] = array[i];
043      for (int i = 0; i < newElements.length; i++)
044         a[i+array.length] = newElements[i];
045      return a;
046   }
047
048   /**
049    * Appends one or more elements to an array.
050    *
051    * @param <T> The element type.
052    * @param array The array to append to.
053    * @param newElements The new elements to append to the array.
054    * @return A new array with the specified elements appended.
055    */
056   @SuppressWarnings("unchecked")
057   public static <T> T[] append(T[] array, Collection<T> newElements) {
058      assertFieldNotNull(array, "array");
059      if (newElements.size() == 0)
060         return array;
061      T[] a = (T[])Array.newInstance(array.getClass().getComponentType(), array.length + newElements.size());
062      for (int i = 0; i < array.length; i++)
063         a[i] = array[i];
064      int l = array.length;
065      for (T t : newElements)
066         a[l++] = t;
067      return a;
068   }
069
070   /**
071    * Combine an arbitrary number of arrays into a single array.
072    *
073    * @param arrays Collection of arrays to combine.
074    * @return A new combined array, or <jk>null</jk> if all arrays are <jk>null</jk>.
075    */
076   @SuppressWarnings("unchecked")
077   public static <T> T[] combine(T[]...arrays) {
078      assertFieldNotNull(arrays, "arrays");
079      int l = 0;
080      T[] a1 = null;
081      for (T[] a : arrays) {
082         if (a1 == null && a != null)
083            a1 = a;
084         l += (a == null ? 0 : a.length);
085      }
086      if (a1 == null)
087         return null;
088      T[] a = (T[])Array.newInstance(a1.getClass().getComponentType(), l);
089      int i = 0;
090      for (T[] aa : arrays)
091         if (aa != null)
092            for (T t : aa)
093               a[i++] = t;
094      return a;
095   }
096
097   /**
098    * Creates a new array with reversed entries.
099    *
100    * @param <T> The class type of the array.
101    * @param array The array to reverse.
102    * @return A new array with reversed entries, or <jk>null</jk> if the array was <jk>null</jk>.
103    */
104   @SuppressWarnings("unchecked")
105   public static <T> T[] reverse(T[] array) {
106      if (array == null)
107         return null;
108      Class<T> c = (Class<T>)array.getClass().getComponentType();
109      T[] a2 = (T[])Array.newInstance(c, array.length);
110      for (int i = 0; i < array.length; i++)
111         a2[a2.length-i-1] = array[i];
112      return a2;
113   }
114
115   /**
116    * Sorts the elements in an array without creating a new array.
117    *
118    * @param array The array to sort.
119    * @return The same array.
120    */
121   public static <T> T[] reverseInline(T[] array) {
122      if (array == null)
123         return null;
124      T t;
125      for (int i = 0, j = array.length-1; i < j; i++, j--) {
126         t = array[i];
127         array[i] = array[j];
128         array[j] = t;
129      }
130      return array;
131   }
132
133   /**
134    * Converts the specified array to a <code>Set</code>.
135    *
136    * <p>
137    * The order of the entries in the set are the same as the array.
138    *
139    * @param <T> The entry type of the array.
140    * @param array The array being wrapped in a <code>Set</code> interface.
141    * @return The new set.
142    */
143   public static <T> Set<T> asSet(final T[] array) {
144      assertFieldNotNull(array, "array");
145      return new AbstractSet<T>() {
146
147         @Override /* Set */
148         public Iterator<T> iterator() {
149            return new Iterator<T>() {
150               int i = 0;
151
152               @Override /* Iterator */
153               public boolean hasNext() {
154                  return i < array.length;
155               }
156
157               @Override /* Iterator */
158               public T next() {
159                  if (i >= array.length)
160                     throw new NoSuchElementException();
161                  T t = array[i];
162                  i++;
163                  return t;
164               }
165
166               @Override /* Iterator */
167               public void remove() {
168                  throw new UnsupportedOperationException();
169               }
170            };
171         }
172
173         @Override /* Set */
174         public int size() {
175            return array.length;
176         }
177      };
178   }
179
180   /**
181    * Returns an iterator against an array.
182    *
183    * <p>
184    * This works with any array type (e.g. <code>String[]</code>, <code>Object[]</code>,
185    * <code><jk>int</jk>[]</code>, etc...).
186    *
187    * @param array The array to create an iterator over.
188    * @return An iterator over the specified array.
189    */
190   public static Iterator<Object> iterator(final Object array) {
191      return new Iterator<Object>() {
192         int i = 0;
193         int length = array == null ? 0 : Array.getLength(array);
194
195         @Override /* Iterator */
196         public boolean hasNext() {
197            return i < length;
198         }
199
200         @Override /* Iterator */
201         public Object next() {
202            if (i >= length)
203               throw new NoSuchElementException();
204            return Array.get(array, i++);
205         }
206
207         @Override /* Iterator */
208         public void remove() {
209            throw new UnsupportedOperationException();
210         }
211      };
212   }
213
214   /**
215    * Converts the specified collection to an array.
216    *
217    * <p>
218    * Works on both object and primitive arrays.
219    *
220    * @param c The collection to convert to an array.
221    * @param componentType The component type of the collection.
222    * @return A new array.
223    */
224   public static <T> Object toArray(Collection<?> c, Class<T> componentType) {
225      Object a = Array.newInstance(componentType, c.size());
226      Iterator<?> it = c.iterator();
227      int i = 0;
228      while (it.hasNext())
229         Array.set(a, i++, it.next());
230      return a;
231   }
232
233   /**
234    * Returns <jk>true</jk> if the specified object is an array.
235    *
236    * @param array The array to test.
237    * @return <jk>true</jk> if the specified object is an array.
238    */
239   public static boolean isArray(Object array) {
240      return array != null && array.getClass().isArray();
241   }
242
243   /**
244    * Converts the specified array to an <code>ArrayList</code>
245    *
246    * @param array The array to convert.
247    * @param componentType
248    *    The type of objects in the array.
249    *    It must match the actual component type in the array.
250    * @return A new {@link ArrayList}
251    */
252   @SuppressWarnings("unchecked")
253   public static <T> List<T> toList(Object array, Class<T> componentType) {
254      List<T> l = new ArrayList<>(Array.getLength(array));
255      for (int i = 0; i < Array.getLength(array); i++)
256         l.add((T)Array.get(array, i));
257      return l;
258   }
259
260   /**
261    * Shortcut for calling <code>myList.toArray(new T[myList.size()]);</code>
262    *
263    * @param c The collection being converted to an array.
264    * @param componentType The component type of the array.
265    * @return The collection converted to an array.
266    */
267   @SuppressWarnings("unchecked")
268   public static <T> T[] toObjectArray(Collection<?> c, Class<T> componentType) {
269      Object a = Array.newInstance(componentType, c.size());
270      Iterator<?> it = c.iterator();
271      int i = 0;
272      while (it.hasNext())
273         Array.set(a, i++, it.next());
274      return (T[])a;
275   }
276
277   /**
278    * Copies the specified array into the specified list.
279    *
280    * <p>
281    * Works on both object and primitive arrays.
282    *
283    * @param array The array to copy into a list.
284    * @param list The list to copy the values into.
285    * @return The same list passed in.
286    */
287   @SuppressWarnings({"unchecked","rawtypes"})
288   public static List copyToList(Object array, List list) {
289      if (array != null) {
290         int length = Array.getLength(array);
291         for (int i = 0; i < length; i++)
292            list.add(Array.get(array, i));
293      }
294      return list;
295   }
296
297   /**
298    * Returns <jk>true</jk> if the specified array contains the specified element using the {@link Object#equals(Object)}
299    * method.
300    *
301    * @param element The element to check for.
302    * @param array The array to check.
303    * @return
304    *    <jk>true</jk> if the specified array contains the specified element,
305    *    <jk>false</jk> if the array or element is <jk>null</jk>.
306    */
307   public static <T> boolean contains(T element, T[] array) {
308      return indexOf(element, array) != -1;
309   }
310
311   /**
312    * Returns <jk>true</jk> if the specified array contains the specified integer
313    *
314    * @param element The element to check for.
315    * @param array The array to check.
316    * @return
317    *    <jk>true</jk> if the specified array contains the specified element,
318    *    <jk>false</jk> if the array or element is <jk>null</jk>.
319    */
320   public static boolean contains(int element, int[] array) {
321      if (array != null)
322         for (int i : array)
323            if (element == i)
324               return true;
325      return false;
326   }
327
328   /**
329    * Returns the index position of the element in the specified array using the {@link Object#equals(Object)} method.
330    *
331    * @param element The element to check for.
332    * @param array The array to check.
333    * @return
334    *    The index position of the element in the specified array, or <code>-1</code> if the array doesn't contain the
335    *    element, or the array or element is <jk>null</jk>.
336    */
337   public static <T> int indexOf(T element, T[] array) {
338      if (element == null)
339         return -1;
340      if (array == null)
341         return -1;
342      for (int i = 0; i < array.length; i++)
343         if (element.equals(array[i]))
344            return i;
345      return -1;
346   }
347
348   /**
349    * Returns <jk>true</jk> if the specified array contains the specified element using the {@link String#equals(Object)}
350    * method.
351    *
352    * @param element The element to check for.
353    * @param array The array to check.
354    * @return
355    *    <jk>true</jk> if the specified array contains the specified element,
356    *    <jk>false</jk> if the array or element is <jk>null</jk>.
357    */
358   public static boolean contains(String element, String[] array) {
359      return indexOf(element, array) != -1;
360   }
361
362   /**
363    * Returns the index position of the element in the specified array using the {@link String#equals(Object)} method.
364    *
365    * @param element The element to check for.
366    * @param array The array to check.
367    * @return
368    *    The index position of the element in the specified array, or
369    *    <code>-1</code> if the array doesn't contain the element, or the array or element is <jk>null</jk>.
370    */
371   public static int indexOf(String element, String[] array) {
372      if (element == null)
373         return -1;
374      if (array == null)
375         return -1;
376      for (int i = 0; i < array.length; i++)
377         if (element.equals(array[i]))
378            return i;
379      return -1;
380   }
381
382   /**
383    * Converts a primitive wrapper array (e.g. <code>Integer[]</code>) to a primitive array (e.g. <code><jk>int</jk>[]</code>).
384    *
385    * @param o The array to convert.  Must be a primitive wrapper array.
386    * @return A new array.
387    * @throws IllegalArgumentException If object is not a wrapper object array.
388    */
389   public static Object toPrimitiveArray(Object o) {
390      Class<?> c = o.getClass();
391      if (! c.isArray())
392         throw new IllegalArgumentException("Cannot pass non-array objects to toPrimitiveArray()");
393      int l = Array.getLength(o);
394      Class<?> tc = ClassUtils.getPrimitiveForWrapper(c.getComponentType());
395      if (tc == null)
396         throw new IllegalArgumentException("Array type is not a primitive wrapper array.");
397      Object a = Array.newInstance(tc, l);
398      for (int i = 0; i < l; i++)
399         Array.set(a, i, Array.get(o, i));
400      return a;
401   }
402
403   /**
404    * Converts an Iterable to a list.
405    *
406    * @param i The iterable to convert.
407    * @return A new list of objects copied from the iterable.
408    */
409   public static List<?> toList(Iterable<?> i) {
410      List<Object> l = new ArrayList<>();
411      Iterator<?> i2 = i.iterator();
412      while (i2.hasNext())
413         l.add(i2.next());
414      return l;
415   }
416
417   /**
418    * Returns the first object in the specified collection or array.
419    *
420    * @param val The collection or array object.
421    * @return
422    *    The first object, or <jk>null</jk> if the collection or array is empty or <jk>null</jk> or the value
423    *    isn't a collection or array.
424    */
425   public static Object getFirst(Object val) {
426      if (val != null) {
427         if (val instanceof Collection) {
428            Collection<?> c = (Collection<?>)val;
429            if (c.isEmpty())
430               return null;
431            return c.iterator().next();
432         }
433         if (val.getClass().isArray())
434            return Array.getLength(val) == 0 ? null : Array.get(val, 0);
435      }
436      return null;
437   }
438
439   /**
440    * Converts the specified collection to an array of strings.
441    *
442    * <p>
443    * Entries are converted to strings using {@link #toString()}.
444    * <jk>null</jk> values remain <jk>null</jk>.
445    *
446    * @param c The collection to convert.
447    * @return The collection as a string array.
448    */
449   public static String[] toStringArray(Collection<?> c) {
450      String[] r = new String[c.size()];
451      int i = 0;
452      for (Object o : c)
453         r[i++] = asString(o);
454      return r;
455   }
456
457   /**
458    * Returns <jk>true</jk> if the following sorted arrays are equals.
459    *
460    * @param a1 Array #1.
461    * @param a2 Array #2.
462    * @return <jk>true</jk> if the following sorted arrays are equals.
463    */
464   public static boolean equals(String[] a1, String[] a2) {
465      if (a1.length != a2.length)
466         return false;
467      for (int i = 0; i < a1.length; i++)
468         if (! StringUtils.isEquals(a1[i], a2[i]))
469            return false;
470      return true;
471   }
472
473   /**
474    * Converts a collection to an array containing the elements in reversed order.
475    *
476    * @param c The component type of the array.
477    * @param l
478    *    The collection to convert.
479    *    <br>The collection is not modified.
480    * @return
481    *    A new array, or <jk>null</jk> if the collection was <jk>null</jk>.
482    */
483   @SuppressWarnings("unchecked")
484   public static <T> T[] toReverseArray(Class<T> c, Collection<T> l) {
485      if (l == null)
486         return null;
487      Object a = Array.newInstance(c, l.size());
488      Iterator<T> i = l.iterator();
489      int j = l.size();
490      while (i.hasNext())
491         Array.set(a, --j, i.next());
492      return (T[])a;
493   }
494
495   /**
496    * Removes the specified element from the specified array.
497    *
498    * @param element The element to remove from the array.
499    * @param array The array to remove the element from.
500    * @return A new array with the element removed, or the original array if the array did not contain the element.
501    */
502   public static Object[] remove(Object element, Object[] array) {
503      if (! contains(element, array))
504         return array;
505      List<Object> l = new ArrayList<>(array.length);
506      for (Object o2 : array) {
507         if (! element.equals(o2))
508            l.add(o2);
509      }
510      return l.toArray(new Object[l.size()]);
511   }
512}