package de.persosim.simulator.utils; import static org.globaltester.logging.BasicLogger.logException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; import java.util.Calendar; import java.util.Date; /** * * * @author slutters * */ public abstract class Utils { public static final byte[] BITMASK = new byte[]{(byte) 0x01, (byte) 0x02, (byte) 0x04, (byte) 0x08, (byte) 0x10, (byte) 0x20, (byte) 0x40, (byte) 0x80}; public static final byte[] BITMASK_COMPLEMENT = new byte[]{(byte) 0xFE, (byte) 0xFD, (byte) 0xFB, (byte) 0xF7, (byte) 0xEF, (byte) 0xDF, (byte) 0xBF, (byte) 0x7F}; public static final short MASK_BYTE_TO_SHORT = (short) 0x00FF; public static final int MASK_BYTE_TO_INT = (short) 0x000000FF; public static final int MASK_SHORT_TO_INT = 0x0000FFFF; public static final byte DATE_SET_MIN_VALUE = (byte) -1; public static final byte DATE_NO_CHECKS = (byte) 0; public static final byte DATE_SET_MAX_VALUE = (byte) 1; /*--------------------------------------------------------------------------------*/ /** * Returns an unsigned byte masked to short * @param byteValue the byte to be masked to short * @return the unsigned byte masked to short */ public static short maskUnsignedByteToShort(byte byteValue) { return (short) (byteValue & MASK_BYTE_TO_SHORT); } /** * Returns an unsigned byte masked to int * @param byteValue the byte to be masked to int * @return the unsigned byte masked to int */ public static int maskUnsignedByteToInt(byte byteValue) { return (int) (byteValue & MASK_BYTE_TO_INT); } /** * Returns an unsigned short masked to int * @param shortValue the short to be masked to int * @return the unsigned short masked to int */ public static int maskUnsignedShortToInt(short shortValue) { return (int) (shortValue & MASK_SHORT_TO_INT); } /*--------------------------------------------------------------------------------*/ /** * Returns a short resulting from concatenating two bytes * @param byte1 the leading byte * @param byte2 the trailing byte * @return a short resulting from concatenating two bytes */ public static short concatenate(byte byte1, byte byte2) { short concatenation; concatenation = maskUnsignedByteToShort(byte1); concatenation <<= 8; concatenation |= maskUnsignedByteToShort(byte2); return concatenation; } /** * Returns whether an array contains duplicate elements. * Array objects must implement Comparable interface. * @param c the array to be checked * @return true if at least one occurrence of duplicate elements has been found, false otherwise */ //XXX LSG Method isnt beeing used anywhere public static <T extends Comparable<T>>boolean containsDuplicateElements(T[] c) { if(c == null) { throw new ClassCastException(); } if(c.length < 2) { return false; } for(int i = 0; i < (c.length - 1); i++) { for(int j = i + 1; j < c.length; j++) { if(c[i].compareTo(c[j]) == 0) { return true; } } } return false; } /* * c1 master * c2 compared to master * * -1 - c2 contains less than all of c1's elements * 0 - c2 contains all of c1's elements * +1 - c2 contains more than all of c1's elements * */ //XXX LSG Method isnt beeing used anywhere public static <T extends Comparable<T>>byte compareSets(T[] c1, T[] c2) { boolean match; if((c1 == null) || (c2 == null)) { throw new ClassCastException(); } if(c1.length == 0) { if(c2.length == 0) { return 0; } else{ return 1; } } else{ if(c2.length == 0) { return -1; } } if(c1.length > c2.length) { return -1; } for(byte i = 0; i < c1.length; i++) { match = false; for(byte j = 0; j < c2.length; j++) { if(c1[i].compareTo(c2[j]) == 0) { match = true; break; } } if(!match) { return -1; } } if(c1.length < c2.length) { return 1; } else{ return 0; } } /** * Returns a concatenation of one or more byte arrays * @param byteArrays one or more byte arrays * @return a concatenation of one or more byte arrays */ public static byte[] concatByteArrays(byte[]... byteArrays) { ByteArrayOutputStream outputStream; outputStream = new ByteArrayOutputStream(); for (byte[] currentByteArray : byteArrays) { if (currentByteArray == null) continue; try { outputStream.write(currentByteArray); } catch (IOException e) { logException(Utils.class, e); } } return outputStream.toByteArray(); } /** * Returns a byte array that has been appended by the provided bytes * @param leadingByteArray leading byte array * @param trailingBytes one or more trailing bytes * @return a byte array that has been appended by the provided bytes */ public static byte[] appendBytes(byte[] leadingByteArray, byte... trailingBytes) { return concatByteArrays(leadingByteArray,trailingBytes); } /** * Returns a byte array converted to a boolean array in binary representation * @param in the byte array to be converted * @return a byte array converted to a boolean array in binary representation */ //XXX LSG Method isnt beeing used anywhere public static boolean[] binaryEncodeByteArray(byte[] in) { boolean[] out; byte bitMask; out = new boolean[8 * in.length]; /* process every bit */ for (int i = 0; i < (8 * in.length); i++) { bitMask = BITMASK[7 - (i % 8)]; out[i] = (in[i / 8] & bitMask) == bitMask; } return out; } //XXX LSG Method isnt beeing used anywhere public static String binaryEncode(byte[] in) { boolean[] out; StringBuilder sb; out = binaryEncodeByteArray(in); sb = new StringBuilder(out.length + (in.length - 1)); for(int i = 0; i < out.length; i++) { /* separate bytes by white space */ if((i > 0) && (i % 8 == 0)) { sb.append(" "); } if(out[i]) { sb.append("1"); } else{ sb.append("0"); } } return sb.toString(); } /** * Returns the logarithm of the given value to the given base * @param value the value * @param base the base * @return the logarithm of the given value to the given base */ public static double logarithm(double value, int base) { return Math.log10(value)/Math.log10(base); } /** * Returns the unsigned absolute value of minimum 1 byte length. * * In general the same as BigInteger's toByteArary():byte[] * with the following differences: * - result is unsigned * - BigInteger.ZERO --> new byte[]{(byte) 0x00} * * @param bigInt signed value * @return unsigned absolute value of minimum 1 byte length */ public static byte[] toUnsignedByteArray(BigInteger bigInt) { if(bigInt == null) {throw new NullPointerException();} byte[] resultTMP, resultFINAL; BigInteger bigIntTmp; /* reset leading sign bit (if present), i.e. compute absolute value */ bigIntTmp = bigInt.abs(); if(bigIntTmp.compareTo(BigInteger.ZERO) == 0) { resultTMP = new byte[]{(byte) 0x00}; } else{ resultTMP = bigIntTmp.toByteArray(); } /* * Crop leading empty byte previously used for sign (if present). * Leading sign byte is 0x00 due to taking absolute value previously. */ if((resultTMP[0] == (byte) 0x00) && (resultTMP.length > 1)) { resultFINAL = new byte[resultTMP.length - 1]; System.arraycopy(resultTMP, 1, resultFINAL, 0, resultFINAL.length); } else{ resultFINAL = resultTMP; } return resultFINAL; } /** * Returns an unsigned byte array representation of an unsigned long. * Returned Array has length 8; unused bytes are padded to 0x00. * @param input the long * @return the unsigned byte array representation of the unsigned long */ //XXX LSG Method isnt beeing used anywhere public static byte[] toUnsignedByteArray(long input) { return new byte[]{ (byte) ((input & 0xFF00000000000000L) >> 56), (byte) ((input & 0x00FF000000000000L) >> 48), (byte) ((input & 0x0000FF0000000000L) >> 40), (byte) ((input & 0x000000FF00000000L) >> 32), (byte) ((input & 0x00000000FF000000L) >> 24), (byte) ((input & 0x0000000000FF0000L) >> 16), (byte) ((input & 0x000000000000FF00L) >> 8), (byte) (input & 0x00000000000000FFL)}; } /** * This method converts an integer to an unsigned byte array without leading zeros * @param input the integer to convert * @return unsigned byte array */ public static byte[] toShortestUnsignedByteArray(int input) { byte [] array = toUnsignedByteArray(input); return removeLeadingZeroBytes(array); } /** * Returns an unsigned byte array representation of an unsigned int. * Returned Array has length 4; unused bytes are padded to 0x00. * @param input the int * @return the unsigned byte array representation of the unsigned int */ public static byte[] toUnsignedByteArray(int input) { return new byte[]{(byte) ((input & 0xFF000000) >> 24), (byte) ((input & 0x00FF0000) >> 16), (byte) ((input & 0x0000FF00) >> 8), (byte) (input & 0x000000FF)}; } /** * Returns an unsigned byte array representation of an unsigned short * Returned Array has length 2; unused bytes are padded to 0x00. * @param input the short * @return the unsigned byte array representation of the unsigned short */ public static byte[] toUnsignedByteArray(short input) { return new byte[]{(byte) ((input & (short) 0xFF00) >> 8), (byte) (input & (short) 0x00FF)}; } /** * Returns an unsigned byte array representation of an unsigned byte * Returned Array has length 1; unused bytes are padded to 0x00. * @param number the byte * @return the unsigned byte array representation of the unsigned byte */ public static byte[] toUnsignedByteArray(byte number) { return new byte[]{number}; } /** * Returns a byte array with leading 0x00 bytes removed. * If the input byte array is all 0x00, all will be removed except for one. * @param input the byte array to be cropped * @return the byte array with leading 0x00 bytes removed. */ public static byte[] removeLeadingZeroBytes(byte[] input) { int index; for(index = 0; index < input.length; index++) { if(input[index] != (byte) 0x00) { break; } } if(index == input.length) { /* if input is all 0x00 */ index--; } return Arrays.copyOfRange(input, index, input.length); } /** * Returns the given input with additional leading zeroes. * * @param input * the byte array to be padded * @param wantedLength * the result array length * @return zero padded byte array */ public static byte [] padWithLeadingZeroes(byte [] input, int wantedLength){ if (wantedLength < input.length){ throw new IllegalArgumentException("Wanted length is smaller than the input length"); } byte [] zeroes = new byte [wantedLength - input.length]; return Utils.concatByteArrays(zeroes, input); } /** * Returns an unsigned int representation of the provided byte array. The * byte value is interpreted as being unsigned. This method works for * integers up to 0xFFFFFFFF. * * @param value * @param maxValue * @return */ private static int getDataTypeFromUnsignedByteArray(byte[] value, long maxValue) { if(value == null) {throw new IllegalArgumentException("value must not be null");} if(value.length < 1) {throw new IllegalArgumentException("value must have byte length > 0");} BigInteger bigInt; bigInt = new BigInteger(1, value); if (bigInt.compareTo(new BigInteger("" +maxValue)) > 0){ throw new IllegalArgumentException("value too big for signed data type"); } return bigInt.intValue(); } /** * Constructs a {@link BigInteger} from an unsigned byte array. * @param unsigned * the byte array to be treated as unsigned * @return the {@link BigInteger} created from a concatenation of a 0x00 * byte and the input */ public static BigInteger getBigIntegerFromUnsignedByteArray(byte[] unsigned) { return new BigInteger(concatByteArrays(new byte[] { 0 }, unsigned)); } public static int getIntFromUnsignedByteArray(byte[] value) { return (int) getDataTypeFromUnsignedByteArray(value, 0x0FFFFFFFFl); } public static short getShortFromUnsignedByteArray(byte[] value) { return (short) getDataTypeFromUnsignedByteArray(value, 0x0FFFFl); } /** * Check if any of the given objects is null. * * @param toTest * @return true, iff one of the given objects is null */ //XXX LSG Method isnt beeing used anywhere public static boolean isAnyNull(Object ...toTest) { for(Object o : toTest){ if (o == null){ return true; } } return false; } /** * This method compares all entries of the given array with the given * object. * * @param array * @param object * @return true, iff one of the arrays entries {@link #equals(Object)} the * given object. */ //XXX LSG Method isnt beeing used anywhere public static boolean arrayContainsEqual(Object[] array, Object object) { for (Object current : array) { if (current.equals(object)) { return true; } } return false; } public static boolean arrayHasPrefix(byte [] data, byte [] prefix){ if (data.length < prefix.length){ return false; } for (int i = 0; i < prefix.length; i++){ if (data [i] != prefix[i]){ return false; } } return true; } /** * * If string does not encode a data as expected (YYYYMMDD) a {@link NumerFormatException} is thrown. * * @see {@link #getDate(String, byte)} * @param dateString the date encoded as follows: YYYYMMDD * @return a {@link Date} object */ public static Date getDate(String dateString) { return getDate(dateString, (byte) 0); } /** * This method creates a {@link Date} object from a {@link String} representation with respect to year, month and day. * The provided String is expected to be exactly 8 characters long and encoded as follows: YYYYMMDD. * If the String parts for month or day contain non-numeric characters, they will be handled according to the second provided parameter: * <ul> * <li>-1: the minimum possible value will be chosen</li> * <li> 0: a NumberFormatException will be thrown</li> * <li> 1: the maximum possible value will be chosen</li> * </ul> * <br/> * Well formatted date strings will not be checked for validity, e.g. 20140199 would not be discarded. * <br/> * All parts not specified within the input string (hour, minutes, seconds and milliseconds) will be set to zero in order to retrieve comparable return values. * When calling this method twice with the same input the return values will be distinct but .equals() evaluates to true; * * @param dateString the date encoded as follows: YYYYMMDD * @param handleNonNumericCharacters determine how non-numeric characters will be handled * @return a {@link Date} object */ public static Date getDate(String dateString, byte handleNonNumericCharacters) { if (dateString == null) {throw new NullPointerException("date must not be null");} if (dateString.length() != 8) {throw new IllegalArgumentException("date must be exactly 8 characters long");} Calendar calendar = Calendar.getInstance(); int year = Integer.parseInt(dateString.substring(0, 4)); calendar.set(Calendar.YEAR, year); int month, day; try { month = Integer.parseInt(dateString.substring(4, 6)); month--; // set month is zero based } catch (NumberFormatException e) { switch (handleNonNumericCharacters) { case -1: month = Calendar.JANUARY; break; case 0: throw e; case 1: month = Calendar.DECEMBER; break; default: throw new IllegalArgumentException("invalid value for handling illegal month"); } } calendar.set(Calendar.MONTH, month); calendar.set(Calendar.DATE, 1); try { day = Integer.parseInt(dateString.substring(6, 8)); } catch (NumberFormatException e) { switch (handleNonNumericCharacters) { case -1: day = 1; break; case 0: throw e; case 1: day = calendar.getActualMaximum(Calendar.DATE); break; default: throw new IllegalArgumentException("invalid value for handling illegal day"); } } calendar.set(Calendar.DATE, day); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } /** * This method encodes a {@link Date} object to a byte[] representation. * The resulting byte[] encodes a single digit out of YYMMDD with every byte. * @param date the {@link Date} object to encode * @return the {@link Date} object encoded as byte[] */ public static byte[] encodeDate(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); int day = calendar.get(Calendar.DAY_OF_MONTH); year %= 100; month++; byte y2 = (byte) (year%10); byte y1 = (byte) ((year-y2)/10); byte m2 = (byte) (month%10); byte m1 = (byte) ((month-m2)/10); byte d2 = (byte) (day%10); byte d1 = (byte) ((day-d2)/10); return new byte[]{y1, y2, m1, m2, d1, d2}; } /** * Creates a new byte array containing the bytes of the given input in * inverted order. * * @param input * @return an inverted order byte array */ public static byte[] invertByteOrder(byte[] input) { byte[] result = new byte[input.length]; for (int i = 0; i < result.length; i++) { result[i] = input[input.length - i - 1]; } return result; } }