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 }