Skip to content

Commit 032384e

Browse files
committed
feat: added search command
And removed `sync` command. That capability is now an option (`--sync`) to the `copy` command.
1 parent 2ad268d commit 032384e

File tree

3 files changed

+169
-18
lines changed

3 files changed

+169
-18
lines changed

src/main/java/org/codejive/jpm/Jpm.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import org.codejive.jpm.json.AppInfo;
77
import org.codejive.jpm.util.FileUtils;
88
import org.codejive.jpm.util.ResolverUtils;
9+
import org.codejive.jpm.util.SearchUtils;
910
import org.codejive.jpm.util.SyncStats;
11+
import org.eclipse.aether.artifact.Artifact;
1012
import org.eclipse.aether.resolution.DependencyResolutionException;
1113

1214
public class Jpm {
@@ -43,16 +45,25 @@ public Jpm build() {
4345
}
4446
}
4547

46-
public SyncStats copy(String[] artifactNames)
48+
public SyncStats copy(String[] artifactNames, boolean sync)
4749
throws IOException, DependencyResolutionException {
4850
List<Path> files = ResolverUtils.resolveArtifactPaths(artifactNames);
49-
return FileUtils.syncArtifacts(files, directory, noLinks, true);
51+
return FileUtils.syncArtifacts(files, directory, noLinks, !sync);
5052
}
5153

