Skip to content

Commit

Permalink
Added byte array message type (#888)
Browse files Browse the repository at this point in the history
* Added byte array message type

* javadoc

* fixing layout

* sonar fix

* belt-and-braces
  • Loading branch information
therealryan authored Aug 13, 2024
1 parent 9861738 commit 14e75c8
Show file tree
Hide file tree
Showing 10 changed files with 570 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ graph TB
builder[<a href='https://github.com/Mastercard/flow/tree/main/builder'>builder</a>]
coppice[<a href='https://github.com/Mastercard/flow/tree/main/validation/coppice'>coppice</a>]
duct[<a href='https://github.com/Mastercard/flow/tree/main/report/duct'>duct</a>]
message-bytes[<a href='https://github.com/Mastercard/flow/tree/main/message/message-bytes'>message-bytes</a>]
message-core[<a href='https://github.com/Mastercard/flow/tree/main/message/message-core'>message-core</a>]
message-http[<a href='https://github.com/Mastercard/flow/tree/main/message/message-http'>message-http</a>]
message-json[<a href='https://github.com/Mastercard/flow/tree/main/message/message-json'>message-json</a>]
Expand All @@ -80,6 +81,7 @@ graph TB
assert-core --> assert-junit4
assert-core --> assert-junit5
assert-filter --> assert-core
message-core --> message-bytes
message-core --> message-http
message-core --> message-json
message-core --> message-sql
Expand Down
6 changes: 6 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>message-bytes</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>message-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Stream<DynamicNode> markdown() {
+ "com.mastercard.test.flow:assert-junit5\n"
+ "com.mastercard.test.flow:builder\n"
+ "com.mastercard.test.flow:duct\n"
+ "com.mastercard.test.flow:message-bytes\n"
+ "com.mastercard.test.flow:message-core\n"
+ "com.mastercard.test.flow:message-http\n"
+ "com.mastercard.test.flow:message-json\n"
Expand Down
1 change: 1 addition & 0 deletions message/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Implementations of the Message interface


* [../flow](https://github.com/Mastercard/flow) Testing framework
* [message-bytes](message-bytes) Raw byte array messages
* [message-core](message-core) Message implementation utilities
* [message-http](message-http) HyperText Transfer Protocol messages
* [message-json](message-json) JavaScript Object Notation messages
Expand Down
32 changes: 32 additions & 0 deletions message/message-bytes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

<!-- title start -->

# message-bytes

Raw byte array messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-bytes/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-bytes)

* [../message](..) Implementations of the Message interface

<!-- title end -->

## Usage

After [importing the `bom`](../../bom):

```xml
<dependency>
<!-- byte array message type -->
<groupId>com.mastercard.test.flow</groupId>
<artifactId>message-bytes</artifactId>
</dependency>
```

The unit test [`BytesTest`][bytes.BytesTest] contains usage examples for the message type supplied by this module.

<!-- code_link_start -->

[bytes.BytesTest]: src/test/java/com/mastercard/test/flow/msg/bytes/BytesTest.java

<!-- code_link_end -->
43 changes: 43 additions & 0 deletions message/message-bytes/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mastercard.test.flow</groupId>
<artifactId>message</artifactId>
<version>1.1.6-SNAPSHOT</version>
</parent>
<artifactId>message-bytes</artifactId>
<packaging>jar</packaging>
<description>Raw byte array messages</description>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>message-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<configuration>
<!-- I'm not saying these *have* to stay at 100, just that
we shouldn't let them slip down by accident -->
<mutationThreshold>100</mutationThreshold>
<coverageThreshold>100</coverageThreshold>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package com.mastercard.test.flow.msg.bytes;

import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.mastercard.test.flow.msg.AbstractMessage;

/**
* Raw byte array message.
* <ul>
* <li>individual bytes are addressed by zero-based index</li>
* <li>byte ranges are address by zero-based index ranges, e.g.:
* <ul>
* <li><code>x..y</code> addresses the bytes from index <code>x</code>
* (inclusive) to <code>y</code> (exclusive)</li>
* <li><code>..y</code> addresses the bytes from index <code>0</code>
* (inclusive) to <code>y</code> (exclusive)</li>
* <li><code>x..</code> addresses the bytes from index <code>x</code>
* (inclusive) to the end of the array</li>
* <li><code>..</code> addresses the bytes from index <code>0</code> (inclusive)
* to the end of the array</li>
* </ul>
* </li>
* </ul>
*/
public class Bytes extends AbstractMessage<Bytes> {

private final Supplier<byte[]> base;

/**
* Builds a new, empty, message
*/
public Bytes() {
base = () -> new byte[0];
}

/**
* Builds a new message
*
* @param content The message content
*/
public Bytes( byte[] content ) {
byte[] copy = copy( content );
base = () -> copy;
}

private Bytes( Bytes parent ) {
base = parent::build;
}

@Override
public Bytes child() {
return copyMasksTo( new Bytes( this ) );
}

@Override
public Bytes peer( byte[] content ) {
return copyMasksTo( new Bytes( content ) );
}

private byte[] build() {
byte[] array = base.get();
for( Update update : updates ) {
byte[] content;
if( update.value() == DELETE ) {
content = new byte[0];
}
else if( update.value() instanceof Byte ) {
content = new byte[] { (byte) update.value() };
}
else {
content = (byte[]) update.value();
}

array = new Range( update.field() ).replace( content, array );
}
return array;
}

@Override
public byte[] content() {
return build();
}

@Override
public Set<String> fields() {
// not supported - how do you enumerate all the ways the bytes could be
// addressed?
return Collections.emptySet();
}

@Override
protected String asHuman() {
StringBuilder sb = new StringBuilder();
for( byte b : build() ) {
sb.append( String.format( "0b%s 0d%03d 0x%02X %s\n",
Integer.toBinaryString( (b & 0xFF) + 0x100 ).substring( 1 ),
b & 0xFF,
b,
0 <= b // if the high bit isn't set...
? Character.getName( b ) // ...then it's a valid character
: "" ) );
}
return sb.toString();
}

@Override
protected byte[] access( String field ) {
return new Range( field ).infix( build() );
}

private static final byte[] copy( byte[] array ) {
byte[] copy = new byte[array.length];
System.arraycopy( array, 0, copy, 0, copy.length );
return copy;
}

@Override
public Bytes set( String field, Object value ) {
// we want this to fail immediately so the exception traces to where the call is
// being made rather than where the message is being compiled
@SuppressWarnings("unused")
Range r = new Range( field );
return super.set( field, value );
}

private static class Range {
private final int start;
private final int end;
private static final Pattern INDEX_PATTERN = Pattern.compile( "(\\d+)" );
private static final Pattern RANGE_PATTERN = Pattern.compile( "(\\d*)\\.\\.(\\d*)" );

/**
* @param s The range specification
*/
Range( String s ) {
if( s == null ) {
throw new IllegalArgumentException( "index range must be non-null" );
}

Matcher im = INDEX_PATTERN.matcher( s );
Matcher rm = RANGE_PATTERN.matcher( s );
if( im.matches() ) {
start = Integer.parseInt( im.group( 1 ) );
end = start + 1;
}
else if( rm.matches() ) {
start = Optional.of( rm.group( 1 ) )
.filter( d -> !d.isEmpty() )
.map( Integer::parseInt )
.orElse( 0 );
end = Optional.of( rm.group( 2 ) )
.filter( d -> !d.isEmpty() )
.map( Integer::parseInt )
.orElse( Integer.MAX_VALUE );
}
else {
throw new IllegalArgumentException( "'" + s + "' is not a valid range" );
}

if( start > end ) {
throw new IllegalArgumentException( "range indices must be in 'a..b' order, where a<=b" );
}
}

/**
* Extracts the prefix content of this range
*
* @param array an array
* @return The bytes of the supplied array that come before this range
*/
byte[] prefix( byte[] array ) {
int s = Math.min( start, array.length );
byte[] prefix = new byte[s];
System.arraycopy( array, 0, prefix, 0, prefix.length );
return prefix;
}

/**
* Extracts the infix content of this range
*
* @param array an array
* @return The bytes of the array that lie in this range
*/
byte[] infix( byte[] array ) {
int s = Math.min( start, array.length );
int e = Math.min( end, array.length );
byte[] infix = new byte[e - s];
System.arraycopy( array, s, infix, 0, infix.length );
return infix;
}

/**
* Extracts the suffix content of this range
*
* @param array an array
* @return The bytes of the supplied array that come after this range
*/
byte[] suffix( byte[] array ) {
int e = Math.min( end, array.length );
byte[] suffix = new byte[array.length - e];
System.arraycopy( array, e, suffix, 0, suffix.length );
return suffix;
}

/**
* Inserts content into this range in an array
*
* @param content The content to insert
* @param array The array to insert into
* @return the new array
*/
byte[] replace( byte[] content, byte[] array ) {
byte[] prefix = prefix( array );
byte[] suffix = suffix( array );
byte[] combined = new byte[prefix.length + content.length + suffix.length];
System.arraycopy( prefix, 0, combined, 0, prefix.length );
System.arraycopy( content, 0, combined, prefix.length, content.length );
System.arraycopy( suffix, 0, combined, prefix.length + content.length, suffix.length );
return combined;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Byte-array {@link com.mastercard.test.flow.Message} implementation
*/
package com.mastercard.test.flow.msg.bytes;
Loading

0 comments on commit 14e75c8

Please sign in to comment.