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