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 }