From 5a27c776429c6e222915b481e192f7f256d564e8 Mon Sep 17 00:00:00 2001 From: Thomas Rubini <74205383+ThomasRubini@users.noreply.github.com> Date: Tue, 13 Dec 2022 19:40:55 +0100 Subject: [PATCH] Refactor async requests to allow easier implementation in other distros --- src/main/java/fr/packageviewer/Pair.java | 36 +++++ .../distribution/ArchDistribution.java | 141 ++++++------------ .../java/fr/packageviewer/pack/Package.java | 9 ++ .../parser/AsyncRequestsParser.java | 81 ++++++++++ 4 files changed, 172 insertions(+), 95 deletions(-) create mode 100644 src/main/java/fr/packageviewer/Pair.java create mode 100644 src/main/java/fr/packageviewer/parser/AsyncRequestsParser.java diff --git a/src/main/java/fr/packageviewer/Pair.java b/src/main/java/fr/packageviewer/Pair.java new file mode 100644 index 0000000..1a5763d --- /dev/null +++ b/src/main/java/fr/packageviewer/Pair.java @@ -0,0 +1,36 @@ +package fr.packageviewer; + +public class Pair { + private K first; + private V second; + + public Pair() { + + } + + public Pair(K first, V second) { + this.first = first; + this.second = second; + } + + public K getFirst() { + return first; + } + + public void setFirst(K first) { + this.first = first; + } + + public V getSecond() { + return second; + } + + public void setSecond(V second) { + this.second = second; + } + + @Override + public String toString() { + return "Pair{key=%s,value=%s}".formatted(first, second); + } +} diff --git a/src/main/java/fr/packageviewer/distribution/ArchDistribution.java b/src/main/java/fr/packageviewer/distribution/ArchDistribution.java index 56a8eaf..ac5ebf9 100644 --- a/src/main/java/fr/packageviewer/distribution/ArchDistribution.java +++ b/src/main/java/fr/packageviewer/distribution/ArchDistribution.java @@ -2,37 +2,69 @@ package fr.packageviewer.distribution; import java.io.IOException; import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.net.http.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import fr.packageviewer.LoggerManager; +import fr.packageviewer.Pair; import fr.packageviewer.pack.Package; import fr.packageviewer.pack.SearchedPackage; +import fr.packageviewer.parser.AsyncRequestsParser; import org.json.*; -public class ArchDistribution implements Distribution { +public class ArchDistribution extends AsyncRequestsParser implements Distribution { - private static final Logger logger = LoggerManager.getLogger("ArchParser"); + private static final Logger logger = LoggerManager.getLogger("ArchDistribution"); /** * Will return the String json of the package from the Arch Linux API * @param packageName the package name to get the json from * @return json of the package */ - public CompletableFuture> getPackageFromAPI(String packageName) { - // create a new http client - HttpClient client = HttpClient.newHttpClient(); - // and create its url - HttpRequest request = HttpRequest.newBuilder(URI.create("https://archlinux.org/packages/search/json/?name="+packageName)).build(); - // send its url and return the string given - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()); - } + +@Override +public CompletableFuture>> getPackageFromAPI(String packageName) { + // create a new http client + HttpClient client = HttpClient.newHttpClient(); + // and create its url + HttpRequest request = HttpRequest.newBuilder(URI.create("https://archlinux.org/packages/search/json/?name="+packageName)).build(); + + CompletableFuture>> futureResult = new CompletableFuture<>(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenAccept(result ->{ + + JSONObject json = new JSONObject(result.body()); + + JSONArray resultsArrayJson = json.getJSONArray("results"); + if(resultsArrayJson.length()==0){ + // unknown package, probably an abstract dependency + futureResult.complete(null); + return; + } + JSONObject resultJson = resultsArrayJson.getJSONObject(0); + + // get infos + + Set dependenciesNames = new HashSet<>(); + for(Object dependency : resultJson.getJSONArray("depends")){ + dependenciesNames.add((String)dependency); + } + futureResult.complete(new Pair<>( + new Package( + resultJson.getString("pkgname"), + resultJson.getString("pkgver"), + resultJson.getString("repo"), + resultJson.getString("pkgdesc") + ), + dependenciesNames + )); + }); + + return futureResult; + +} /** @@ -74,85 +106,4 @@ public class ArchDistribution implements Distribution { return searchedPackagesList; } - - - /** - * Will generate a dependencies tree of depth 'depth' given the package name - * @param packageName the package name to search - * @param depth depth to search dependencies - * @return new Package - */ - public CompletableFuture getPackageTree(String packageName, int depth) { - - // parse the json - var futurePackage = new CompletableFuture(); - - logger.fine("Querying package %s from API... (depth=%s)".formatted(packageName, depth)); - - CompletableFuture> futureRequest; - try{ - futureRequest = getPackageFromAPI(packageName); - }catch(IllegalArgumentException e){ - logger.warning("Caught exception for package %s :\n%s".formatted(packageName, e)); - return CompletableFuture.completedFuture(null); - } - futureRequest.thenAccept(result->{ - List deps = new ArrayList<>(); - String name, version, repo, description; - - JSONObject json = new JSONObject(result.body()); - - JSONArray resultsArrayJson = json.getJSONArray("results"); - if(resultsArrayJson.length()==0){ - // unknown package, probably an abstract dependency - logger.fine("Completing callback INVALID for package %s (depth=%s)".formatted(packageName, depth)); - futurePackage.complete(null); - return; - } - JSONObject resultJson = resultsArrayJson.getJSONObject(0); - - // get infos except dependencies - name = resultJson.getString("pkgname"); - version = resultJson.getString("pkgver"); - repo = resultJson.getString("repo"); - description = resultJson.getString("pkgdesc"); - - // if we're at the maximum depth, return the package without its dependencies - if(depth==0) { - logger.fine("Completing callback NODEP for package %s (depth=%s)".formatted(packageName, depth)); - futurePackage.complete(new Package(name, version, repo, description, Collections.emptyList())); - return; - } - // iterate for every package in the list - List> futureDeps = new ArrayList<>(); - for (Object depPackageNameObj : resultJson.getJSONArray("depends")) { - // convert object into String - String depPackageName = (String) depPackageNameObj; - // add package into Package List - futureDeps.add(getPackageTree(depPackageName, depth - 1)); - } - for(CompletableFuture future : futureDeps){ - Package dep; - try { - dep = future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - if(dep!=null){ - deps.add(dep); - } - } - - // TODO this doesn't seem clean - logger.fine("Completing callback DEPS for package %s (depth=%s)".formatted(packageName, depth)); - futurePackage.complete(new Package(name, version, repo, description, deps)); - }).exceptionally((e2->{ - logger.warning("Error while fetching package %s (depth=%s) from the API : \n%s".formatted(packageName, depth, e2)); - e2.printStackTrace(); - futurePackage.complete(null); - return null; - })); - - return futurePackage; - } } diff --git a/src/main/java/fr/packageviewer/pack/Package.java b/src/main/java/fr/packageviewer/pack/Package.java index 855fef9..0371559 100644 --- a/src/main/java/fr/packageviewer/pack/Package.java +++ b/src/main/java/fr/packageviewer/pack/Package.java @@ -1,5 +1,7 @@ package fr.packageviewer.pack; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class Package extends SearchedPackage { @@ -9,6 +11,13 @@ public class Package extends SearchedPackage { return deps; } + public void addDep(Package pack) { + deps.add(pack); + } + + public Package(String name, String version, String repo, String description) { + this(name, version, repo, description, new ArrayList<>()); + } public Package(String name, String version, String repo, String description, List deps) { super(name, version, repo, description); this.deps = deps; diff --git a/src/main/java/fr/packageviewer/parser/AsyncRequestsParser.java b/src/main/java/fr/packageviewer/parser/AsyncRequestsParser.java new file mode 100644 index 0000000..a279cf6 --- /dev/null +++ b/src/main/java/fr/packageviewer/parser/AsyncRequestsParser.java @@ -0,0 +1,81 @@ +package fr.packageviewer.parser; + +import fr.packageviewer.LoggerManager; +import fr.packageviewer.Pair; +import fr.packageviewer.pack.Package; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +public abstract class AsyncRequestsParser { + + private static final Logger logger = LoggerManager.getLogger("AsyncRequestsParser"); + + protected abstract CompletableFuture>> getPackageFromAPI(String name); + + public CompletableFuture getPackageTree(String packageName, int depth) { + // parse the json + var futurePackage = new CompletableFuture(); + + logger.fine("Querying package %s from API... (depth=%s)".formatted(packageName, depth)); + + CompletableFuture>> futureRequest; + try { + futureRequest = getPackageFromAPI(packageName); + } catch (IllegalArgumentException e) { + logger.warning("Caught exception for package %s :\n%s".formatted(packageName, e)); + return CompletableFuture.completedFuture(null); + } + futureRequest.thenAccept(result -> { + if(result==null){ + logger.fine("Completing callback INVALID for package %s (depth=%s)".formatted(packageName, depth)); + futurePackage.complete(null); + return; + } + Package pack = result.getFirst(); + Set dependenciesNames = result.getSecond(); + + + // if we're at the maximum depth, return the package without its dependencies + if (depth == 0) { + logger.fine("Completing callback NODEP for package %s (depth=%s)".formatted(packageName, depth)); + futurePackage.complete(pack); + return; + } + + // iterate for every package in the list + List> futureDeps = new ArrayList<>(); + for (String depPackageName : dependenciesNames) { + // convert object into String + // add package into Package List + futureDeps.add(getPackageTree(depPackageName, depth - 1)); + } + for (CompletableFuture future : futureDeps) { + Package dep; + try { + dep = future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + if (dep != null) { + pack.addDep(dep); + } + } + + // TODO this doesn't seem clean + logger.fine("Completing callback DEPS for package %s (depth=%s)".formatted(packageName, depth)); + futurePackage.complete(pack); + }).exceptionally((e2 -> { + logger.warning("Error while fetching package %s (depth=%s) from the API : \n%s".formatted(packageName, depth, e2)); + e2.printStackTrace(); + futurePackage.complete(null); + return null; + })); + + return futurePackage; + } +}