001    package com.mockrunner.util.common;
002    
003    import java.lang.reflect.Array;
004    import java.util.ArrayList;
005    import java.util.HashMap;
006    import java.util.Iterator;
007    import java.util.List;
008    import java.util.Map;
009    
010    /**
011     * Util class for arrays
012     */
013    public class ArrayUtil
014    {
015        /**
016         * Returns a <code>List</code> containing the bytes from the
017         * specified array as <code>Byte</code> objects.
018         * @param data the byte data
019         * @return the <code>List</code> with the <code>Byte</code> objects
020         */
021        public static List getListFromByteArray(byte[] data)
022        {
023            ArrayList list = new ArrayList(data.length);
024            for(int ii = 0; ii < data.length; ii++)
025            {
026                list.add(new Byte(data[ii]));
027            }
028            return list;
029        }
030        
031        /**
032         * Returns a byte array containing the bytes from the <code>List</code>.
033         * The <code>List</code> must contain <code>Byte</code> objects.
034         * <code>null</code> entries in the <code>List</code> are
035         * allowed, the resulting byte will be 0.
036         * @param data the <code>List</code>
037         * @return the resulting byte array
038         */
039        public static byte[] getByteArrayFromList(List data)
040        {
041            return getByteArrayFromList(data, 0);
042        }
043        
044        /**
045         * Returns a byte array containing the bytes from the <code>List</code>.
046         * The <code>List</code> must contain <code>Byte</code> objects.
047         * <code>null</code> entries in the <code>List</code> are
048         * allowed, the resulting byte will be 0.
049         * @param data the <code>List</code>
050         * @param index the index at which to start
051         * @return the resulting byte array
052         */
053        public static byte[] getByteArrayFromList(List data, int index)
054        {
055            return getByteArrayFromList(data, index, data.size() - index);
056        }
057        
058        /**
059         * Returns a byte array containing the bytes from the <code>List</code>.
060         * The <code>List</code> must contain <code>Byte</code> objects.
061         * <code>null</code> entries in the <code>List</code> are
062         * allowed, the resulting byte will be 0.
063         * @param data the <code>List</code>
064         * @param index the index at which to start
065         * @param len the number of bytes
066         * @return the resulting byte array
067         */
068        public static byte[] getByteArrayFromList(List data, int index, int len)
069        {
070            if(data.size() == 0) return new byte[0];
071            if(index >= data.size())
072            {
073                throw new IndexOutOfBoundsException("Position " + index + " invalid in List of size " + data.size());
074            }
075            byte[] byteData = new byte[len];
076            for(int ii = index; ii < data.size() && ii < index + len; ii++)
077            {
078                Byte nextValue = (Byte)data.get(ii);
079                if(null != nextValue)
080                {
081                    byteData[ii - index] = nextValue.byteValue();
082                }
083            }
084            return byteData;
085        }
086        
087        /**
088         * Copies the bytes from the specified array to the specified
089         * <code>List</code> as <code>Byte</code> objects starting
090         * at the specified index. Grows the list if necessary.
091         * <i>index</i> must be a valid index in the list.
092         * @param data the byte data
093         * @param list the <code>List</code>
094         * @param index the index at which to start copying
095         */
096        public static void addBytesToList(byte[] data, List list, int index)
097        {
098            addBytesToList(data, 0, data.length, list, index);
099        }
100        
101        /**
102         * Copies the bytes from the specified array to the specified
103         * <code>List</code> as <code>Byte</code> objects starting
104         * at the specified index. Grows the list if necessary.
105         * <i>index</i> must be a valid index in the list.
106         * @param data the byte data
107         * @param offset the offset into the byte array at which to start
108         * @param len the number of bytes to copy
109         * @param list the <code>List</code>
110         * @param index the index at which to start copying
111         */
112        public static void addBytesToList(byte[] data, int offset, int len, List list, int index)
113        {
114            int bytesToIncrease = index + len - list.size();
115            if(bytesToIncrease > 0)
116            {
117                for(int ii = 0; ii < bytesToIncrease; ii++)
118                {
119                    list.add(null);
120                }
121            }
122            for(int ii = index; ii < index + len; ii++)
123            {
124                list.set(ii, new Byte(data[offset + ii - index]));
125            }
126        }
127    
128        /**
129         * Returns a truncated copy of <i>sourceArray</i>. <i>len</i>
130         * entries are copied.
131         * @param sourceArray the source array
132         * @param len the truncate length
133         * @return the truncated array
134         * @throws IllegalArgumentException if the specified object
135         *         is not an array (either of reference or primitive
136         *         component type)
137         */
138        public static Object truncateArray(Object sourceArray, int len)
139        {
140            return truncateArray(sourceArray, 0, len);
141        }
142        
143        /**
144         * Returns a truncated copy of <i>sourceArray</i>. <i>len</i>
145         * entries are copied starting at the specified index.
146         * @param sourceArray the source array
147         * @param index the start index
148         * @param len the truncate length
149         * @return the truncated array
150         * @throws IllegalArgumentException if the specified object
151         *         is not an array (either of reference or primitive
152         *         component type)
153         */
154        public static Object truncateArray(Object sourceArray, int index, int len)
155        {
156            if(!sourceArray.getClass().isArray())
157            {
158                throw new IllegalArgumentException("sourceArray must be an array");
159            }
160            Object targetArray = Array.newInstance(sourceArray.getClass().getComponentType(), len);
161            System.arraycopy(sourceArray, index, targetArray, 0, len);
162            return targetArray;
163        }
164        
165        /**
166         * Returns a copy of the specified array. If <i>array</i>
167         * is not an array, the object itself will be returned.
168         * Otherwise a copy of the array will be returned. The components
169         * themselves are not cloned.
170         * @param array the array
171         * @return the copy of the array
172         */
173        public static Object copyArray(Object array)
174        {
175            if(!array.getClass().isArray()) return array;
176            Class componentType = array.getClass().getComponentType();
177            int length = Array.getLength(array);
178            Object copy = Array.newInstance(componentType, Array.getLength(array));
179            for(int ii = 0; ii < length; ii++)
180            {
181                Array.set(copy, ii, Array.get(array, ii));
182            }
183            return copy;
184        }
185        
186        /**
187         * Returns an object array by wrapping primitive types. If the 
188         * specified array is of primitive component type, an <code>Object[]</code>
189         * with the corresponding wrapper component type is returned.
190         * If the specified array is already an object array, the instance is
191         * returned unchanged.
192         * @param sourceArray the array
193         * @return the corresponding object array
194         * @throws IllegalArgumentException if the specified object
195         *         is not an array (either of reference or primitive
196         *         component type)
197         */
198        public static Object[] convertToObjectArray(Object sourceArray)
199        {
200            if(!sourceArray.getClass().isArray())
201            {
202                throw new IllegalArgumentException("sourceArray must be an array");
203            }
204            Class componentType = sourceArray.getClass().getComponentType();
205            if(!componentType.isPrimitive())
206            {
207                return (Object[])sourceArray;
208            }
209            if(componentType.equals(Boolean.TYPE))
210            {
211                componentType = Boolean.class;
212            }
213            else if(componentType.equals(Byte.TYPE)) 
214            {
215                componentType = Byte.class;
216            }
217            else if(componentType.equals(Character.TYPE))
218            {
219                componentType = Character.class;
220            }
221            else if(componentType.equals(Short.TYPE))
222            {
223                componentType = Short.class;
224            }
225            else if(componentType.equals(Integer.TYPE))
226            {
227                componentType = Integer.class;
228            }
229            else if(componentType.equals(Long.TYPE))
230            {
231                componentType = Long.class;
232            }
233            else if(componentType.equals(Float.TYPE))
234            {
235                componentType = Float.class;
236            }
237            else if(componentType.equals(Double.TYPE))
238            {
239                componentType = Double.class;
240            }
241            int length = Array.getLength(sourceArray);
242            Object[] targetArray = (Object[])Array.newInstance(componentType, length);
243            for(int ii = 0; ii < length; ii++)
244            {
245                targetArray[ii] = Array.get(sourceArray, ii);
246            }
247            return targetArray;
248        }
249        
250        /**
251         * Returns a primitive array by unwrapping the corresponding types. If the 
252         * specified array is not an array of primitive wrapper types (e.g. <code>Integer[]</code>), 
253         * an <code>IllegalArgumentException</code> will be thrown.
254         * If an array element is <code>null</code>, an <code>IllegalArgumentException</code> 
255         * will be thrown.
256         * @param sourceArray the array
257         * @return the corresponding primitive array
258         * @throws IllegalArgumentException if the specified array
259         *         is not an array of primitive wrapper types or if an
260         *         array element is <code>null</code>
261         */
262        public static Object convertToPrimitiveArray(Object[] sourceArray)
263        {
264            Class componentType = sourceArray.getClass().getComponentType();
265            if(componentType.equals(Boolean.class))
266            {
267                componentType = Boolean.TYPE;
268            }
269            else if(componentType.equals(Byte.class)) 
270            {
271                componentType = Byte.TYPE;
272            }
273            else if(componentType.equals(Character.class))
274            {
275                componentType = Character.TYPE;
276            }
277            else if(componentType.equals(Short.class))
278            {
279                componentType = Short.TYPE;
280            }
281            else if(componentType.equals(Integer.class))
282            {
283                componentType = Integer.TYPE;
284            }
285            else if(componentType.equals(Long.class))
286            {
287                componentType = Long.TYPE;
288            }
289            else if(componentType.equals(Float.class))
290            {
291                componentType = Float.TYPE;
292            }
293            else if(componentType.equals(Double.class))
294            {
295                componentType = Double.TYPE;
296            }
297            else
298            {
299                throw new IllegalArgumentException("sourceArray is of type " + componentType + " which is not allowed");
300            }
301            int length = Array.getLength(sourceArray);
302            Object targetArray = Array.newInstance(componentType, length);
303            for(int ii = 0; ii < length; ii++)
304            {
305                Array.set(targetArray, ii, Array.get(sourceArray, ii));
306            }
307            return targetArray;
308        }
309        
310        /**
311         * Creates an array with a single object as component.
312         * If the specified object is an array, it will be returned
313         * unchanged. Otherwise an array with the specified object
314         * as the single element will be returned.
315         * @param object the object
316         * @return the corresponding array
317         */
318        public static Object convertToArray(Object object)
319        {
320            if(object.getClass().isArray()) return object;
321            Object array = Array.newInstance(object.getClass(), 1);
322            Array.set(array, 0, object);
323            return array;
324        }
325        
326        /**
327         * Compares the two specified arrays. If both passed objects are
328         * <code>null</code>, <code>true</code> is returned. If both passed 
329         * objects are not arrays, they are compared using <code>equals</code>. 
330         * Otherwise all array elements are compared using <code>equals</code>.
331         * This method does not handle multidimensional arrays, i.e. if an
332         * array contains another array, comparison is based on identity.
333         * @param array1 the first array
334         * @param array2 the second array
335         * @return <code>true</code> if the arrays are equal, <code>false</code>
336         *         otherwise
337         */
338        public static boolean areArraysEqual(Object array1, Object array2)
339        {
340            if(null == array1 && null == array2) return true;
341            if(null == array1 || null == array2) return false;
342            if(!array1.getClass().isArray() && !array2.getClass().isArray()) return array1.equals(array2);
343            if(!array1.getClass().isArray() || !array2.getClass().isArray()) return false;
344            int length1 = Array.getLength(array1);
345            int length2 = Array.getLength(array2);
346            if(length1 != length2) return false;
347            for(int ii = 0; ii < length1; ii++)
348            {
349                Object value1 = Array.get(array1, ii);
350                Object value2 = Array.get(array2, ii);
351                if(null != value1 && !value1.equals(value2)) return false;
352                if(null == value1 && null != value2) return false;
353            }
354            return true;
355        }
356        
357        /**
358         * Returns a suitable hash code for the specified array. If the passed
359         * object is <code>null</code>, <code>0</code> is returned.
360         * It is allowed to pass an object that is not an array, in this case, 
361         * the hash code of the object will be returned. Otherwise the hash code
362         * will be based on the array elements. <code>null</code> elements are
363         * allowed.
364         * This method does not handle multidimensional arrays, i.e. if an
365         * array contains another array, the hash code is based on identity.
366         * @param array the array
367         * @return a suitable hash code
368         */
369        public static int computeHashCode(Object array)
370        {
371            if(null == array) return 0;
372            if(!array.getClass().isArray()) return array.hashCode();
373            int length = Array.getLength(array);
374            int hashCode = 17;
375            for(int ii = 0; ii < length; ii++)
376            {
377                Object value = Array.get(array, ii);
378                if(null != value) hashCode = (31 * hashCode) + value.hashCode();
379            }
380            return hashCode;
381        }
382        
383        /**
384         * Returns the index of the first occurence of the
385         * array <i>bytes</i> in the array <i>source</i>.
386         * @param source the array in which to search
387         * @param bytes the array to search
388         * @return the index of the first occurence or
389         *         -1, if <i>source</i> does not contain <i>bytes</i>
390         */
391        public static int indexOf(byte[] source, byte[] bytes)
392        {
393            return indexOf(source, bytes, 0);
394        }
395        
396        /**
397         * Returns the index of the first occurence of the
398         * array <i>bytes</i> in the array <i>source</i>.
399         * @param source the array in which to search
400         * @param bytes the array to search
401         * @param index the index where to begin the search
402         * @return the index of the first occurence or
403         *         -1, if <i>source</i> does not contain <i>bytes</i>
404         */
405        public static int indexOf(byte[] source, byte[] bytes, int index)
406        {
407            if(index + bytes.length > source.length) return -1;
408            for(int ii = index; ii <= source.length - bytes.length; ii++)
409            {
410                int yy = 0; 
411                while(yy < bytes.length && bytes[yy] == source[ii + yy]) yy++;
412                if(yy == bytes.length) return ii;
413            }
414            return -1;
415        }
416        
417        /**
418         * Ensures that each entry in the specified string array
419         * is unique by adding a number to duplicate entries.
420         * I.e. if the string <code>"entry"</code> occurs three
421         * times, the three entries will be renamed to <code>"entry1"</code>,
422         * <code>"entry2"</code> and <code>"entry3"</code>.
423         * @param values the array of strings
424         */
425        public static void ensureUnique(String[] values)
426        {
427            Map nameMap = collectOccurences(values);
428            renameDuplicates(values, nameMap);
429        }
430        
431        private static void renameDuplicates(String[] names, Map nameMap)
432        {
433            Iterator iterator = nameMap.keySet().iterator();
434            while(iterator.hasNext())
435            {
436                String nextName = (String)iterator.next();
437                Integer nextValue = (Integer)nameMap.get(nextName);
438                if(nextValue.intValue() > 1)
439                {
440                    int number = 1;
441                    for(int ii = 0; ii < names.length; ii++)
442                    {
443                        if(names[ii].equals(nextName))
444                        {
445                            names[ii] = nextName + number;
446                            number++;
447                        }
448                    }
449                }
450            }
451        }
452    
453        private static Map collectOccurences(String[] names)
454        {
455            Map nameMap = new HashMap();
456            for(int ii = 0; ii < names.length; ii++)
457            {
458                Integer currentValue = (Integer)nameMap.get(names[ii]);            
459                if(null == currentValue)
460                {
461                    nameMap.put(names[ii], new Integer(1));
462                }
463                else
464                {
465                    nameMap.put(names[ii], new Integer(currentValue.intValue() + 1));
466                }
467            }
468            return nameMap;
469        }
470    }