package freenet.support; import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.StringTokenizer; import java.util.TimeZone; import freenet.l10n.NodeL10n; import freenet.support.Logger.LogLevel; /** * This class contains static methods used for parsing boolean and unsigned * long fields in Freenet messages. Also some general utility methods for * dealing with string and numeric data. * * @author oskar */ public abstract class Fields { private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } /** * All possible chars for representing a number as a String. Used to * optimize numberList(). */ private final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; private static final long[] MULTIPLES = { 1000, 1l << 10, 1000 * 1000, 1l << 20, 1000l * 1000l * 1000l, 1l << 30, 1000l * 1000l * 1000l * 1000l, 1l << 40, 1000l * 1000l * 1000l * 1000l * 1000, 1l << 50, 1000l * 1000l * 1000l * 1000l * 1000l * 1000l, 1l << 60 }; private static final String[] MULTIPLES_2 = { "k", "K", "m", "M", "g", "G", "t", "T", "p", "P", "e", "E" }; /** * Converts a hex string into a long. Long.parseLong(hex, 16) assumes the * input is nonnegative unless there is a preceding minus sign. This method * reads the input as twos complement instead, so if the input is 8 bytes * long, it will correctly restore a negative long produced by * Long.toHexString() but not necessarily one produced by * Long.toString(x,16) since that method will produce a string like '-FF' * for negative longs values. * * @param hex * A string in capital or lower case hex, of no more then 16 * characters. * @throws NumberFormatException * if the string is more than 16 characters long, or if any * character is not in the set [0-9a-fA-f] */ public static long hexToLong(String hex) throws NumberFormatException { int len = hex.length(); if(len > 16) throw new NumberFormatException(); long l = 0; for(int i = 0; i < len; i++) { l <<= 4; int c = Character.digit(hex.charAt(i), 16); if(c < 0) throw new NumberFormatException(); l |= c; } return l; } /** * Converts a hex string into an int. Integer.parseInt(hex, 16) assumes the * input is nonnegative unless there is a preceding minus sign. This method * reads the input as twos complement instead, so if the input is 8 bytes * long, it will correctly restore a negative int produced by * Integer.toHexString() but not necessarily one produced by * Integer.toString(x,16) since that method will produce a string like * '-FF' for negative integer values. * * @param hex * A string in capital or lower case hex, of no more then 16 * characters. * @throws NumberFormatException * if the string is more than 16 characters long, or if any * character is not in the set [0-9a-fA-f] */ public static int hexToInt(String hex) throws NumberFormatException { int len = hex.length(); if(len > 16) throw new NumberFormatException(); int l = 0; for(int i = 0; i < len; i++) { l <<= 4; int c = Character.digit(hex.charAt(i), 16); if(c < 0) throw new NumberFormatException(); l |= c; } return l; } /** * Finds the boolean value of the field, by doing a caseless match with the * strings "true" and "false". * * @param s * The string * @param def * The default value if the string can't be parsed. If the * default is true, it checks that the string is not "false"; if * it is false, it checks whether the string is "true". * @return the boolean field value or the default value if the field value * couldn't be parsed. */ /* wooo, rocket science! (this is purely abstraction people) */ public static boolean stringToBool(String s, boolean def) { if(s == null) return def; return (def ? !s.equalsIgnoreCase("false") : s.equalsIgnoreCase("true")); } /** * Find the boolean value of the field. Throw if the string is neither "yes"/"true" nor "no"/"false". * @param s * @return */ public static boolean stringToBool(String s) throws NumberFormatException { if(s == null) throw new NumberFormatException("Null"); if(s.equalsIgnoreCase("false") || s.equalsIgnoreCase("no")) return false; if(s.equalsIgnoreCase("true") || s.equalsIgnoreCase("yes")) return true; throw new NumberFormatException("Invalid boolean: " + s); } /** * Converts a boolean to a String of either "true" or "false". * * @param b * the boolean value to convert. * @return A "true" or "false" String. */ public static String boolToString(boolean b) { return b ? "true" : "false"; } public static String[] commaList(String ls) { if(ls == null) return null; StringTokenizer st = new StringTokenizer(ls, ","); String[] r = new String[st.countTokens()]; for(int i = 0; i < r.length; i++) { r[i] = st.nextToken().trim(); } return r; } public static String commaList(String[] ls) { return textList(ls, ','); } public static String textList(String[] ls, char ch) { if (ls.length == 0) return ""; StringBuilder sb = new StringBuilder(); for(String s: ls) { sb.append(s); sb.append(ch); } // assert(sb.length() > 0); -- always true as ls.length != 0 // remove last ch sb.deleteCharAt(sb.length()-1); return sb.toString(); } public static long[] numberList(String ls) throws NumberFormatException { StringTokenizer st = new StringTokenizer(ls, ","); long[] r = new long[st.countTokens()]; for(int i = 0; i < r.length; i++) { r[i] = hexToLong(st.nextToken()); } return r; } public static String numberList(long[] ls) { if (ls.length == 0) return ""; char[] numberBuf = new char[64]; StringBuilder listBuf = new StringBuilder(ls.length * 18); for(long l: ls) { // Convert the number into a string in a fixed size buffer. int charPos = 64; do { numberBuf[--charPos] = digits[(int) (l & 0x0F)]; l >>>= 4; } while(l != 0); listBuf.append(numberBuf, charPos, (64 - charPos)); listBuf.append(','); } // assert(listBuf.length() > 0); -- always true as ls.length != 0 // remove last comma listBuf.deleteCharAt(listBuf.length()-1); return listBuf.toString(); } /** * Parses a time and date value, using a very strict format. The value has * to be of the form YYYYMMDD-HH:MM:SS (where seconds may include a * decimal) or YYYYMMDD (in which case 00:00:00 is assumed for time). * Another accepted format is +/-{integer}{day|month|year|minute|second} * * @return millis of the epoch of at the time described. */ public static long dateTime(String date) throws NumberFormatException { if(date.length() == 0) throw new NumberFormatException("Date time empty"); if((date.charAt(0) == '-') || (date.charAt(0) == '+')) { // Relative date StringBuilder sb = new StringBuilder(10); for(int x = 1; x < date.length(); x++) { char c = date.charAt(x); if(Character.isDigit(c)) sb.append(c); else break; } int num = Integer.parseInt(sb.toString()); int chop = 1 + sb.length(); int deltaType = 0; if(date.length() == chop) deltaType = Calendar.DAY_OF_YEAR; else { String deltaTypeString = date.substring(chop).toLowerCase(); if(deltaTypeString.equals("y") || deltaTypeString.equals("year")) deltaType = Calendar.YEAR; else if(deltaTypeString.equals("month") || deltaTypeString.equals("mo")) deltaType = Calendar.MONTH; else if(deltaTypeString.equals("week") || deltaTypeString.equals("w")) deltaType = Calendar.WEEK_OF_YEAR; else if(deltaTypeString.equals("day") || deltaTypeString.equals("d")) deltaType = Calendar.DAY_OF_YEAR; else if(deltaTypeString.equals("hour") || deltaTypeString.equals("h")) deltaType = Calendar.HOUR; else if(deltaTypeString.equals("minute") || deltaTypeString.equals("min")) deltaType = Calendar.MINUTE; else if(deltaTypeString.equals("second") || deltaTypeString.equals("s") || deltaTypeString.equals("sec")) deltaType = Calendar.SECOND; else throw new NumberFormatException( "unknown time/date delta type: " + deltaTypeString); GregorianCalendar gc = new GregorianCalendar(); gc.add(deltaType, (date.charAt(0) == '+') ? num : -num); return gc.getTime().getTime(); } } int dash = date.indexOf('-'); if(!((dash == -1) && (date.length() == 8)) && !((dash == 8) && (date.length() == 17))) throw new NumberFormatException( "Date time: " + date + " not correct."); int year = Integer.parseInt(date.substring(0, 4)); int month = Integer.parseInt(date.substring(4, 6)); int day = Integer.parseInt(date.substring(6, 8)); int hour = dash == -1 ? 0 : Integer.parseInt(date.substring(9, 11)); int minute = dash == -1 ? 0 : Integer.parseInt(date.substring(12, 14)); int second = dash == -1 ? 0 : Integer.parseInt(date.substring(15, 17)); // Note that month is zero based in GregorianCalender! try { return (new GregorianCalendar( year, month - 1, day, hour, minute, second)).getTime().getTime(); } catch(Exception e) { e.printStackTrace(); // The API docs don't say which exception is thrown on bad numbers! throw new NumberFormatException("Invalid date " + date + ": " + e); } } public static String secToDateTime(long time) { //Calendar c = Calendar.getInstance(); //c.setTime(new Date(time)); //gc.setTimeInMillis(time*1000); DateFormat f = new SimpleDateFormat("yyyyMMdd-HH:mm:ss"); f.setTimeZone(TimeZone.getTimeZone("GMT")); //String dateString = f.format(c.getTime()); String dateString = f.format(new Date(time * 1000)); if(dateString.endsWith("-00:00:00")) dateString = dateString.substring(0, 8); return dateString; } public static int compareBytes(byte[] b1, byte[] b2) { int len = Math.max(b1.length, b2.length); for(int i = 0; i < len; ++i) { if(i == b1.length) return i == b2.length ? 0 : -1; else if(i == b2.length) return 1; else if((0xff & b1[i]) > (0xff & b2[i])) return 1; else if((0xff & b1[i]) < (0xff & b2[i])) return -1; } return 0; } public static int compareBytes( byte[] a, byte[] b, int aoff, int boff, int len) { for(int i = 0; i < len; ++i) { if(i + aoff == a.length) return i + boff == b.length ? 0 : -1; else if(i + boff == b.length) return 1; else if((0xff & a[i + aoff]) > (0xff & b[i + boff])) return 1; else if((0xff & a[i + aoff]) < (0xff & b[i + boff])) return -1; } return 0; } public static boolean byteArrayEqual(byte[] a, byte[] b) { if(a.length != b.length) return false; for(int i = 0; i < a.length; ++i) if(a[i] != b[i]) return false; return true; } public static boolean byteArrayEqual( byte[] a, byte[] b, int aoff, int boff, int len) { if((a.length < aoff + len) || (b.length < boff + len)) return false; for(int i = 0; i < len; ++i) if(a[i + aoff] != b[i + boff]) return false; return true; } /** * Compares byte arrays lexicographically. */ public static final class ByteArrayComparator implements Comparator<byte[]> { @Override public final int compare(byte[] o1, byte[] o2) { return compareBytes(o1, o2); } } // could add stuff like IntegerComparator, LongComparator etc. // if we need it public static int hashCode(byte[] b) { return hashCode(b, 0, b.length); } /** * A generic hashcode suited for byte arrays that are more or less random. */ public static int hashCode(byte[] b, int ptr, int length) { int h = 0; for(int i = length - 1; i >= 0; --i) { int x = b[ptr + i] & 0xff; h ^= x << ((i & 3) << 3); } return h; } /** * Long version of above Not believed to be secure in any sense of the word :) */ public static long longHashCode(byte[] b) { return longHashCode(b, 0, b.length); } /** * Long version of above Not believed to be secure in any sense of the word :) */ public static long longHashCode(byte[] b, int offset, int length) { long h = 0; for(int i = length - 1; i >= 0; --i) { int x = b[i + offset] & 0xff; h ^= ((long) x) << ((i & 7) << 3); } return h; } public static String commaList(Object[] addr) { return commaList(addr, ','); } /** * @param addr * @return */ public static String commaList(Object[] addr, char comma) { if (addr.length == 0) return ""; StringBuilder sb = new StringBuilder(); for(Object a: addr) { sb.append(a); sb.append(comma); } // assert(sb.length() > 0); -- always true as addr.length != 0 // remove last comma sb.deleteCharAt(sb.length()-1); return sb.toString(); } /** * Convert an array of longs to an array of bytes, using a * consistent endianness. */ public static byte[] longsToBytes(long[] longs) { byte[] buf = new byte[longs.length * 8]; for(int i = 0; i < longs.length; i++) { long x = longs[i]; for(int j = 0; j < 8; j++) { buf[i * 8 + j] = (byte) x; x >>>= 8; } } return buf; } /** * Convert an array of bytes to an array of longs. */ public static long[] bytesToLongs(byte[] buf) { return bytesToLongs(buf, 0, buf.length); } /** * Convert an array of bytes to an array of longs. * @param buf * @param length * @param offset * @return */ public static long[] bytesToLongs(byte[] buf, int offset, int length) { if(length % 8 != 0) throw new IllegalArgumentException(); long[] longs = new long[length / 8]; for(int i = 0; i < longs.length; i++) { long x = 0; for(int j = 7; j >= 0; j--) { long y = (buf[offset + i * 8 + j] & 0xff); x = (x << 8) | y; } longs[i] = x; } return longs; } /** * Convert an array of bytes to a single long. */ public static long bytesToLong(byte[] buf) { return bytesToLong(buf, 0); } /** * Convert an array of bytes to a single long. */ public static long bytesToLong(byte[] buf, int offset) { if(buf.length < 8 + offset) throw new IllegalArgumentException(); long x = 0; for(int j = 7; j >= 0; j--) { long y = (buf[j + offset] & 0xff); x = (x << 8) | y; } return x; } public static int bytesToInt(byte[] buf) { return bytesToInt(buf, 0); } /** * Convert an array of bytes to a single int. */ public static int bytesToInt(byte[] buf, int offset) { if(buf.length < 4) throw new IllegalArgumentException(); int x = 0; for(int j = 3; j >= 0; j--) { int y = (buf[j + offset] & 0xff); x = (x << 8) | y; } return x; } /** * Convert an array of bytes to a single int. */ public static short bytesToShort(byte[] buf, int offset) { if(buf.length < 2) throw new IllegalArgumentException(); short x = 0; for(int j = 1; j >= 0; j--) { short y = (short)(buf[j + offset] & 0xff); x = (short)((x << 8) | y); } return x; } public static int[] bytesToInts(byte[] buf, int offset, int length) { if(length % 4 != 0) throw new IllegalArgumentException(); int[] ints = new int[length / 4]; for(int i = 0; i < ints.length; i++) { int x = 0; for(int j = 3; j >= 0; j--) { int y = (buf[j + offset + i * 4] & 0xff); x = (x << 8) | y; } ints[i] = x; } return ints; } public static int[] bytesToInts(byte[] buf) { return bytesToInts(buf, 0, buf.length); } public static byte[] longToBytes(long x) { byte[] buf = new byte[8]; for(int j = 0; j < 8; j++) { buf[j] = (byte) x; x >>>= 8; } return buf; } public static byte[] intsToBytes(int[] ints) { return intsToBytes(ints, 0, ints.length); } public static byte[] intsToBytes(int[] ints, int offset, int length) { byte[] buf = new byte[length * 4]; for(int i = 0; i < length; i++) { long x = ints[i + offset]; for(int j = 0; j < 4; j++) { buf[i * 4 + j] = (byte) x; x >>>= 8; } } return buf; } public static byte[] intToBytes(int x) { byte[] buf = new byte[4]; for(int j = 0; j < 4; j++) { buf[j] = (byte) x; x >>>= 8; } return buf; } public static byte[] shortToBytes(short x) { byte[] buf = new byte[2]; for(int j = 0; j < 2; j++) { buf[j] = (byte) x; x >>>= 8; } return buf; } public static long parseLong(String s, long defaultValue) { try { return Long.parseLong(s); } catch(NumberFormatException e) { Logger.error(Fields.class, "Failed to parse value as long: " + s + " : " + e, e); return defaultValue; } } public static int parseInt(String s, int defaultValue) { try { return Integer.parseInt(s); } catch(NumberFormatException e) { Logger.error(Fields.class, "Failed to parse value as int: " + s + " : " + e, e); return defaultValue; } } public static long parseShort(String s, short defaultValue) { try { return Short.parseShort(s); } catch(NumberFormatException e) { Logger.error(Fields.class, "Failed to parse value as short: " + s + " : " + e, e); return defaultValue; } } /** * Parse a human-readable string possibly including SI and ICE units into a short. * @throws NumberFormatException * if the string is not parseable */ public static short parseShort(String s) throws NumberFormatException { s = s.replaceFirst("(i)*B$", ""); short res = 1; int x = s.length() - 1; int idx; try { while((x >= 0) && ((idx = "kK".indexOf(s.charAt(x))) != -1)) { x--; res *= MULTIPLES[idx]; } res *= Double.parseDouble(s.substring(0, x + 1)); } catch(ArithmeticException e) { res = Short.MAX_VALUE; throw new NumberFormatException(e.getMessage()); } return res; } /* Removes up to one "(bits) per second" qualifier at the end of the string. If present such a qualifier will * prevent parsing as a size. * @see freenet.support.Fields#parseInt(String) */ public static String trimPerSecond(String limit) { limit = limit.trim(); if(limit.isEmpty()) return ""; /* * IEC endings are case sensitive, so the input string's case should not be modified. However, the * qualifiers should not be case sensitive. */ final String lower = limit.toLowerCase(); for(String ending : new String[] { "/s", "/sec", "/second", "bps", NodeL10n.getBase().getString("FirstTimeWizardToadlet.bandwidthPerSecond").toLowerCase() }) { if(lower.endsWith(ending)) { return limit.substring(0, limit.length() - ending.length()); } } return limit; } /** * Parse a human-readable string possibly including SI and ICE units into an integer. * @throws NumberFormatException * if the string is not parseable */ public static int parseInt(String s) throws NumberFormatException { s = s.replaceFirst("(i)*B$", ""); int res = 1; int x = s.length() - 1; int idx; try { while((x >= 0) && ((idx = "kKmMgG".indexOf(s.charAt(x))) != -1)) { x--; res *= MULTIPLES[idx]; } res *= Double.parseDouble(s.substring(0, x + 1)); } catch(ArithmeticException e) { res = Integer.MAX_VALUE; throw new NumberFormatException(e.getMessage()); } return res; } /** * Parse a human-readable string possibly including SI and ICE units into a long. * @throws NumberFormatException * if the string is not parseable */ public static long parseLong(String s) throws NumberFormatException { s = s.replaceFirst("(i)*B$", ""); long res = 1; int x = s.length() - 1; int idx; try { while((x >= 0) && ((idx = "kKmMgGtTpPeE".indexOf(s.charAt(x))) != -1)) { x--; res *= MULTIPLES[idx]; } String multiplier = s.substring(0, x + 1).trim(); if(multiplier.indexOf('.') > -1 || multiplier.indexOf('E') > -1) { res *= Double.parseDouble(multiplier); if(logMINOR) Logger.minor(Fields.class, "Parsed " + multiplier + " of " + s + " as double: " + res); } else { res *= Long.parseLong(multiplier); if(logMINOR) Logger.minor(Fields.class, "Parsed " + multiplier + " of " + s + " as long: " + res); } } catch(ArithmeticException e) { res = Long.MAX_VALUE; throw new NumberFormatException(e.getMessage()); } return res; } public static String longToString(long val, boolean isSize) { String ret = Long.toString(val); if(val <= 0) return ret; for(int i = MULTIPLES.length - 1; i >= 0; i--) { if(val > MULTIPLES[i] && val % MULTIPLES[i] == 0 && (isSize || MULTIPLES[i] % 1000 == 0)) { ret = (val / MULTIPLES[i]) + MULTIPLES_2[i]; if(!MULTIPLES_2[i].toLowerCase().equals(MULTIPLES_2[i])) ret += "iB"; break; } } return ret; } public static String intToString(int val, boolean isSize) { String ret = Integer.toString(val); if(val <= 0) return ret; for(int i = MULTIPLES.length - 1; i >= 0; i--) { if(val > MULTIPLES[i] && val % MULTIPLES[i] == 0 && (isSize || MULTIPLES[i] % 1000 == 0)) { ret = (val / MULTIPLES[i]) + MULTIPLES_2[i]; if(!MULTIPLES_2[i].toLowerCase().equals(MULTIPLES_2[i])) ret += "iB"; break; } } return ret; } public static String shortToString(short val, boolean isSize) { String ret = Short.toString(val); if(val <= 0) return ret; for(int i = MULTIPLES.length - 1; i >= 0; i--) { if(val > MULTIPLES[i] && val % MULTIPLES[i] == 0 && (isSize || MULTIPLES[i] % 1000 == 0)) { ret = (val / MULTIPLES[i]) + MULTIPLES_2[i]; if(!MULTIPLES_2[i].toLowerCase().equals(MULTIPLES_2[i])) ret += "iB"; break; } } return ret; } public static double[] bytesToDoubles(byte[] data, int offset, int length) { long[] longs = bytesToLongs(data, offset, length); double[] doubles = new double[longs.length]; for(int i = 0; i < longs.length; i++) doubles[i] = Double.longBitsToDouble(longs[i]); return doubles; } public static byte[] doublesToBytes(double[] doubles) { long[] longs = new long[doubles.length]; for(int i = 0; i < longs.length; i++) longs[i] = Double.doubleToLongBits(doubles[i]); return longsToBytes(longs); } public static double[] bytesToDoubles(byte[] data) { return bytesToDoubles(data, 0, data.length); } /** * Remove empty lines and trim head/trailing space * * @param str string to be trimmed * @return result string */ public static String trimLines(String str) { StringBuilder r = new StringBuilder(str.length()); for (String line : str.split("\n")) { line = line.trim(); if (line.length() == 0) continue; r.append(line); r.append('\n'); } return r.toString(); } /** Compare two versions. */ public static int compareVersion(String x, String y) { // Used by the updater code so I don't want to risk excessive recursion with regexes. int i = 0; int j = 0; boolean wantDigits = false; while(true) { String xDigits = null, yDigits = null; int digits = getDigits(x, i, wantDigits); if(digits > 0) { xDigits = x.substring(i, i+digits); i += digits; } digits = getDigits(y, j, wantDigits); if(digits > 0) { yDigits = y.substring(j, j+digits); j += digits; } if(xDigits != null && yDigits == null) return 1; // numbers > not numbers. if(yDigits != null && xDigits == null) return -1; // numbers > not numbers. if(xDigits != null && yDigits != null) { if(!xDigits.equals(yDigits)) { if(wantDigits) { try { long a = Integer.parseInt(xDigits); long b = Integer.parseInt(yDigits); if(a > b) return 1; if(a < b) return -1; if(xDigits.length() > yDigits.length()) return -1; // Extra 0's at beginning. if(yDigits.length() > xDigits.length()) return 1; // Extra 0's at beginning. } catch (NumberFormatException e) { // Too many digits! return xDigits.compareTo(yDigits); } } else { return xDigits.compareTo(yDigits); } } } if(i >= x.length() && j >= y.length()) return 0; wantDigits = !wantDigits; } } static int getDigits(String x, int i, boolean wantDigits) { int origI = i; for(;i<x.length();i++) { if(Character.isDigit(x.charAt(i)) != wantDigits) break; } return i - origI; } public static int compareObjectID(Object o1, Object o2) { int id1 = System.identityHashCode(o1); int id2 = System.identityHashCode(o2); if(id1 > id2) return 1; if(id2 > id1) return -1; return 0; } /** Avoid issues with overflow, 2's complement. E.g. 0-Integer.MIN_VALUE = Integer.MIN_VALUE-0. */ public static final int compare(int x, int y) { if(x > y) return 1; if(y > x) return -1; return 0; } /** Avoid issues with overflow, 2's complement. */ public static final int compare(long x, long y) { if(x > y) return 1; if(y > x) return -1; return 0; } /** Avoid issues with NaN's. */ public static final int compare(double x, double y) { if(Double.isNaN(x)) { if(Double.isNaN(y)) { return 0; // kind of! } else { return -1; // second is better } } else if(Double.isNaN(y)) { return 1; // first is better } else { if(x > y) return 1; else if(x < y) return -1; } return 0; } /** Avoid issues with NaN's. */ public static final int compare(float x, float y) { if(Float.isNaN(x)) { if(Float.isNaN(y)) { return 0; // kind of! } else { return -1; // second is better } } else if(Float.isNaN(y)) { return 1; // first is better } else { if(x > y) return 1; else if(x < y) return -1; } return 0; } public static final int compare(Date a, Date b) { // Replace null Dates with real ones so we can use Date.compareTo() a = (a != null ? a : new Date(0)); b = (b != null ? b : new Date(0)); return a.compareTo(b); } /** Copy all of the remaining bytes in the buffer to a byte array. * @param buf The input buffer. Position will be at the limit when returning. */ public static byte[] copyToArray(ByteBuffer buf) { byte[] ret = new byte[buf.remaining()]; buf.get(ret); return ret; } }