Skip to content

Commit f9fc35e

Browse files
committed
Fixed #97 and added unit tests
1 parent 2479a41 commit f9fc35e

9 files changed

Lines changed: 101 additions & 52 deletions

File tree

common/src/main/java/xyz/gianlu/librespot/common/Base62.java

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
import java.io.ByteArrayOutputStream;
5+
import java.util.Arrays;
56

67
/**
78
* A Base62 encoder/decoder.
@@ -46,15 +47,38 @@ public static Base62 createInstanceWithInvertedCharacterSet() {
4647
return new Base62(CharacterSets.INVERTED);
4748
}
4849

50+
/**
51+
* Encodes a sequence of bytes in Base62 encoding and pads it accordingly.
52+
*
53+
* @param message a byte sequence.
54+
* @param length the expected length.
55+
* @return a sequence of Base62-encoded bytes.
56+
*/
57+
public byte[] encode(byte[] message, int length) {
58+
byte[] indices = convert(message, STANDARD_BASE, TARGET_BASE, length);
59+
return translate(indices, alphabet);
60+
}
61+
4962
/**
5063
* Encodes a sequence of bytes in Base62 encoding.
5164
*
5265
* @param message a byte sequence.
5366
* @return a sequence of Base62-encoded bytes.
5467
*/
5568
public byte[] encode(byte[] message) {
56-
byte[] indices = convert(message, STANDARD_BASE, TARGET_BASE);
57-
return translate(indices, alphabet);
69+
return encode(message, -1);
70+
}
71+
72+
/**
73+
* Decodes a sequence of Base62-encoded bytes and pads it accordingly.
74+
*
75+
* @param encoded a sequence of Base62-encoded bytes.
76+
* @param length the expected length.
77+
* @return a byte sequence.
78+
*/
79+
public byte[] decode(byte[] encoded, int length) {
80+
byte[] prepared = translate(encoded, lookup);
81+
return convert(prepared, TARGET_BASE, STANDARD_BASE, length);
5882
}
5983

6084
/**
@@ -64,8 +88,7 @@ public byte[] encode(byte[] message) {
6488
* @return a byte sequence.
6589
*/
6690
public byte[] decode(byte[] encoded) {
67-
byte[] prepared = translate(encoded, lookup);
68-
return convert(prepared, TARGET_BASE, STANDARD_BASE);
91+
return decode(encoded, -1);
6992
}
7093

