aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2021-07-31 02:19:36 +0200
committerSven Gothel <[email protected]>2021-07-31 02:19:36 +0200
commit0e2d05a2b7542c4c2865f736ddc7a1272c730f9d (patch)
treef2abd1538456a32ca39704b86f5b19bf3e3c20af
parent47d0206cc9d2044379e00619a843101ce9d3778f (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.java78
-rw-r--r--java/org/direct_bt/EUI48Sub.java180
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>