Skip to content

Commit

Permalink
[#157] Improved ByteString comments and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
susanw1 committed Jul 11, 2024
1 parent 8b6eca6 commit bf0eb9b
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 14 deletions.
40 changes: 30 additions & 10 deletions util/misc/src/main/java/net/zscript/util/ByteString.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

import static java.nio.charset.StandardCharsets.UTF_8;

/**
* A buildable byte string to make it easy to work with byte-oriented text data.
*/
public interface ByteString {
/**
* Returns (a copy of) the content of this ByteString.
Expand Down Expand Up @@ -138,23 +141,37 @@ public <B extends ByteStringBuilder> B appendByte(int b) {
return appendByte((byte) b);
}

/**
* Appends the supplied single byte, without having to check for range.
*
* @param b any value 0x00-0xff
* @return this builder, to facilitate chaining
*/
public <B extends ByteStringBuilder> B appendByte(byte b) {
baos.write(b);
return asTypeB();
}

/**
* Appends the supplied byte array.
*
* @param bytes any value 0x00-0xff
* @return this builder, to facilitate chaining
* @throws IllegalArgumentException if value is out of range 0-ff
*/
public <B extends ByteStringBuilder> B appendRaw(byte[] bytes) {
baos.writeBytes(bytes);
return asTypeB();
}

/**
* Appends the supplied byte string (creates temporary copy unless it's immutable).
*
* @param byteString any existing ByteString
* @return this builder, to facilitate chaining
*/
public <B extends ByteStringBuilder> B append(ByteString byteString) {
baos.writeBytes(byteString.toByteArray());
return asTypeB();
}

public <B extends ByteStringBuilder> B append(ImmutableByteString byteString) {
baos.writeBytes(byteString.bytes);
baos.writeBytes(byteString instanceof ImmutableByteString ? ((ImmutableByteString) byteString).bytes : byteString.toByteArray());
return asTypeB();
}

Expand Down Expand Up @@ -190,14 +207,17 @@ public <B extends ByteStringBuilder> B appendNumeric32(long value) {
}

/**
* Appends a number as up to 4 nibbles of hex. All leading zeroes EXCEPT ONE are suppressed.
* Appends a number as up to 8 nibbles of hex. All leading zeroes EXCEPT ONE are suppressed.
*
* @param value the value to append
* @return this builder, to facilitate chaining
* @throws IllegalArgumentException if value is out of range
*/
public <B extends ByteStringBuilder> B appendNumeric32KeepZero(long value) {
if (value > 0x10000) {
if ((value & ~0xffffffffL) != 0) {
throw new IllegalArgumentException("Numeric32 fields must be 0x0-0xffffffff: " + value);
}
if (value >= 0x10000) {
appendNumericKeepZero((int) (value >>> 16));
appendHexPair((byte) (value >>> 8));
return appendHexPair((byte) (value));
Expand Down Expand Up @@ -226,7 +246,7 @@ public <B extends ByteStringBuilder> B appendNumeric(int value) {
*/
public <B extends ByteStringBuilder> B appendNumericKeepZero(int value) {
if ((value & ~0xffff) != 0) {
throw new IllegalArgumentException("Numeric fields must be 0x0-0xffffffff: " + value);
throw new IllegalArgumentException("Numeric fields must be 0x0-0xffff: " + value);
}
if (value >= 0x1000) {
baos.write(toHex(value >>> 12));
Expand All @@ -242,7 +262,7 @@ public <B extends ByteStringBuilder> B appendNumericKeepZero(int value) {
}

@SuppressWarnings("unchecked")
public <B extends ByteStringBuilder> B asTypeB() {
private <B extends ByteStringBuilder> B asTypeB() {
return (B) this;
}

Expand Down
8 changes: 4 additions & 4 deletions util/misc/src/main/java/net/zscript/util/OptIterator.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface OptIterator<T> {
* to highlight improper use, but they might not.
*
* @return value items wrapped as Optional, or {@link java.util.Optional#empty()} to signal end of items
* @exception NoSuchElementException on going beyond the end (optional)
* @throws NoSuchElementException on going beyond the end (optional)
*/
Optional<T> next();

Expand All @@ -42,12 +42,12 @@ default void forEach(final Consumer<T> action) {
}

/**
* Exposes the emelements as a conventional {@link java.util.stream.Stream}.
* Exposes the elements as a conventional {@link java.util.stream.Stream}.
*
* @return a stream of elements
*/
default Stream<T> stream() {
return Stream.iterate(next(), t -> t.isPresent(), t -> next()).map(t -> t.get());
return Stream.iterate(next(), Optional::isPresent, t -> next()).map(Optional::get);
}

/**
Expand All @@ -59,7 +59,7 @@ default Stream<T> stream() {
*/
static <T> OptIterator<T> of(final Iterable<T> iterable) {
return new OptIterator<T>() {
Iterator<T> origIterator = iterable.iterator();
private final Iterator<T> origIterator = iterable.iterator();

@Override
public Optional<T> next() {
Expand Down
52 changes: 52 additions & 0 deletions util/misc/src/test/java/net/zscript/util/ByteStringTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import net.zscript.util.ByteString.ImmutableByteString;

Expand All @@ -30,6 +33,22 @@ public void shouldAppendBytes() throws IOException {
.isInstanceOf(IllegalArgumentException.class);
}

@Test
public void shouldInitializeNewBuilderWithExistingContent() {
ByteString.ByteStringBuilder b = ByteString.builder().appendByte('Z').appendNumeric(0x01a2);
assertThat(ByteString.builder(b).appendByte('X').appendNumeric(0x1a2b).toByteArray())
.containsExactly('Z', '1', 'a', '2', 'X', '1', 'a', '2', 'b');
}

@Test
public void shouldAppendExistingContent() {
ByteString.ByteStringBuilder b = ByteString.builder().appendByte('Z').appendNumeric(0xa2);
assertThat(ByteString.builder().appendByte('X').append(b).appendByte('Y').toByteArray())
.containsExactly('X', 'Z', 'a', '2', 'Y');
assertThat(ByteString.builder().appendByte('X').append(b.build()).appendByte('Y').toByteArray())
.containsExactly('X', 'Z', 'a', '2', 'Y');
}

@Test
public void shouldWriteToByteArrayNumbers() {
assertThat(ByteString.builder().appendByte('Z').appendNumeric(0x1a2b).toByteArray())
Expand All @@ -54,6 +73,33 @@ public void shouldThrowOnOutOfRangeNumericValue() {
.isInstanceOf(IllegalArgumentException.class);
}

@ParameterizedTest
@CsvSource({ "0xf1e2d3c4,Zf1e2d3c4", "0xe2d3c4,Ze2d3c4", "0x3c4,Z3c4", "0x10000,Z10000", "0xffff,Zffff", "0,Z" })
public void shouldWriteToByteArrayNumbers32(long value, String expected) {
assertThat(ByteString.builder().appendByte('Z').appendNumeric32(value).toByteArray())
.containsExactly(expected.getBytes(StandardCharsets.ISO_8859_1));
}

@Test
public void shouldOmitNumberZero32() {
assertThat(ByteString.builder().appendByte('Z').appendNumeric32(0).toByteArray())
.containsExactly('Z');
}

@Test
public void shouldWriteNumberZero32() {
assertThat(ByteString.builder().appendByte('Z').appendNumeric32KeepZero(0).toByteArray())
.containsExactly('Z', '0');
}

@Test
public void shouldThrowOnOutOfRangeNumericValue32() {
assertThatThrownBy(() -> ByteString.builder().appendByte('Z').appendNumeric32(0x123456789L).toByteArray())
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> ByteString.builder().appendByte('Z').appendNumeric32(-1).toByteArray())
.isInstanceOf(IllegalArgumentException.class);
}

@Test
public void shouldWriteHex() {
ByteString.ByteStringBuilder builder = ByteString.builder();
Expand All @@ -75,4 +121,10 @@ public void toStringContract() {
assertThat(ByteString.builder().appendByte('Z').appendNumeric(0x12).build().toString())
.isEqualTo("ImmutableByteString[Z12]");
}

@Test
public void builderToStringContract() {
assertThat(ByteString.builder().appendByte('Z').appendNumeric(0x12).toString())
.isEqualTo("ByteStringBuilder[Z12]");
}
}

0 comments on commit bf0eb9b

Please sign in to comment.