Skip to content

Commit 833c325

Browse files
committed
Make TorrentFileParserImpl reusable and thread safe
1 parent 5495ed4 commit 833c325

5 files changed

Lines changed: 161 additions & 129 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.robothaver</groupId>
88
<artifactId>TorrentFileParser</artifactId>
9-
<version>1.0-SNAPSHOT</version>
9+
<version>1.1.7</version>
1010

1111
<properties>
1212
<maven.compiler.source>17</maven.compiler.source>

src/main/java/com/robothaver/torrentfileparser/Main.java

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,26 @@
33
import com.robothaver.torrentfileparser.domain.Torrent;
44
import com.robothaver.torrentfileparser.exception.MalformedTorrentFileException;
55
import com.robothaver.torrentfileparser.parser.TorrentFileParser;
6+
import com.robothaver.torrentfileparser.parser.TorrentFileParserImpl;
67

78
import java.io.IOException;
89
import java.nio.file.Files;
910
import java.nio.file.Path;
10-
import java.nio.file.Paths;
1111
import java.time.Duration;
1212
import java.time.Instant;
1313

1414
public class Main {
15-
public static void main(String[] args) {
16-
Path path = Paths.get("Path to torrent file");
17-
try {
18-
byte[] bytes = Files.readAllBytes(path);
19-
Instant start = Instant.now();
15+
public static void main(String[] args) throws IOException, MalformedTorrentFileException {
16+
Path path = Path.of("Path to torrent file");
17+
byte[] bytes = Files.readAllBytes(path);
18+
Instant start = Instant.now();
2019

21-
TorrentFileParser torrentParser = new TorrentFileParser(bytes, true);
22-
Torrent torrent = torrentParser.parseToTorrent();
23-
System.out.println(torrent.getName());
20+
TorrentFileParser torrentParser = new TorrentFileParserImpl();
21+
Torrent torrent = torrentParser.parseToTorrent(bytes, true);
22+
Instant finish = Instant.now();
2423

25-
Instant finish = Instant.now();
26-
long timeElapsed = Duration.between(start, finish).toMillis();
27-
System.out.println("Parsing finished in " + timeElapsed + "ms");
28-
} catch (IOException | MalformedTorrentFileException e) {
29-
throw new RuntimeException(e);
30-
}
24+
System.out.println(torrent.getName());
25+
long timeElapsed = Duration.between(start, finish).toMillis();
26+
System.out.println("Parsing finished in " + timeElapsed + "ms");
3127
}
3228
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.robothaver.torrentfileparser.parser;
2+
3+
import com.robothaver.torrentfileparser.domain.Torrent;
4+
import com.robothaver.torrentfileparser.exception.MalformedTorrentFileException;
5+
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.*;
8+
9+
class ParseWorker {
10+
private final byte[] bytes;
11+
private final boolean computeInfoHash;
12+
private TorrentBuilder torrentBuilder;
13+
private int iterator;
14+
private int infoDictStartIndex;
15+
16+
public ParseWorker(byte[] bytes, boolean computeInfoHash) {
17+
this.bytes = bytes;
18+
this.computeInfoHash = computeInfoHash;
19+
this.iterator = 0;
20+
this.infoDictStartIndex = -1;
21+
}
22+
23+
public Torrent parseToTorrent() throws MalformedTorrentFileException {
24+
torrentBuilder = new TorrentBuilder();
25+
parse();
26+
return torrentBuilder.getTorrent();
27+
}
28+
29+
@SuppressWarnings("unchecked")
30+
public Map<String, Object> parseToMap() throws MalformedTorrentFileException {
31+
Object parse = parse();
32+
33+
if (!(parse instanceof Map)) {
34+
throw new MalformedTorrentFileException("Root element is not a dictionary");
35+
}
36+
37+
return (Map<String, Object>) parse;
38+
}
39+
40+
private Object parse() throws MalformedTorrentFileException {
41+
return switch (bytes[iterator]) {
42+
case 'i' -> parseInt();
43+
case 'l' -> parseList();
44+
case 'd' -> parseDict();
45+
default -> {
46+
if (isInt(bytes[iterator])) {
47+
yield parseString();
48+
} else {
49+
throw new MalformedTorrentFileException("Illegal character " + (char) bytes[iterator] + " at " + iterator);
50+
}
51+
}
52+
};
53+
}
54+
55+
private boolean isInt(byte currentByte) {
56+
return (byte) '0' <= currentByte && currentByte <= (byte) '9';
57+
}
58+
59+
private List<Object> parseList() throws MalformedTorrentFileException {
60+
iterator++; // Skipping the l
61+
List<Object> list = new ArrayList<>();
62+
while (bytes[iterator] != 'e') {
63+
list.add(parse());
64+
}
65+
iterator++; // Skipping closing e
66+
return list;
67+
}
68+
69+
private Map<String, Object> parseDict() throws MalformedTorrentFileException {
70+
iterator++; // Skipping the d
71+
Map<String, Object> map = new HashMap<>();
72+
String key = null;
73+
while (bytes[iterator] != 'e') {
74+
Object parseValue = parse();
75+
if (key == null) {
76+
key = String.valueOf(parseValue);
77+
if (key.equals("info")) {
78+
infoDictStartIndex = iterator;
79+
}
80+
} else {
81+
map.put(key, parseValue);
82+
if (key.equals("info") && computeInfoHash && torrentBuilder != null) {
83+
torrentBuilder.setInfoHash(Arrays.copyOfRange(bytes, infoDictStartIndex, iterator));
84+
}
85+
if (torrentBuilder != null) {
86+
torrentBuilder.processKeyValue(key, parseValue);
87+
}
88+
key = null;
89+
}
90+
}
91+
iterator++; // Skipping closing e
92+
return map;
93+
}
94+
95+
private String parseString() throws MalformedTorrentFileException {
96+
int startIndex = iterator;
97+
int colonIndex = 0;
98+
while (iterator < bytes.length) {
99+
if (bytes[iterator] == (byte) ':') {
100+
colonIndex = iterator;
101+
break;
102+
} else {
103+
iterator++;
104+
}
105+
}
106+
int length;
107+
try {
108+
length = Integer.parseInt(new String(Arrays.copyOfRange(bytes, startIndex, colonIndex), StandardCharsets.UTF_8));
109+
} catch (NumberFormatException e) {
110+
throw new MalformedTorrentFileException();
111+
}
112+
113+
String value = new String(Arrays.copyOfRange(bytes, colonIndex + 1, colonIndex + length + 1), StandardCharsets.UTF_8);
114+
iterator += length + 1;
115+
return value;
116+
}
117+
118+
private Long parseInt() {
119+
int startIndex = iterator + 1;
120+
while (bytes[iterator] != 'e') {
121+
iterator++;
122+
}
123+
byte[] valueBytes = Arrays.copyOfRange(bytes, startIndex, iterator);
124+
iterator++; // Skipping closing e
125+
return Long.parseLong(new String(valueBytes, StandardCharsets.UTF_8));
126+
}
127+
}

src/main/java/com/robothaver/torrentfileparser/parser/TorrentFileParser.java

Lines changed: 4 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -3,119 +3,10 @@
33
import com.robothaver.torrentfileparser.domain.Torrent;
44
import com.robothaver.torrentfileparser.exception.MalformedTorrentFileException;
55

6-
import java.nio.charset.StandardCharsets;
7-
import java.util.*;
6+
import java.util.Map;
87

9-
public class TorrentFileParser {
10-
private final byte[] bytes;
11-
private final boolean computeInfoHash;
12-
private int iterator = 0;
13-
private TorrentBuilder torrentBuilder = null;
14-
private int infoDictStartIndex = -1;
8+
public interface TorrentFileParser {
9+
Torrent parseToTorrent(byte[] bytes, boolean computeInfoHash) throws MalformedTorrentFileException;
1510

16-
public TorrentFileParser(byte[] bytes, boolean computeInfoHash) {
17-
this.bytes = bytes;
18-
this.computeInfoHash = computeInfoHash;
19-
}
20-
21-
public Torrent parseToTorrent() throws MalformedTorrentFileException {
22-
torrentBuilder = new TorrentBuilder();
23-
parse();
24-
iterator = 0;
25-
return torrentBuilder.getTorrent();
26-
}
27-
28-
public Map<String, Object> parseToMap() throws MalformedTorrentFileException {
29-
Object parse = parse();
30-
iterator = 0;
31-
return (Map<String, Object>) parse;
32-
}
33-
34-
private Object parse() throws MalformedTorrentFileException {
35-
return switch (bytes[iterator]) {
36-
case 'i' -> parseInt();
37-
case 'l' -> parseList();
38-
case 'd' -> parseDict();
39-
default -> {
40-
if (isInt(bytes[iterator])) {
41-
yield parseString();
42-
} else {
43-
throw new MalformedTorrentFileException("Illegal character " + (char) bytes[iterator] + " at " + iterator);
44-
}
45-
}
46-
};
47-
}
48-
49-
private boolean isInt(byte currentByte) {
50-
return (byte) '0' <= currentByte && currentByte <= (byte) '9';
51-
}
52-
53-
private List<Object> parseList() throws MalformedTorrentFileException {
54-
iterator++; // Skipping the l
55-
List<Object> list = new ArrayList<>();
56-
while (bytes[iterator] != 'e') {
57-
list.add(parse());
58-
}
59-
iterator++; // Skipping closing e
60-
return list;
61-
}
62-
63-
private Map<String, Object> parseDict() throws MalformedTorrentFileException {
64-
iterator++; // Skipping the d
65-
Map<String, Object> map = new HashMap<>();
66-
String key = null;
67-
while (bytes[iterator] != 'e') {
68-
Object parseValue = parse();
69-
if (key == null) {
70-
key = String.valueOf(parseValue);
71-
if (key.equals("info")) {
72-
infoDictStartIndex = iterator;
73-
}
74-
} else {
75-
map.put(key, parseValue);
76-
if (key.equals("info") && computeInfoHash && torrentBuilder != null) {
77-
torrentBuilder.setInfoHash(Arrays.copyOfRange(bytes, infoDictStartIndex, iterator));
78-
}
79-
if (torrentBuilder != null) {
80-
torrentBuilder.processKeyValue(key, parseValue);
81-
}
82-
key = null;
83-
}
84-
}
85-
iterator++; // Skipping closing e
86-
return map;
87-
}
88-
89-
private String parseString() throws MalformedTorrentFileException {
90-
int startIndex = iterator;
91-
int colonIndex = 0;
92-
while (iterator < bytes.length) {
93-
if (bytes[iterator] == (byte) ':') {
94-
colonIndex = iterator;
95-
break;
96-
} else {
97-
iterator++;
98-
}
99-
}
100-
int length;
101-
try {
102-
length = Integer.parseInt(new String(Arrays.copyOfRange(bytes, startIndex, colonIndex), StandardCharsets.UTF_8));
103-
} catch (NumberFormatException e) {
104-
throw new MalformedTorrentFileException();
105-
}
106-
107-
String value = new String(Arrays.copyOfRange(bytes, colonIndex + 1, colonIndex + length + 1), StandardCharsets.UTF_8);
108-
iterator += length + 1;
109-
return value;
110-
}
111-
112-
private Long parseInt() {
113-
int startIndex = iterator + 1;
114-
while (bytes[iterator] != 'e') {
115-
iterator++;
116-
}
117-
byte[] valueBytes = Arrays.copyOfRange(bytes, startIndex, iterator);
118-
iterator++; // Skipping closing e
119-
return Long.parseLong(new String(valueBytes, StandardCharsets.UTF_8));
120-
}
11+
Map<String, Object> parseToMap(byte[] bytes, boolean computeInfoHash) throws MalformedTorrentFileException;
12112
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.robothaver.torrentfileparser.parser;
2+
3+
import com.robothaver.torrentfileparser.domain.Torrent;
4+
import com.robothaver.torrentfileparser.exception.MalformedTorrentFileException;
5+
6+
import java.util.Map;
7+
8+
public class TorrentFileParserImpl implements TorrentFileParser {
9+
@Override
10+
public Torrent parseToTorrent(byte[] bytes, boolean computeInfoHash) throws MalformedTorrentFileException {
11+
return new ParseWorker(bytes, computeInfoHash).parseToTorrent();
12+
}
13+
14+
@Override
15+
public Map<String, Object> parseToMap(byte[] bytes, boolean computeInfoHash) throws MalformedTorrentFileException {
16+
return new ParseWorker(bytes, computeInfoHash).parseToMap();
17+
}
18+
}

0 commit comments

Comments
 (0)