diff options
author | Sven Gothel <[email protected]> | 2021-07-31 02:19:36 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2021-07-31 02:19:36 +0200 |
commit | 0e2d05a2b7542c4c2865f736ddc7a1272c730f9d (patch) | |
tree | f2abd1538456a32ca39704b86f5b19bf3e3c20af | |
parent | 47d0206cc9d2044379e00619a843101ce9d3778f (diff) |
Java: EUI48Sub add: hash_code(), clear(), indexOf(), contains(), operator==() etc; EUI48[Sub]: Add static 'scanEUI48[Sub]'(string&) and static 'indexOf()'
-rw-r--r-- | java/org/direct_bt/EUI48.java | 78 | ||||
-rw-r--r-- | java/org/direct_bt/EUI48Sub.java | 180 |
2 files changed, 205 insertions, 53 deletions
diff --git a/java/org/direct_bt/EUI48.java b/java/org/direct_bt/EUI48.java index b17a1e63..af23eed7 100644 --- a/java/org/direct_bt/EUI48.java +++ b/java/org/direct_bt/EUI48.java @@ -60,24 +60,48 @@ public class EUI48 { /* pp */ static final int byte_size = 6; /** - * Construct instance via given string representation. + * Fills given EUI48 instance via given string representation. * <p> * Implementation is consistent with {@link #toString()}. * </p> * @param str a string of exactly 17 characters representing 6 bytes as hexadecimal numbers separated via colon {@code "01:02:03:0A:0B:0C"}. + * @param dest EUI48 to set its value + * @param errmsg error parsing message if returning false + * @return true if successful, otherwise false + * @see #EUI48(String) * @see #toString() */ - public EUI48(final String str) throws IllegalArgumentException { + public static boolean scanEUI48(final String str, final EUI48 dest, final StringBuilder errmsg) { if( 17 != str.length() ) { - throw new IllegalArgumentException("EUI48 string not of length 17 but "+str.length()+": "+str); + errmsg.append("EUI48 string not of length 17 but "+str.length()+": "+str); + return false; } - b = new byte[byte_size]; try { for(int i=0; i<byte_size; i++) { - b[byte_size-1-i] = Integer.valueOf(str.substring(i*2+i, i*2+i+2), 16).byteValue(); + dest.b[byte_size-1-i] = Integer.valueOf(str.substring(i*2+i, i*2+i+2), 16).byteValue(); } } catch (final NumberFormatException e) { - throw new IllegalArgumentException("EUI48 string not in format '01:02:03:0A:0B:0C' but "+str, e); + errmsg.append("EUI48 string not in format '01:02:03:0A:0B:0C' but "+str+"; "+e.getMessage()); + return false; + } + return true; + } + + /** + * Construct instance via given string representation. + * <p> + * Implementation is consistent with {@link #toString()}. + * </p> + * @param str a string of exactly 17 characters representing 6 bytes as hexadecimal numbers separated via colon {@code "01:02:03:0A:0B:0C"}. + * @see #scanEUI48(String, byte[], StringBuilder) + * @see #toString() + * @throws IllegalArgumentException if given string doesn't comply with EUI48 + */ + public EUI48(final String str) throws IllegalArgumentException { + final StringBuilder errmsg = new StringBuilder(); + b = new byte[byte_size]; + if( !scanEUI48(str, this, errmsg) ) { + throw new IllegalArgumentException(errmsg.toString()); } } @@ -98,7 +122,7 @@ public class EUI48 { b = address; } - /** Construct emoty unset instance. */ + /** Construct empty unset instance. */ public EUI48() { b = new byte[byte_size]; } @@ -165,12 +189,8 @@ public class EUI48 { */ public void clear() { hash = 0; - b[0] = 0; - b[1] = 0; - b[2] = 0; - b[3] = 0; - b[4] = 0; - b[5] = 0; + b[0] = 0; b[1] = 0; b[2] = 0; + b[3] = 0; b[4] = 0; b[5] = 0; } /** @@ -232,32 +252,8 @@ public class EUI48 { /** * Finds the index of given EUI48Sub. */ - public int indexOf(final EUI48Sub other) { - if( 0 == other.length ) { - return 0; - } - final byte first = other.b[0]; - final int outerEnd = 6 - other.length + 1; // exclusive - - for (int i = 0; i < outerEnd; i++) { - // find first char of other - while( b[i] != first ) { - if( ++i == outerEnd ) { - return -1; - } - } - if( i < outerEnd ) { // otherLen chars left to match? - // continue matching other chars - final int innerEnd = i + other.length; // exclusive - int j = i, k=0; - do { - if( ++j == innerEnd ) { - return i; // gotcha - } - } while( b[j] == other.b[++k] ); - } - } - return -1; + public int indexOf(final EUI48Sub needle) { + return EUI48Sub.indexOf(b, 6, needle.b, needle.length); } /** @@ -266,8 +262,8 @@ public class EUI48 { * If the sub is zero, true is returned. * </p> */ - public boolean contains(final EUI48Sub other) { - return 0 <= indexOf(other); + public boolean contains(final EUI48Sub needle) { + return 0 <= indexOf(needle); } /** diff --git a/java/org/direct_bt/EUI48Sub.java b/java/org/direct_bt/EUI48Sub.java index 54c3d5c5..31a90d37 100644 --- a/java/org/direct_bt/EUI48Sub.java +++ b/java/org/direct_bt/EUI48Sub.java @@ -23,33 +23,51 @@ */ package org.direct_bt; +import java.util.Arrays; + /** * A 48 bit EUI-48 sub-identifier, see {@link EUI48}. */ public class EUI48Sub { + /** EUI48Sub MAC address matching any device, i.e. '0:0:0:0:0:0'. */ + public static final EUI48Sub ANY_DEVICE = new EUI48Sub( new byte[] { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 }, 0, 6 ); + /** EUI48Sub MAC address matching all device, i.e. 'ff:ff:ff:ff:ff:ff'. */ + public static final EUI48Sub ALL_DEVICE = new EUI48Sub( new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff }, 0, 6 ); + /** EUI48Sub MAC address matching local device, i.e. '0:0:0:ff:ff:ff'. */ + public static final EUI48Sub LOCAL_DEVICE = new EUI48Sub( new byte[] { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0xff, (byte)0xff, (byte)0xff }, 0, 6 ); + /** - * The <= 6 byte EUI48 sub-address. + * The EUI48 sub-address, always 6 bytes reserved. */ - public final byte b[]; + public final byte b[/* 6 octets */]; + + private volatile int hash; // default 0, cache /** * The actual length in bytes of the EUI48 sub-address, less or equal 6 bytes. */ - public final int length; + public int length; /** - * Construct a sub EUI48 via given string representation. + * Fills given EUI48Sub instance via given string representation. * <p> * Implementation is consistent with {@link #toString()}. * </p> * @param str a string of less or equal of 17 characters representing less or equal of 6 bytes as hexadecimal numbers separated via colon, * e.g. {@code "01:02:03:0A:0B:0C"}, {@code "01:02:03:0A"}, {@code ":"}, {@code ""}. + * @param dest EUI48Sub to set its value + * @param errmsg error parsing message if returning false + * @return true if successful, otherwise false + * @see #EUI48Sub(String) * @see #toString() */ - public EUI48Sub(final String str) throws IllegalArgumentException { + public static boolean scanEUI48Sub(final String str, final EUI48Sub dest, final StringBuilder errmsg) { final int str_len = str.length(); + dest.clear(); + if( 17 < str_len ) { // not exceeding EUI48.byte_size - throw new IllegalArgumentException("EUI48 sub-string must be less or equal length 17 but "+str.length()+": "+str); + errmsg.append("EUI48 sub-string must be less or equal length 17 but "+str.length()+": "+str); + return false; } final byte b_[] = new byte[ 6 ]; // intermediate result high -> low int len_ = 0; @@ -64,13 +82,38 @@ public class EUI48Sub { ++len_; } } - length = len_; - b = new byte[ length ]; - for(j=0; j<length; ++j) { // swap low->high - b[j] = b_[length-1-j]; + dest.length = len_; + for(j=0; j<len_; ++j) { // swap low->high + dest.b[j] = b_[len_-1-j]; } } catch (final NumberFormatException e) { - throw new IllegalArgumentException("EUI48 sub-string not in format '01:02:03:0A:0B:0C' but "+str, e); + errmsg.append("EUI48 sub-string not in format '01:02:03:0A:0B:0C' but "+str); + return false; + } + return true; + } + + /** Construct empty unset instance. */ + public EUI48Sub() { + b = new byte[6]; + length = 0; + } + + /** + * Construct a sub EUI48 via given string representation. + * <p> + * Implementation is consistent with {@link #toString()}. + * </p> + * @param str a string of less or equal of 17 characters representing less or equal of 6 bytes as hexadecimal numbers separated via colon, + * e.g. {@code "01:02:03:0A:0B:0C"}, {@code "01:02:03:0A"}, {@code ":"}, {@code ""}. + * @see #toString() + * @throws IllegalArgumentException if given string doesn't comply with EUI48 + */ + public EUI48Sub(final String str) throws IllegalArgumentException { + final StringBuilder errmsg = new StringBuilder(); + b = new byte[ 6 ]; + if( !scanEUI48Sub(str, this, errmsg) ) { + throw new IllegalArgumentException(errmsg.toString()); } } @@ -79,11 +122,124 @@ public class EUI48Sub { if( len_ > EUI48.byte_size || pos + len_ > stream.length ) { throw new IllegalArgumentException("EUI48 stream ( pos "+pos+", len "+len_+" > EUI48 size "+EUI48.byte_size+" or stream.length "+stream.length); } - b = new byte[len_]; + b = new byte[6]; System.arraycopy(stream, pos, b, 0, len_); length = len_; } + @Override + public final boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if (obj == null || !(obj instanceof EUI48Sub)) { + return false; + } + final int length2 = ((EUI48Sub)obj).length; + if( length != length2 ) { + return false; + } + final byte[] b2 = ((EUI48Sub)obj).b; + return Arrays.equals(b, 0, length, b2, 0, length2); + } + + /** + * {@inheritDoc} + * <p> + * Implementation uses a lock-free volatile cache. + * </p> + * @see #clearHash() + */ + @Override + public final int hashCode() { + int h = hash; + if( 0 == h ) { + // 31 * x == (x << 5) - x + h = length; + for(int i=0; i<length; i++) { + h = ( ( h << 5 ) - h ) + b[i]; + } + hash = h; + } + return h; + } + + /** + * Method clears the cached hash value. + * @see #clear() + */ + public void clearHash() { hash = 0; } + + /** + * Method clears the underlying byte array {@link #b} and sets length to zero. The cached hash value is also cleared. + * @see #clearHash() + */ + public void clear() { + hash = 0; + b[0] = 0; b[1] = 0; b[2] = 0; + b[3] = 0; b[4] = 0; b[5] = 0; + length = 0; + } + + /** + * Find index of needle within haystack. + * @param haystack_b haystack data + * @param haystack_length haystack length + * @param needle_b needle data + * @param needle_length needle length + * @return index of first element of needle within haystack or -1 if not found. If the needle length is zero, 0 (found) is returned. + */ + public static int indexOf(final byte haystack_b[], final int haystack_length, + final byte needle_b[], final int needle_length) { + if( 0 == needle_length ) { + return 0; + } + if( haystack_length < needle_length ) { + return -1; + } + final byte first = needle_b[0]; + final int outerEnd = haystack_length - needle_length + 1; // exclusive + + for (int i = 0; i < outerEnd; i++) { + // find first char of other + while( haystack_b[i] != first ) { + if( ++i == outerEnd ) { + return -1; + } + } + if( i < outerEnd ) { // otherLen chars left to match? + // continue matching other chars + final int innerEnd = i + needle_length; // exclusive + int j = i, k=0; + do { + if( ++j == innerEnd ) { + return i; // gotcha + } + } while( haystack_b[j] == needle_b[++k] ); + } + } + return -1; + } + + /** + * Finds the index of given EUI48Sub needle within this instance haystack. + * @param needle + * @return index of first element of needle within this instance haystack or -1 if not found. If the needle length is zero, 0 (found) is returned. + */ + public int indexOf(final EUI48Sub needle) { + return indexOf(b, length, needle.b, needle.length); + } + + /** + * Returns true, if given EUI48Sub needle is contained in this instance haystack. + * <p> + * If the sub is zero, true is returned. + * </p> + */ + public boolean contains(final EUI48Sub needle) { + return 0 <= indexOf(needle); + } + /** * {@inheritDoc} * <p> |