Skip to content

Commit d89bfd8

Browse files
committed
Add logical expressions in validator and starts/endswith
1 parent 3acc8fe commit d89bfd8

File tree

6 files changed

+252
-27
lines changed

6 files changed

+252
-27
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ Bytes.from(array).encodeBase36(); //5qpdvuwjvu5
190190
A simple validation framework which can be used to check the internal byte array:
191191

192192
```java
193-
Bytes.wrap(new byte[]{8, 3, 9}.validate(BytesValidators.atLeast(3)); // true
193+
Bytes.wrap(new byte[]{8, 3, 9}.validate(BytesValidators.startsWith((byte) 8), BytesValidators.atLeast(3)); // true
194194
```
195195

196196
This is especially convenient when combining validators:
@@ -199,6 +199,23 @@ This is especially convenient when combining validators:
199199
Bytes.wrap(new byte[]{0, 1}.validate(BytesValidators.atMost(2), BytesValidators.notOnlyOf((byte) 0)); // true
200200
```
201201

202+
Validators also support nestable logical expressions AND, OR as well as NOT:
203+
204+
```java
205+
Bytes.allocate(0).validate(or(exactLength(1), exactLength(0))) //true
206+
Bytes.allocate(21).validate(and(atLeast(3), atMost(20))) //true
207+
Bytes.allocate(2).validate(not(onlyOf((byte) 0))); //false
208+
```
209+
210+
Nesting is also possible:
211+
212+
```java
213+
assertTrue(Bytes.allocate(16).validate(
214+
or(
215+
and(atLeast(8),not(onlyOf(((byte) 0)))),
216+
or(exactLength(16), exactLength(12)))));
217+
```
218+
202219
### Converting
203220

204221
### Mutable and Read-Only

src/main/java/at/favre/lib/bytes/Bytes.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -810,18 +810,14 @@ public boolean validateNotOnlyZeros() {
810810
}
811811

812812
/**
813-
* Applies all given validators and returns true if all of them return true
813+
* Applies all given validators and returns true if all of them return true (default AND concatenation).
814814
*
815815
* @param bytesValidators array of validators to check against the byte array
816816
* @return true if all validators return true
817817
*/
818818
public boolean validate(BytesValidator... bytesValidators) {
819819
Objects.requireNonNull(bytesValidators);
820-
boolean valid = true;
821-
for (BytesValidator bytesValidator : bytesValidators) {
822-
valid &= bytesValidator.validate(internalArray());
823-
}
824-
return valid;
820+
return BytesValidators.and(bytesValidators).validate(internalArray());
825821
}
826822

827823
/* ATTRIBUTES ************************************************************************************************/

src/main/java/at/favre/lib/bytes/BytesValidator.java

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121

2222
package at.favre.lib.bytes;
2323

24+
import java.util.List;
25+
26+
import static at.favre.lib.bytes.BytesValidator.Logical.Operator.NOT;
27+
import static at.favre.lib.bytes.BytesValidator.Logical.Operator.OR;
28+
2429
/**
2530
* Interface for validating byte arrays
2631
*/
@@ -64,7 +69,6 @@ public boolean validate(byte[] byteArrayToValidate) {
6469
}
6570
}
6671

67-
6872
/**
6973
* Checks if a byte array contains only the same value
7074
*/
@@ -98,4 +102,79 @@ public boolean validate(byte[] byteArrayToValidate) {
98102
return mode == Mode.NONE_OF || mode == Mode.ONLY_OF;
99103
}
100104
}
105+
106+
/**
107+
* Checks if arrays end or start with given array
108+
*/
109+
final class PrePostFix implements BytesValidator {
110+
111+
private final byte[] pfix;
112+
private final boolean startsWith;
113+
114+
public PrePostFix(boolean startsWith, byte... pfix) {
115+
this.pfix = pfix;
116+
this.startsWith = startsWith;
117+
}
118+
119+
@Override
120+
public boolean validate(byte[] byteArrayToValidate) {
121+
if (pfix.length > byteArrayToValidate.length) {
122+
return false;
123+
}
124+
125+
for (int i = 0; i < pfix.length; i++) {
126+
if (startsWith && pfix[i] != byteArrayToValidate[i]) {
127+
return false;
128+
}
129+
if (!startsWith && pfix[i] != byteArrayToValidate[byteArrayToValidate.length - pfix.length + i]) {
130+
return false;
131+
}
132+
}
133+
return true;
134+
}
135+
}
136+
137+
/**
138+
* Logical operations over multiple validators
139+
*/
140+
final class Logical implements BytesValidator {
141+
enum Operator {
142+
OR, AND, NOT
143+
}
144+
145+
private final List<BytesValidator> validatorList;
146+
147+
private final Operator operator;
148+
149+
public Logical(List<BytesValidator> validatorList, Operator operator) {
150+
if (validatorList.isEmpty()) throw new IllegalArgumentException("must contain at least 1 element");
151+
if (operator == NOT && validatorList.size() != 1)
152+
throw new IllegalArgumentException("not operator can only be applied to single element");
153+
this.validatorList = validatorList;
154+
this.operator = operator;
155+
156+
}
157+
158+
@Override
159+
public boolean validate(byte[] byteArrayToValidate) {
160+
if (operator == NOT) {
161+
return !validatorList.get(0).validate(byteArrayToValidate);
162+
}
163+
164+
boolean bool = operator != OR;
165+
for (BytesValidator bytesValidator : validatorList) {
166+
switch (operator) {
167+
default:
168+
case OR:
169+
bool |= bytesValidator.validate(byteArrayToValidate);
170+
break;
171+
case AND:
172+
bool &= bytesValidator.validate(byteArrayToValidate);
173+
break;
174+
}
175+
}
176+
return bool;
177+
}
178+
179+
}
101180
}

src/main/java/at/favre/lib/bytes/BytesValidators.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
package at.favre.lib.bytes;
2323

24+
import java.util.Arrays;
25+
import java.util.Collections;
26+
2427
/**
2528
* Util and easy access for {@link BytesValidators}
2629
*/
@@ -79,6 +82,26 @@ public static BytesValidator notOnlyOf(byte refByte) {
7982
return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.NOT_ONLY_OF);
8083
}
8184

85+
/**
86+
* Checks if the internal byte array starts with given bytes
87+
*
88+
* @param startsWithBytes the supposed prefix
89+
* @return true all startsWithBytes match the first bytes in the internal array
90+
*/
91+
public static BytesValidator startsWith(byte... startsWithBytes) {
92+
return new BytesValidator.PrePostFix(true, startsWithBytes);
93+
}
94+
95+
/**
96+
* Checks if the internal byte array ends with given bytes
97+
*
98+
* @param endsWithBytes the supposed postfix
99+
* @return true all startsWithBytes match the first bytes in the internal array
100+
*/
101+
public static BytesValidator endsWith(byte... endsWithBytes) {
102+
return new BytesValidator.PrePostFix(false, endsWithBytes);
103+
}
104+
82105
/**
83106
* Checks individual byte content
84107
*
@@ -88,4 +111,35 @@ public static BytesValidator notOnlyOf(byte refByte) {
88111
public static BytesValidator noneOf(byte refByte) {
89112
return new BytesValidator.IdenticalContent(refByte, BytesValidator.IdenticalContent.Mode.NONE_OF);
90113
}
114+
115+
116+
/**
117+
* This will execute all passed validators and returns true if at least one returns true (i.e. OR concatenation)
118+
*
119+
* @param validators at least one validator must be passed
120+
* @return true if at least one validator returns true
121+
*/
122+
public static BytesValidator or(BytesValidator... validators) {
123+
return new BytesValidator.Logical(Arrays.asList(validators), BytesValidator.Logical.Operator.OR);
124+
}
125+
126+
/**
127+
* This will execute all passed validators and returns true if all return true (i.e. AND concatenation)
128+
*
129+
* @param validators at least one validator must be passed
130+
* @return true if all return true
131+
*/
132+
public static BytesValidator and(BytesValidator... validators) {
133+
return new BytesValidator.Logical(Arrays.asList(validators), BytesValidator.Logical.Operator.AND);
134+
}
135+
136+
/**
137+
* This will negate the result of the passed validator
138+
*
139+
* @param validator to negate
140+
* @return negated result
141+
*/
142+
public static BytesValidator not(BytesValidator validator) {
143+
return new BytesValidator.Logical(Collections.singletonList(validator), BytesValidator.Logical.Operator.NOT);
144+
}
91145
}

src/test/java/at/favre/lib/bytes/BytesConstructorTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
package at.favre.lib.bytes;
2323

24+
import org.junit.Ignore;
2425
import org.junit.Rule;
2526
import org.junit.Test;
2627
import org.junit.rules.TemporaryFolder;
@@ -290,6 +291,28 @@ public void fromFile() throws Exception {
290291
assertArrayEquals(randomBytes.array(), Bytes.from(tempFile).array());
291292
}
292293

294+
@Test(expected = IllegalArgumentException.class)
295+
public void fromFileNotExisting() throws Exception {
296+
Bytes.from(new File("doesnotexist"));
297+
}
298+
299+
@Test
300+
@Ignore("setting file NOT readable does not seem to work")
301+
public void fromFileCannotRead() throws Exception {
302+
File tempFile = testFolder.newFile("out-test2.txt");
303+
Bytes randomBytes = Bytes.random(500);
304+
305+
try (FileOutputStream stream = new FileOutputStream(tempFile)) {
306+
stream.write(randomBytes.array());
307+
}
308+
assertTrue(tempFile.setReadable(false));
309+
try {
310+
Bytes.from(tempFile);
311+
fail();
312+
} catch (IllegalStateException e) {
313+
}
314+
}
315+
293316
@Test
294317
public void fromObjectArray() throws Exception {
295318
Byte[] objectArray = new Byte[]{0x01, 0x02, 0x03, 0x04};

src/test/java/at/favre/lib/bytes/BytesValidatorTest.java

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323

2424
import org.junit.Test;
2525

26-
import static org.junit.Assert.assertFalse;
27-
import static org.junit.Assert.assertTrue;
26+
import static at.favre.lib.bytes.BytesValidators.*;
27+
import static org.junit.Assert.*;
2828

2929
public class BytesValidatorTest extends ABytesTest {
3030

@@ -36,28 +36,84 @@ public void testOnlyOfValidator() throws Exception {
3636
assertTrue(Bytes.wrap(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 1}).validateNotOnlyZeros());
3737
assertTrue(Bytes.random(128).validateNotOnlyZeros());
3838

39-
assertTrue(Bytes.allocate(1).validate(BytesValidators.onlyOf((byte) 0)));
40-
assertFalse(Bytes.allocate(1).validate(BytesValidators.noneOf((byte) 0)));
41-
assertFalse(Bytes.allocate(1).validate(BytesValidators.onlyOf((byte) 1)));
42-
assertTrue(Bytes.allocate(1).validate(BytesValidators.noneOf((byte) 1)));
43-
assertTrue(Bytes.allocate(1).validate(BytesValidators.noneOf((byte) 1)));
44-
assertTrue(Bytes.wrap(new byte[]{1, 1, 1, 1, 0, 1}).validate(BytesValidators.notOnlyOf((byte) 1)));
45-
assertFalse(Bytes.wrap(new byte[]{1, 1, 1, 1, 1}).validate(BytesValidators.notOnlyOf((byte) 1)));
46-
assertTrue(Bytes.wrap(new byte[]{1, 1, 1, 1, 1, 1}).validate(BytesValidators.onlyOf((byte) 1)));
39+
assertTrue(Bytes.allocate(1).validate(onlyOf((byte) 0)));
40+
assertFalse(Bytes.allocate(1).validate(noneOf((byte) 0)));
41+
assertFalse(Bytes.allocate(1).validate(onlyOf((byte) 1)));
42+
assertTrue(Bytes.allocate(1).validate(noneOf((byte) 1)));
43+
assertTrue(Bytes.allocate(1).validate(noneOf((byte) 1)));
44+
assertTrue(Bytes.wrap(new byte[]{1, 1, 1, 1, 0, 1}).validate(notOnlyOf((byte) 1)));
45+
assertFalse(Bytes.wrap(new byte[]{1, 1, 1, 1, 1}).validate(notOnlyOf((byte) 1)));
46+
assertTrue(Bytes.wrap(new byte[]{1, 1, 1, 1, 1, 1}).validate(onlyOf((byte) 1)));
4747
}
4848

4949
@Test
5050
public void testLengthValidators() throws Exception {
51-
assertFalse(Bytes.allocate(0).validate(BytesValidators.atLeast(1)));
52-
assertTrue(Bytes.allocate(1).validate(BytesValidators.atLeast(1)));
53-
assertTrue(Bytes.allocate(2).validate(BytesValidators.atLeast(1)));
51+
assertFalse(Bytes.allocate(0).validate(atLeast(1)));
52+
assertTrue(Bytes.allocate(1).validate(atLeast(1)));
53+
assertTrue(Bytes.allocate(2).validate(atLeast(1)));
5454

55-
assertFalse(Bytes.allocate(2).validate(BytesValidators.atMost(1)));
56-
assertTrue(Bytes.allocate(1).validate(BytesValidators.atMost(1)));
57-
assertTrue(Bytes.allocate(0).validate(BytesValidators.atMost(1)));
55+
assertFalse(Bytes.allocate(2).validate(atMost(1)));
56+
assertTrue(Bytes.allocate(1).validate(atMost(1)));
57+
assertTrue(Bytes.allocate(0).validate(atMost(1)));
5858

59-
assertFalse(Bytes.allocate(0).validate(BytesValidators.exactLength(1)));
60-
assertTrue(Bytes.allocate(1).validate(BytesValidators.exactLength(1)));
61-
assertFalse(Bytes.allocate(2).validate(BytesValidators.exactLength(1)));
59+
assertFalse(Bytes.allocate(0).validate(exactLength(1)));
60+
assertTrue(Bytes.allocate(1).validate(exactLength(1)));
61+
assertFalse(Bytes.allocate(2).validate(exactLength(1)));
62+
}
63+
64+
@Test
65+
public void testOrValidation() throws Exception {
66+
assertTrue(Bytes.allocate(0).validate(or(exactLength(1), exactLength(0))));
67+
assertTrue(Bytes.allocate(2).validate(or(atLeast(3), onlyOf((byte) 0))));
68+
assertTrue(Bytes.allocate(3).validate(or(onlyOf((byte) 1), onlyOf((byte) 0))));
69+
assertTrue(Bytes.wrap(new byte[]{0, 0}).validate(or(onlyOf((byte) 1), onlyOf((byte) 0))));
70+
assertFalse(Bytes.wrap(new byte[]{1, 0}).validate(or(onlyOf((byte) 2), onlyOf((byte) 1), onlyOf((byte) 0))));
71+
}
72+
73+
@Test
74+
public void testAndValidation() throws Exception {
75+
assertFalse(Bytes.allocate(5).validate(and(atLeast(3), notOnlyOf((byte) 0))));
76+
assertFalse(Bytes.wrap(new byte[]{1, 0}).validate(and(atLeast(3), notOnlyOf((byte) 0))));
77+
assertTrue(Bytes.wrap(new byte[]{1, 0, 0}).validate(and(atLeast(3), notOnlyOf((byte) 0))));
78+
assertFalse(Bytes.allocate(21).validate(and(atLeast(3), atMost(20))));
79+
}
80+
81+
@Test
82+
public void testNotValidation() throws Exception {
83+
assertEquals(Bytes.allocate(2).validate(not(onlyOf((byte) 0))), Bytes.allocate(2).validate(notOnlyOf((byte) 0)));
84+
assertTrue(Bytes.allocate(2).validate(not(atLeast(16))));
85+
assertFalse(Bytes.allocate(2).validate(not(atMost(16))));
86+
}
87+
88+
@Test
89+
public void testNestedValidation() throws Exception {
90+
assertTrue(Bytes.allocate(16).validate(
91+
or(and(atLeast(8), not(onlyOf(((byte) 0)))),
92+
or(exactLength(16), exactLength(12)))));
93+
94+
assertTrue(Bytes.allocate(16).validate(or(exactLength(16), exactLength(12))));
95+
assertFalse(Bytes.allocate(16).validate(and(atLeast(8), not(onlyOf(((byte) 0))))));
96+
assertTrue(Bytes.allocate(16).validate(and(atLeast(8), onlyOf(((byte) 0)))));
97+
assertTrue(Bytes.allocate(16).validate(or(not(onlyOf(((byte) 0))), exactLength(16))));
98+
}
99+
100+
@Test
101+
public void testStartWithValidate() throws Exception {
102+
assertTrue(Bytes.wrap(new byte[]{0, 3, 0}).validate(startsWith((byte) 0, (byte) 3)));
103+
assertFalse(Bytes.wrap(new byte[]{0, 2, 0}).validate(startsWith((byte) 0, (byte) 3)));
104+
assertTrue(Bytes.wrap(new byte[]{0, 2, 0}).validate(startsWith((byte) 0)));
105+
assertFalse(Bytes.wrap(new byte[]{0, 2, 0}).validate(startsWith((byte) 2)));
106+
assertTrue(Bytes.allocate(16).validate(startsWith((byte) 0)));
107+
assertFalse(Bytes.allocate(16).validate(startsWith(Bytes.allocate(17).array())));
108+
}
109+
110+
@Test
111+
public void testEndsWithValidate() throws Exception {
112+
assertTrue(Bytes.wrap(new byte[]{1, 2, 3}).validate(endsWith((byte) 2, (byte) 3)));
113+
assertFalse(Bytes.wrap(new byte[]{0, 2, 0}).validate(endsWith((byte) 3, (byte) 0)));
114+
assertTrue(Bytes.wrap(new byte[]{0, 2, 0}).validate(endsWith((byte) 0)));
115+
assertFalse(Bytes.wrap(new byte[]{0, 2, 0}).validate(endsWith((byte) 2)));
116+
assertTrue(Bytes.allocate(16).validate(endsWith((byte) 0)));
117+
assertFalse(Bytes.allocate(16).validate(endsWith(Bytes.allocate(17).array())));
62118
}
63119
}

0 commit comments

Comments
 (0)