7194
/**
@@ -83,10 +106,10 @@ private byte[] translate(byte[] indices, byte[] dictionary) {
83106
/**
84107
* Converts a byte array from a source base to a target base using the alphabet.
85108
*/
86-
private byte[] convert(byte[] message, int sourceBase, int targetBase) {
109+
private byte[] convert(byte[] message, int sourceBase, int targetBase, int length) {
87110
// This algorithm is inspired by: http://codegolf.stackexchange.com/a/21672
88111

89-
int estimatedLength = estimateOutputLength(message.length, sourceBase, targetBase);
112+
int estimatedLength = length == -1 ? estimateOutputLength(message.length, sourceBase, targetBase) : length;
90113
ByteArrayOutputStream out = new ByteArrayOutputStream(estimatedLength);
91114
byte[] source = message;
92115
while (source.length > 0) {
@@ -104,11 +127,17 @@ private byte[] convert(byte[] message, int sourceBase, int targetBase) {
104127
source = quotient.toByteArray();
105128
}
106129

107-
// pad output with zeroes corresponding to the number of leading zeroes in the message
108-
for (int i = 0; i < estimatedLength - out.size(); i++)
109-
out.write(0);
130+
if (out.size() < estimatedLength) {
131+
int size = out.size();
132+
for (int i = 0; i < estimatedLength - size; i++)
133+
out.write(0);
110134

111-
return reverse(out.toByteArray());
135+
return reverse(out.toByteArray());
136+
} else if (out.size() > estimatedLength) {
137+
return reverse(Arrays.copyOfRange(out.toByteArray(), 0, estimatedLength));
138+
} else {
139+
return reverse(out.toByteArray());
140+
}
112141
}
113142

114143
/**

common/src/main/java/xyz/gianlu/librespot/common/Utils.java

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import org.jetbrains.annotations.NotNull;
1010
import org.jetbrains.annotations.Nullable;
1111
import xyz.gianlu.librespot.common.proto.Metadata;
12-
import xyz.gianlu.librespot.common.proto.Spirc;
1312

1413
import javax.sound.sampled.Mixer;
1514
import java.io.ByteArrayInputStream;
@@ -24,7 +23,6 @@
2423
import java.security.Permission;
2524
import java.security.PermissionCollection;
2625
import java.util.Arrays;
27-
import java.util.Base64;
2826
import java.util.List;
2927
import java.util.Map;
3028
import java.util.concurrent.atomic.AtomicReference;
@@ -33,11 +31,14 @@
3331
/**
3432
* @author Gianlu
3533
*/
36-
public class Utils {
34+
public final class Utils {
3735
public static final byte[] EOL = new byte[]{'\r', '\n'};
3836
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
3937
private static final Logger LOGGER = Logger.getLogger(Utils.class);
4038

39+
private Utils() {
40+
}
41+
4142
@NotNull
4243
public static String decodeGZip(@NotNull ByteString bytes) throws IOException {
4344
if (bytes.isEmpty()) return "";
@@ -52,23 +53,6 @@ public static String decodeGZip(@NotNull ByteString bytes) throws IOException {
5253
}
5354
}
5455

55-
@NotNull
56-
public static String toBase64(@NotNull ByteString bytes) {
57-
return Base64.getEncoder().encodeToString(bytes.toByteArray());
58-
}
59-
60-
public static int indexOf(@NotNull List<Spirc.TrackRef> list, @NotNull Spirc.TrackRef ref) {
61-
for (int i = 0; i < list.size(); i++) {
62-
Spirc.TrackRef item = list.get(i);
63-
if (item.hasGid() && ref.hasGid() && item.getGid().equals(ref.getGid()))
64-
return i;
65-
else if (item.hasUri() && ref.hasUri() && item.getUri().equals(ref.getUri()))
66-
return i;
67-
}
68-
69-
return -1;
70-
}
71-
7256
@NotNull
7357
public static String readLine(@NotNull InputStream in) throws IOException {
7458
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
@@ -206,16 +190,6 @@ public static String bytesToHex(byte[] bytes) {
206190
return bytesToHex(bytes, 0, bytes.length, false, -1);
207191
}
208192

209-
@NotNull
210-
public static String bytesToHex(byte[] bytes, boolean trim) {
211-
return bytesToHex(bytes, 0, bytes.length, trim, -1);
212-
}
213-
214-
@NotNull
215-
public static String bytesToHex(byte[] bytes, boolean trim, int minLength) {
216-
return bytesToHex(bytes, 0, bytes.length, trim, minLength);
217-
}
218-
219193
@NotNull
220194
public static String bytesToHex(byte[] bytes, int offset, int length, boolean trim, int minLength) {
221195
if (bytes == null) return "";

core/src/main/java/xyz/gianlu/librespot/mercury/model/AlbumId.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ public static AlbumId fromUri(@NotNull String uri) {
2424
Matcher matcher = PATTERN.matcher(uri);
2525
if (matcher.find()) {
2626
String id = matcher.group(1);
27-
return new AlbumId(Utils.bytesToHex(BASE62.decode(id.getBytes()), true, 16));
27+
return new AlbumId(Utils.bytesToHex(BASE62.decode(id.getBytes(), 16)));
2828
} else {
2929
throw new IllegalArgumentException("Not a Spotify album ID: " + uri);
3030
}
3131
}
3232

3333
@NotNull
3434
public static AlbumId fromBase62(@NotNull String base62) {
35-
return new AlbumId(Utils.bytesToHex(BASE62.decode(base62.getBytes()), true, 16));
35+
return new AlbumId(Utils.bytesToHex(BASE62.decode(base62.getBytes(), 16)));
3636
}
3737

3838
@NotNull

core/src/main/java/xyz/gianlu/librespot/mercury/model/ArtistId.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ public static ArtistId fromUri(@NotNull String uri) {
2424
Matcher matcher = PATTERN.matcher(uri);
2525
if (matcher.find()) {
2626
String id = matcher.group(1);
27-
return new ArtistId(Utils.bytesToHex(BASE62.decode(id.getBytes()), true, 16));
27+
return new ArtistId(Utils.bytesToHex(BASE62.decode(id.getBytes(), 16)));
2828
} else {
2929
throw new IllegalArgumentException("Not a Spotify artist ID: " + uri);
3030
}
3131
}
3232

3333
@NotNull
3434
public static ArtistId fromBase62(@NotNull String base62) {
35-
return new ArtistId(Utils.bytesToHex(BASE62.decode(base62.getBytes()), true, 16));
35+
return new ArtistId(Utils.bytesToHex(BASE62.decode(base62.getBytes(), 16)));
3636
}
3737

3838
@NotNull

core/src/main/java/xyz/gianlu/librespot/mercury/model/EpisodeId.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static EpisodeId fromUri(@NotNull String uri) {
2525
Matcher matcher = PATTERN.matcher(uri);
2626
if (matcher.find()) {
2727
String id = matcher.group(1);
28-
return new EpisodeId(Utils.bytesToHex(BASE62.decode(id.getBytes()), true, 16));
28+
return new EpisodeId(Utils.bytesToHex(BASE62.decode(id.getBytes(), 16)));
2929
} else {
3030
throw new IllegalArgumentException("Not a Spotify episode ID: " + uri);
3131
}
@@ -44,7 +44,7 @@ public static EpisodeId fromTrackRef(@NotNull Spirc.TrackRef ref) {
4444

4545
@NotNull
4646
public static EpisodeId fromBase62(@NotNull String base62) {
47-
return new EpisodeId(Utils.bytesToHex(BASE62.decode(base62.getBytes()), true, 16));
47+
return new EpisodeId(Utils.bytesToHex(BASE62.decode(base62.getBytes(), 16)));
4848
}
4949

5050
@NotNull

core/src/main/java/xyz/gianlu/librespot/mercury/model/ShowId.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ public static ShowId fromUri(@NotNull String uri) {
2424
Matcher matcher = PATTERN.matcher(uri);
2525
if (matcher.find()) {
2626
String id = matcher.group(1);
27-
return new ShowId(Utils.bytesToHex(BASE62.decode(id.getBytes()), true, 16));
27+
return new ShowId(Utils.bytesToHex(BASE62.decode(id.getBytes(), 16)));
2828
} else {
2929
throw new IllegalArgumentException("Not a Spotify show ID: " + uri);
3030
}
3131
}
3232

3333
@NotNull
3434
public static ShowId fromBase62(@NotNull String base62) {
35-
return new ShowId(Utils.bytesToHex(BASE62.decode(base62.getBytes()), true, 16));
35+
return new ShowId(Utils.bytesToHex(BASE62.decode(base62.getBytes(), 16)));
3636
}
3737

3838
@NotNull

core/src/main/java/xyz/gianlu/librespot/mercury/model/TrackId.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* @author Gianlu
1313
*/
1414
public final class TrackId implements SpotifyId, PlayableId {
15-
static final Pattern PATTERN = Pattern.compile("spotify:track:(.{22}|.{21})");
15+
static final Pattern PATTERN = Pattern.compile("spotify:track:(.{22})");
1616
private static final Base62 BASE62 = Base62.createInstanceWithInvertedCharacterSet();
1717
private final String hexId;
1818

@@ -25,15 +25,15 @@ public static TrackId fromUri(@NotNull String uri) {
2525
Matcher matcher = PATTERN.matcher(uri);
2626
if (matcher.find()) {
2727
String id = matcher.group(1);
28-
return new TrackId(Utils.bytesToHex(BASE62.decode(id.getBytes()), true, 16));
28+
return new TrackId(Utils.bytesToHex(BASE62.decode(id.getBytes(), 16)));
2929
} else {
3030
throw new IllegalArgumentException("Not a Spotify track ID: " + uri);
3131
}
3232
}
3333

3434
@NotNull
3535
public static TrackId fromBase62(@NotNull String base62) {
36-
return new TrackId(Utils.bytesToHex(BASE62.decode(base62.getBytes()), true, 16));
36+
return new TrackId(Utils.bytesToHex(BASE62.decode(base62.getBytes(), 16)));
3737
}
3838

3939
@NotNull
@@ -59,7 +59,7 @@ public static TrackId fromHex(@NotNull String hex) {
5959

6060
@Override
6161
public @NotNull String toSpotifyUri() {
62-
return "spotify:track:" + new String(BASE62.encode(Utils.hexToBytes(hexId)));
62+
return "spotify:track:" + new String(BASE62.encode(Utils.hexToBytes(hexId), 22));
6363
}
6464

6565
@Override
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package xyz.gianlu.librespot;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.junit.jupiter.api.Test;
5+
import xyz.gianlu.librespot.mercury.model.EpisodeId;
6+
import xyz.gianlu.librespot.mercury.model.TrackId;
7+
8+
import java.util.Arrays;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
12+
/**
13+
* @author Gianlu
14+
*/
15+
class SpotifyUrisTest {
16+
17+
private static void testTrackId(@NotNull String uri) {
18+
TrackId id = TrackId.fromUri(uri);
19+
assertEquals(uri, id.toSpotifyUri());
20+
assertEquals(32, id.hexId().length());
21+
}
22+
23+
private static void testEpisodeId(@NotNull String uri) {
24+
EpisodeId id = EpisodeId.fromUri(uri);
25+
assertEquals(uri, id.toSpotifyUri());
26+
assertEquals(32, id.hexId().length());
27+
}
28+
29+
@Test
30+
void test() {
31+
for (String uri : Arrays.asList("spotify:track:00yinrzqPZKA2vbnwnn3hS", "spotify:track:0FknXlaOeXD7Vto1Hx9wtP",
32+
"spotify:track:6r027Faonff84hxFMjMbIH", "spotify:track:0000000000000000000000"))
33+
testTrackId(uri);
34+
35+
for (String uri : Arrays.asList("spotify:episode:04XPBNQX6S9bhKsvA7xhFK", "spotify:episode:0000000000000000000000"))
36+
testEpisodeId(uri);
37+
}
38+
}

pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@
6969
<version>1.7.25</version>
7070
<scope>runtime</scope>
7171
</dependency>
72+
73+
<!-- Unit tests -->
74+
<dependency>
75+
<groupId>org.junit.jupiter</groupId>
76+
<artifactId>junit-jupiter</artifactId>
77+
<version>5.4.2</version>
78+
<scope>test</scope>
79+
</dependency>
7280
</dependencies>
7381

7482
<distributionManagement>

0 commit comments

Comments
 (0)