From 9d41991c758353ab5b8d797fae558cadd7e2b3b7 Mon Sep 17 00:00:00 2001 From: Florian CUNY Date: Tue, 12 Feb 2019 15:34:26 +0100 Subject: [PATCH] Added API to get data from GitHub's Web API This took me quite a long time, but I did it! --- .../bentobox/api/github/Contributor.java | 39 +++++ .../bentobox/api/github/GitHubConnector.java | 124 ++++++++++++++ .../bentobox/api/github/GitHubURL.java | 37 +++++ .../bentobox/bentobox/api/github/Release.java | 130 +++++++++++++++ .../bentobox/api/github/Repository.java | 152 ++++++++++++++++++ .../bentobox/api/github/package-info.java | 5 + 6 files changed, 487 insertions(+) create mode 100644 src/main/java/world/bentobox/bentobox/api/github/Contributor.java create mode 100644 src/main/java/world/bentobox/bentobox/api/github/GitHubConnector.java create mode 100644 src/main/java/world/bentobox/bentobox/api/github/GitHubURL.java create mode 100644 src/main/java/world/bentobox/bentobox/api/github/Release.java create mode 100644 src/main/java/world/bentobox/bentobox/api/github/Repository.java create mode 100644 src/main/java/world/bentobox/bentobox/api/github/package-info.java diff --git a/src/main/java/world/bentobox/bentobox/api/github/Contributor.java b/src/main/java/world/bentobox/bentobox/api/github/Contributor.java new file mode 100644 index 000000000..5147c5435 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/github/Contributor.java @@ -0,0 +1,39 @@ +package world.bentobox.bentobox.api.github; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * Represents a Contributor on a GitHub repository. + * @author Poslovitch + * @since 1.3.0 + */ +public class Contributor { + + private final @NonNull String name; + private final @NonNull String profile; + private int commits; + + public Contributor(@NonNull String name, int commits) { + this.name = name; + this.profile = "https://github.com/" + name; + this.commits = commits; + } + + @NonNull + public String getName() { + return name; + } + + @NonNull + public String getProfile() { + return profile; + } + + public int getCommits() { + return commits; + } + + public void setCommits(int commits) { + this.commits = commits; + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/github/GitHubConnector.java b/src/main/java/world/bentobox/bentobox/api/github/GitHubConnector.java new file mode 100644 index 000000000..db5f85c0a --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/github/GitHubConnector.java @@ -0,0 +1,124 @@ +package world.bentobox.bentobox.api.github; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.util.Util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.util.stream.Collectors; + +/** + * Handles connection to the GitHub API, retrieves data and handles the {@link Repository} data that emerges from it. + * @author Poslovitch + * @since 1.3.0 + */ +public class GitHubConnector { + + private @NonNull String repositoryName; + private Repository repository; + + public GitHubConnector(@NonNull String repositoryName) { + this.repositoryName = repositoryName; + } + + public void connect() { + JsonElement repositoryData; + JsonElement releasesData; + JsonElement contributorData; + + // Get the data + try { + repositoryData = getData(null); + // TODO getting other data is pointless if we couldn't get the data from the repository + contributorData = getData("contributors"); + releasesData = getData("releases"); + } catch (IOException e) { + e.printStackTrace(); + // TODO better logging and do not override data instead + return; + } + + // Parse the data + /* It must be done in a specific order: + + 1. repository; + 2. contributors; + 3. releases. + */ + parseRepositoryData(repositoryData); + parseContributorsData(contributorData); + parseReleasesData(releasesData); + } + + @NonNull + private JsonElement getData(@Nullable String suffix) throws IOException { + HttpURLConnection connection = new GitHubURL(getRepositoryName(), suffix).openConnection(); + String data = new BufferedReader(new InputStreamReader(connection.getInputStream())).lines().collect(Collectors.joining("\n")); + return new JsonParser().parse(data); + } + + private void parseRepositoryData(@NonNull JsonElement jsonElement) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + Repository.Builder builder = new Repository.Builder(repositoryName.split("/")[0], repositoryName.split("/")[1]) + .stars(jsonObject.get("stargazers_count").getAsInt()) + .forks(jsonObject.get("forks_count").getAsInt()) + .openIssues(jsonObject.get("open_issues_count").getAsInt()) + .latestCommit(Util.parseGitHubDate(jsonObject.get("pushed_at").getAsString())); + + this.repository = builder.build(); + } + + private void parseContributorsData(@NonNull JsonElement jsonElement) { + for (JsonElement contributorElement : jsonElement.getAsJsonArray()) { + JsonObject contributor = contributorElement.getAsJsonObject(); + this.repository.getContributors().add(new Contributor(contributor.get("login").getAsString(), contributor.get("contributions").getAsInt())); + } + } + + private void parseReleasesData(@NonNull JsonElement jsonElement) { + for (JsonElement releaseElement : jsonElement.getAsJsonArray()) { + JsonObject release = releaseElement.getAsJsonObject(); + + String tag = release.get("tag_name").getAsString(); + String url = repository.getUrl() + "/releases/tag/" + tag; + + Release.Builder builder = new Release.Builder(release.get("name").getAsString(), tag, url) + .preRelease(release.get("prerelease").getAsBoolean()) + .publishedAt(Util.parseGitHubDate( release.get("published_at").getAsString())); + + // Run through the releases assets and try to find the correct one + for (JsonElement assetElement : release.get("assets").getAsJsonArray()) { + JsonObject asset = assetElement.getAsJsonObject(); + + String assetName = asset.get("name").getAsString(); + if (assetName.endsWith(".jar") && !assetName.contains("javadoc") && !assetName.contains("sources")) { + // We found our asset! + + builder.downloadUrl(asset.get("browser_download_url").getAsString()) + .downloadSize(asset.get("size").getAsLong()) + .downloadCount(asset.get("download_count").getAsInt()); + + break; + } + } + + this.repository.getReleases().add(builder.build()); + } + } + + @NonNull + public String getRepositoryName() { + return repositoryName; + } + + public Repository getRepository() { + return repository; + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/github/GitHubURL.java b/src/main/java/world/bentobox/bentobox/api/github/GitHubURL.java new file mode 100644 index 000000000..989ff4cab --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/github/GitHubURL.java @@ -0,0 +1,37 @@ +package world.bentobox.bentobox.api.github; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Somehow wraps {@link URL} and {@link java.net.URLConnection} to avoid boilerplate code when accessing to the GitHub API. + * @author Poslovitch + * @since 1.3.0 + */ +public class GitHubURL { + + private @NonNull final URL url; + + public GitHubURL(@NonNull String repository, @Nullable String suffix) throws MalformedURLException { + suffix = (suffix != null && !suffix.isEmpty()) ? "/" + suffix : ""; + this.url = new URL("https://api.github.com/repos/" + repository + suffix); + } + + @NonNull + public URL toURL() { + return url; + } + + public HttpURLConnection openConnection() throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(2500); + connection.addRequestProperty("User-Agent", "BentoBox GitHubLink (@BentoBoxWorld)"); + connection.setDoOutput(true); + return connection; + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/github/Release.java b/src/main/java/world/bentobox/bentobox/api/github/Release.java new file mode 100644 index 000000000..01f71a1a6 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/github/Release.java @@ -0,0 +1,130 @@ +package world.bentobox.bentobox.api.github; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Date; + +/** + * Represents a release on a Github repository. + * See https://api.github.com/repos/BentoBoxWorld/BentoBox/releases. + * @author Poslovitch + * @since 1.3.0 + */ +public class Release { + + private final @NonNull String name; + private final @NonNull String tag; + private final @NonNull String url; + + private final boolean preRelease; + private final @Nullable Date publishedAt; + + /* Release asset related fields */ + private final @Nullable String downloadUrl; + private final long downloadSize; + private int downloadCount; + + private Release(@NonNull Builder builder) { + this.name = builder.name; + this.tag = builder.tag; + this.url = builder.url; + this.preRelease = builder.preRelease; + this.publishedAt = builder.publishedAt; + this.downloadUrl = builder.downloadUrl; + this.downloadSize = builder.downloadSize; + this.downloadCount = builder.downloadCount; + } + + @NonNull + public String getName() { + return name; + } + + @NonNull + public String getTag() { + return tag; + } + + @NonNull + public String getUrl() { + return url; + } + + public boolean isPreRelease() { + return preRelease; + } + + @Nullable + public Date getPublishedAt() { + return publishedAt; + } + + @Nullable + public String getDownloadUrl() { + return downloadUrl; + } + + public long getDownloadSize() { + return downloadSize; + } + + public int getDownloadCount() { + return downloadCount; + } + + public void setDownloadCount(int downloadCount) { + this.downloadCount = downloadCount; + } + + public static class Builder { + private final @NonNull String name; + private final @NonNull String tag; + private final @NonNull String url; + + private boolean preRelease; + private @Nullable Date publishedAt; + private @Nullable String downloadUrl; + private long downloadSize; + private int downloadCount; + + public Builder(@NonNull String name, @NonNull String tag, @NonNull String url) { + this.name = name; + this.tag = tag; + this.url = url; + this.preRelease = false; + this.downloadSize = 0L; + this.downloadCount = 0; + } + + public Builder preRelease(boolean preRelease) { + this.preRelease = preRelease; + return this; + } + + public Builder publishedAt(@Nullable Date publishedAt) { + this.publishedAt = publishedAt; + return this; + } + + public Builder downloadUrl(@Nullable String downloadUrl) { + this.downloadUrl = downloadUrl; + return this; + } + + public Builder downloadSize(long downloadSize) { + this.downloadSize = downloadSize; + return this; + } + + public Builder downloadCount(int downloadCount) { + this.downloadCount = downloadCount; + return this; + } + + public Release build() { + return new Release(this); + } + } + +} diff --git a/src/main/java/world/bentobox/bentobox/api/github/Repository.java b/src/main/java/world/bentobox/bentobox/api/github/Repository.java new file mode 100644 index 000000000..2dff21421 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/github/Repository.java @@ -0,0 +1,152 @@ +package world.bentobox.bentobox.api.github; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * Represents a GitHub repository. + * @author Poslovitch + * @since 1.3.0 + */ +public class Repository { + + private final @NonNull String owner; + private final @NonNull String name; + + private final @NonNull List contributors; + private final @NonNull List releases; + + private int stars; + private int forks; + private int openIssues; + + private @Nullable Date latestCommit; + + private Repository(@NonNull Builder builder) { + this.owner = builder.owner; + this.name = builder.name; + this.contributors = builder.contributors; + this.releases = builder.releases; + + this.stars = builder.stars; + this.forks = builder.forks; + this.openIssues = builder.openIssues; + this.latestCommit = builder.latestCommit; + } + + @NonNull + public String getOwner() { + return owner; + } + + @NonNull + public String getName() { + return name; + } + + @NonNull + public List getContributors() { + return contributors; + } + + @NonNull + public List getReleases() { + return releases; + } + + public int getStars() { + return stars; + } + + public void setStars(int stars) { + this.stars = stars; + } + + public int getForks() { + return forks; + } + + public void setForks(int forks) { + this.forks = forks; + } + + public int getOpenIssues() { + return openIssues; + } + + public void setOpenIssues(int openIssues) { + this.openIssues = openIssues; + } + + @NonNull + public String getUrl() { + return "https://github.com/" + getOwner() + "/" + getName(); + } + + @Nullable + public Date getLatestCommit() { + return latestCommit; + } + + public void setLatestCommit(@Nullable Date latestCommit) { + this.latestCommit = latestCommit; + } + + public static class Builder { + private final @NonNull String owner; + private final @NonNull String name; + private final @NonNull List contributors; + private final @NonNull List releases; + + private int stars; + private int forks; + private int openIssues; + private @Nullable Date latestCommit; + + public Builder(@NonNull String owner, @NonNull String name) { + this.owner = owner; + this.name = name; + this.contributors = new LinkedList<>(); + this.releases = new LinkedList<>(); + } + + public Builder contributors(@NonNull Contributor... contributors) { + this.contributors.addAll(Arrays.asList(contributors)); + return this; + } + + public Builder releases(@NonNull Release... releases) { + this.releases.addAll(Arrays.asList(releases)); + return this; + } + + public Builder stars(int stars) { + this.stars = stars; + return this; + } + + public Builder forks(int forks) { + this.forks = forks; + return this; + } + + public Builder openIssues(int openIssues) { + this.openIssues = openIssues; + return this; + } + + public Builder latestCommit(@Nullable Date latestCommit) { + this.latestCommit = latestCommit; + return this; + } + + public Repository build() { + return new Repository(this); + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/github/package-info.java b/src/main/java/world/bentobox/bentobox/api/github/package-info.java new file mode 100644 index 000000000..7678dc2eb --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/github/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains objects and classes to get, retrieve and store data from GitHub. + * @since 1.3.0 + */ +package world.bentobox.bentobox.api.github;