52-
public SyncStats sync(String[] artifactNames)
53-
throws IOException, DependencyResolutionException {
54-
List<Path> files = ResolverUtils.resolveArtifactPaths(artifactNames);
55-
return FileUtils.syncArtifacts(files, directory, noLinks, false);
54+
public String[] search(String artifactPattern, int count) throws IOException {
55+
List<Artifact> artifacts = new ArrayList<>();
56+
int max = count <= 0 || count > 200 ? 200 : count;
57+
SearchUtils.SearchResult result = SearchUtils.findArtifacts(artifactPattern, max);
58+
while (result != null) {
59+
artifacts.addAll(result.artifacts);
60+
result = count <= 0 ? SearchUtils.findNextArtifacts(result) : null;
61+
}
62+
return artifacts.stream().map(Jpm::artifactGav).toArray(String[]::new);
63+
}
64+
65+
private static String artifactGav(Artifact artifact) {
66+
return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion();
5667
}
5768

5869
public SyncStats install(String[] artifactNames)

src/main/java/org/codejive/jpm/Main.java

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,21 @@ static class Copy implements Callable<Integer> {
4040
@Mixin QuietMixin quietMixin;
4141
@Mixin ArtifactsMixin artifactsMixin;
4242

43+
@Option(
44+
names = {"-s", "--sync"},
45+
description =
46+
"Makes sure the target directory will only contain the mentioned artifacts and their dependencies, possibly removing other files present in the directory",
47+
defaultValue = "false")
48+
private boolean sync;
49+
4350
@Override
4451
public Integer call() throws Exception {
4552
SyncStats stats =
4653
Jpm.builder()
4754
.directory(artifactsMixin.copyMixin.directory)
4855
.noLinks(artifactsMixin.copyMixin.noLinks)
4956
.build()
50-
.copy(artifactsMixin.artifactNames);
57+
.copy(artifactsMixin.artifactNames, sync);
5158
if (!quietMixin.quiet) {
5259
printStats(stats);
5360
}
@@ -56,26 +63,36 @@ public Integer call() throws Exception {
5663
}
5764

5865
@Command(
59-
name = "sync",
66+
name = "search",
6067
aliases = {"s"},
6168
description =
62-
"Resolves one or more artifacts and copies them and all their dependencies to a target directory while at the same time removing any artifacts that are no longer needed (ie the ones that are not mentioned when running this command). "
63-
+ "By default jpm will try to create symbolic links to conserve space.\n\n"
64-
+ "Example:\n jpm sync org.apache.httpcomponents:httpclient:4.5.14\n")
69+
"Finds and returns the names of those artifacts that match the given (partial) name.\n\n"
70+
+ "Example:\n jpm search httpclient\n")
6571
static class Sync implements Callable<Integer> {
6672
@Mixin QuietMixin quietMixin;
67-
@Mixin ArtifactsMixin artifactsMixin;
73+
@Mixin CopyMixin copyMixin;
74+
75+
@Option(
76+
names = {"-m", "--max"},
77+
description = "Maximum number of results to return",
78+
defaultValue = "20")
79+
private int max;
80+
81+
@Parameters(
82+
paramLabel = "artifactPattern",
83+
description = "Partial or full artifact name to search for.")
84+
private String artifactPattern;
6885

6986
@Override
7087
public Integer call() throws Exception {
71-
SyncStats stats =
88+
String[] artifactNames =
7289
Jpm.builder()
73-
.directory(artifactsMixin.copyMixin.directory)
74-
.noLinks(artifactsMixin.copyMixin.noLinks)
90+
.directory(copyMixin.directory)
91+
.noLinks(copyMixin.noLinks)
7592
.build()
76-
.sync(artifactsMixin.artifactNames);
77-
if (!quietMixin.quiet) {
78-
printStats(stats);
93+
.search(artifactPattern, Math.min(max, 200));
94+
if (artifactNames.length > 0) {
95+
Arrays.stream(artifactNames).forEach(System.out::println);
7996
}
8097
return 0;
8198
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.codejive.jpm.util;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.GsonBuilder;
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.io.InputStreamReader;
8+
import java.net.URLEncoder;
9+
import java.util.Collections;
10+
import java.util.List;
11+
import org.apache.http.client.methods.CloseableHttpResponse;
12+
import org.apache.http.client.methods.HttpGet;
13+
import org.apache.http.impl.client.CloseableHttpClient;
14+
import org.apache.http.impl.client.HttpClients;
15+
import org.eclipse.aether.artifact.Artifact;
16+
import org.eclipse.aether.artifact.DefaultArtifact;
17+
18+
public class SearchUtils {
19+
20+
public static class SearchResult {
21+
public final List<? extends Artifact> artifacts;
22+
public final String query;
23+
public final int start;
24+
public final int count;
25+
public final int total;
26+
27+
public SearchResult(
28+
List<? extends Artifact> artifacts, String query, int start, int count, int total) {
29+
this.artifacts = Collections.unmodifiableList(artifacts);
30+
this.query = query;
31+
this.start = start;
32+
this.count = count;
33+
this.total = total;
34+
}
35+
}
36+
37+
public static SearchResult findArtifacts(String artifactPattern, int count) throws IOException {
38+
return select(artifactPattern, 0, count);
39+
}
40+
41+
public static SearchResult findNextArtifacts(SearchResult prevResult) throws IOException {
42+
if (prevResult.start + prevResult.count >= prevResult.total) {
43+
return null;
44+
}
45+
SearchResult result =
46+
select(prevResult.query, prevResult.start + prevResult.count, prevResult.count);
47+
return result.artifacts.isEmpty() ? null : result;
48+
}
49+
50+
private static SearchResult select(String query, int start, int count) throws IOException {
51+
String[] parts = query.split(":", -1);
52+
String finalQuery;
53+
if (parts.length >= 3) {
54+
finalQuery = "g:%s AND a:%s".formatted(parts[0], parts[1]);
55+
} else if (parts.length == 2) {
56+
finalQuery = "%s AND %s".formatted(parts[0], parts[1]);
57+
} else {
58+
finalQuery = query;
59+
}
60+
String searchUrl =
61+
"https://search.maven.org/solrsearch/select?start=%d&rows=%d&q=%s"
62+
.formatted(start, count, URLEncoder.encode(finalQuery, "UTF-8"));
63+
if (parts.length >= 3) {
64+
searchUrl += "&core=gav";
65+
}
66+
String agent =
67+
String.format(
68+
"jpm/%s (%s %s)",
69+
Version.get(),
70+
System.getProperty("os.name"),
71+
System.getProperty("os.arch"));
72+
try (CloseableHttpClient httpClient = HttpClients.custom().setUserAgent(agent).build()) {
73+
HttpGet request = new HttpGet(searchUrl);
74+
try (CloseableHttpResponse response = httpClient.execute(request)) {
75+
Gson gson = new GsonBuilder().create();
76+
InputStream ins = response.getEntity().getContent();
77+
InputStreamReader rdr = new InputStreamReader(ins);
78+
MvnSearchResult result = gson.fromJson(rdr, MvnSearchResult.class);
79+
if (result.responseHeader.status != 0) {
80+
throw new IOException("Search failed");
81+
}
82+
List<DefaultArtifact> artifacts =
83+
result.response.docs.stream()
84+
.filter(
85+
d ->
86+
parts.length != 2
87+
|| d.g.contains(parts[0])
88+
&& d.a.contains(parts[1]))
89+
.map(
90+
d ->
91+
new DefaultArtifact(
92+
d.g,
93+
d.a,
94+
"",
95+
d.v != null ? d.v : d.latestVersion))
96+
.toList();
97+
return new SearchResult(artifacts, query, start, count, result.response.numFound);
98+
}
99+
}
100+
}
101+
}
102+
103+
class MvnSearchResult {
104+
public MsrHeader responseHeader;
105+
public MsrResponse response;
106+
}
107+
108+
class MsrHeader {
109+
public int status;
110+
}
111+
112+
class MsrResponse {
113+
public List<MsrDoc> docs;
114+
public int numFound;
115+
public int start;
116+
}
117+
118+
class MsrDoc {
119+
public String g;
120+
public String a;
121+
public String v;
122+
public String latestVersion;
123+
}

0 commit comments

Comments
 (0)