From 7fdfb855d606571518b781e50287090d7efbb875 Mon Sep 17 00:00:00 2001 From: Liam Newman <bitwiseman@gmail.com> Date: Tue, 22 Apr 2025 20:20:14 -0700 Subject: [PATCH 1/4] Refactor pagination --- .../org/kohsuke/github/GHAppInstallation.java | 3 +- .../github/GHAppInstallationRequest.java | 1 - .../github/GHAppInstallationsIterable.java | 51 +-- .../github/GHAppInstallationsPage.java | 7 +- .../kohsuke/github/GHArtifactsIterable.java | 53 +--- .../org/kohsuke/github/GHArtifactsPage.java | 21 +- .../GHAuthenticatedAppInstallation.java | 3 +- .../kohsuke/github/GHCheckRunsIterable.java | 51 +-- .../org/kohsuke/github/GHCheckRunsPage.java | 21 +- .../kohsuke/github/GHCommitFileIterable.java | 83 ++--- .../org/kohsuke/github/GHCommitFilesPage.java | 9 +- .../kohsuke/github/GHCommitSearchBuilder.java | 22 +- .../java/org/kohsuke/github/GHCompare.java | 91 +----- .../github/GHContentSearchBuilder.java | 2 +- .../github/GHExternalGroupIterable.java | 76 ++--- .../kohsuke/github/GHExternalGroupPage.java | 6 +- .../kohsuke/github/GHIssueSearchBuilder.java | 4 +- .../kohsuke/github/GHNotificationStream.java | 3 +- .../github/GHPullRequestSearchBuilder.java | 2 +- .../github/GHRepositorySearchBuilder.java | 4 +- .../org/kohsuke/github/GHSearchBuilder.java | 18 +- .../kohsuke/github/GHUserSearchBuilder.java | 2 +- .../github/GHWorkflowJobsIterable.java | 49 +-- .../kohsuke/github/GHWorkflowJobsPage.java | 21 +- .../github/GHWorkflowRunsIterable.java | 54 +--- .../kohsuke/github/GHWorkflowRunsPage.java | 21 +- .../kohsuke/github/GHWorkflowsIterable.java | 56 +--- .../org/kohsuke/github/GHWorkflowsPage.java | 21 +- .../github/GitHubEndpointIterable.java | 292 ++++++++++++++++++ .../github/GitHubEndpointPageIterator.java | 135 ++++++++ .../java/org/kohsuke/github/GitHubPage.java | 16 + .../github/GitHubPageContentsIterable.java | 93 ------ .../github/GitHubPageItemIterator.java | 138 +++++++++ .../kohsuke/github/GitHubPageIterator.java | 162 +++------- .../org/kohsuke/github/GitHubResponse.java | 2 +- .../org/kohsuke/github/PagedIterable.java | 138 +-------- .../org/kohsuke/github/PagedIterator.java | 128 +------- .../kohsuke/github/PagedSearchIterable.java | 92 +----- .../java/org/kohsuke/github/Requester.java | 32 +- .../java/org/kohsuke/github/SearchResult.java | 6 +- .../no-reflect-and-serialization-list | 2 +- 41 files changed, 867 insertions(+), 1124 deletions(-) create mode 100644 src/main/java/org/kohsuke/github/GitHubEndpointIterable.java create mode 100644 src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java create mode 100644 src/main/java/org/kohsuke/github/GitHubPage.java delete mode 100644 src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java create mode 100644 src/main/java/org/kohsuke/github/GitHubPageItemIterator.java diff --git a/src/main/java/org/kohsuke/github/GHAppInstallation.java b/src/main/java/org/kohsuke/github/GHAppInstallation.java index e92c744e99..ec343924e1 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallation.java @@ -267,6 +267,7 @@ public PagedSearchIterable<GHRepository> listRepositories() { request = root().createRequest().withUrlPath("/installation/repositories").build(); - return new PagedSearchIterable<>(root(), request, GHAppInstallationRepositoryResult.class); + return new PagedSearchIterable<>(new GitHubEndpointIterable<>(root() + .getClient(), request, GHAppInstallationRepositoryResult.class, GHRepository.class, null)); } } diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java b/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java index 44ace753a2..a358435008 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationRequest.java @@ -10,7 +10,6 @@ */ public class GHAppInstallationRequest extends GHObject { private GHOrganization account; - private GHUser requester; /** diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java index fc89d371ee..d089e24e49 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java @@ -1,9 +1,5 @@ package org.kohsuke.github; -import java.util.Iterator; - -import javax.annotation.Nonnull; - // TODO: Auto-generated Javadoc /** * Iterable for GHAppInstallation listing. @@ -12,8 +8,6 @@ class GHAppInstallationsIterable extends PagedIterable<GHAppInstallation> { /** The Constant APP_INSTALLATIONS_URL. */ public static final String APP_INSTALLATIONS_URL = "/user/installations"; - private GHAppInstallationsPage result; - private final transient GitHub root; /** * Instantiates a new GH app installations iterable. @@ -22,45 +16,10 @@ class GHAppInstallationsIterable extends PagedIterable<GHAppInstallation> { * the root */ public GHAppInstallationsIterable(GitHub root) { - this.root = root; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHAppInstallation> _iterator(int pageSize) { - final GitHubRequest request = root.createRequest().withUrlPath(APP_INSTALLATIONS_URL).build(); - return new PagedIterator<>( - adapt(GitHubPageIterator.create(root.getClient(), GHAppInstallationsPage.class, request, pageSize)), - null); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<GHAppInstallation[]> adapt(final Iterator<GHAppInstallationsPage> base) { - return new Iterator<GHAppInstallation[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public GHAppInstallation[] next() { - GHAppInstallationsPage v = base.next(); - if (result == null) { - result = v; - } - return v.getInstallations(); - } - }; + super(new GitHubEndpointIterable<>(root.getClient(), + root.createRequest().withUrlPath(APP_INSTALLATIONS_URL).build(), + GHAppInstallationsPage.class, + GHAppInstallation.class, + null)); } } diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java index cd8f9a1f7e..0c593662d1 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java @@ -4,10 +4,15 @@ /** * Represents the one page of GHAppInstallations. */ -class GHAppInstallationsPage { +class GHAppInstallationsPage implements GitHubPage<GHAppInstallation> { private GHAppInstallation[] installations; private int totalCount; + @Override + public GHAppInstallation[] getItems() { + return getInstallations(); + } + /** * Gets the total count. * diff --git a/src/main/java/org/kohsuke/github/GHArtifactsIterable.java b/src/main/java/org/kohsuke/github/GHArtifactsIterable.java index 2a574150cc..b16678ac28 100644 --- a/src/main/java/org/kohsuke/github/GHArtifactsIterable.java +++ b/src/main/java/org/kohsuke/github/GHArtifactsIterable.java @@ -1,18 +1,10 @@ package org.kohsuke.github; -import java.util.Iterator; - -import javax.annotation.Nonnull; - // TODO: Auto-generated Javadoc /** * Iterable for artifacts listing. */ class GHArtifactsIterable extends PagedIterable<GHArtifact> { - private final transient GHRepository owner; - private final GitHubRequest request; - - private GHArtifactsPage result; /** * Instantiates a new GH artifacts iterable. @@ -23,45 +15,10 @@ class GHArtifactsIterable extends PagedIterable<GHArtifact> { * the request builder */ public GHArtifactsIterable(GHRepository owner, GitHubRequest.Builder<?> requestBuilder) { - this.owner = owner; - this.request = requestBuilder.build(); - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHArtifact> _iterator(int pageSize) { - return new PagedIterator<>( - adapt(GitHubPageIterator.create(owner.root().getClient(), GHArtifactsPage.class, request, pageSize)), - null); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<GHArtifact[]> adapt(final Iterator<GHArtifactsPage> base) { - return new Iterator<GHArtifact[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public GHArtifact[] next() { - GHArtifactsPage v = base.next(); - if (result == null) { - result = v; - } - return v.getArtifacts(owner); - } - }; + super(new GitHubEndpointIterable<>(owner.root().getClient(), + requestBuilder.build(), + GHArtifactsPage.class, + GHArtifact.class, + item -> item.wrapUp(owner))); } } diff --git a/src/main/java/org/kohsuke/github/GHArtifactsPage.java b/src/main/java/org/kohsuke/github/GHArtifactsPage.java index 8b3675bb11..c4edc38515 100644 --- a/src/main/java/org/kohsuke/github/GHArtifactsPage.java +++ b/src/main/java/org/kohsuke/github/GHArtifactsPage.java @@ -8,10 +8,15 @@ */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") -class GHArtifactsPage { +class GHArtifactsPage implements GitHubPage<GHArtifact> { private GHArtifact[] artifacts; private int totalCount; + @Override + public GHArtifact[] getItems() { + return artifacts; + } + /** * Gets the total count. * @@ -20,18 +25,4 @@ class GHArtifactsPage { public int getTotalCount() { return totalCount; } - - /** - * Gets the artifacts. - * - * @param owner - * the owner - * @return the artifacts - */ - GHArtifact[] getArtifacts(GHRepository owner) { - for (GHArtifact artifact : artifacts) { - artifact.wrapUp(owner); - } - return artifacts; - } } diff --git a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java index 73d55ba4c1..875b285287 100644 --- a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java @@ -39,7 +39,8 @@ public PagedSearchIterable<GHRepository> listRepositories() { request = root().createRequest().withUrlPath("/installation/repositories").build(); - return new PagedSearchIterable<>(root(), request, GHAuthenticatedAppInstallationRepositoryResult.class); + return new PagedSearchIterable<>(new GitHubEndpointIterable<>(root() + .getClient(), request, GHAuthenticatedAppInstallationRepositoryResult.class, GHRepository.class, null)); } } diff --git a/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java b/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java index 0866bd1f58..5f75eccc4e 100644 --- a/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java +++ b/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java @@ -1,19 +1,10 @@ package org.kohsuke.github; -import java.util.Iterator; - -import javax.annotation.Nonnull; - // TODO: Auto-generated Javadoc /** * Iterable for check-runs listing. */ class GHCheckRunsIterable extends PagedIterable<GHCheckRun> { - private final GHRepository owner; - private final GitHubRequest request; - - private GHCheckRunsPage result; - /** * Instantiates a new GH check runs iterable. * @@ -23,45 +14,7 @@ class GHCheckRunsIterable extends PagedIterable<GHCheckRun> { * the request */ public GHCheckRunsIterable(GHRepository owner, GitHubRequest request) { - this.owner = owner; - this.request = request; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHCheckRun> _iterator(int pageSize) { - return new PagedIterator<>( - adapt(GitHubPageIterator.create(owner.root().getClient(), GHCheckRunsPage.class, request, pageSize)), - null); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<GHCheckRun[]> adapt(final Iterator<GHCheckRunsPage> base) { - return new Iterator<GHCheckRun[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public GHCheckRun[] next() { - GHCheckRunsPage v = base.next(); - if (result == null) { - result = v; - } - return v.getCheckRuns(owner); - } - }; + super(new GitHubEndpointIterable<>(owner.root() + .getClient(), request, GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(owner))); } } diff --git a/src/main/java/org/kohsuke/github/GHCheckRunsPage.java b/src/main/java/org/kohsuke/github/GHCheckRunsPage.java index d0b5d012f2..4f36d526c6 100644 --- a/src/main/java/org/kohsuke/github/GHCheckRunsPage.java +++ b/src/main/java/org/kohsuke/github/GHCheckRunsPage.java @@ -8,10 +8,15 @@ */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") -class GHCheckRunsPage { +class GHCheckRunsPage implements GitHubPage<GHCheckRun> { private GHCheckRun[] checkRuns; private int totalCount; + @Override + public GHCheckRun[] getItems() { + return checkRuns; + } + /** * Gets the total count. * @@ -20,18 +25,4 @@ class GHCheckRunsPage { public int getTotalCount() { return totalCount; } - - /** - * Gets the check runs. - * - * @param owner - * the owner - * @return the check runs - */ - GHCheckRun[] getCheckRuns(GHRepository owner) { - for (GHCheckRun checkRun : checkRuns) { - checkRun.wrap(owner); - } - return checkRuns; - } } diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index 808f036017..1d83abc4a1 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -2,12 +2,8 @@ import org.kohsuke.github.GHCommit.File; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -import javax.annotation.Nonnull; - /** * Iterable for commit listing. * @@ -21,76 +17,41 @@ class GHCommitFileIterable extends PagedIterable<GHCommit.File> { */ private static final int GH_FILE_LIMIT_PER_COMMIT_PAGE = 300; - private final File[] files; - private final GHRepository owner; - private final String sha; - - /** - * Instantiates a new GH commit iterable. - * - * @param owner - * the owner - * @param sha - * the SHA of the commit - * @param files - * the list of files initially populated - */ - public GHCommitFileIterable(GHRepository owner, String sha, List<File> files) { - this.owner = owner; - this.sha = sha; - this.files = files != null ? files.toArray(new File[0]) : null; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHCommit.File> _iterator(int pageSize) { - - Iterator<GHCommit.File[]> pageIterator; - + private static GitHubEndpointIterable<GHCommitFilesPage, File> createEndpointIterable(GHRepository owner, + String sha, + GHCommit.File[] files) { + GitHubEndpointIterable<GHCommitFilesPage, File> iterable; if (files != null && files.length < GH_FILE_LIMIT_PER_COMMIT_PAGE) { // create a page iterator that only provides one page - pageIterator = Collections.singleton(files).iterator(); + iterable = GitHubEndpointIterable.ofSingleton(new GHCommitFilesPage(files)); } else { - // page size is controlled by the server for this iterator, do not allow it to be set by the caller - pageSize = 0; - GitHubRequest request = owner.root() .createRequest() .withUrlPath(owner.getApiTailUrl("commits/" + sha)) .build(); - - pageIterator = adapt( - GitHubPageIterator.create(owner.root().getClient(), GHCommitFilesPage.class, request, pageSize)); + iterable = new GitHubEndpointIterable<>(owner.root() + .getClient(), request, GHCommitFilesPage.class, GHCommit.File.class, null); } - - return new PagedIterator<>(pageIterator, null); + return iterable; } /** - * Adapt. + * Instantiates a new GH commit iterable. * - * @param base - * the base commit page - * @return the iterator + * @param owner + * the owner + * @param sha + * the SHA of the commit + * @param files + * the list of files initially populated */ - protected Iterator<GHCommit.File[]> adapt(final Iterator<GHCommitFilesPage> base) { - return new Iterator<GHCommit.File[]>() { - - public boolean hasNext() { - return base.hasNext(); - } + public GHCommitFileIterable(GHRepository owner, String sha, List<GHCommit.File> files) { + super(createEndpointIterable(owner, sha, files != null ? files.toArray(new File[0]) : null)); + } - public GHCommit.File[] next() { - GHCommitFilesPage v = base.next(); - return v.getFiles(); - } - }; + @Override + public PagedIterable<File> withPageSize(int i) { + // page size is controlled by the server for this iterable, do not allow it to be set by the caller + return this; } } diff --git a/src/main/java/org/kohsuke/github/GHCommitFilesPage.java b/src/main/java/org/kohsuke/github/GHCommitFilesPage.java index d2ab10dc98..c23761ae27 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFilesPage.java +++ b/src/main/java/org/kohsuke/github/GHCommitFilesPage.java @@ -7,24 +7,23 @@ * * @author Stephen Horgan */ -class GHCommitFilesPage { +class GHCommitFilesPage implements GitHubPage<GHCommit.File> { private File[] files; public GHCommitFilesPage() { } - public GHCommitFilesPage(File[] files) { + GHCommitFilesPage(File[] files) { this.files = files; } /** * Gets the files. * - * @param owner - * the owner * @return the files */ - File[] getFiles() { + @Override + public File[] getItems() { return files; } } diff --git a/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java index 3a1ddbffce..ecc1042df5 100644 --- a/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java @@ -33,14 +33,6 @@ private static class CommitSearchResult extends SearchResult<GHCommit> { @Override GHCommit[] getItems(GitHub root) { - for (GHCommit commit : items) { - String repoName = getRepoName(commit.url); - try { - GHRepository repo = root.getRepository(repoName); - commit.wrapUp(repo); - } catch (IOException ioe) { - } - } return items; } } @@ -66,7 +58,7 @@ private static String getRepoName(String commitUrl) { * the root */ GHCommitSearchBuilder(GitHub root) { - super(root, CommitSearchResult.class); + super(root, CommitSearchResult.class, GHCommit.class); } /** @@ -179,6 +171,18 @@ public GHCommitSearchBuilder is(String v) { return q("is:" + v); } + @Override + public PagedSearchIterable<GHCommit> list() { + return list(item -> { + String repoName = getRepoName(item.url); + try { + GHRepository repo = root().getRepository(repoName); + item.wrapUp(repo); + } catch (IOException ioe) { + } + }); + } + /** * Merge gh commit search builder. * diff --git a/src/main/java/org/kohsuke/github/GHCompare.java b/src/main/java/org/kohsuke/github/GHCompare.java index 48340fda36..52ccb8a922 100644 --- a/src/main/java/org/kohsuke/github/GHCompare.java +++ b/src/main/java/org/kohsuke/github/GHCompare.java @@ -5,10 +5,6 @@ import java.io.IOException; import java.net.URL; -import java.util.Collections; -import java.util.Iterator; - -import javax.annotation.Nonnull; // TODO: Auto-generated Javadoc /** @@ -16,7 +12,7 @@ * * @author Michael Clarke */ -public class GHCompare { +public class GHCompare implements GitHubPage<GHCompare.Commit> { /** * Compare commits had a child commit element with additional details we want to capture. This extension of GHCommit @@ -113,6 +109,7 @@ public String getUrl() { return url; } } + /** * The enum Status. */ @@ -158,70 +155,9 @@ public String getUrl() { return url; } } - /** - * Iterable for commit listing. - */ - class GHCompareCommitsIterable extends PagedIterable<Commit> { - - private GHCompare result; - - /** - * Instantiates a new GH compare commits iterable. - */ - public GHCompareCommitsIterable() { - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<Commit> _iterator(int pageSize) { - GitHubRequest request = owner.root() - .createRequest() - .injectMappingValue("GHCompare_usePaginatedCommits", usePaginatedCommits) - .withUrlPath(owner.getApiTailUrl(url.substring(url.lastIndexOf("/compare/")))) - .build(); - - // page_size must be set for GHCompare commit pagination - if (pageSize == 0) { - pageSize = 10; - } - return new PagedIterator<>( - adapt(GitHubPageIterator.create(owner.root().getClient(), GHCompare.class, request, pageSize)), - item -> item.wrapUp(owner)); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<Commit[]> adapt(final Iterator<GHCompare> base) { - return new Iterator<Commit[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public Commit[] next() { - GHCompare v = base.next(); - if (result == null) { - result = v; - } - return v.commits; - } - }; - } - } private int aheadBy, behindBy, totalCommits; - private Commit baseCommit, mergeBaseCommit; + private Commit baseCommit, mergeBaseCommit; private Commit[] commits; private GHCommit.File[] files; @@ -324,6 +260,11 @@ public URL getHtmlUrl() { return GitHubClient.parseURL(htmlUrl); } + @Override + public GHCompare.Commit[] getItems() { + return commits; + } + /** * Gets merge base commit. * @@ -395,16 +336,16 @@ public URL getUrl() { */ public PagedIterable<Commit> listCommits() { if (usePaginatedCommits) { - return new GHCompareCommitsIterable(); + final GHRepository owner = this.owner; + return owner.root() + .createRequest() + .injectMappingValue("GHCompare_usePaginatedCommits", usePaginatedCommits) + .withUrlPath(owner.getApiTailUrl(url.substring(url.lastIndexOf("/compare/")))) + .toIterable(GHCompare.class, Commit.class, item -> item.wrapUp(owner)) + .withPageSize(10); } else { // if not using paginated commits, adapt the returned commits array - return new PagedIterable<Commit>() { - @Nonnull - @Override - public PagedIterator<Commit> _iterator(int pageSize) { - return new PagedIterator<>(Collections.singleton(commits).iterator(), null); - } - }; + return new PagedIterable<>(GitHubEndpointIterable.ofSingleton(this.commits)); } } diff --git a/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java b/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java index bdd16cea3e..7aa1243f38 100644 --- a/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHContentSearchBuilder.java @@ -36,7 +36,7 @@ GHContent[] getItems(GitHub root) { * the root */ GHContentSearchBuilder(GitHub root) { - super(root, ContentSearchResult.class); + super(root, ContentSearchResult.class, GHContent.class); } /** diff --git a/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java b/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java index f921fdd920..a4a032bab7 100644 --- a/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java +++ b/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java @@ -1,9 +1,6 @@ package org.kohsuke.github; -import java.util.Arrays; -import java.util.Iterator; - -import javax.annotation.Nonnull; +import org.jetbrains.annotations.NotNull; /** * Iterable for external group listing. @@ -12,12 +9,6 @@ */ class GHExternalGroupIterable extends PagedIterable<GHExternalGroup> { - private final GHOrganization owner; - - private final GitHubRequest request; - - private GHExternalGroupPage result; - /** * Instantiates a new GH external groups iterable. * @@ -26,52 +17,25 @@ class GHExternalGroupIterable extends PagedIterable<GHExternalGroup> { * @param requestBuilder * the request builder */ - GHExternalGroupIterable(final GHOrganization owner, final GitHubRequest.Builder<?> requestBuilder) { - this.owner = owner; - this.request = requestBuilder.build(); - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHExternalGroup> _iterator(int pageSize) { - return new PagedIterator<>( - adapt(GitHubPageIterator - .create(owner.root().getClient(), GHExternalGroupPage.class, request, pageSize)), - null); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - private Iterator<GHExternalGroup[]> adapt(final Iterator<GHExternalGroupPage> base) { - return new Iterator<GHExternalGroup[]>() { - public boolean hasNext() { - try { - return base.hasNext(); - } catch (final GHException e) { - throw EnterpriseManagedSupport.forOrganization(owner).filterException(e).orElse(e); - } - } - - public GHExternalGroup[] next() { - GHExternalGroupPage v = base.next(); - if (result == null) { - result = v; - } - Arrays.stream(v.getGroups()).forEach(g -> g.wrapUp(owner)); - return v.getGroups(); + GHExternalGroupIterable(final GHOrganization owner, GitHubRequest.Builder<?> requestBuilder) { + super(new GitHubEndpointIterable<>(owner.root().getClient(), + requestBuilder.build(), + GHExternalGroupPage.class, + GHExternalGroup.class, + item -> item.wrapUp(owner)) { + @NotNull @Override + public GitHubEndpointPageIterator<GHExternalGroupPage, GHExternalGroup> pageIterator() { + return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer) { + @Override + public boolean hasNext() { + try { + return super.hasNext(); + } catch (final GHException e) { + throw EnterpriseManagedSupport.forOrganization(owner).filterException(e).orElse(e); + } + } + }; } - }; + }); } } diff --git a/src/main/java/org/kohsuke/github/GHExternalGroupPage.java b/src/main/java/org/kohsuke/github/GHExternalGroupPage.java index d47b49678c..02441fcfff 100644 --- a/src/main/java/org/kohsuke/github/GHExternalGroupPage.java +++ b/src/main/java/org/kohsuke/github/GHExternalGroupPage.java @@ -7,7 +7,7 @@ * * @author Miguel Esteban GutiƩrrez */ -class GHExternalGroupPage { +class GHExternalGroupPage implements GitHubPage<GHExternalGroup> { private static final GHExternalGroup[] GH_EXTERNAL_GROUPS = new GHExternalGroup[0]; @@ -31,4 +31,8 @@ public GHExternalGroup[] getGroups() { return groups; } + @Override + public GHExternalGroup[] getItems() { + return getGroups(); + } } diff --git a/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java b/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java index 3967691ac3..507a254864 100644 --- a/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java @@ -32,8 +32,6 @@ private static class IssueSearchResult extends SearchResult<GHIssue> { @Override GHIssue[] getItems(GitHub root) { - for (GHIssue i : items) { - } return items; } } @@ -45,7 +43,7 @@ GHIssue[] getItems(GitHub root) { * the root */ GHIssueSearchBuilder(GitHub root) { - super(root, IssueSearchResult.class); + super(root, IssueSearchResult.class, GHIssue.class); } /** diff --git a/src/main/java/org/kohsuke/github/GHNotificationStream.java b/src/main/java/org/kohsuke/github/GHNotificationStream.java index 269ddf972f..28e075b812 100644 --- a/src/main/java/org/kohsuke/github/GHNotificationStream.java +++ b/src/main/java/org/kohsuke/github/GHNotificationStream.java @@ -143,8 +143,7 @@ GHThread fetch() { } Requester requester = req.withUrlPath(apiUrl); - GitHubResponse<GHThread[]> response = ((GitHubPageContentsIterable<GHThread>) requester - .toIterable(GHThread[].class, null)).toResponse(); + GitHubResponse<GHThread[]> response = requester.toIterable(GHThread[].class, null).toResponse(); threads = response.body(); if (threads == null) { diff --git a/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java index 143f6e6ae2..910b8c6163 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestSearchBuilder.java @@ -44,7 +44,7 @@ GHPullRequest[] getItems(GitHub root) { * the root */ GHPullRequestSearchBuilder(GitHub root) { - super(root, PullRequestSearchResult.class); + super(root, PullRequestSearchResult.class, GHPullRequest.class); } /** diff --git a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java index 9e600ec927..c0d0896658 100644 --- a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java @@ -32,8 +32,6 @@ private static class RepositorySearchResult extends SearchResult<GHRepository> { @Override GHRepository[] getItems(GitHub root) { - for (GHRepository item : items) { - } return items; } } @@ -45,7 +43,7 @@ GHRepository[] getItems(GitHub root) { * the root */ GHRepositorySearchBuilder(GitHub root) { - super(root, RepositorySearchResult.class); + super(root, RepositorySearchResult.class, GHRepository.class); } /** diff --git a/src/main/java/org/kohsuke/github/GHSearchBuilder.java b/src/main/java/org/kohsuke/github/GHSearchBuilder.java index ea6317426c..3cabb7800c 100644 --- a/src/main/java/org/kohsuke/github/GHSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHSearchBuilder.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -18,11 +19,12 @@ */ public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> { + private final Class<T> itemType; + /** * Data transfer object that receives the result of search. */ private final Class<? extends SearchResult<T>> receiverType; - /** The terms. */ protected final List<String> terms = new ArrayList<String>(); @@ -34,9 +36,10 @@ public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> { * @param receiverType * the receiver type */ - GHSearchBuilder(GitHub root, Class<? extends SearchResult<T>> receiverType) { + GHSearchBuilder(GitHub root, Class<? extends SearchResult<T>> receiverType, Class<T> itemType) { super(root); this.receiverType = receiverType; + this.itemType = itemType; req.withUrlPath(getApiUrl()); req.rateLimit(RateLimitTarget.SEARCH); } @@ -48,9 +51,7 @@ public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> { */ @Override public PagedSearchIterable<T> list() { - - req.set("q", StringUtils.join(terms, " ")); - return new PagedSearchIterable<>(root(), req.build(), receiverType); + return list(null); } /** @@ -72,6 +73,13 @@ public GHQueryBuilder<T> q(String term) { */ protected abstract String getApiUrl(); + PagedSearchIterable<T> list(Consumer<T> itemInitializer) { + + req.set("q", StringUtils.join(terms, " ")); + return new PagedSearchIterable<>( + new GitHubEndpointIterable<>(root().getClient(), req.build(), receiverType, itemType, itemInitializer)); + } + /** * Add a search term with qualifier. * diff --git a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java index 0193b2139e..6852114b26 100644 --- a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java @@ -38,7 +38,7 @@ GHUser[] getItems(GitHub root) { * the root */ GHUserSearchBuilder(GitHub root) { - super(root, UserSearchResult.class); + super(root, UserSearchResult.class, GHUser.class); } /** diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java index 6ab751850d..38fe89f524 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java @@ -1,17 +1,10 @@ package org.kohsuke.github; -import java.util.Iterator; - -import javax.annotation.Nonnull; - // TODO: Auto-generated Javadoc /** * Iterable for workflow run jobs listing. */ class GHWorkflowJobsIterable extends PagedIterable<GHWorkflowJob> { - private final GHRepository repo; - private final GitHubRequest request; - private GHWorkflowJobsPage result; /** @@ -23,45 +16,7 @@ class GHWorkflowJobsIterable extends PagedIterable<GHWorkflowJob> { * the request */ public GHWorkflowJobsIterable(GHRepository repo, GitHubRequest request) { - this.repo = repo; - this.request = request; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHWorkflowJob> _iterator(int pageSize) { - return new PagedIterator<>( - adapt(GitHubPageIterator.create(repo.root().getClient(), GHWorkflowJobsPage.class, request, pageSize)), - null); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<GHWorkflowJob[]> adapt(final Iterator<GHWorkflowJobsPage> base) { - return new Iterator<GHWorkflowJob[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public GHWorkflowJob[] next() { - GHWorkflowJobsPage v = base.next(); - if (result == null) { - result = v; - } - return v.getWorkflowJobs(repo); - } - }; + super(new GitHubEndpointIterable<>(repo.root() + .getClient(), request, GHWorkflowJobsPage.class, GHWorkflowJob.class, item -> item.wrapUp(repo))); } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java b/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java index 8d4a7ca772..02d45de338 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJobsPage.java @@ -8,10 +8,15 @@ */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") -class GHWorkflowJobsPage { +class GHWorkflowJobsPage implements GitHubPage<GHWorkflowJob> { private GHWorkflowJob[] jobs; private int totalCount; + @Override + public GHWorkflowJob[] getItems() { + return jobs; + } + /** * Gets the total count. * @@ -20,18 +25,4 @@ class GHWorkflowJobsPage { public int getTotalCount() { return totalCount; } - - /** - * Gets the workflow jobs. - * - * @param repo - * the repo - * @return the workflow jobs - */ - GHWorkflowJob[] getWorkflowJobs(GHRepository repo) { - for (GHWorkflowJob job : jobs) { - job.wrapUp(repo); - } - return jobs; - } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java index 4a525a83dc..532d3e6097 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java @@ -1,19 +1,10 @@ package org.kohsuke.github; -import java.util.Iterator; - -import javax.annotation.Nonnull; - // TODO: Auto-generated Javadoc /** * Iterable for workflow runs listing. */ class GHWorkflowRunsIterable extends PagedIterable<GHWorkflowRun> { - private final GHRepository owner; - private final GitHubRequest request; - - private GHWorkflowRunsPage result; - /** * Instantiates a new GH workflow runs iterable. * @@ -23,45 +14,10 @@ class GHWorkflowRunsIterable extends PagedIterable<GHWorkflowRun> { * the request builder */ public GHWorkflowRunsIterable(GHRepository owner, GitHubRequest.Builder<?> requestBuilder) { - this.owner = owner; - this.request = requestBuilder.build(); - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHWorkflowRun> _iterator(int pageSize) { - return new PagedIterator<>( - adapt(GitHubPageIterator.create(owner.root().getClient(), GHWorkflowRunsPage.class, request, pageSize)), - null); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<GHWorkflowRun[]> adapt(final Iterator<GHWorkflowRunsPage> base) { - return new Iterator<GHWorkflowRun[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public GHWorkflowRun[] next() { - GHWorkflowRunsPage v = base.next(); - if (result == null) { - result = v; - } - return v.getWorkflowRuns(owner); - } - }; + super(new GitHubEndpointIterable<>(owner.root().getClient(), + requestBuilder.build(), + GHWorkflowRunsPage.class, + GHWorkflowRun.class, + item -> item.wrapUp(owner))); } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRunsPage.java b/src/main/java/org/kohsuke/github/GHWorkflowRunsPage.java index 8df067dead..5e0ebaca99 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRunsPage.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRunsPage.java @@ -8,10 +8,15 @@ */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") -class GHWorkflowRunsPage { +class GHWorkflowRunsPage implements GitHubPage<GHWorkflowRun> { private int totalCount; private GHWorkflowRun[] workflowRuns; + @Override + public GHWorkflowRun[] getItems() { + return workflowRuns; + } + /** * Gets the total count. * @@ -20,18 +25,4 @@ class GHWorkflowRunsPage { public int getTotalCount() { return totalCount; } - - /** - * Gets the workflow runs. - * - * @param owner - * the owner - * @return the workflow runs - */ - GHWorkflowRun[] getWorkflowRuns(GHRepository owner) { - for (GHWorkflowRun workflowRun : workflowRuns) { - workflowRun.wrapUp(owner); - } - return workflowRuns; - } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java index 66d3d9480f..265d1700f5 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java @@ -1,17 +1,10 @@ package org.kohsuke.github; -import java.util.Iterator; - -import javax.annotation.Nonnull; - // TODO: Auto-generated Javadoc /** * Iterable for workflows listing. */ class GHWorkflowsIterable extends PagedIterable<GHWorkflow> { - private final transient GHRepository owner; - - private GHWorkflowsPage result; /** * Instantiates a new GH workflows iterable. @@ -20,49 +13,10 @@ class GHWorkflowsIterable extends PagedIterable<GHWorkflow> { * the owner */ public GHWorkflowsIterable(GHRepository owner) { - this.owner = owner; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<GHWorkflow> _iterator(int pageSize) { - GitHubRequest request = owner.root() - .createRequest() - .withUrlPath(owner.getApiTailUrl("actions/workflows")) - .build(); - - return new PagedIterator<>( - adapt(GitHubPageIterator.create(owner.root().getClient(), GHWorkflowsPage.class, request, pageSize)), - null); - } - - /** - * Adapt. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<GHWorkflow[]> adapt(final Iterator<GHWorkflowsPage> base) { - return new Iterator<GHWorkflow[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public GHWorkflow[] next() { - GHWorkflowsPage v = base.next(); - if (result == null) { - result = v; - } - return v.getWorkflows(owner); - } - }; + super(new GitHubEndpointIterable<>(owner.root().getClient(), + owner.root().createRequest().withUrlPath(owner.getApiTailUrl("actions/workflows")).build(), + GHWorkflowsPage.class, + GHWorkflow.class, + item -> item.wrapUp(owner))); } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowsPage.java b/src/main/java/org/kohsuke/github/GHWorkflowsPage.java index 1fb87a8147..134bdd6419 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowsPage.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowsPage.java @@ -8,10 +8,15 @@ */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API") -class GHWorkflowsPage { +class GHWorkflowsPage implements GitHubPage<GHWorkflow> { private int totalCount; private GHWorkflow[] workflows; + @Override + public GHWorkflow[] getItems() { + return workflows; + } + /** * Gets the total count. * @@ -20,18 +25,4 @@ class GHWorkflowsPage { public int getTotalCount() { return totalCount; } - - /** - * Gets the workflows. - * - * @param owner - * the owner - * @return the workflows - */ - GHWorkflow[] getWorkflows(GHRepository owner) { - for (GHWorkflow workflow : workflows) { - workflow.wrapUp(owner); - } - return workflows; - } } diff --git a/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java b/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java new file mode 100644 index 0000000000..804a497a1d --- /dev/null +++ b/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java @@ -0,0 +1,292 @@ +package org.kohsuke.github; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.*; +import java.util.function.Consumer; + +import javax.annotation.Nonnull; + +/** + * {@link GitHubEndpointIterable} implementation that take a {@link Consumer} that initializes all the items on each + * page as they are retrieved. + * + * {@link GitHubEndpointIterable} is immutable and thread-safe, but the iterator returned from {@link #iterator()} is + * not. Any one instance of iterator should only be called from a single thread. + * + * @author Liam Newman + * @param <Item> + * the type of items on each page + */ +class GitHubEndpointIterable<Page extends GitHubPage<Item>, Item> implements Iterable<Item> { + + private static class ArrayIterable<I> extends GitHubEndpointIterable<GitHubPage<I>, I> { + + private class ArrayIterator extends GitHubEndpointPageIterator<GitHubPage<I>, I> { + + ArrayIterator(GitHubClient client, + Class<GitHubPage<I>> pageType, + GitHubRequest request, + int pageSize, + Consumer<I> itemInitializer) { + super(client, pageType, request, pageSize, itemInitializer); + } + + @Override + @NotNull protected GitHubResponse<GitHubPage<I>> sendNextRequest() throws IOException { + GitHubResponse<I[]> response = client.sendRequest(nextRequest, + (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, receiverType)); + return new GitHubResponse<>(response, new GitHubArrayPage<>(response.body())); + } + + } + + private final Class<I[]> receiverType; + + private ArrayIterable(GitHubClient client, + GitHubRequest request, + Class<I[]> receiverType, + Consumer<I> itemInitializer) { + super(client, + request, + GitHubArrayPage.getArrayPageClass(receiverType), + (Class<I>) receiverType.getComponentType(), + itemInitializer); + this.receiverType = receiverType; + } + + @NotNull @Override + public GitHubEndpointPageIterator<GitHubPage<I>, I> pageIterator() { + return new ArrayIterator(client, pageType, request, pageSize, itemInitializer); + } + } + + /** + * Represents the result of a search. + * + * @author Kohsuke Kawaguchi + * @param <I> + * the generic type + */ + private static class GitHubArrayPage<I> implements GitHubPage<I> { + + private static <P extends GitHubPage<I>, I> Class<P> getArrayPageClass(Class<I[]> receiverType) { + return (Class<P>) new GitHubArrayPage<>(receiverType).getClass(); + } + + private final I[] items; + + public GitHubArrayPage(I[] items) { + this.items = items; + } + + private GitHubArrayPage(Class<I[]> receiverType) { + this.items = (I[]) Array.newInstance(receiverType.getComponentType(), 0); + } + + public I[] getItems() { + return items; + } + } + + static <I> GitHubEndpointIterable<GitHubPage<I>, I> ofArrayEndpoint(GitHubClient client, + GitHubRequest request, + Class<I[]> receiverType, + Consumer<I> itemInitializer) { + return new ArrayIterable<>(client, request, receiverType, itemInitializer); + } + + static <I> GitHubEndpointIterable<GitHubPage<I>, I> ofSingleton(I[] array) { + return ofSingleton(new GitHubArrayPage<>(array)); + } + + static <P extends GitHubPage<I>, I> GitHubEndpointIterable<P, I> ofSingleton(P page) { + Class<I> itemType = (Class<I>) page.getItems().getClass().getComponentType(); + return new GitHubEndpointIterable<>(null, null, (Class<P>) page.getClass(), itemType, null) { + @Nonnull + @Override + public GitHubPageIterator<P, I> pageIterator() { + return GitHubPageIterator.ofSingleton(page); + } + }; + } + + protected final GitHubClient client; + protected final Consumer<Item> itemInitializer; + protected final Class<Item> itemType; + + /** + * Page size. 0 is default. + */ + protected int pageSize = 0; + + protected final Class<Page> pageType; + + protected final GitHubRequest request; + + /** + * Instantiates a new git hub page contents iterable. + * + * @param client + * the client + * @param request + * the request + * @param pageType + * the receiver type + * @param itemInitializer + * the item initializer + */ + GitHubEndpointIterable(GitHubClient client, + GitHubRequest request, + Class<Page> pageType, + Class<Item> itemType, + Consumer<Item> itemInitializer) { + this.client = client; + this.request = request; + this.pageType = pageType; + this.itemType = itemType; + this.itemInitializer = itemInitializer; + } + + @Nonnull + public final GitHubPageItemIterator<Page, Item> itemIterator() { + return new GitHubPageItemIterator<>(this.pageIterator()); + } + @Nonnull + @Override + public final Iterator<Item> iterator() { + return this.itemIterator(); + } + + /** + * + * @return + */ + @Nonnull + public GitHubPageIterator<Page, Item> pageIterator() { + return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer); + } + + /** + * Eagerly walk {@link Iterable} and return the result in an array. + * + * @return the list + * @throws IOException + * if an I/O exception occurs. + */ + @Nonnull + public final Item[] toArray() throws IOException { + return toArray(pageIterator(), itemType); + } + + /** + * Eagerly walk {@link Iterable} and return the result in a list. + * + * @return the list + * @throws IOException + * if an I/O Exception occurs + */ + @Nonnull + public final List<Item> toList() throws IOException { + return Collections.unmodifiableList(Arrays.asList(this.toArray())); + } + + /** + * Eagerly walk {@link Iterable} and return the result in a set. + * + * @return the set + * @throws IOException + * if an I/O Exception occurs + */ + @Nonnull + public final Set<Item> toSet() throws IOException { + return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(this.toArray()))); + } + + /** + * Sets the pagination size. + * + * <p> + * When set to non-zero, each API call will retrieve this many entries. + * + * @param size + * the size + * @return the paged iterable + */ + public final GitHubEndpointIterable<Page, Item> withPageSize(int size) { + this.pageSize = size; + return this; + } + + /** + * Concatenates a list of arrays into a single array. + * + * @param pages + * the list of arrays to be concatenated. + * @param totalLength + * the total length of the returned array. + * @return an array containing all elements from all pages. + */ + @Nonnull + private Item[] concatenatePages(List<Item[]> pages, int totalLength) { + Item[] result = (Item[]) Array.newInstance(itemType, totalLength); + + int position = 0; + for (Item[] page : pages) { + final int pageLength = Array.getLength(page); + System.arraycopy(page, 0, result, position, pageLength); + position += pageLength; + } + return result; + } + + /** + * Eagerly walk {@link PagedIterator} and return the result in an array. + * + * @param iterator + * the {@link PagedIterator} to read + * @return an array of all elements from the {@link PagedIterator} + * @throws IOException + * if an I/O exception occurs. + */ + private Item[] toArray(final GitHubPageIterator<Page, Item> iterator, Class<Item> itemType) throws IOException { + try { + ArrayList<Item[]> pages = new ArrayList<>(); + int totalSize = 0; + Item[] item; + while (iterator.hasNext()) { + item = iterator.next().getItems(); + totalSize += Array.getLength(item); + pages.add(item); + } + + return concatenatePages(pages, totalSize); + } catch (GHException e) { + // if there was an exception inside the iterator it is wrapped as a GHException + // if the wrapped exception is an IOException, throw that + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw e; + } + } + } + + /** + * Eagerly walk {@link Iterable} and return the result in a {@link GitHubResponse} containing an array of {@code T} + * items. + * + * @return the last response with an array containing all the results from all pages. + * @throws IOException + * if an I/O exception occurs. + */ + @Nonnull + final GitHubResponse<Item[]> toResponse() throws IOException { + GitHubPageIterator<Page, Item> iterator = pageIterator(); + Item[] items = toArray(iterator, itemType); + GitHubResponse<Page> lastResponse = iterator.finalResponse(); + return new GitHubResponse<>(lastResponse, items); + } +} diff --git a/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java b/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java new file mode 100644 index 0000000000..9440024b77 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java @@ -0,0 +1,135 @@ +package org.kohsuke.github; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.net.URL; +import java.util.function.Consumer; + +/** + * May be used for any item that has pagination information. Iterates over paginated {@code P} objects (not the items + * inside the page). Also exposes {@link #finalResponse()} to allow getting a full {@link GitHubResponse}{@code + * +<P> + * } after iterating completes. + * <p> + * Works for array responses, also works for search results which are single instances with an array of items inside. + * <p> + * This class is not thread-safe. Any one instance should only be called from a single thread. + * + * @author Liam Newman + * @param <P> + * type of each page (not the items in the page). + */ +class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> extends GitHubPageIterator<P, Item> { + + /** + * When done iterating over pages, it is on rare occasions useful to be able to get information from the final + * response that was retrieved. + */ + private GitHubResponse<P> finalResponse = null; + + protected final GitHubClient client; + + /** + * The request that will be sent when to get a new response page if {@link #next} is {@code null}. Will be + * {@code null} when there are no more pages to fetch. + */ + protected GitHubRequest nextRequest; + + GitHubEndpointPageIterator(GitHubClient client, + Class<P> pageType, + GitHubRequest request, + int pageSize, + Consumer<Item> itemInitializer) { + super(pageType, itemInitializer); + + if (pageSize > 0) { + GitHubRequest.Builder<?> builder = request.toBuilder().with("per_page", pageSize); + request = builder.build(); + } + + if (request != null && !"GET".equals(request.method())) { + throw new IllegalArgumentException("Request method \"GET\" is required for page iterator."); + } + + this.client = client; + this.nextRequest = request; + } + + /** + * On rare occasions the final response from iterating is needed. + * + * @return the final response of the iterator. + */ + public GitHubResponse<P> finalResponse() { + if (hasNext()) { + throw new GHException("Final response is not available until after iterator is done."); + } + return finalResponse; + } + + /** + * Locate the next page from the pagination "Link" tag. + */ + private void updateNextRequest(GitHubResponse<P> nextResponse) { + GitHubRequest result = null; + String link = nextResponse.header("Link"); + if (link != null) { + for (String token : link.split(", ")) { + if (token.endsWith("rel=\"next\"")) { + // found the next page. This should look something like + // <https://api.github.com/repos?page=3&per_page=100>; rel="next" + int idx = token.indexOf('>'); + result = nextRequest.toBuilder().setRawUrlPath(token.substring(1, idx)).build(); + break; + } + } + } + nextRequest = result; + if (nextRequest == null) { + // If this is the last page, keep the response + finalResponse = nextResponse; + } + } + + /** + * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is + * needed. + * <p> + * If {@link #next} is not {@code null}, no further action is needed. If {@link #next} is {@code null} and + * {@link #nextRequest} is {@code null}, there are no more pages to fetch. + * </p> + * <p> + * Otherwise, a new response page is fetched using {@link #nextRequest}. The response is then checked to see if + * there is a page after it and {@link #nextRequest} is updated to point to it. If there are no pages available + * after the current response, {@link #nextRequest} is set to {@code null}. + * </p> + */ + @Override + protected P fetchNext() { + if (next != null || nextRequest == null) + return null; // already fetched or no more data to fetch + + P result; + + URL url = nextRequest.url(); + try { + GitHubResponse<P> nextResponse = sendNextRequest(); + assert nextResponse.body() != null; + result = nextResponse.body(); + updateNextRequest(nextResponse); + } catch (IOException e) { + // Iterators do not throw IOExceptions, so we wrap any IOException + // in a runtime GHException to bubble out if needed. + throw new GHException("Failed to retrieve " + url, e); + } + return result; + } + + @NotNull protected GitHubResponse<P> sendNextRequest() throws IOException { + return client.sendRequest(nextRequest, + (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, pageType)); + } + +} diff --git a/src/main/java/org/kohsuke/github/GitHubPage.java b/src/main/java/org/kohsuke/github/GitHubPage.java new file mode 100644 index 0000000000..0125b911a7 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GitHubPage.java @@ -0,0 +1,16 @@ +package org.kohsuke.github; + +/** + * A page of results from GitHub. + * + * @param <I> + * the type of items on the page. + */ +interface GitHubPage<I> { + /** + * Wraps up the retrieved object and return them. Only called once. + * + * @return the items + */ + I[] getItems(); +} diff --git a/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java b/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java deleted file mode 100644 index f8fc7e4907..0000000000 --- a/src/main/java/org/kohsuke/github/GitHubPageContentsIterable.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.kohsuke.github; - -import java.io.IOException; -import java.util.function.Consumer; - -import javax.annotation.Nonnull; - -// TODO: Auto-generated Javadoc -/** - * {@link PagedIterable} implementation that take a {@link Consumer} that initializes all the items on each page as they - * are retrieved. - * - * {@link GitHubPageContentsIterable} is immutable and thread-safe, but the iterator returned from {@link #iterator()} - * is not. Any one instance of iterator should only be called from a single thread. - * - * @author Liam Newman - * @param <T> - * the type of items on each page - */ -class GitHubPageContentsIterable<T> extends PagedIterable<T> { - - /** - * This class is not thread-safe. Any one instance should only be called from a single thread. - */ - private class GitHubPageContentsIterator extends PagedIterator<T> { - - public GitHubPageContentsIterator(GitHubPageIterator<T[]> iterator, Consumer<T> itemInitializer) { - super(iterator, itemInitializer); - } - - /** - * Gets the {@link GitHubResponse} for the last page received. - * - * @return the {@link GitHubResponse} for the last page received. - */ - private GitHubResponse<T[]> lastResponse() { - return ((GitHubPageIterator<T[]>) base).finalResponse(); - } - } - - private final GitHubClient client; - private final Consumer<T> itemInitializer; - private final Class<T[]> receiverType; - private final GitHubRequest request; - - /** - * Instantiates a new git hub page contents iterable. - * - * @param client - * the client - * @param request - * the request - * @param receiverType - * the receiver type - * @param itemInitializer - * the item initializer - */ - GitHubPageContentsIterable(GitHubClient client, - GitHubRequest request, - Class<T[]> receiverType, - Consumer<T> itemInitializer) { - this.client = client; - this.request = request; - this.receiverType = receiverType; - this.itemInitializer = itemInitializer; - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public PagedIterator<T> _iterator(int pageSize) { - final GitHubPageIterator<T[]> iterator = GitHubPageIterator.create(client, receiverType, request, pageSize); - return new GitHubPageContentsIterator(iterator, itemInitializer); - } - - /** - * Eagerly walk {@link Iterable} and return the result in a {@link GitHubResponse} containing an array of {@code T} - * items. - * - * @return the last response with an array containing all the results from all pages. - * @throws IOException - * if an I/O exception occurs. - */ - @Nonnull - GitHubResponse<T[]> toResponse() throws IOException { - GitHubPageContentsIterator iterator = (GitHubPageContentsIterator) iterator(); - T[] items = toArray(iterator); - GitHubResponse<T[]> lastResponse = iterator.lastResponse(); - return new GitHubResponse<>(lastResponse, items); - } -} diff --git a/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java b/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java new file mode 100644 index 0000000000..54cdcd220d --- /dev/null +++ b/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java @@ -0,0 +1,138 @@ +package org.kohsuke.github; + +import java.util.*; + +import javax.annotation.Nonnull; + +/** + * This class is not thread-safe. Any one instance should only be called from a single thread. + */ +class GitHubPageItemIterator<Page extends GitHubPage<Item>, Item> implements Iterator<Item> { + + /** + * Current batch of items. Each time {@link #next()} is called the next item in this array will be returned. After + * the last item of the array is returned, when {@link #next()} is called again, a new page of items will be fetched + * and iterating will continue from the first item in the new page. + * + * @see #fetchNext() {@link #fetchNext()} for details on how this field is used. + */ + private Page currentPage; + + /** + * The index of the next item on the page, the item that will be returned when {@link #next()} is called. + * + * @see #fetchNext() {@link #fetchNext()} for details on how this field is used. + */ + private int nextItemIndex; + + private final GitHubPageIterator<Page, Item> pageIterator; + + GitHubPageItemIterator(GitHubPageIterator<Page, Item> pageIterator) { + this.pageIterator = pageIterator; + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return peek() != null; + } + + /** + * {@inheritDoc} + */ + public Item next() { + Item result = peek(); + if (result == null) + throw new NoSuchElementException(); + nextItemIndex++; + return result; + } + + /** + * Gets the next page worth of data. + * + * @return the list + */ + public List<Item> nextPage() { + return Arrays.asList(nextPageArray()); + } + + /** + * + * @return + */ + public Item peek() { + Item result = lookupItem(); + if (result == null && pageIterator.hasNext()) { + result = fetchNext(); + } + return result; + } + + /** + * Fetch is called at the start of {@link #next()} or {@link #hasNext()} to fetch another page of data if it is + * needed and available. + * <p> + * If there is no current page yet (at the start of iterating), a page is fetched. If {@link #nextItemIndex} points + * to an item in the current page array, the state is valid - no more work is needed. If {@link #nextItemIndex} is + * greater than the last index in the current page array, the method checks if there is another page of data + * available. + * </p> + * <p> + * If there is another page, get that page of data and reset the check {@link #nextItemIndex} to the start of the + * new page. + * </p> + * <p> + * If no more pages are available, leave the page and index unchanged. In this case, {@link #hasNext()} will return + * {@code false} and {@link #next()} will throw an exception. + * </p> + */ + private Item fetchNext() { + // On first call, always get next page (may be empty array) + currentPage = Objects.requireNonNull(pageIterator.next()); + nextItemIndex = 0; + return lookupItem(); + } + + private Item lookupItem() { + return currentPage != null && currentPage.getItems().length > nextItemIndex + ? currentPage.getItems()[nextItemIndex] + : null; + } + + /** + * Gets the next page worth of data. + * + * @return the list + */ + protected Page currentPage() { + peek(); + return currentPage; + } + + /** + * Gets the next page worth of data. + * + * @return the list + */ + @Nonnull + Item[] nextPageArray() { + // if we have not fetched any pages yet, always fetch. + // If we have fetched at least one page, check hasNext() + if (currentPage == null) { + peek(); + } else if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Current should never be null after fetch + Objects.requireNonNull(currentPage); + Item[] r = currentPage.getItems(); + if (nextItemIndex != 0) { + r = Arrays.copyOfRange(r, nextItemIndex, r.length); + } + nextItemIndex = currentPage.getItems().length; + return r; + } +} diff --git a/src/main/java/org/kohsuke/github/GitHubPageIterator.java b/src/main/java/org/kohsuke/github/GitHubPageIterator.java index 4a831bf3f8..cabe9a1d1b 100644 --- a/src/main/java/org/kohsuke/github/GitHubPageIterator.java +++ b/src/main/java/org/kohsuke/github/GitHubPageIterator.java @@ -1,63 +1,33 @@ package org.kohsuke.github; -import java.io.IOException; -import java.net.URL; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.function.Consumer; import javax.annotation.Nonnull; -// TODO: Auto-generated Javadoc /** - * May be used for any item that has pagination information. Iterates over paginated {@code T} objects (not the items - * inside the page). Also exposes {@link #finalResponse()} to allow getting a full {@link GitHubResponse}{@code <T>} - * after iterating completes. + * May be used for any item that has pagination information. Iterates over paginated {@code P} objects (not the items + * inside the page). Also exposes {@link #finalResponse()} to allow getting a full {@link GitHubResponse} {@code + * +<P> + * } after iterating completes. * * Works for array responses, also works for search results which are single instances with an array of items inside. * * This class is not thread-safe. Any one instance should only be called from a single thread. * * @author Liam Newman - * @param <T> + * @param <P> * type of each page (not the items in the page). */ -class GitHubPageIterator<T> implements Iterator<T> { - - /** - * Loads paginated resources. - * - * @param <T> - * type of each page (not the items in the page). - * @param client - * the {@link GitHubClient} from which to request responses - * @param type - * type of each page (not the items in the page). - * @param request - * the request - * @param pageSize - * the page size - * @return iterator - */ - static <T> GitHubPageIterator<T> create(GitHubClient client, Class<T> type, GitHubRequest request, int pageSize) { - - if (pageSize > 0) { - GitHubRequest.Builder<?> builder = request.toBuilder().with("per_page", pageSize); - request = builder.build(); - } +class GitHubPageIterator<P extends GitHubPage<Item>, Item> implements Iterator<P> { - if (!"GET".equals(request.method())) { - throw new IllegalArgumentException("Request method \"GET\" is required for page iterator."); - } - - return new GitHubPageIterator<>(client, type, request); + static <P extends GitHubPage<Item>, Item> GitHubPageIterator<P, Item> ofSingleton(final P page) { + return new GitHubPageIterator<>(page); } - private final GitHubClient client; - /** - * When done iterating over pages, it is on rare occasions useful to be able to get information from the final - * response that was retrieved. - */ - private GitHubResponse<T> finalResponse = null; + private final Consumer<Item> itemInitializer; /** * The page that will be returned when {@link #next()} is called. @@ -66,23 +36,20 @@ static <T> GitHubPageIterator<T> create(GitHubClient client, Class<T> type, GitH * Will be {@code null} after {@link #next()} is called. * </p> * <p> - * Will not be {@code null} after {@link #fetch()} is called if a new page was fetched. + * Will not be {@code null} after {@link #fetchNext()} is called if a new page was fetched. * </p> */ - private T next; + protected P next; + protected final Class<P> pageType; - /** - * The request that will be sent when to get a new response page if {@link #next} is {@code null}. Will be - * {@code null} when there are no more pages to fetch. - */ - private GitHubRequest nextRequest; - - private final Class<T> type; + private GitHubPageIterator(P page) { + this((Class<P>) page.getClass(), null); + this.next = page; + } - private GitHubPageIterator(GitHubClient client, Class<T> type, GitHubRequest request) { - this.client = client; - this.type = type; - this.nextRequest = request; + protected GitHubPageIterator(Class<P> pageType, Consumer<Item> itemInitializer) { + this.pageType = pageType; + this.itemInitializer = itemInitializer; } /** @@ -90,91 +57,62 @@ private GitHubPageIterator(GitHubClient client, Class<T> type, GitHubRequest req * * @return the final response of the iterator. */ - public GitHubResponse<T> finalResponse() { - if (hasNext()) { - throw new GHException("Final response is not available until after iterator is done."); - } - return finalResponse; + public GitHubResponse<P> finalResponse() { + return null; } /** * {@inheritDoc} */ public boolean hasNext() { - fetch(); - return next != null; + return peek() != null; } /** - * Gets the next page. - * - * @return the next page. + * {@inheritDoc} */ @Nonnull - public T next() { - fetch(); - T result = next; + public P next() { + P result = peek(); if (result == null) throw new NoSuchElementException(); - // If this is the last page, keep the response next = null; return result; } /** - * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is - * needed. - * <p> - * If {@link #next} is not {@code null}, no further action is needed. If {@link #next} is {@code null} and - * {@link #nextRequest} is {@code null}, there are no more pages to fetch. - * </p> - * <p> - * Otherwise, a new response page is fetched using {@link #nextRequest}. The response is then checked to see if - * there is a page after it and {@link #nextRequest} is updated to point to it. If there are no pages available - * after the current response, {@link #nextRequest} is set to {@code null}. - * </p> + * + * @return */ - private void fetch() { - if (next != null) - return; // already fetched - if (nextRequest == null) - return; // no more data to fetch - - URL url = nextRequest.url(); - try { - GitHubResponse<T> nextResponse = client.sendRequest(nextRequest, - (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, type)); - assert nextResponse.body() != null; - next = nextResponse.body(); - nextRequest = findNextURL(nextRequest, nextResponse); - if (nextRequest == null) { - finalResponse = nextResponse; + public P peek() { + if (next == null) { + P result = fetchNext(); + if (result != null) { + next = result; + initializeItems(); } - } catch (IOException e) { - // Iterators do not throw IOExceptions, so we wrap any IOException - // in a runtime GHException to bubble out if needed. - throw new GHException("Failed to retrieve " + url, e); } + return next; } /** - * Locate the next page from the pagination "Link" tag. + * This method initializes items with local data after they are fetched. It is up to the implementer to decide what + * local data to apply. + * */ - private GitHubRequest findNextURL(GitHubRequest nextRequest, GitHubResponse<T> nextResponse) { - GitHubRequest result = null; - String link = nextResponse.header("Link"); - if (link != null) { - for (String token : link.split(", ")) { - if (token.endsWith("rel=\"next\"")) { - // found the next page. This should look something like - // <https://api.github.com/repos?page=3&per_page=100>; rel="next" - int idx = token.indexOf('>'); - result = nextRequest.toBuilder().setRawUrlPath(token.substring(1, idx)).build(); - break; - } + private void initializeItems() { + if (itemInitializer != null) { + for (Item item : next.getItems()) { + itemInitializer.accept(item); } } - return result; } + /** + * This method is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it + * is needed. + */ + protected P fetchNext() { + return null; + } } diff --git a/src/main/java/org/kohsuke/github/GitHubResponse.java b/src/main/java/org/kohsuke/github/GitHubResponse.java index 8ac65391f7..180f04998d 100644 --- a/src/main/java/org/kohsuke/github/GitHubResponse.java +++ b/src/main/java/org/kohsuke/github/GitHubResponse.java @@ -162,7 +162,7 @@ static <T> T parseBody(GitHubConnectorResponse connectorResponse, T instance) th * @param body * the body */ - GitHubResponse(GitHubResponse<T> response, @CheckForNull T body) { + GitHubResponse(GitHubResponse<?> response, @CheckForNull T body) { this.statusCode = response.statusCode(); this.headers = response.headers; this.body = body; diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index a916af8009..e598832361 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -1,11 +1,6 @@ package org.kohsuke.github; import java.io.IOException; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -20,146 +15,47 @@ * @param <T> * the type of items on each page */ -public abstract class PagedIterable<T> implements Iterable<T> { - /** - * Page size. 0 is default. - */ - private int pageSize = 0; +public class PagedIterable<T> implements Iterable<T> { + + private final GitHubEndpointIterable<?, T> paginatedEndpoint; /** - * Instantiate a PagedIterable. + * Instantiates a new git hub page contents iterable. */ - public PagedIterable() { + PagedIterable(GitHubEndpointIterable<?, T> paginatedEndpoint) { + this.paginatedEndpoint = paginatedEndpoint; } - /** - * Iterator over page items. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - public abstract PagedIterator<T> _iterator(int pageSize); + public PagedIterator<T> _iterator(int pageSize) { + throw new RuntimeException("No longer used."); + } - /** - * Returns an iterator over elements of type {@code T}. - * - * @return an Iterator. - */ @Nonnull public final PagedIterator<T> iterator() { - return _iterator(pageSize); + return new PagedIterator<>(paginatedEndpoint.itemIterator()); } - /** - * Eagerly walk {@link Iterable} and return the result in an array. - * - * @return the list - * @throws IOException - * if an I/O exception occurs. - */ @Nonnull public T[] toArray() throws IOException { - return toArray(iterator()); + return paginatedEndpoint.toArray(); } - /** - * Eagerly walk {@link Iterable} and return the result in a list. - * - * @return the list - * @throws IOException - * if an I/O Exception occurs - */ @Nonnull public List<T> toList() throws IOException { - return Collections.unmodifiableList(Arrays.asList(this.toArray())); + return paginatedEndpoint.toList(); } - /** - * Eagerly walk {@link Iterable} and return the result in a set. - * - * @return the set - * @throws IOException - * if an I/O Exception occurs - */ @Nonnull public Set<T> toSet() throws IOException { - return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(this.toArray()))); + return paginatedEndpoint.toSet(); } - /** - * Sets the pagination size. - * - * <p> - * When set to non-zero, each API call will retrieve this many entries. - * - * @param size - * the size - * @return the paged iterable - */ - public PagedIterable<T> withPageSize(int size) { - this.pageSize = size; + public PagedIterable<T> withPageSize(int i) { + paginatedEndpoint.withPageSize(i); return this; } - /** - * Concatenates a list of arrays into a single array. - * - * @param type - * the type of array to be returned. - * @param pages - * the list of arrays to be concatenated. - * @param totalLength - * the total length of the returned array. - * @return an array containing all elements from all pages. - */ - @Nonnull - private T[] concatenatePages(Class<T[]> type, List<T[]> pages, int totalLength) { - - T[] result = type.cast(Array.newInstance(type.getComponentType(), totalLength)); - - int position = 0; - for (T[] page : pages) { - final int pageLength = Array.getLength(page); - System.arraycopy(page, 0, result, position, pageLength); - position += pageLength; - } - return result; - } - - /** - * Eagerly walk {@link PagedIterator} and return the result in an array. - * - * @param iterator - * the {@link PagedIterator} to read - * @return an array of all elements from the {@link PagedIterator} - * @throws IOException - * if an I/O exception occurs. - */ - protected T[] toArray(final PagedIterator<T> iterator) throws IOException { - try { - ArrayList<T[]> pages = new ArrayList<>(); - int totalSize = 0; - T[] item; - do { - item = iterator.nextPageArray(); - totalSize += Array.getLength(item); - pages.add(item); - } while (iterator.hasNext()); - - Class<T[]> type = (Class<T[]>) item.getClass(); - - return concatenatePages(type, pages, totalSize); - } catch (GHException e) { - // if there was an exception inside the iterator it is wrapped as a GHException - // if the wrapped exception is an IOException, throw that - if (e.getCause() instanceof IOException) { - throw (IOException) e.getCause(); - } else { - throw e; - } - } + GitHubResponse<T[]> toResponse() throws IOException { + return paginatedEndpoint.toResponse(); } - } diff --git a/src/main/java/org/kohsuke/github/PagedIterator.java b/src/main/java/org/kohsuke/github/PagedIterator.java index ac6e54e826..d4c334692d 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -1,14 +1,7 @@ package org.kohsuke.github; -import java.util.Arrays; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.function.Consumer; - -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; // TODO: Auto-generated Javadoc /** @@ -26,133 +19,34 @@ */ public class PagedIterator<T> implements Iterator<T> { - /** - * Current batch of items. Each time {@link #next()} is called the next item in this array will be returned. After - * the last item of the array is returned, when {@link #next()} is called again, a new page of items will be fetched - * and iterating will continue from the first item in the new page. - * - * @see #fetch() {@link #fetch()} for details on how this field is used. - */ - private T[] currentPage; - - @CheckForNull - private final Consumer<T> itemInitializer; - - /** - * The index of the next item on the page, the item that will be returned when {@link #next()} is called. - * - * @see #fetch() {@link #fetch()} for details on how this field is used. - */ - private int nextItemIndex; - - /** The base. */ - @Nonnull - protected final Iterator<T[]> base; + private final GitHubPageItemIterator<?, T> endpointIterator; /** * Instantiates a new paged iterator. * - * @param base + * @param endpointIterator * the base - * @param itemInitializer - * the item initializer */ - PagedIterator(@Nonnull Iterator<T[]> base, @CheckForNull Consumer<T> itemInitializer) { - this.base = base; - this.itemInitializer = itemInitializer; + PagedIterator(GitHubPageItemIterator<?, T> endpointIterator) { + this.endpointIterator = endpointIterator; } - /** - * {@inheritDoc} - */ public boolean hasNext() { - fetch(); - return (currentPage != null && currentPage.length > nextItemIndex); + return endpointIterator.hasNext(); } - /** - * {@inheritDoc} - */ public T next() { - if (!hasNext()) - throw new NoSuchElementException(); - return currentPage[nextItemIndex++]; + return endpointIterator.next(); } /** - * Gets the next page worth of data. + * Get the next page of items. * - * @return the list + * @return a list of the next page of items. + * @deprecated use PagedIterable.pageIterator(). */ + @Deprecated public List<T> nextPage() { - return Arrays.asList(nextPageArray()); - } - - /** - * Fetch is called at the start of {@link #next()} or {@link #hasNext()} to fetch another page of data if it is - * needed and available. - * <p> - * If there is no current page yet (at the start of iterating), a page is fetched. If {@link #nextItemIndex} points - * to an item in the current page array, the state is valid - no more work is needed. If {@link #nextItemIndex} is - * greater than the last index in the current page array, the method checks if there is another page of data - * available. - * </p> - * <p> - * If there is another page, get that page of data and reset the check {@link #nextItemIndex} to the start of the - * new page. - * </p> - * <p> - * If no more pages are available, leave the page and index unchanged. In this case, {@link #hasNext()} will return - * {@code false} and {@link #next()} will throw an exception. - * </p> - */ - private void fetch() { - if ((currentPage == null || currentPage.length <= nextItemIndex) && base.hasNext()) { - // On first call, always get next page (may be empty array) - T[] result = Objects.requireNonNull(base.next()); - wrapUp(result); - currentPage = result; - nextItemIndex = 0; - } - } - - /** - * This poorly named method, initializes items with local data after they are fetched. It is up to the implementer - * to decide what local data to apply. - * - * @param page - * the page of items to be initialized - */ - protected void wrapUp(T[] page) { - if (itemInitializer != null) { - for (T item : page) { - itemInitializer.accept(item); - } - } - } - - /** - * Gets the next page worth of data. - * - * @return the list - */ - @Nonnull - T[] nextPageArray() { - // if we have not fetched any pages yet, always fetch. - // If we have fetched at least one page, check hasNext() - if (currentPage == null) { - fetch(); - } else if (!hasNext()) { - throw new NoSuchElementException(); - } - - // Current should never be null after fetch - Objects.requireNonNull(currentPage); - T[] r = currentPage; - if (nextItemIndex != 0) { - r = Arrays.copyOfRange(r, nextItemIndex, r.length); - } - nextItemIndex = currentPage.length; - return r; + return endpointIterator.nextPage(); } } diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 8c6d00a26d..29c62e818c 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -2,10 +2,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.Iterator; - -import javax.annotation.Nonnull; - // TODO: Auto-generated Javadoc /** * {@link PagedIterable} enhanced to report search result specific information. @@ -19,46 +15,15 @@ "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, justification = "Constructed by JSON API") public class PagedSearchIterable<T> extends PagedIterable<T> { - private final Class<? extends SearchResult<T>> receiverType; - private final GitHubRequest request; + private final GitHubEndpointIterable<? extends SearchResult<T>, T> paginatedEndpoint; /** - * As soon as we have any result fetched, it's set here so that we can report the total count. - */ - private SearchResult<T> result; - - private final transient GitHub root; - - /** - * Instantiates a new paged search iterable. - * - * @param root - * the root - * @param request - * the request - * @param receiverType - * the receiver type + * Instantiates a new git hub page contents iterable. */ - PagedSearchIterable(GitHub root, GitHubRequest request, Class<? extends SearchResult<T>> receiverType) { - this.root = root; - this.request = request; - this.receiverType = receiverType; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator<T> _iterator(int pageSize) { - final Iterator<T[]> adapter = adapt( - GitHubPageIterator.create(root.getClient(), receiverType, request, pageSize)); - return new PagedIterator<T>(adapter, null); + <Result extends SearchResult<T>> PagedSearchIterable(GitHubEndpointIterable<Result, T> paginatedEndpoint) { + super(paginatedEndpoint); + this.paginatedEndpoint = paginatedEndpoint; } /** @@ -67,8 +32,8 @@ public PagedIterator<T> _iterator(int pageSize) { * @return the total count */ public int getTotalCount() { - populate(); - return result.totalCount; + // populate(); + return paginatedEndpoint.itemIterator().currentPage().totalCount; } /** @@ -77,46 +42,7 @@ public int getTotalCount() { * @return the boolean */ public boolean isIncomplete() { - populate(); - return result.incompleteResults; - } - - /** - * With page size. - * - * @param size - * the size - * @return the paged search iterable - */ - @Override - public PagedSearchIterable<T> withPageSize(int size) { - return (PagedSearchIterable<T>) super.withPageSize(size); - } - - private void populate() { - if (result == null) - iterator().hasNext(); - } - - /** - * Adapts {@link Iterator}. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator<T[]> adapt(final Iterator<? extends SearchResult<T>> base) { - return new Iterator<T[]>() { - public boolean hasNext() { - return base.hasNext(); - } - - public T[] next() { - SearchResult<T> v = base.next(); - if (result == null) - result = v; - return v.getItems(root); - } - }; + // populate(); + return paginatedEndpoint.itemIterator().currentPage().incompleteResults; } } diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 95f0366ebd..e6fd73168d 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -189,16 +189,42 @@ public void sendGraphQL() throws IOException { * or {@link Iterator#hasNext()} are called. * </p> * + * @param <P> + * the page type for the pages returned from * @param <R> * the element type for the pages returned from - * @param type + * @param pageType * the type of the pages to retrieve. * @param itemInitializer * the consumer to execute on each paged item retrieved. * @return the {@link PagedIterable} for this builder. */ - public <R> PagedIterable<R> toIterable(Class<R[]> type, Consumer<R> itemInitializer) { - return new GitHubPageContentsIterable<>(client, build(), type, itemInitializer); + public <P extends GitHubPage<R>, R> PagedIterable<R> toIterable(Class<P> pageType, + Class<R> itemType, + Consumer<R> itemInitializer) { + GitHubRequest request = build(); + return new PagedIterable<>(new GitHubEndpointIterable<>(client, request, pageType, itemType, itemInitializer)); + } + /** + * Creates {@link PagedIterable <R>} from this builder using the provided {@link Consumer}{@code <R>}. + * <p> + * This method and the {@link PagedIterable <R>} do not actually begin fetching data until {@link Iterator#next()} + * or {@link Iterator#hasNext()} are called. + * </p> + * + * @param <R> + * the element type for the pages returned from + * @param receiverType + * the type of the array to retrieve. + * @param itemInitializer + * the consumer to execute on each paged item retrieved. + * @return the {@link PagedIterable} for this builder. + */ + public <R> PagedIterable<R> toIterable(Class<R[]> receiverType, Consumer<R> itemInitializer) { + GitHubRequest request = build(); + return new PagedIterable<>( + GitHubEndpointIterable.ofArrayEndpoint(client, request, receiverType, itemInitializer)); } + } diff --git a/src/main/java/org/kohsuke/github/SearchResult.java b/src/main/java/org/kohsuke/github/SearchResult.java index fe7e350439..2707b86753 100644 --- a/src/main/java/org/kohsuke/github/SearchResult.java +++ b/src/main/java/org/kohsuke/github/SearchResult.java @@ -8,7 +8,7 @@ * @param <T> * the generic type */ -abstract class SearchResult<T> { +abstract class SearchResult<T> implements GitHubPage<T> { /** The incomplete results. */ boolean incompleteResults; @@ -16,6 +16,10 @@ abstract class SearchResult<T> { /** The total count. */ int totalCount; + public T[] getItems() { + return getItems(null); + } + /** * Wraps up the retrieved object and return them. Only called once. * diff --git a/src/test/resources/no-reflect-and-serialization-list b/src/test/resources/no-reflect-and-serialization-list index 4ad893272c..86aae594b3 100644 --- a/src/test/resources/no-reflect-and-serialization-list +++ b/src/test/resources/no-reflect-and-serialization-list @@ -25,7 +25,7 @@ org.kohsuke.github.GitHubClient$BodyHandler org.kohsuke.github.GitHubClient$GHApiInfo org.kohsuke.github.GitHubClient$RetryRequestException org.kohsuke.github.GitHubConnectorResponseErrorHandler -org.kohsuke.github.GitHubPageIterator +org.kohsuke.github.GitHubEndpointPageIterator org.kohsuke.github.GitHubRateLimitChecker org.kohsuke.github.GitHubRateLimitHandler org.kohsuke.github.GitHubRateLimitHandler$1 From 78d1a9ed0079f184c3256d9b8751237a0212a05d Mon Sep 17 00:00:00 2001 From: Liam Newman <bitwiseman@gmail.com> Date: Sat, 3 May 2025 23:29:30 -0700 Subject: [PATCH 2/4] Flatten tree --- .../github/GitHubEndpointIterable.java | 10 +- .../github/GitHubEndpointPageIterator.java | 83 +++++++++++- .../github/GitHubPageItemIterator.java | 57 +++------ .../kohsuke/github/GitHubPageIterator.java | 118 ------------------ .../kohsuke/github/PagedSearchIterable.java | 4 +- 5 files changed, 103 insertions(+), 169 deletions(-) delete mode 100644 src/main/java/org/kohsuke/github/GitHubPageIterator.java diff --git a/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java b/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java index 804a497a1d..6f0676d0c5 100644 --- a/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java +++ b/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java @@ -107,8 +107,8 @@ static <P extends GitHubPage<I>, I> GitHubEndpointIterable<P, I> ofSingleton(P p return new GitHubEndpointIterable<>(null, null, (Class<P>) page.getClass(), itemType, null) { @Nonnull @Override - public GitHubPageIterator<P, I> pageIterator() { - return GitHubPageIterator.ofSingleton(page); + public GitHubEndpointPageIterator<P, I> pageIterator() { + return GitHubEndpointPageIterator.ofSingleton(page); } }; } @@ -165,7 +165,7 @@ public final Iterator<Item> iterator() { * @return */ @Nonnull - public GitHubPageIterator<Page, Item> pageIterator() { + public GitHubEndpointPageIterator<Page, Item> pageIterator() { return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer); } @@ -251,7 +251,7 @@ private Item[] concatenatePages(List<Item[]> pages, int totalLength) { * @throws IOException * if an I/O exception occurs. */ - private Item[] toArray(final GitHubPageIterator<Page, Item> iterator, Class<Item> itemType) throws IOException { + private Item[] toArray(final GitHubEndpointPageIterator<Page, Item> iterator, Class<Item> itemType) throws IOException { try { ArrayList<Item[]> pages = new ArrayList<>(); int totalSize = 0; @@ -284,7 +284,7 @@ private Item[] toArray(final GitHubPageIterator<Page, Item> iterator, Class<Item */ @Nonnull final GitHubResponse<Item[]> toResponse() throws IOException { - GitHubPageIterator<Page, Item> iterator = pageIterator(); + GitHubEndpointPageIterator<Page, Item> iterator = pageIterator(); Item[] items = toArray(iterator, itemType); GitHubResponse<Page> lastResponse = iterator.finalResponse(); return new GitHubResponse<>(lastResponse, items); diff --git a/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java b/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java index 9440024b77..5f50835e6e 100644 --- a/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java +++ b/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java @@ -2,8 +2,10 @@ import org.jetbrains.annotations.NotNull; +import javax.annotation.Nonnull; import java.io.IOException; import java.net.URL; +import java.util.NoSuchElementException; import java.util.function.Consumer; /** @@ -21,8 +23,21 @@ * @param <P> * type of each page (not the items in the page). */ -class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> extends GitHubPageIterator<P, Item> { +class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> implements java.util.Iterator<P> { + protected final Class<P> pageType; + private final Consumer<Item> itemInitializer; + /** + * The page that will be returned when {@link #next()} is called. + * + * <p> + * Will be {@code null} after {@link #next()} is called. + * </p> + * <p> + * Will not be {@code null} after {@link #fetchNext()} is called if a new page was fetched. + * </p> + */ + private P next; /** * When done iterating over pages, it is on rare occasions useful to be able to get information from the final * response that was retrieved. @@ -37,12 +52,18 @@ class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> extends GitHu */ protected GitHubRequest nextRequest; + private GitHubEndpointPageIterator(P page) { + this(null, (Class<P>)page.getClass(), null, 0, null); + this.next = page; + } + GitHubEndpointPageIterator(GitHubClient client, Class<P> pageType, GitHubRequest request, int pageSize, Consumer<Item> itemInitializer) { - super(pageType, itemInitializer); + this.pageType = pageType; + this.itemInitializer = itemInitializer; if (pageSize > 0) { GitHubRequest.Builder<?> builder = request.toBuilder().with("per_page", pageSize); @@ -57,6 +78,10 @@ class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> extends GitHu this.nextRequest = request; } + static <P extends GitHubPage<Item>, Item> GitHubEndpointPageIterator<P, Item> ofSingleton(final P page) { + return new GitHubEndpointPageIterator<>(page); + } + /** * On rare occasions the final response from iterating is needed. * @@ -97,7 +122,8 @@ private void updateNextRequest(GitHubResponse<P> nextResponse) { * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is * needed. * <p> - * If {@link #next} is not {@code null}, no further action is needed. If {@link #next} is {@code null} and + * If {@link #next} is not {@code null}, no further action is needed. + * If {@link #next} is {@code null} and * {@link #nextRequest} is {@code null}, there are no more pages to fetch. * </p> * <p> @@ -106,10 +132,9 @@ private void updateNextRequest(GitHubResponse<P> nextResponse) { * after the current response, {@link #nextRequest} is set to {@code null}. * </p> */ - @Override protected P fetchNext() { - if (next != null || nextRequest == null) - return null; // already fetched or no more data to fetch + if (nextRequest == null) + return null; // no more data to fetch P result; @@ -132,4 +157,50 @@ protected P fetchNext() { (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, pageType)); } + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return peek() != null; + } + + /** + * {@inheritDoc} + */ + @Nonnull + public P next() { + P result = peek(); + if (result == null) + throw new NoSuchElementException(); + next = null; + return result; + } + + /** + * + * @return + */ + public P peek() { + if (next == null) { + P result = fetchNext(); + if (result != null) { + next = result; + initializeItems(); + } + } + return next; + } + + /** + * This method initializes items with local data after they are fetched. It is up to the implementer to decide what + * local data to apply. + * + */ + private void initializeItems() { + if (itemInitializer != null) { + for (Item item : next.getItems()) { + itemInitializer.accept(item); + } + } + } } diff --git a/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java b/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java index 54cdcd220d..31869c3dde 100644 --- a/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java +++ b/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java @@ -25,9 +25,9 @@ class GitHubPageItemIterator<Page extends GitHubPage<Item>, Item> implements Ite */ private int nextItemIndex; - private final GitHubPageIterator<Page, Item> pageIterator; + private final GitHubEndpointPageIterator<Page, Item> pageIterator; - GitHubPageItemIterator(GitHubPageIterator<Page, Item> pageIterator) { + GitHubPageItemIterator(GitHubEndpointPageIterator<Page, Item> pageIterator) { this.pageIterator = pageIterator; } @@ -54,8 +54,24 @@ public Item next() { * * @return the list */ + @Deprecated public List<Item> nextPage() { - return Arrays.asList(nextPageArray()); + // if we have not fetched any pages yet, always fetch. + // If we have fetched at least one page, check hasNext() + if (currentPage == null) { + peek(); + } else if (!hasNext()) { + throw new NoSuchElementException(); + } + + // Current should never be null after fetch + Objects.requireNonNull(currentPage); + Item[] r = currentPage.getItems(); + if (nextItemIndex != 0) { + r = Arrays.copyOfRange(r, nextItemIndex, r.length); + } + nextItemIndex = currentPage.getItems().length; + return Arrays.asList(r); } /** @@ -100,39 +116,4 @@ private Item lookupItem() { ? currentPage.getItems()[nextItemIndex] : null; } - - /** - * Gets the next page worth of data. - * - * @return the list - */ - protected Page currentPage() { - peek(); - return currentPage; - } - - /** - * Gets the next page worth of data. - * - * @return the list - */ - @Nonnull - Item[] nextPageArray() { - // if we have not fetched any pages yet, always fetch. - // If we have fetched at least one page, check hasNext() - if (currentPage == null) { - peek(); - } else if (!hasNext()) { - throw new NoSuchElementException(); - } - - // Current should never be null after fetch - Objects.requireNonNull(currentPage); - Item[] r = currentPage.getItems(); - if (nextItemIndex != 0) { - r = Arrays.copyOfRange(r, nextItemIndex, r.length); - } - nextItemIndex = currentPage.getItems().length; - return r; - } } diff --git a/src/main/java/org/kohsuke/github/GitHubPageIterator.java b/src/main/java/org/kohsuke/github/GitHubPageIterator.java deleted file mode 100644 index cabe9a1d1b..0000000000 --- a/src/main/java/org/kohsuke/github/GitHubPageIterator.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.kohsuke.github; - -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.function.Consumer; - -import javax.annotation.Nonnull; - -/** - * May be used for any item that has pagination information. Iterates over paginated {@code P} objects (not the items - * inside the page). Also exposes {@link #finalResponse()} to allow getting a full {@link GitHubResponse} {@code - * -<P> - * } after iterating completes. - * - * Works for array responses, also works for search results which are single instances with an array of items inside. - * - * This class is not thread-safe. Any one instance should only be called from a single thread. - * - * @author Liam Newman - * @param <P> - * type of each page (not the items in the page). - */ -class GitHubPageIterator<P extends GitHubPage<Item>, Item> implements Iterator<P> { - - static <P extends GitHubPage<Item>, Item> GitHubPageIterator<P, Item> ofSingleton(final P page) { - return new GitHubPageIterator<>(page); - } - - private final Consumer<Item> itemInitializer; - - /** - * The page that will be returned when {@link #next()} is called. - * - * <p> - * Will be {@code null} after {@link #next()} is called. - * </p> - * <p> - * Will not be {@code null} after {@link #fetchNext()} is called if a new page was fetched. - * </p> - */ - protected P next; - protected final Class<P> pageType; - - private GitHubPageIterator(P page) { - this((Class<P>) page.getClass(), null); - this.next = page; - } - - protected GitHubPageIterator(Class<P> pageType, Consumer<Item> itemInitializer) { - this.pageType = pageType; - this.itemInitializer = itemInitializer; - } - - /** - * On rare occasions the final response from iterating is needed. - * - * @return the final response of the iterator. - */ - public GitHubResponse<P> finalResponse() { - return null; - } - - /** - * {@inheritDoc} - */ - public boolean hasNext() { - return peek() != null; - } - - /** - * {@inheritDoc} - */ - @Nonnull - public P next() { - P result = peek(); - if (result == null) - throw new NoSuchElementException(); - next = null; - return result; - } - - /** - * - * @return - */ - public P peek() { - if (next == null) { - P result = fetchNext(); - if (result != null) { - next = result; - initializeItems(); - } - } - return next; - } - - /** - * This method initializes items with local data after they are fetched. It is up to the implementer to decide what - * local data to apply. - * - */ - private void initializeItems() { - if (itemInitializer != null) { - for (Item item : next.getItems()) { - itemInitializer.accept(item); - } - } - } - - /** - * This method is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it - * is needed. - */ - protected P fetchNext() { - return null; - } -} diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 29c62e818c..3f7d5af54d 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -33,7 +33,7 @@ <Result extends SearchResult<T>> PagedSearchIterable(GitHubEndpointIterable<Resu */ public int getTotalCount() { // populate(); - return paginatedEndpoint.itemIterator().currentPage().totalCount; + return paginatedEndpoint.pageIterator().peek().totalCount; } /** @@ -43,6 +43,6 @@ public int getTotalCount() { */ public boolean isIncomplete() { // populate(); - return paginatedEndpoint.itemIterator().currentPage().incompleteResults; + return paginatedEndpoint.pageIterator().peek().incompleteResults; } } From 024cb8f935d1b7a190e72f6ab59b803fbf5db672 Mon Sep 17 00:00:00 2001 From: Liam Newman <bitwiseman@gmail.com> Date: Sun, 4 May 2025 00:25:53 -0700 Subject: [PATCH 3/4] Rename --- .../org/kohsuke/github/GHAppInstallation.java | 2 +- .../github/GHAppInstallationsIterable.java | 2 +- .../kohsuke/github/GHArtifactsIterable.java | 2 +- .../GHAuthenticatedAppInstallation.java | 2 +- .../kohsuke/github/GHCheckRunsIterable.java | 2 +- .../kohsuke/github/GHCommitFileIterable.java | 8 +- .../java/org/kohsuke/github/GHCompare.java | 2 +- .../github/GHExternalGroupIterable.java | 6 +- .../org/kohsuke/github/GHSearchBuilder.java | 2 +- .../github/GHWorkflowJobsIterable.java | 2 +- .../github/GHWorkflowRunsIterable.java | 2 +- .../kohsuke/github/GHWorkflowsIterable.java | 2 +- .../org/kohsuke/github/PagedIterable.java | 6 +- .../org/kohsuke/github/PagedIterator.java | 4 +- .../kohsuke/github/PagedSearchIterable.java | 8 +- ...ntIterable.java => PaginatedEndpoint.java} | 92 +++++-------- ...rator.java => PaginatedEndpointItems.java} | 8 +- ...rator.java => PaginatedEndpointPages.java} | 128 +++++++++--------- .../java/org/kohsuke/github/Requester.java | 5 +- 19 files changed, 128 insertions(+), 157 deletions(-) rename src/main/java/org/kohsuke/github/{GitHubEndpointIterable.java => PaginatedEndpoint.java} (66%) rename src/main/java/org/kohsuke/github/{GitHubPageItemIterator.java => PaginatedEndpointItems.java} (93%) rename src/main/java/org/kohsuke/github/{GitHubEndpointPageIterator.java => PaginatedEndpointPages.java} (92%) diff --git a/src/main/java/org/kohsuke/github/GHAppInstallation.java b/src/main/java/org/kohsuke/github/GHAppInstallation.java index ec343924e1..cbfbef2df0 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallation.java @@ -267,7 +267,7 @@ public PagedSearchIterable<GHRepository> listRepositories() { request = root().createRequest().withUrlPath("/installation/repositories").build(); - return new PagedSearchIterable<>(new GitHubEndpointIterable<>(root() + return new PagedSearchIterable<>(new PaginatedEndpoint<>(root() .getClient(), request, GHAppInstallationRepositoryResult.class, GHRepository.class, null)); } } diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java index d089e24e49..88e9281775 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java @@ -16,7 +16,7 @@ class GHAppInstallationsIterable extends PagedIterable<GHAppInstallation> { * the root */ public GHAppInstallationsIterable(GitHub root) { - super(new GitHubEndpointIterable<>(root.getClient(), + super(new PaginatedEndpoint<>(root.getClient(), root.createRequest().withUrlPath(APP_INSTALLATIONS_URL).build(), GHAppInstallationsPage.class, GHAppInstallation.class, diff --git a/src/main/java/org/kohsuke/github/GHArtifactsIterable.java b/src/main/java/org/kohsuke/github/GHArtifactsIterable.java index b16678ac28..bf33db5845 100644 --- a/src/main/java/org/kohsuke/github/GHArtifactsIterable.java +++ b/src/main/java/org/kohsuke/github/GHArtifactsIterable.java @@ -15,7 +15,7 @@ class GHArtifactsIterable extends PagedIterable<GHArtifact> { * the request builder */ public GHArtifactsIterable(GHRepository owner, GitHubRequest.Builder<?> requestBuilder) { - super(new GitHubEndpointIterable<>(owner.root().getClient(), + super(new PaginatedEndpoint<>(owner.root().getClient(), requestBuilder.build(), GHArtifactsPage.class, GHArtifact.class, diff --git a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java index 875b285287..0eaa1e52ea 100644 --- a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java @@ -39,7 +39,7 @@ public PagedSearchIterable<GHRepository> listRepositories() { request = root().createRequest().withUrlPath("/installation/repositories").build(); - return new PagedSearchIterable<>(new GitHubEndpointIterable<>(root() + return new PagedSearchIterable<>(new PaginatedEndpoint<>(root() .getClient(), request, GHAuthenticatedAppInstallationRepositoryResult.class, GHRepository.class, null)); } diff --git a/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java b/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java index 5f75eccc4e..6186b96b72 100644 --- a/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java +++ b/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java @@ -14,7 +14,7 @@ class GHCheckRunsIterable extends PagedIterable<GHCheckRun> { * the request */ public GHCheckRunsIterable(GHRepository owner, GitHubRequest request) { - super(new GitHubEndpointIterable<>(owner.root() + super(new PaginatedEndpoint<>(owner.root() .getClient(), request, GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(owner))); } } diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index 1d83abc4a1..9ec907010c 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -17,19 +17,19 @@ class GHCommitFileIterable extends PagedIterable<GHCommit.File> { */ private static final int GH_FILE_LIMIT_PER_COMMIT_PAGE = 300; - private static GitHubEndpointIterable<GHCommitFilesPage, File> createEndpointIterable(GHRepository owner, + private static PaginatedEndpoint<GHCommitFilesPage, File> createEndpointIterable(GHRepository owner, String sha, GHCommit.File[] files) { - GitHubEndpointIterable<GHCommitFilesPage, File> iterable; + PaginatedEndpoint<GHCommitFilesPage, File> iterable; if (files != null && files.length < GH_FILE_LIMIT_PER_COMMIT_PAGE) { // create a page iterator that only provides one page - iterable = GitHubEndpointIterable.ofSingleton(new GHCommitFilesPage(files)); + iterable = PaginatedEndpoint.ofSingleton(new GHCommitFilesPage(files)); } else { GitHubRequest request = owner.root() .createRequest() .withUrlPath(owner.getApiTailUrl("commits/" + sha)) .build(); - iterable = new GitHubEndpointIterable<>(owner.root() + iterable = new PaginatedEndpoint<>(owner.root() .getClient(), request, GHCommitFilesPage.class, GHCommit.File.class, null); } return iterable; diff --git a/src/main/java/org/kohsuke/github/GHCompare.java b/src/main/java/org/kohsuke/github/GHCompare.java index 52ccb8a922..d9993f54db 100644 --- a/src/main/java/org/kohsuke/github/GHCompare.java +++ b/src/main/java/org/kohsuke/github/GHCompare.java @@ -345,7 +345,7 @@ public PagedIterable<Commit> listCommits() { .withPageSize(10); } else { // if not using paginated commits, adapt the returned commits array - return new PagedIterable<>(GitHubEndpointIterable.ofSingleton(this.commits)); + return new PagedIterable<>(PaginatedEndpoint.ofSingleton(this.commits)); } } diff --git a/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java b/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java index a4a032bab7..850dbdff4e 100644 --- a/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java +++ b/src/main/java/org/kohsuke/github/GHExternalGroupIterable.java @@ -18,14 +18,14 @@ class GHExternalGroupIterable extends PagedIterable<GHExternalGroup> { * the request builder */ GHExternalGroupIterable(final GHOrganization owner, GitHubRequest.Builder<?> requestBuilder) { - super(new GitHubEndpointIterable<>(owner.root().getClient(), + super(new PaginatedEndpoint<>(owner.root().getClient(), requestBuilder.build(), GHExternalGroupPage.class, GHExternalGroup.class, item -> item.wrapUp(owner)) { @NotNull @Override - public GitHubEndpointPageIterator<GHExternalGroupPage, GHExternalGroup> pageIterator() { - return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer) { + public PaginatedEndpointPages<GHExternalGroupPage, GHExternalGroup> pages() { + return new PaginatedEndpointPages<>(client, pageType, request, pageSize, itemInitializer) { @Override public boolean hasNext() { try { diff --git a/src/main/java/org/kohsuke/github/GHSearchBuilder.java b/src/main/java/org/kohsuke/github/GHSearchBuilder.java index 3cabb7800c..78a5bda076 100644 --- a/src/main/java/org/kohsuke/github/GHSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHSearchBuilder.java @@ -77,7 +77,7 @@ PagedSearchIterable<T> list(Consumer<T> itemInitializer) { req.set("q", StringUtils.join(terms, " ")); return new PagedSearchIterable<>( - new GitHubEndpointIterable<>(root().getClient(), req.build(), receiverType, itemType, itemInitializer)); + new PaginatedEndpoint<>(root().getClient(), req.build(), receiverType, itemType, itemInitializer)); } /** diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java index 38fe89f524..704765f4cb 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java @@ -16,7 +16,7 @@ class GHWorkflowJobsIterable extends PagedIterable<GHWorkflowJob> { * the request */ public GHWorkflowJobsIterable(GHRepository repo, GitHubRequest request) { - super(new GitHubEndpointIterable<>(repo.root() + super(new PaginatedEndpoint<>(repo.root() .getClient(), request, GHWorkflowJobsPage.class, GHWorkflowJob.class, item -> item.wrapUp(repo))); } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java index 532d3e6097..f9bb2f2728 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java @@ -14,7 +14,7 @@ class GHWorkflowRunsIterable extends PagedIterable<GHWorkflowRun> { * the request builder */ public GHWorkflowRunsIterable(GHRepository owner, GitHubRequest.Builder<?> requestBuilder) { - super(new GitHubEndpointIterable<>(owner.root().getClient(), + super(new PaginatedEndpoint<>(owner.root().getClient(), requestBuilder.build(), GHWorkflowRunsPage.class, GHWorkflowRun.class, diff --git a/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java index 265d1700f5..034c0201f8 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java @@ -13,7 +13,7 @@ class GHWorkflowsIterable extends PagedIterable<GHWorkflow> { * the owner */ public GHWorkflowsIterable(GHRepository owner) { - super(new GitHubEndpointIterable<>(owner.root().getClient(), + super(new PaginatedEndpoint<>(owner.root().getClient(), owner.root().createRequest().withUrlPath(owner.getApiTailUrl("actions/workflows")).build(), GHWorkflowsPage.class, GHWorkflow.class, diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index e598832361..951cad5567 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -17,12 +17,12 @@ */ public class PagedIterable<T> implements Iterable<T> { - private final GitHubEndpointIterable<?, T> paginatedEndpoint; + private final PaginatedEndpoint<?, T> paginatedEndpoint; /** * Instantiates a new git hub page contents iterable. */ - PagedIterable(GitHubEndpointIterable<?, T> paginatedEndpoint) { + PagedIterable(PaginatedEndpoint<?, T> paginatedEndpoint) { this.paginatedEndpoint = paginatedEndpoint; } @@ -32,7 +32,7 @@ public PagedIterator<T> _iterator(int pageSize) { @Nonnull public final PagedIterator<T> iterator() { - return new PagedIterator<>(paginatedEndpoint.itemIterator()); + return new PagedIterator<>(paginatedEndpoint.items()); } @Nonnull diff --git a/src/main/java/org/kohsuke/github/PagedIterator.java b/src/main/java/org/kohsuke/github/PagedIterator.java index d4c334692d..a85bf7128d 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -19,7 +19,7 @@ */ public class PagedIterator<T> implements Iterator<T> { - private final GitHubPageItemIterator<?, T> endpointIterator; + private final PaginatedEndpointItems<?, T> endpointIterator; /** * Instantiates a new paged iterator. @@ -27,7 +27,7 @@ public class PagedIterator<T> implements Iterator<T> { * @param endpointIterator * the base */ - PagedIterator(GitHubPageItemIterator<?, T> endpointIterator) { + PagedIterator(PaginatedEndpointItems<?, T> endpointIterator) { this.endpointIterator = endpointIterator; } diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 3f7d5af54d..722d0c1bc8 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -16,12 +16,12 @@ justification = "Constructed by JSON API") public class PagedSearchIterable<T> extends PagedIterable<T> { - private final GitHubEndpointIterable<? extends SearchResult<T>, T> paginatedEndpoint; + private final PaginatedEndpoint<? extends SearchResult<T>, T> paginatedEndpoint; /** * Instantiates a new git hub page contents iterable. */ - <Result extends SearchResult<T>> PagedSearchIterable(GitHubEndpointIterable<Result, T> paginatedEndpoint) { + <Result extends SearchResult<T>> PagedSearchIterable(PaginatedEndpoint<Result, T> paginatedEndpoint) { super(paginatedEndpoint); this.paginatedEndpoint = paginatedEndpoint; } @@ -33,7 +33,7 @@ <Result extends SearchResult<T>> PagedSearchIterable(GitHubEndpointIterable<Resu */ public int getTotalCount() { // populate(); - return paginatedEndpoint.pageIterator().peek().totalCount; + return paginatedEndpoint.pages().peek().totalCount; } /** @@ -43,6 +43,6 @@ public int getTotalCount() { */ public boolean isIncomplete() { // populate(); - return paginatedEndpoint.pageIterator().peek().incompleteResults; + return paginatedEndpoint.pages().peek().incompleteResults; } } diff --git a/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java similarity index 66% rename from src/main/java/org/kohsuke/github/GitHubEndpointIterable.java rename to src/main/java/org/kohsuke/github/PaginatedEndpoint.java index 6f0676d0c5..9d69ae2fdf 100644 --- a/src/main/java/org/kohsuke/github/GitHubEndpointIterable.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java @@ -10,21 +10,21 @@ import javax.annotation.Nonnull; /** - * {@link GitHubEndpointIterable} implementation that take a {@link Consumer} that initializes all the items on each - * page as they are retrieved. + * {@link PaginatedEndpoint} implementation that take a {@link Consumer} that initializes all the items on each page as + * they are retrieved. * - * {@link GitHubEndpointIterable} is immutable and thread-safe, but the iterator returned from {@link #iterator()} is - * not. Any one instance of iterator should only be called from a single thread. + * {@link PaginatedEndpoint} is immutable and thread-safe, but the iterator returned from {@link #iterator()} is not. + * Any one instance of iterator should only be called from a single thread. * * @author Liam Newman * @param <Item> * the type of items on each page */ -class GitHubEndpointIterable<Page extends GitHubPage<Item>, Item> implements Iterable<Item> { +class PaginatedEndpoint<Page extends GitHubPage<Item>, Item> implements Iterable<Item> { - private static class ArrayIterable<I> extends GitHubEndpointIterable<GitHubPage<I>, I> { + private static class ArrayIterable<I> extends PaginatedEndpoint<GitHubPage<I>, I> { - private class ArrayIterator extends GitHubEndpointPageIterator<GitHubPage<I>, I> { + private class ArrayIterator extends PaginatedEndpointPages<GitHubPage<I>, I> { ArrayIterator(GitHubClient client, Class<GitHubPage<I>> pageType, @@ -58,7 +58,7 @@ private ArrayIterable(GitHubClient client, } @NotNull @Override - public GitHubEndpointPageIterator<GitHubPage<I>, I> pageIterator() { + public PaginatedEndpointPages<GitHubPage<I>, I> pages() { return new ArrayIterator(client, pageType, request, pageSize, itemInitializer); } } @@ -91,24 +91,24 @@ public I[] getItems() { } } - static <I> GitHubEndpointIterable<GitHubPage<I>, I> ofArrayEndpoint(GitHubClient client, + static <I> PaginatedEndpoint<GitHubPage<I>, I> ofArrayEndpoint(GitHubClient client, GitHubRequest request, Class<I[]> receiverType, Consumer<I> itemInitializer) { return new ArrayIterable<>(client, request, receiverType, itemInitializer); } - static <I> GitHubEndpointIterable<GitHubPage<I>, I> ofSingleton(I[] array) { + static <I> PaginatedEndpoint<GitHubPage<I>, I> ofSingleton(I[] array) { return ofSingleton(new GitHubArrayPage<>(array)); } - static <P extends GitHubPage<I>, I> GitHubEndpointIterable<P, I> ofSingleton(P page) { + static <P extends GitHubPage<I>, I> PaginatedEndpoint<P, I> ofSingleton(P page) { Class<I> itemType = (Class<I>) page.getItems().getClass().getComponentType(); - return new GitHubEndpointIterable<>(null, null, (Class<P>) page.getClass(), itemType, null) { + return new PaginatedEndpoint<>(null, null, (Class<P>) page.getClass(), itemType, null) { @Nonnull @Override - public GitHubEndpointPageIterator<P, I> pageIterator() { - return GitHubEndpointPageIterator.ofSingleton(page); + public PaginatedEndpointPages<P, I> pages() { + return PaginatedEndpointPages.ofSingleton(page); } }; } @@ -138,7 +138,7 @@ public GitHubEndpointPageIterator<P, I> pageIterator() { * @param itemInitializer * the item initializer */ - GitHubEndpointIterable(GitHubClient client, + PaginatedEndpoint(GitHubClient client, GitHubRequest request, Class<Page> pageType, Class<Item> itemType, @@ -151,13 +151,13 @@ public GitHubEndpointPageIterator<P, I> pageIterator() { } @Nonnull - public final GitHubPageItemIterator<Page, Item> itemIterator() { - return new GitHubPageItemIterator<>(this.pageIterator()); + public final PaginatedEndpointItems<Page, Item> items() { + return new PaginatedEndpointItems<>(this.pages()); } @Nonnull @Override public final Iterator<Item> iterator() { - return this.itemIterator(); + return this.items(); } /** @@ -165,8 +165,8 @@ public final Iterator<Item> iterator() { * @return */ @Nonnull - public GitHubEndpointPageIterator<Page, Item> pageIterator() { - return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer); + public PaginatedEndpointPages<Page, Item> pages() { + return new PaginatedEndpointPages<>(client, pageType, request, pageSize, itemInitializer); } /** @@ -178,7 +178,7 @@ public GitHubEndpointPageIterator<Page, Item> pageIterator() { */ @Nonnull public final Item[] toArray() throws IOException { - return toArray(pageIterator(), itemType); + return toList().toArray((Item[]) Array.newInstance(itemType, 0)); } /** @@ -190,7 +190,7 @@ public final Item[] toArray() throws IOException { */ @Nonnull public final List<Item> toList() throws IOException { - return Collections.unmodifiableList(Arrays.asList(this.toArray())); + return Collections.unmodifiableList(toList(pages(), itemType)); } /** @@ -202,7 +202,7 @@ public final List<Item> toList() throws IOException { */ @Nonnull public final Set<Item> toSet() throws IOException { - return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(this.toArray()))); + return Collections.unmodifiableSet(new LinkedHashSet<>(toList())); } /** @@ -215,33 +215,11 @@ public final Set<Item> toSet() throws IOException { * the size * @return the paged iterable */ - public final GitHubEndpointIterable<Page, Item> withPageSize(int size) { + public final PaginatedEndpoint<Page, Item> withPageSize(int size) { this.pageSize = size; return this; } - /** - * Concatenates a list of arrays into a single array. - * - * @param pages - * the list of arrays to be concatenated. - * @param totalLength - * the total length of the returned array. - * @return an array containing all elements from all pages. - */ - @Nonnull - private Item[] concatenatePages(List<Item[]> pages, int totalLength) { - Item[] result = (Item[]) Array.newInstance(itemType, totalLength); - - int position = 0; - for (Item[] page : pages) { - final int pageLength = Array.getLength(page); - System.arraycopy(page, 0, result, position, pageLength); - position += pageLength; - } - return result; - } - /** * Eagerly walk {@link PagedIterator} and return the result in an array. * @@ -251,18 +229,14 @@ private Item[] concatenatePages(List<Item[]> pages, int totalLength) { * @throws IOException * if an I/O exception occurs. */ - private Item[] toArray(final GitHubEndpointPageIterator<Page, Item> iterator, Class<Item> itemType) throws IOException { + private List<Item> toList(final PaginatedEndpointPages<Page, Item> iterator, Class<Item> itemType) + throws IOException { try { - ArrayList<Item[]> pages = new ArrayList<>(); - int totalSize = 0; - Item[] item; - while (iterator.hasNext()) { - item = iterator.next().getItems(); - totalSize += Array.getLength(item); - pages.add(item); - } - - return concatenatePages(pages, totalSize); + ArrayList<Item> pageList = new ArrayList<>(); + iterator.forEachRemaining(page -> { + pageList.addAll(Arrays.asList(page.getItems())); + }); + return pageList; } catch (GHException e) { // if there was an exception inside the iterator it is wrapped as a GHException // if the wrapped exception is an IOException, throw that @@ -284,8 +258,8 @@ private Item[] toArray(final GitHubEndpointPageIterator<Page, Item> iterator, Cl */ @Nonnull final GitHubResponse<Item[]> toResponse() throws IOException { - GitHubEndpointPageIterator<Page, Item> iterator = pageIterator(); - Item[] items = toArray(iterator, itemType); + PaginatedEndpointPages<Page, Item> iterator = pages(); + Item[] items = toArray(); GitHubResponse<Page> lastResponse = iterator.finalResponse(); return new GitHubResponse<>(lastResponse, items); } diff --git a/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java similarity index 93% rename from src/main/java/org/kohsuke/github/GitHubPageItemIterator.java rename to src/main/java/org/kohsuke/github/PaginatedEndpointItems.java index 31869c3dde..305a47be56 100644 --- a/src/main/java/org/kohsuke/github/GitHubPageItemIterator.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java @@ -2,12 +2,10 @@ import java.util.*; -import javax.annotation.Nonnull; - /** * This class is not thread-safe. Any one instance should only be called from a single thread. */ -class GitHubPageItemIterator<Page extends GitHubPage<Item>, Item> implements Iterator<Item> { +class PaginatedEndpointItems<Page extends GitHubPage<Item>, Item> implements Iterator<Item> { /** * Current batch of items. Each time {@link #next()} is called the next item in this array will be returned. After @@ -25,9 +23,9 @@ class GitHubPageItemIterator<Page extends GitHubPage<Item>, Item> implements Ite */ private int nextItemIndex; - private final GitHubEndpointPageIterator<Page, Item> pageIterator; + private final PaginatedEndpointPages<Page, Item> pageIterator; - GitHubPageItemIterator(GitHubEndpointPageIterator<Page, Item> pageIterator) { + PaginatedEndpointItems(PaginatedEndpointPages<Page, Item> pageIterator) { this.pageIterator = pageIterator; } diff --git a/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java similarity index 92% rename from src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java rename to src/main/java/org/kohsuke/github/PaginatedEndpointPages.java index 5f50835e6e..a075832a30 100644 --- a/src/main/java/org/kohsuke/github/GitHubEndpointPageIterator.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java @@ -2,12 +2,13 @@ import org.jetbrains.annotations.NotNull; -import javax.annotation.Nonnull; import java.io.IOException; import java.net.URL; import java.util.NoSuchElementException; import java.util.function.Consumer; +import javax.annotation.Nonnull; + /** * May be used for any item that has pagination information. Iterates over paginated {@code P} objects (not the items * inside the page). Also exposes {@link #finalResponse()} to allow getting a full {@link GitHubResponse}{@code @@ -23,9 +24,16 @@ * @param <P> * type of each page (not the items in the page). */ -class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> implements java.util.Iterator<P> { +class PaginatedEndpointPages<P extends GitHubPage<Item>, Item> implements java.util.Iterator<P> { - protected final Class<P> pageType; + static <P extends GitHubPage<Item>, Item> PaginatedEndpointPages<P, Item> ofSingleton(final P page) { + return new PaginatedEndpointPages<>(page); + } + /** + * When done iterating over pages, it is on rare occasions useful to be able to get information from the final + * response that was retrieved. + */ + private GitHubResponse<P> finalResponse = null; private final Consumer<Item> itemInitializer; /** * The page that will be returned when {@link #next()} is called. @@ -38,11 +46,6 @@ class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> implements ja * </p> */ private P next; - /** - * When done iterating over pages, it is on rare occasions useful to be able to get information from the final - * response that was retrieved. - */ - private GitHubResponse<P> finalResponse = null; protected final GitHubClient client; @@ -52,12 +55,14 @@ class GitHubEndpointPageIterator<P extends GitHubPage<Item>, Item> implements ja */ protected GitHubRequest nextRequest; - private GitHubEndpointPageIterator(P page) { - this(null, (Class<P>)page.getClass(), null, 0, null); + protected final Class<P> pageType; + + private PaginatedEndpointPages(P page) { + this(null, (Class<P>) page.getClass(), null, 0, null); this.next = page; } - GitHubEndpointPageIterator(GitHubClient client, + PaginatedEndpointPages(GitHubClient client, Class<P> pageType, GitHubRequest request, int pageSize, @@ -78,10 +83,6 @@ private GitHubEndpointPageIterator(P page) { this.nextRequest = request; } - static <P extends GitHubPage<Item>, Item> GitHubEndpointPageIterator<P, Item> ofSingleton(final P page) { - return new GitHubEndpointPageIterator<>(page); - } - /** * On rare occasions the final response from iterating is needed. * @@ -94,6 +95,53 @@ public GitHubResponse<P> finalResponse() { return finalResponse; } + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return peek() != null; + } + + /** + * {@inheritDoc} + */ + @Nonnull + public P next() { + P result = peek(); + if (result == null) + throw new NoSuchElementException(); + next = null; + return result; + } + + /** + * + * @return + */ + public P peek() { + if (next == null) { + P result = fetchNext(); + if (result != null) { + next = result; + initializeItems(); + } + } + return next; + } + + /** + * This method initializes items with local data after they are fetched. It is up to the implementer to decide what + * local data to apply. + * + */ + private void initializeItems() { + if (itemInitializer != null) { + for (Item item : next.getItems()) { + itemInitializer.accept(item); + } + } + } + /** * Locate the next page from the pagination "Link" tag. */ @@ -122,8 +170,7 @@ private void updateNextRequest(GitHubResponse<P> nextResponse) { * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is * needed. * <p> - * If {@link #next} is not {@code null}, no further action is needed. - * If {@link #next} is {@code null} and + * If {@link #next} is not {@code null}, no further action is needed. If {@link #next} is {@code null} and * {@link #nextRequest} is {@code null}, there are no more pages to fetch. * </p> * <p> @@ -156,51 +203,4 @@ protected P fetchNext() { return client.sendRequest(nextRequest, (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, pageType)); } - - /** - * {@inheritDoc} - */ - public boolean hasNext() { - return peek() != null; - } - - /** - * {@inheritDoc} - */ - @Nonnull - public P next() { - P result = peek(); - if (result == null) - throw new NoSuchElementException(); - next = null; - return result; - } - - /** - * - * @return - */ - public P peek() { - if (next == null) { - P result = fetchNext(); - if (result != null) { - next = result; - initializeItems(); - } - } - return next; - } - - /** - * This method initializes items with local data after they are fetched. It is up to the implementer to decide what - * local data to apply. - * - */ - private void initializeItems() { - if (itemInitializer != null) { - for (Item item : next.getItems()) { - itemInitializer.accept(item); - } - } - } } diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index e6fd73168d..2a3d80140b 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -203,7 +203,7 @@ public <P extends GitHubPage<R>, R> PagedIterable<R> toIterable(Class<P> pageTyp Class<R> itemType, Consumer<R> itemInitializer) { GitHubRequest request = build(); - return new PagedIterable<>(new GitHubEndpointIterable<>(client, request, pageType, itemType, itemInitializer)); + return new PagedIterable<>(new PaginatedEndpoint<>(client, request, pageType, itemType, itemInitializer)); } /** @@ -223,8 +223,7 @@ public <P extends GitHubPage<R>, R> PagedIterable<R> toIterable(Class<P> pageTyp */ public <R> PagedIterable<R> toIterable(Class<R[]> receiverType, Consumer<R> itemInitializer) { GitHubRequest request = build(); - return new PagedIterable<>( - GitHubEndpointIterable.ofArrayEndpoint(client, request, receiverType, itemInitializer)); + return new PagedIterable<>(PaginatedEndpoint.ofArrayEndpoint(client, request, receiverType, itemInitializer)); } } From 01f8dfacb6ed101cb185e9c5b682d3b94a1bca97 Mon Sep 17 00:00:00 2001 From: Liam Newman <bitwiseman@gmail.com> Date: Mon, 5 May 2025 09:33:55 -0700 Subject: [PATCH 4/4] Remove extraneous classes --- .../github/GHAppInstallationsIterable.java | 25 ------------------- .../kohsuke/github/GHArtifactsIterable.java | 24 ------------------ .../kohsuke/github/GHCheckRunsIterable.java | 20 --------------- .../java/org/kohsuke/github/GHMyself.java | 9 ++++++- .../java/org/kohsuke/github/GHRepository.java | 18 ++++++++++--- .../java/org/kohsuke/github/GHWorkflow.java | 6 ++++- .../github/GHWorkflowJobQueryBuilder.java | 3 ++- .../github/GHWorkflowJobsIterable.java | 22 ---------------- .../org/kohsuke/github/GHWorkflowRun.java | 6 ++++- .../github/GHWorkflowRunQueryBuilder.java | 6 ++++- .../github/GHWorkflowRunsIterable.java | 23 ----------------- .../kohsuke/github/GHWorkflowsIterable.java | 22 ---------------- 12 files changed, 39 insertions(+), 145 deletions(-) delete mode 100644 src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java delete mode 100644 src/main/java/org/kohsuke/github/GHArtifactsIterable.java delete mode 100644 src/main/java/org/kohsuke/github/GHCheckRunsIterable.java delete mode 100644 src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java delete mode 100644 src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java delete mode 100644 src/main/java/org/kohsuke/github/GHWorkflowsIterable.java diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java deleted file mode 100644 index 88e9281775..0000000000 --- a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.kohsuke.github; - -// TODO: Auto-generated Javadoc -/** - * Iterable for GHAppInstallation listing. - */ -class GHAppInstallationsIterable extends PagedIterable<GHAppInstallation> { - - /** The Constant APP_INSTALLATIONS_URL. */ - public static final String APP_INSTALLATIONS_URL = "/user/installations"; - - /** - * Instantiates a new GH app installations iterable. - * - * @param root - * the root - */ - public GHAppInstallationsIterable(GitHub root) { - super(new PaginatedEndpoint<>(root.getClient(), - root.createRequest().withUrlPath(APP_INSTALLATIONS_URL).build(), - GHAppInstallationsPage.class, - GHAppInstallation.class, - null)); - } -} diff --git a/src/main/java/org/kohsuke/github/GHArtifactsIterable.java b/src/main/java/org/kohsuke/github/GHArtifactsIterable.java deleted file mode 100644 index bf33db5845..0000000000 --- a/src/main/java/org/kohsuke/github/GHArtifactsIterable.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.kohsuke.github; - -// TODO: Auto-generated Javadoc -/** - * Iterable for artifacts listing. - */ -class GHArtifactsIterable extends PagedIterable<GHArtifact> { - - /** - * Instantiates a new GH artifacts iterable. - * - * @param owner - * the owner - * @param requestBuilder - * the request builder - */ - public GHArtifactsIterable(GHRepository owner, GitHubRequest.Builder<?> requestBuilder) { - super(new PaginatedEndpoint<>(owner.root().getClient(), - requestBuilder.build(), - GHArtifactsPage.class, - GHArtifact.class, - item -> item.wrapUp(owner))); - } -} diff --git a/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java b/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java deleted file mode 100644 index 6186b96b72..0000000000 --- a/src/main/java/org/kohsuke/github/GHCheckRunsIterable.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.kohsuke.github; - -// TODO: Auto-generated Javadoc -/** - * Iterable for check-runs listing. - */ -class GHCheckRunsIterable extends PagedIterable<GHCheckRun> { - /** - * Instantiates a new GH check runs iterable. - * - * @param owner - * the owner - * @param request - * the request - */ - public GHCheckRunsIterable(GHRepository owner, GitHubRequest request) { - super(new PaginatedEndpoint<>(owner.root() - .getClient(), request, GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(owner))); - } -} diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index 05e52cd5ac..ba0a8ae410 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -38,6 +38,9 @@ public enum RepositoryListFilter { PUBLIC; } + /** The Constant APP_INSTALLATIONS_URL. */ + public static final String APP_INSTALLATIONS_URL = "/user/installations"; + /** * Create default GHMyself instance */ @@ -110,7 +113,11 @@ public synchronized Map<String, GHRepository> getAllRepositories() { * app installations accessible to the user access token</a> */ public PagedIterable<GHAppInstallation> getAppInstallations() { - return new GHAppInstallationsIterable(root()); + return new PagedIterable<>(new PaginatedEndpoint<>(root().getClient(), + root().createRequest().withUrlPath(APP_INSTALLATIONS_URL).build(), + GHAppInstallationsPage.class, + GHAppInstallation.class, + null)); } /** diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 547a663ad8..f19a7bd7d3 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -1192,7 +1192,8 @@ public PagedIterable<GHCheckRun> getCheckRuns(String ref) { GitHubRequest request = root().createRequest() .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) .build(); - return new GHCheckRunsIterable(this, request); + return new PagedIterable<>(new PaginatedEndpoint<>(root() + .getClient(), request, GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(this))); } /** @@ -1211,7 +1212,8 @@ public PagedIterable<GHCheckRun> getCheckRuns(String ref, Map<String, Object> pa .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) .with(params) .build(); - return new GHCheckRunsIterable(this, request); + return new PagedIterable<>(new PaginatedEndpoint<>(root() + .getClient(), request, GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(this))); } /** @@ -2548,7 +2550,11 @@ public boolean isVulnerabilityAlertsEnabled() throws IOException { * @return the paged iterable */ public PagedIterable<GHArtifact> listArtifacts() { - return new GHArtifactsIterable(this, root().createRequest().withUrlPath(getApiTailUrl("actions/artifacts"))); + return new PagedIterable<>(new PaginatedEndpoint<>(this.root().getClient(), + root().createRequest().withUrlPath(getApiTailUrl("actions/artifacts")).build(), + GHArtifactsPage.class, + GHArtifact.class, + item -> item.wrapUp(this))); } /** @@ -2956,7 +2962,11 @@ public List<String> listTopics() throws IOException { * @return the paged iterable */ public PagedIterable<GHWorkflow> listWorkflows() { - return new GHWorkflowsIterable(this); + return new PagedIterable<>(new PaginatedEndpoint<>(root().getClient(), + root().createRequest().withUrlPath(getApiTailUrl("actions/workflows")).build(), + GHWorkflowsPage.class, + GHWorkflow.class, + item -> item.wrapUp(this))); } /** diff --git a/src/main/java/org/kohsuke/github/GHWorkflow.java b/src/main/java/org/kohsuke/github/GHWorkflow.java index dff9ffdc3d..2e99fcd826 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflow.java +++ b/src/main/java/org/kohsuke/github/GHWorkflow.java @@ -153,7 +153,11 @@ public String getState() { * @return the paged iterable */ public PagedIterable<GHWorkflowRun> listRuns() { - return new GHWorkflowRunsIterable(owner, root().createRequest().withUrlPath(getApiRoute(), "runs")); + return new PagedIterable<>(new PaginatedEndpoint<>(owner.root().getClient(), + root().createRequest().withUrlPath(getApiRoute(), "runs").build(), + GHWorkflowRunsPage.class, + GHWorkflowRun.class, + item -> item.wrapUp(owner))); } private String getApiRoute() { diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java b/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java index 9f011e9612..7556737e03 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java @@ -48,6 +48,7 @@ public GHWorkflowJobQueryBuilder latest() { */ @Override public PagedIterable<GHWorkflowJob> list() { - return new GHWorkflowJobsIterable(repo, req.build()); + return new PagedIterable<>(new PaginatedEndpoint<>(repo.root() + .getClient(), req.build(), GHWorkflowJobsPage.class, GHWorkflowJob.class, item -> item.wrapUp(repo))); } } diff --git a/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java deleted file mode 100644 index 704765f4cb..0000000000 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobsIterable.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.kohsuke.github; - -// TODO: Auto-generated Javadoc -/** - * Iterable for workflow run jobs listing. - */ -class GHWorkflowJobsIterable extends PagedIterable<GHWorkflowJob> { - private GHWorkflowJobsPage result; - - /** - * Instantiates a new GH workflow jobs iterable. - * - * @param repo - * the repo - * @param request - * the request - */ - public GHWorkflowJobsIterable(GHRepository repo, GitHubRequest request) { - super(new PaginatedEndpoint<>(repo.root() - .getClient(), request, GHWorkflowJobsPage.class, GHWorkflowJob.class, item -> item.wrapUp(repo))); - } -} diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRun.java b/src/main/java/org/kohsuke/github/GHWorkflowRun.java index 7e25b29b5f..090720bd8f 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRun.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRun.java @@ -558,7 +558,11 @@ public PagedIterable<GHWorkflowJob> listAllJobs() { * @return the paged iterable */ public PagedIterable<GHArtifact> listArtifacts() { - return new GHArtifactsIterable(owner, root().createRequest().withUrlPath(getApiRoute(), "artifacts")); + return new PagedIterable<>(new PaginatedEndpoint<>(owner.root().getClient(), + root().createRequest().withUrlPath(getApiRoute(), "artifacts").build(), + GHArtifactsPage.class, + GHArtifact.class, + item -> item.wrapUp(owner))); } /** diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java b/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java index 105dd77a84..60c891e69b 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java @@ -133,7 +133,11 @@ public GHWorkflowRunQueryBuilder headSha(String headSha) { */ @Override public PagedIterable<GHWorkflowRun> list() { - return new GHWorkflowRunsIterable(repo, req.withUrlPath(repo.getApiTailUrl("actions/runs"))); + return new PagedIterable<>(new PaginatedEndpoint<>(repo.root().getClient(), + req.withUrlPath(repo.getApiTailUrl("actions/runs")).build(), + GHWorkflowRunsPage.class, + GHWorkflowRun.class, + item -> item.wrapUp(repo))); } /** diff --git a/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java deleted file mode 100644 index f9bb2f2728..0000000000 --- a/src/main/java/org/kohsuke/github/GHWorkflowRunsIterable.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.kohsuke.github; - -// TODO: Auto-generated Javadoc -/** - * Iterable for workflow runs listing. - */ -class GHWorkflowRunsIterable extends PagedIterable<GHWorkflowRun> { - /** - * Instantiates a new GH workflow runs iterable. - * - * @param owner - * the owner - * @param requestBuilder - * the request builder - */ - public GHWorkflowRunsIterable(GHRepository owner, GitHubRequest.Builder<?> requestBuilder) { - super(new PaginatedEndpoint<>(owner.root().getClient(), - requestBuilder.build(), - GHWorkflowRunsPage.class, - GHWorkflowRun.class, - item -> item.wrapUp(owner))); - } -} diff --git a/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java b/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java deleted file mode 100644 index 034c0201f8..0000000000 --- a/src/main/java/org/kohsuke/github/GHWorkflowsIterable.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.kohsuke.github; - -// TODO: Auto-generated Javadoc -/** - * Iterable for workflows listing. - */ -class GHWorkflowsIterable extends PagedIterable<GHWorkflow> { - - /** - * Instantiates a new GH workflows iterable. - * - * @param owner - * the owner - */ - public GHWorkflowsIterable(GHRepository owner) { - super(new PaginatedEndpoint<>(owner.root().getClient(), - owner.root().createRequest().withUrlPath(owner.getApiTailUrl("actions/workflows")).build(), - GHWorkflowsPage.class, - GHWorkflow.class, - item -> item.wrapUp(owner))); - } -}