Skip to content

Commit 775ebb0

Browse files
committed
Add mavenArtifact function to standard library #12
1 parent 7cadea7 commit 775ebb0

File tree

7 files changed

+241
-0
lines changed

7 files changed

+241
-0
lines changed

doc/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
| [javac](api/javac.md) | Compiles java files. |
2323
| [junit](api/junit.md) | Runs junit tests. |
2424
| [map](api/map.md) | Applies given function to each element of an array. |
25+
| [mavenArtifact](api/mavenArtifact.md) | Downloads a Java library from Maven Central repository. |
2526
| [not](api/not.md) | Returns negation of its argument. |
2627
| [or](api/or.md) | Returns `true` if any argument is `true`. |
2728
| [size](api/size.md) | Returns size of an array. |

doc/api/mavenArtifact.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## File mavenArtifact<>(String groupId, String artifactId, String version)
2+
3+
Downloads a Java library from Maven Central repository.
4+
5+
| Name | Type | Default | Description |
6+
|------------|--------|---------|------------------------------------------------------------|
7+
| groupId | String | | Group ID of the Maven artifact (e.g., "org.junit.jupiter") |
8+
| artifactId | String | | Artifact ID of the Maven artifact (e.g., "junit-jupiter") |
9+
| version | String | | Version of the Maven artifact (e.g., "5.8.1") |
10+
11+
Returns __File__ containing the downloaded JAR file.
12+
13+
### examples
14+
15+
Download JUnit Jupiter API:
16+
```
17+
File junit = mavenArtifact("org.junit.jupiter", "junit-jupiter-api", "5.8.1");
18+
```
19+
20+
Download Google Guava:
21+
```
22+
File guava = mavenArtifact("com.google.guava", "guava", "31.1-jre");
23+
```
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.smoothbuild.stdlib.java;
2+
3+
import static java.lang.Thread.currentThread;
4+
import static java.net.http.HttpResponse.BodyHandlers.ofInputStream;
5+
import static okio.Okio.buffer;
6+
import static okio.Okio.source;
7+
8+
import java.io.IOException;
9+
import java.net.URI;
10+
import java.net.http.HttpClient;
11+
import java.net.http.HttpRequest;
12+
import org.smoothbuild.virtualmachine.bytecode.BytecodeException;
13+
import org.smoothbuild.virtualmachine.bytecode.expr.base.BString;
14+
import org.smoothbuild.virtualmachine.bytecode.expr.base.BTuple;
15+
import org.smoothbuild.virtualmachine.bytecode.expr.base.BValue;
16+
import org.smoothbuild.virtualmachine.evaluate.plugin.NativeApi;
17+
18+
public class MavenArtifactFunc {
19+
public static BValue func(NativeApi nativeApi, BTuple args) throws BytecodeException {
20+
try (var httpClient = HttpClient.newHttpClient()) {
21+
return func(nativeApi, args, httpClient);
22+
}
23+
}
24+
25+
static BValue func(NativeApi nativeApi, BTuple args, HttpClient httpClient)
26+
throws BytecodeException {
27+
var coordinate = mavenCoordinate(args);
28+
try {
29+
var response = httpClient.send(httpRequest(coordinate.url()), ofInputStream());
30+
if (response.statusCode() != 200) {
31+
var template =
32+
"""
33+
Failed to download Maven artifact %s
34+
from %s
35+
Status code: %d""";
36+
var message = template.formatted(coordinate, coordinate.url(), response.statusCode());
37+
nativeApi.log().error(message);
38+
return null;
39+
}
40+
41+
try (var blobBuilder = nativeApi.factory().blobBuilder()) {
42+
try (var sourceBuffer = buffer(source(response.body()))) {
43+
sourceBuffer.readAll(blobBuilder);
44+
}
45+
var content = blobBuilder.build();
46+
var jarName = nativeApi.factory().string(coordinate.jarName());
47+
return nativeApi.factory().file(content, jarName);
48+
}
49+
} catch (IOException e) {
50+
nativeApi.log().error("Error downloading Maven artifact: " + e.getMessage());
51+
return null;
52+
} catch (InterruptedException e) {
53+
nativeApi.log().error("Error downloading Maven artifact: " + e.getMessage());
54+
currentThread().interrupt();
55+
return null;
56+
}
57+
}
58+
59+
private static HttpRequest httpRequest(String mavenUrl) {
60+
return HttpRequest.newBuilder().uri(URI.create(mavenUrl)).GET().build();
61+
}
62+
63+
private static MavenCoordinate mavenCoordinate(BTuple args) throws BytecodeException {
64+
String groupIdPath = ((BString) args.get(0)).toJavaString();
65+
String artifactIdStr = ((BString) args.get(1)).toJavaString();
66+
String versionStr = ((BString) args.get(2)).toJavaString();
67+
return new MavenCoordinate(groupIdPath, artifactIdStr, versionStr);
68+
}
69+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.smoothbuild.stdlib.java;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
public record MavenCoordinate(String groupId, String artifactId, String version) {
6+
private static final String MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2";
7+
8+
public MavenCoordinate {
9+
requireNonNull(groupId, "groupId cannot be null");
10+
requireNonNull(artifactId, "artifactId cannot be null");
11+
requireNonNull(version, "version cannot be null");
12+
}
13+
14+
public String url() {
15+
var groupIdPath = groupId.replace('.', '/');
16+
return MAVEN_CENTRAL_URL + "/" + groupIdPath + "/" + artifactId + "/" + version + "/"
17+
+ jarName();
18+
}
19+
20+
public String jarName() {
21+
return artifactId + "-" + version + ".jar";
22+
}
23+
24+
@Override
25+
public String toString() {
26+
return groupId + ":" + artifactId + ":" + version;
27+
}
28+
}

src/standard-library/src/main/smooth/std_lib.smooth

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ String toString(Blob blob);
8282
@Native("org.smoothbuild.stdlib.compress.ZipFunc")
8383
Blob compressZip([File] files);
8484

85+
@Native("org.smoothbuild.stdlib.java.MavenFunc")
86+
File mavenArtifact(String groupId, String artifactId, String version);
87+
8588
A id<A>(A a) = a;
8689

8790
@Native("org.smoothbuild.stdlib.core.ErrorFunc")
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.smoothbuild.stdlib.java;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.mockito.ArgumentMatchers.any;
5+
import static org.mockito.ArgumentMatchers.eq;
6+
import static org.mockito.Mockito.mock;
7+
import static org.mockito.Mockito.when;
8+
9+
import java.io.ByteArrayInputStream;
10+
import java.io.InputStream;
11+
import java.net.http.HttpClient;
12+
import java.net.http.HttpRequest;
13+
import java.net.http.HttpResponse;
14+
import java.net.http.HttpResponse.BodyHandlers;
15+
import org.junit.jupiter.api.Test;
16+
import org.smoothbuild.virtualmachine.testing.VmTestContext;
17+
18+
public class MavenArtifactFuncTest extends VmTestContext {
19+
@Test
20+
void downloads_artifact_via_http_client() throws Exception {
21+
var jarContent = "jar content";
22+
HttpClient httpClient = mock();
23+
HttpResponse<InputStream> response = mock();
24+
25+
when(httpClient.send(any(HttpRequest.class), eq(BodyHandlers.ofInputStream())))
26+
.thenReturn(response);
27+
when(response.statusCode()).thenReturn(200);
28+
when(response.body()).thenReturn(new ByteArrayInputStream(jarContent.getBytes()));
29+
30+
var result = MavenArtifactFunc.func(
31+
nativeApi(),
32+
bTuple(bString("com.example"), bString("library"), bString("1.0.0")),
33+
httpClient);
34+
35+
assertThat(result).isEqualTo(bFile("library-1.0.0.jar", jarContent));
36+
}
37+
38+
@Test
39+
void reports_error_when_downloading_fails() throws Exception {
40+
HttpClient httpClient = mock();
41+
HttpResponse<InputStream> response = mock();
42+
when(httpClient.send(any(HttpRequest.class), eq(BodyHandlers.ofInputStream())))
43+
.thenReturn(response);
44+
when(response.statusCode()).thenReturn(404);
45+
46+
var nativeApi = nativeApi();
47+
var result = MavenArtifactFunc.func(
48+
nativeApi,
49+
bTuple(bString("com.example"), bString("library"), bString("1.0.0")),
50+
httpClient);
51+
52+
assertThat(result).isNull();
53+
assertThat(nativeApi.messages())
54+
.isEqualTo(
55+
bArray(
56+
bErrorLog(
57+
"""
58+
Failed to download Maven artifact com.example:library:1.0.0
59+
from https://repo1.maven.org/maven2/com/example/library/1.0.0/library-1.0.0.jar
60+
Status code: 404""")));
61+
}
62+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.smoothbuild.stdlib.java;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Nested;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class MavenCoordinateTest {
10+
@Test
11+
void to_string() {
12+
var mavenCoordinate = new MavenCoordinate("com.example", "example", "1.0.0");
13+
assertThat(mavenCoordinate.toString()).isEqualTo("com.example:example:1.0.0");
14+
}
15+
16+
@Nested
17+
class _url {
18+
@Test
19+
void with_simple_groupId() {
20+
var mavenCoordinate = new MavenCoordinate("group", "artifact", "1.0.0");
21+
assertThat(mavenCoordinate.url())
22+
.isEqualTo("https://repo1.maven.org/maven2/group/artifact/1.0.0/artifact-1.0.0.jar");
23+
}
24+
25+
@Test
26+
void with_dotted_groupId() {
27+
var mavenCoordinate = new MavenCoordinate("com.example.group", "artifact", "1.0.0");
28+
assertThat(mavenCoordinate.url())
29+
.isEqualTo(
30+
"https://repo1.maven.org/maven2/com/example/group/artifact/1.0.0/artifact-1.0.0.jar");
31+
}
32+
}
33+
34+
@Test
35+
void jar_name() {
36+
var mavenCoordinate = new MavenCoordinate("group", "artifact", "1.0.0");
37+
assertThat(mavenCoordinate.jarName()).isEqualTo("artifact-1.0.0.jar");
38+
}
39+
40+
@Test
41+
void null_groupId_throws_exception() {
42+
assertThrows(
43+
NullPointerException.class, () -> new MavenCoordinate(null, "artifact", "version"));
44+
}
45+
46+
@Test
47+
void null_artifactId_throws_exception() {
48+
assertThrows(NullPointerException.class, () -> new MavenCoordinate("group", null, "version"));
49+
}
50+
51+
@Test
52+
void null_version_throws_exception() {
53+
assertThrows(NullPointerException.class, () -> new MavenCoordinate("group", "artifact", null));
54+
}
55+
}

0 commit comments

Comments
 (0)