Skip to content

Commit 1f7413d

Browse files
committed
Add tests for binary literal parsing
1 parent 93721d7 commit 1f7413d

8 files changed

Lines changed: 131 additions & 23 deletions

File tree

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolBinaryLiteral.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.yocto.yoclib.imap.protocol;
22

33
import java.nio.charset.StandardCharsets;
4+
import java.util.Arrays;
45
import java.util.Base64;
6+
import java.util.Objects;
57

68
public class ProtocolBinaryLiteral extends ProtocolObject {
79

@@ -26,6 +28,18 @@ public ProtocolBinaryLiteral(String value){
2628
this(value.getBytes(StandardCharsets.ISO_8859_1));
2729
}
2830

31+
@Override
32+
public boolean equals(Object o) {
33+
if (!(o instanceof ProtocolBinaryLiteral)) return false;
34+
ProtocolBinaryLiteral that = (ProtocolBinaryLiteral) o;
35+
return isNonSynchronizing == that.isNonSynchronizing && Objects.deepEquals(value, that.value);
36+
}
37+
38+
@Override
39+
public int hashCode() {
40+
return Objects.hash(Arrays.hashCode(value), isNonSynchronizing);
41+
}
42+
2943
public byte[] getValue(){
3044
return this.value;
3145
}

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolList.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.yocto.yoclib.imap.protocol;
22

33
import java.util.Arrays;
4+
import java.util.Objects;
45
import java.util.stream.Collectors;
56

67
public class ProtocolList extends ProtocolObject {
@@ -11,6 +12,18 @@ public ProtocolList(ProtocolObject[] objects){
1112
this.objects = objects;
1213
}
1314

15+
@Override
16+
public boolean equals(Object o) {
17+
if (!(o instanceof ProtocolList)) return false;
18+
ProtocolList that = (ProtocolList) o;
19+
return Objects.deepEquals(objects, that.objects);
20+
}
21+
22+
@Override
23+
public int hashCode() {
24+
return Arrays.hashCode(objects);
25+
}
26+
1427
public ProtocolObject[] getObjects() {
1528
return this.objects;
1629
}

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolLiteral.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.yocto.yoclib.imap.protocol;
22

3+
import java.util.Objects;
4+
35
public class ProtocolLiteral extends ProtocolString {
46

57
private final boolean isNonSynchronizing;
@@ -14,6 +16,19 @@ public ProtocolLiteral(String value){
1416
this(value,false);
1517
}
1618

19+
@Override
20+
public boolean equals(Object o) {
21+
if (!(o instanceof ProtocolLiteral)) return false;
22+
if (!super.equals(o)) return false;
23+
ProtocolLiteral that = (ProtocolLiteral) o;
24+
return isNonSynchronizing == that.isNonSynchronizing;
25+
}
26+
27+
@Override
28+
public int hashCode() {
29+
return Objects.hash(super.hashCode(), isNonSynchronizing);
30+
}
31+
1732
public boolean isNonSynchronizing(){
1833
return this.isNonSynchronizing;
1934
}

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolParser.java

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import java.util.ArrayList;
55
import java.util.List;
66

7-
public class ProtocolParser {
7+
public final class ProtocolParser {
88

99
public static ProtocolObject[] parse(String input) {
1010
if (input == null || input.trim().isEmpty()) {
@@ -29,8 +29,8 @@ private static ProtocolObject parseToken(Tokenizer t) {
2929

3030
char c = t.peekCharNoSkip();
3131

32-
// Binary literal: ~{n} or ~{n+}
33-
if (c == '~') {
32+
// Binary literal: ~{n} or ~{n+} ← Only if ~ is immediately followed by {
33+
if (c == '~' && t.isNextCharAfter('~', '{')) {
3434
t.consumeNoSkip('~');
3535
return parseBinaryLiteral(t);
3636
}
@@ -44,7 +44,7 @@ private static ProtocolObject parseToken(Tokenizer t) {
4444
if (c == '[') return parseSubordinate(t);
4545
if (c == '"') return parseQuoted(t);
4646

47-
// Atom
47+
// Atom (includes lone "~", etc.)
4848
String atomValue = t.readAtom();
4949
ProtocolAtom atom = new ProtocolAtom(atomValue);
5050

@@ -55,36 +55,23 @@ private static ProtocolObject parseToken(Tokenizer t) {
5555
return atom;
5656
}
5757

58-
// ====================== Split Literal Parsers ======================
58+
// ====================== Literal Parsers ======================
5959

60-
/**
61-
* Parses regular literals: {n} or {n+}
62-
*/
6360
private static ProtocolLiteral parseRegularLiteral(Tokenizer t) {
6461
LiteralHeader header = parseLiteralHeader(t, false);
6562
t.skipCRLF();
66-
6763
String data = t.readExactly(header.size);
6864
return new ProtocolLiteral(data, header.nonSynchronizing);
6965
}
7066

71-
/**
72-
* Parses binary literals: ~{n} or ~{n+}
73-
*/
7467
private static ProtocolBinaryLiteral parseBinaryLiteral(Tokenizer t) {
7568
LiteralHeader header = parseLiteralHeader(t, true);
7669
t.skipCRLF();
77-
7870
String rawData = t.readExactly(header.size);
7971
byte[] binaryData = rawData.getBytes(StandardCharsets.ISO_8859_1);
80-
8172
return new ProtocolBinaryLiteral(binaryData, header.nonSynchronizing);
8273
}
8374

84-
/**
85-
* Common helper: parses the header part {size} or {size+}
86-
* (tilde ~ is already consumed for binary case)
87-
*/
8875
private static LiteralHeader parseLiteralHeader(Tokenizer t, boolean isBinary) {
8976
t.consumeNoSkip('{');
9077

@@ -101,7 +88,7 @@ private static LiteralHeader parseLiteralHeader(Tokenizer t, boolean isBinary) {
10188
} else if (c == '}') {
10289
break;
10390
} else {
104-
break; // malformed
91+
break;
10592
}
10693
}
10794

@@ -117,7 +104,7 @@ private static LiteralHeader parseLiteralHeader(Tokenizer t, boolean isBinary) {
117104
return new LiteralHeader(size, nonSynchronizing);
118105
}
119106

120-
// ====================== Other Parsers (unchanged) ======================
107+
// ====================== Other Parsers ======================
121108

122109
private static ProtocolList parseList(Tokenizer t) {
123110
t.consume('(');
@@ -222,7 +209,7 @@ private static ProtocolQuoted parseQuoted(Tokenizer t) {
222209
return new ProtocolQuoted(sb.toString());
223210
}
224211

225-
// ====================== Tokenizer (unchanged) ======================
212+
// ====================== Tokenizer ======================
226213

227214
private static class Tokenizer {
228215
final String input;
@@ -261,6 +248,13 @@ public boolean isNextCharImmediate(char expected) {
261248
return pos < input.length() && input.charAt(pos) == expected;
262249
}
263250

251+
public boolean isNextCharAfter(char current, char expected) {
252+
if (pos >= input.length() || input.charAt(pos) != current) {
253+
return false;
254+
}
255+
return pos + 1 < input.length() && input.charAt(pos + 1) == expected;
256+
}
257+
264258
public String readAtom() {
265259
skipWhitespace();
266260
int start = pos;

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolSectionPartial.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.yocto.yoclib.imap.protocol;
22

3+
import java.util.Objects;
4+
35
public class ProtocolSectionPartial extends ProtocolObject{
46

57
private final ProtocolAtom atom;
@@ -56,6 +58,18 @@ public Integer getPartialLength() {
5658
return this.partialLength;
5759
}
5860

61+
@Override
62+
public boolean equals(Object o) {
63+
if (!(o instanceof ProtocolSectionPartial)) return false;
64+
ProtocolSectionPartial that = (ProtocolSectionPartial) o;
65+
return Objects.equals(atom, that.atom) && Objects.equals(subordinate, that.subordinate) && Objects.equals(partialOffset, that.partialOffset) && Objects.equals(partialLength, that.partialLength);
66+
}
67+
68+
@Override
69+
public int hashCode() {
70+
return Objects.hash(atom, subordinate, partialOffset, partialLength);
71+
}
72+
5973
@Override
6074
public String toProtocolString() {
6175
StringBuilder sb = new StringBuilder();

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolString.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.yocto.yoclib.imap.protocol;
22

3+
import java.util.Objects;
4+
35
public abstract class ProtocolString extends ProtocolObject {
46

57
protected final String value;
@@ -8,6 +10,18 @@ protected ProtocolString(String value){
810
this.value = value;
911
}
1012

13+
@Override
14+
public boolean equals(Object o) {
15+
if (!(o instanceof ProtocolString)) return false;
16+
ProtocolString that = (ProtocolString) o;
17+
return Objects.equals(value, that.value);
18+
}
19+
20+
@Override
21+
public int hashCode() {
22+
return Objects.hashCode(value);
23+
}
24+
1125
public String getValue(){
1226
return this.value;
1327
}

src/main/java/com/yocto/yoclib/imap/protocol/ProtocolSubordinate.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.yocto.yoclib.imap.protocol;
22

33
import java.util.Arrays;
4+
import java.util.Objects;
45
import java.util.stream.Collectors;
56

67
public class ProtocolSubordinate extends ProtocolObject {
@@ -15,6 +16,18 @@ public ProtocolObject[] getObjects(){
1516
return this.objects;
1617
}
1718

19+
@Override
20+
public boolean equals(Object o) {
21+
if (!(o instanceof ProtocolSubordinate)) return false;
22+
ProtocolSubordinate that = (ProtocolSubordinate) o;
23+
return Objects.deepEquals(objects, that.objects);
24+
}
25+
26+
@Override
27+
public int hashCode() {
28+
return Arrays.hashCode(objects);
29+
}
30+
1831
@Override
1932
public String toProtocolString(){
2033
return ProtocolConstants.SQUARE_BRACKET_LEFT+ProtocolObject.joinObjects(this.objects)+ProtocolConstants.SQUARE_BRACKET_RIGHT;

src/test/java/com/yocto/yoclib/imap/tests/protocol/ProtocolParserTest.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,47 @@
11
package com.yocto.yoclib.imap.tests.protocol;
22

33
import com.yocto.yoclib.imap.protocol.ProtocolAtom;
4+
import com.yocto.yoclib.imap.protocol.ProtocolBinaryLiteral;
45
import com.yocto.yoclib.imap.protocol.ProtocolObject;
56
import com.yocto.yoclib.imap.protocol.ProtocolParser;
67

78
import org.junit.jupiter.api.Test;
89

9-
import static org.junit.jupiter.api.Assertions.assertEquals;
10-
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
10+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
1111

1212
public class ProtocolParserTest{
1313

14+
@Test
15+
public void testParsingBinaryLiteral(){
16+
assertArrayEquals(new ProtocolObject[]{
17+
new ProtocolAtom("~"),
18+
},ProtocolParser.parse("~\r\n"));
19+
20+
assertArrayEquals(new ProtocolObject[]{
21+
new ProtocolAtom("~/Mail/foo"),
22+
},ProtocolParser.parse("~/Mail/foo\r\n"));
23+
24+
assertArrayEquals(new ProtocolObject[]{
25+
new ProtocolBinaryLiteral(""),
26+
new ProtocolAtom("abc"),
27+
},ProtocolParser.parse("~{0}\r\n abc\r\n"));
28+
29+
assertArrayEquals(new ProtocolObject[]{
30+
new ProtocolBinaryLiteral("def"),
31+
new ProtocolAtom("abc"),
32+
},ProtocolParser.parse("~{3}\r\ndef abc\r\n"));
33+
34+
assertArrayEquals(new ProtocolObject[]{
35+
new ProtocolBinaryLiteral("",true),
36+
new ProtocolAtom("abc"),
37+
},ProtocolParser.parse("~{0+}\r\n abc\r\n"));
38+
39+
assertArrayEquals(new ProtocolObject[]{
40+
new ProtocolBinaryLiteral("def",true),
41+
new ProtocolAtom("abc"),
42+
},ProtocolParser.parse("~{3+}\r\ndef abc\r\n"));
43+
}
44+
1445
@Test
1546
public void testParsingComplexAtom(){
1647
//ProtocolObject[] objects = ProtocolParser.parse("!#$$&'+,-.0123456789:;<=>?@^_`|[}");

0 commit comments

Comments
 (0)