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 the index position of the element in the specified array using the {@link Object#equals(Object)} method.
313    *
314    * @param element The element to check for.
315    * @param array The array to check.
316    * @return
317    *    The index position of the element in the specified array, or <code>-1</code> if the array doesn't contain the
318    *    element, or the array or element is <jk>null</jk>.
319    */
320   public static <T> int indexOf(T element, T[] array) {
321      if (element == null)
322         return -1;
323      if (array == null)
324         return -1;
325      for (int i = 0; i < array.length; i++)
326         if (element.equals(array[i]))
327            return i;
328      return -1;
329   }
330
331   /**
332    * Returns <jk>true</jk> if the specified array contains the specified element using the {@link String#equals(Object)}
333    * method.
334    *
335    * @param element The element to check for.
336    * @param array The array to check.
337    * @return
338    *    <jk>true</jk> if the specified array contains the specified element,
339    *    <jk>false</jk> if the array or element is <jk>null</jk>.
340    */
341   public static boolean contains(String element, String[] array) {
342      return indexOf(element, array) != -1;
343   }
344
345   /**
346    * Returns the index position of the element in the specified array using the {@link String#equals(Object)} method.
347    *
348    * @param element The element to check for.
349    * @param array The array to check.
350    * @return
351    *    The index position of the element in the specified array, or
352    *    <code>-1</code> if the array doesn't contain the element, or the array or element is <jk>null</jk>.
353    */
354   public static int indexOf(String element, String[] array) {
355      if (element == null)
356         return -1;
357      if (array == null)
358         return -1;
359      for (int i = 0; i < array.length; i++)
360         if (element.equals(array[i]))
361            return i;
362      return -1;
363   }
364
365   /**
366    * Converts a primitive wrapper array (e.g. <code>Integer[]</code>) to a primitive array (e.g. <code><jk>int</jk>[]</code>).
367    *
368    * @param o The array to convert.  Must be a primitive wrapper array.
369    * @return A new array.
370    * @throws IllegalArgumentException If object is not a wrapper object array.
371    */
372   public static Object toPrimitiveArray(Object o) {
373      Class<?> c = o.getClass();
374      if (! c.isArray())
375         throw new IllegalArgumentException("Cannot pass non-array objects to toPrimitiveArray()");
376      int l = Array.getLength(o);
377      Class<?> tc = ClassUtils.getPrimitiveForWrapper(c.getComponentType());
378      if (tc == null)
379         throw new IllegalArgumentException("Array type is not a primitive wrapper array.");
380      Object a = Array.newInstance(tc, l);
381      for (int i = 0; i < l; i++)
382         Array.set(a, i, Array.get(o, i));
383      return a;
384   }
385
386   /**
387    * Converts an Iterable to a list.
388    *
389    * @param i The iterable to convert.
390    * @return A new list of objects copied from the iterable.
391    */
392   public static List<?> toList(Iterable<?> i) {
393      List<Object> l = new ArrayList<>();
394      Iterator<?> i2 = i.iterator();
395      while (i2.hasNext())
396         l.add(i2.next());
397      return l;
398   }
399
400   /**
401    * Returns the first object in the specified collection or array.
402    *
403    * @param val The collection or array object.
404    * @return
405    *    The first object, or <jk>null</jk> if the collection or array is empty or <jk>null</jk> or the value
406    *    isn't a collection or array.
407    */
408   public static Object getFirst(Object val) {
409      if (val != null) {
410         if (val instanceof Collection) {
411            Collection<?> c = (Collection<?>)val;
412            if (c.isEmpty())
413               return null;
414            return c.iterator().next();
415         }
416         if (val.getClass().isArray())
417            return Array.getLength(val) == 0 ? null : Array.get(val, 0);
418      }
419      return null;
420   }
421
422   /**
423    * Converts the specified collection to an array of strings.
424    *
425    * <p>
426    * Entries are converted to strings using {@link #toString()}.
427    * <jk>null</jk> values remain <jk>null</jk>.
428    *
429    * @param c The collection to convert.
430    * @return The collection as a string array.
431    */
432   public static String[] toStringArray(Collection<?> c) {
433      String[] r = new String[c.size()];
434      int i = 0;
435      for (Object o : c)
436         r[i++] = asString(o);
437      return r;
438   }
439
440   /**
441    * Returns <jk>true</jk> if the following sorted arrays are equals.
442    *
443    * @param a1 Array #1.
444    * @param a2 Array #2.
445    * @return <jk>true</jk> if the following sorted arrays are equals.
446    */
447   public static boolean equals(String[] a1, String[] a2) {
448      if (a1.length != a2.length)
449         return false;
450      for (int i = 0; i < a1.length; i++)
451         if (! StringUtils.isEquals(a1[i], a2[i]))
452            return false;
453      return true;
454   }
455
456   /**
457    * Converts a collection to an array containing the elements in reversed order.
458    *
459    * @param c The component type of the array.
460    * @param l
461    *    The collection to convert.
462    *    <br>The collection is not modified.
463    * @return
464    *    A new array, or <jk>null</jk> if the collection was <jk>null</jk>.
465    */
466   @SuppressWarnings("unchecked")
467   public static <T> T[] toReverseArray(Class<T> c, Collection<T> l) {
468      if (l == null)
469         return null;
470      Object a = Array.newInstance(c, l.size());
471      Iterator<T> i = l.iterator();
472      int j = l.size();
473      while (i.hasNext())
474         Array.set(a, --j, i.next());
475      return (T[])a;
476   }
477
478   /**
479    * Removes the specified element from the specified array.
480    *
481    * @param element The element to remove from the array.
482    * @param array The array to remove the element from.
483    * @return A new array with the element removed, or the original array if the array did not contain the element.
484    */
485   public static Object[] remove(Object element, Object[] array) {
486      if (! contains(element, array))
487         return array;
488      List<Object> l = new ArrayList<>(array.length);
489      for (Object o2 : array) {
490         if (! element.equals(o2))
491            l.add(o2);
492      }
493      return l.toArray(new Object[l.size()]);
494   }
495}