From 7fdfb855d606571518b781e50287090d7efbb875 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Tue, 22 Apr 2025 20:20:14 -0700 Subject: [PATCH 01/15] 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 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 { /** 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 { * the root */ public GHAppInstallationsIterable(GitHub root) { - this.root = root; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator _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 adapt(final Iterator base) { - return new Iterator() { - 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 { 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 { - 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 { * 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 _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 adapt(final Iterator base) { - return new Iterator() { - 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 { 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 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 { - 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 { * 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 _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 adapt(final Iterator base) { - return new Iterator() { - 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 { 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 { */ 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 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 _iterator(int pageSize) { - - Iterator pageIterator; - + private static GitHubEndpointIterable createEndpointIterable(GHRepository owner, + String sha, + GHCommit.File[] files) { + GitHubEndpointIterable 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 adapt(final Iterator base) { - return new Iterator() { - - public boolean hasNext() { - return base.hasNext(); - } + public GHCommitFileIterable(GHRepository owner, String sha, List 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 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 { 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 { @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 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 { /** * 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 { - - 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 _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 adapt(final Iterator base) { - return new Iterator() { - 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 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() { - @Nonnull - @Override - public PagedIterator _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 { - 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 { * @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 _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 adapt(final Iterator base) { - return new Iterator() { - 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 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 { 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 { @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 response = ((GitHubPageContentsIterable) requester - .toIterable(GHThread[].class, null)).toResponse(); + GitHubResponse 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 { @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 extends GHQueryBuilder { + private final Class itemType; + /** * Data transfer object that receives the result of search. */ private final Class> receiverType; - /** The terms. */ protected final List terms = new ArrayList(); @@ -34,9 +36,10 @@ public abstract class GHSearchBuilder extends GHQueryBuilder { * @param receiverType * the receiver type */ - GHSearchBuilder(GitHub root, Class> receiverType) { + GHSearchBuilder(GitHub root, Class> receiverType, Class itemType) { super(root); this.receiverType = receiverType; + this.itemType = itemType; req.withUrlPath(getApiUrl()); req.rateLimit(RateLimitTarget.SEARCH); } @@ -48,9 +51,7 @@ public abstract class GHSearchBuilder extends GHQueryBuilder { */ @Override public PagedSearchIterable list() { - - req.set("q", StringUtils.join(terms, " ")); - return new PagedSearchIterable<>(root(), req.build(), receiverType); + return list(null); } /** @@ -72,6 +73,13 @@ public GHQueryBuilder q(String term) { */ protected abstract String getApiUrl(); + PagedSearchIterable list(Consumer 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 { - private final GHRepository repo; - private final GitHubRequest request; - private GHWorkflowJobsPage result; /** @@ -23,45 +16,7 @@ class GHWorkflowJobsIterable extends PagedIterable { * 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 _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 adapt(final Iterator base) { - return new Iterator() { - 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 { 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 { - 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 { * 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 _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 adapt(final Iterator base) { - return new Iterator() { - 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 { 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 { - private final transient GHRepository owner; - - private GHWorkflowsPage result; /** * Instantiates a new GH workflows iterable. @@ -20,49 +13,10 @@ class GHWorkflowsIterable extends PagedIterable { * the owner */ public GHWorkflowsIterable(GHRepository owner) { - this.owner = owner; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator _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 adapt(final Iterator base) { - return new Iterator() { - 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 { 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 + * the type of items on each page + */ +class GitHubEndpointIterable, Item> implements Iterable { + + private static class ArrayIterable extends GitHubEndpointIterable, I> { + + private class ArrayIterator extends GitHubEndpointPageIterator, I> { + + ArrayIterator(GitHubClient client, + Class> pageType, + GitHubRequest request, + int pageSize, + Consumer itemInitializer) { + super(client, pageType, request, pageSize, itemInitializer); + } + + @Override + @NotNull protected GitHubResponse> sendNextRequest() throws IOException { + GitHubResponse response = client.sendRequest(nextRequest, + (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, receiverType)); + return new GitHubResponse<>(response, new GitHubArrayPage<>(response.body())); + } + + } + + private final Class receiverType; + + private ArrayIterable(GitHubClient client, + GitHubRequest request, + Class receiverType, + Consumer itemInitializer) { + super(client, + request, + GitHubArrayPage.getArrayPageClass(receiverType), + (Class) receiverType.getComponentType(), + itemInitializer); + this.receiverType = receiverType; + } + + @NotNull @Override + public GitHubEndpointPageIterator, I> pageIterator() { + return new ArrayIterator(client, pageType, request, pageSize, itemInitializer); + } + } + + /** + * Represents the result of a search. + * + * @author Kohsuke Kawaguchi + * @param + * the generic type + */ + private static class GitHubArrayPage implements GitHubPage { + + private static

, I> Class

getArrayPageClass(Class receiverType) { + return (Class

) new GitHubArrayPage<>(receiverType).getClass(); + } + + private final I[] items; + + public GitHubArrayPage(I[] items) { + this.items = items; + } + + private GitHubArrayPage(Class receiverType) { + this.items = (I[]) Array.newInstance(receiverType.getComponentType(), 0); + } + + public I[] getItems() { + return items; + } + } + + static GitHubEndpointIterable, I> ofArrayEndpoint(GitHubClient client, + GitHubRequest request, + Class receiverType, + Consumer itemInitializer) { + return new ArrayIterable<>(client, request, receiverType, itemInitializer); + } + + static GitHubEndpointIterable, I> ofSingleton(I[] array) { + return ofSingleton(new GitHubArrayPage<>(array)); + } + + static

, I> GitHubEndpointIterable ofSingleton(P page) { + Class itemType = (Class) page.getItems().getClass().getComponentType(); + return new GitHubEndpointIterable<>(null, null, (Class

) page.getClass(), itemType, null) { + @Nonnull + @Override + public GitHubPageIterator pageIterator() { + return GitHubPageIterator.ofSingleton(page); + } + }; + } + + protected final GitHubClient client; + protected final Consumer itemInitializer; + protected final Class itemType; + + /** + * Page size. 0 is default. + */ + protected int pageSize = 0; + + protected final Class 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 pageType, + Class itemType, + Consumer itemInitializer) { + this.client = client; + this.request = request; + this.pageType = pageType; + this.itemType = itemType; + this.itemInitializer = itemInitializer; + } + + @Nonnull + public final GitHubPageItemIterator itemIterator() { + return new GitHubPageItemIterator<>(this.pageIterator()); + } + @Nonnull + @Override + public final Iterator iterator() { + return this.itemIterator(); + } + + /** + * + * @return + */ + @Nonnull + public GitHubPageIterator 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 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 toSet() throws IOException { + return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(this.toArray()))); + } + + /** + * Sets the pagination size. + * + *

+ * When set to non-zero, each API call will retrieve this many entries. + * + * @param size + * the size + * @return the paged iterable + */ + public final GitHubEndpointIterable 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 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 iterator, Class itemType) throws IOException { + try { + ArrayList 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 toResponse() throws IOException { + GitHubPageIterator iterator = pageIterator(); + Item[] items = toArray(iterator, itemType); + GitHubResponse 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 + * +

+ * } 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

+ * type of each page (not the items in the page). + */ +class GitHubEndpointPageIterator

, Item> extends GitHubPageIterator { + + /** + * 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

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

pageType, + GitHubRequest request, + int pageSize, + Consumer 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

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

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 + // ; 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. + *

+ * 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. + *

+ *

+ * 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}. + *

+ */ + @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

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

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 + * the type of items on the page. + */ +interface GitHubPage { + /** + * 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 - * the type of items on each page - */ -class GitHubPageContentsIterable extends PagedIterable { - - /** - * This class is not thread-safe. Any one instance should only be called from a single thread. - */ - private class GitHubPageContentsIterator extends PagedIterator { - - public GitHubPageContentsIterator(GitHubPageIterator iterator, Consumer itemInitializer) { - super(iterator, itemInitializer); - } - - /** - * Gets the {@link GitHubResponse} for the last page received. - * - * @return the {@link GitHubResponse} for the last page received. - */ - private GitHubResponse lastResponse() { - return ((GitHubPageIterator) base).finalResponse(); - } - } - - private final GitHubClient client; - private final Consumer itemInitializer; - private final Class 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 receiverType, - Consumer itemInitializer) { - this.client = client; - this.request = request; - this.receiverType = receiverType; - this.itemInitializer = itemInitializer; - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public PagedIterator _iterator(int pageSize) { - final GitHubPageIterator 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 toResponse() throws IOException { - GitHubPageContentsIterator iterator = (GitHubPageContentsIterator) iterator(); - T[] items = toArray(iterator); - GitHubResponse 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, Item> implements Iterator { + + /** + * 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 pageIterator; + + GitHubPageItemIterator(GitHubPageIterator 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 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. + *

+ * 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. + *

+ *

+ * If there is another page, get that page of data and reset the check {@link #nextItemIndex} to the start of the + * new page. + *

+ *

+ * 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. + *

+ */ + 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 } - * 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 + * +

+ * } 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 + * @param

* type of each page (not the items in the page). */ -class GitHubPageIterator implements Iterator { - - /** - * Loads paginated resources. - * - * @param - * 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 GitHubPageIterator create(GitHubClient client, Class type, GitHubRequest request, int pageSize) { - - if (pageSize > 0) { - GitHubRequest.Builder builder = request.toBuilder().with("per_page", pageSize); - request = builder.build(); - } +class GitHubPageIterator

, Item> implements Iterator

{ - if (!"GET".equals(request.method())) { - throw new IllegalArgumentException("Request method \"GET\" is required for page iterator."); - } - - return new GitHubPageIterator<>(client, type, request); + static

, Item> GitHubPageIterator 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 finalResponse = null; + private final Consumer itemInitializer; /** * The page that will be returned when {@link #next()} is called. @@ -66,23 +36,20 @@ static GitHubPageIterator create(GitHubClient client, Class type, GitH * Will be {@code null} after {@link #next()} is called. *

*

- * 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. *

*/ - private T next; + protected P next; + protected final Class

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 type; + private GitHubPageIterator(P page) { + this((Class

) page.getClass(), null); + this.next = page; + } - private GitHubPageIterator(GitHubClient client, Class type, GitHubRequest request) { - this.client = client; - this.type = type; - this.nextRequest = request; + protected GitHubPageIterator(Class

pageType, Consumer itemInitializer) { + this.pageType = pageType; + this.itemInitializer = itemInitializer; } /** @@ -90,91 +57,62 @@ private GitHubPageIterator(GitHubClient client, Class type, GitHubRequest req * * @return the final response of the iterator. */ - public GitHubResponse finalResponse() { - if (hasNext()) { - throw new GHException("Final response is not available until after iterator is done."); - } - return finalResponse; + public GitHubResponse

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. - *

- * 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. - *

- *

- * 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}. - *

+ * + * @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 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 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 - // ; 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 parseBody(GitHubConnectorResponse connectorResponse, T instance) th * @param body * the body */ - GitHubResponse(GitHubResponse 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 * the type of items on each page */ -public abstract class PagedIterable implements Iterable { - /** - * Page size. 0 is default. - */ - private int pageSize = 0; +public class PagedIterable implements Iterable { + + private final GitHubEndpointIterable paginatedEndpoint; /** - * Instantiate a PagedIterable. + * Instantiates a new git hub page contents iterable. */ - public PagedIterable() { + PagedIterable(GitHubEndpointIterable paginatedEndpoint) { + this.paginatedEndpoint = paginatedEndpoint; } - /** - * Iterator over page items. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - public abstract PagedIterator _iterator(int pageSize); + public PagedIterator _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 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 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 toSet() throws IOException { - return Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(this.toArray()))); + return paginatedEndpoint.toSet(); } - /** - * Sets the pagination size. - * - *

- * When set to non-zero, each API call will retrieve this many entries. - * - * @param size - * the size - * @return the paged iterable - */ - public PagedIterable withPageSize(int size) { - this.pageSize = size; + public PagedIterable 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 type, List 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 iterator) throws IOException { - try { - ArrayList pages = new ArrayList<>(); - int totalSize = 0; - T[] item; - do { - item = iterator.nextPageArray(); - totalSize += Array.getLength(item); - pages.add(item); - } while (iterator.hasNext()); - - Class type = (Class) 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 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 implements Iterator { - /** - * 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 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 base; + private final GitHubPageItemIterator endpointIterator; /** * Instantiates a new paged iterator. * - * @param base + * @param endpointIterator * the base - * @param itemInitializer - * the item initializer */ - PagedIterator(@Nonnull Iterator base, @CheckForNull Consumer itemInitializer) { - this.base = base; - this.itemInitializer = itemInitializer; + PagedIterator(GitHubPageItemIterator 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 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. - *

- * 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. - *

- *

- * If there is another page, get that page of data and reset the check {@link #nextItemIndex} to the start of the - * new page. - *

- *

- * 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. - *

- */ - 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 extends PagedIterable { - private final Class> receiverType; - private final GitHubRequest request; + private final GitHubEndpointIterable, T> paginatedEndpoint; /** - * As soon as we have any result fetched, it's set here so that we can report the total count. - */ - private SearchResult 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> receiverType) { - this.root = root; - this.request = request; - this.receiverType = receiverType; - } - - /** - * Iterator. - * - * @param pageSize - * the page size - * @return the paged iterator - */ - @Nonnull - @Override - public PagedIterator _iterator(int pageSize) { - final Iterator adapter = adapt( - GitHubPageIterator.create(root.getClient(), receiverType, request, pageSize)); - return new PagedIterator(adapter, null); + > PagedSearchIterable(GitHubEndpointIterable paginatedEndpoint) { + super(paginatedEndpoint); + this.paginatedEndpoint = paginatedEndpoint; } /** @@ -67,8 +32,8 @@ public PagedIterator _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 withPageSize(int size) { - return (PagedSearchIterable) super.withPageSize(size); - } - - private void populate() { - if (result == null) - iterator().hasNext(); - } - - /** - * Adapts {@link Iterator}. - * - * @param base - * the base - * @return the iterator - */ - protected Iterator adapt(final Iterator> base) { - return new Iterator() { - public boolean hasNext() { - return base.hasNext(); - } - - public T[] next() { - SearchResult 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. *

* + * @param

+ * the page type for the pages returned from * @param * 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 PagedIterable toIterable(Class type, Consumer itemInitializer) { - return new GitHubPageContentsIterable<>(client, build(), type, itemInitializer); + public

, R> PagedIterable toIterable(Class

pageType, + Class itemType, + Consumer itemInitializer) { + GitHubRequest request = build(); + return new PagedIterable<>(new GitHubEndpointIterable<>(client, request, pageType, itemType, itemInitializer)); + } + /** + * Creates {@link PagedIterable } from this builder using the provided {@link Consumer}{@code }. + *

+ * This method and the {@link PagedIterable } do not actually begin fetching data until {@link Iterator#next()} + * or {@link Iterator#hasNext()} are called. + *

+ * + * @param + * 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 PagedIterable toIterable(Class receiverType, Consumer 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 * the generic type */ -abstract class SearchResult { +abstract class SearchResult implements GitHubPage { /** The incomplete results. */ boolean incompleteResults; @@ -16,6 +16,10 @@ abstract class SearchResult { /** 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 Date: Sat, 3 May 2025 23:29:30 -0700 Subject: [PATCH 02/15] 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

, I> GitHubEndpointIterable ofSingleton(P p return new GitHubEndpointIterable<>(null, null, (Class

) page.getClass(), itemType, null) { @Nonnull @Override - public GitHubPageIterator pageIterator() { - return GitHubPageIterator.ofSingleton(page); + public GitHubEndpointPageIterator pageIterator() { + return GitHubEndpointPageIterator.ofSingleton(page); } }; } @@ -165,7 +165,7 @@ public final Iterator iterator() { * @return */ @Nonnull - public GitHubPageIterator pageIterator() { + public GitHubEndpointPageIterator pageIterator() { return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer); } @@ -251,7 +251,7 @@ private Item[] concatenatePages(List pages, int totalLength) { * @throws IOException * if an I/O exception occurs. */ - private Item[] toArray(final GitHubPageIterator iterator, Class itemType) throws IOException { + private Item[] toArray(final GitHubEndpointPageIterator iterator, Class itemType) throws IOException { try { ArrayList pages = new ArrayList<>(); int totalSize = 0; @@ -284,7 +284,7 @@ private Item[] toArray(final GitHubPageIterator iterator, Class toResponse() throws IOException { - GitHubPageIterator iterator = pageIterator(); + GitHubEndpointPageIterator iterator = pageIterator(); Item[] items = toArray(iterator, itemType); GitHubResponse 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

* type of each page (not the items in the page). */ -class GitHubEndpointPageIterator

, Item> extends GitHubPageIterator { +class GitHubEndpointPageIterator

, Item> implements java.util.Iterator

{ + protected final Class

pageType; + private final Consumer itemInitializer; + /** + * The page that will be returned when {@link #next()} is called. + * + *

+ * Will be {@code null} after {@link #next()} is called. + *

+ *

+ * Will not be {@code null} after {@link #fetchNext()} is called if a new page was fetched. + *

+ */ + 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

, Item> extends GitHu */ protected GitHubRequest nextRequest; + private GitHubEndpointPageIterator(P page) { + this(null, (Class

)page.getClass(), null, 0, null); + this.next = page; + } + GitHubEndpointPageIterator(GitHubClient client, Class

pageType, GitHubRequest request, int pageSize, Consumer 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

, Item> extends GitHu this.nextRequest = request; } + static

, Item> GitHubEndpointPageIterator 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

nextResponse) { * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is * needed. *

- * 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. *

*

@@ -106,10 +132,9 @@ private void updateNextRequest(GitHubResponse

nextResponse) { * after the current response, {@link #nextRequest} is set to {@code null}. *

*/ - @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, Item> implements Ite */ private int nextItemIndex; - private final GitHubPageIterator pageIterator; + private final GitHubEndpointPageIterator pageIterator; - GitHubPageItemIterator(GitHubPageIterator pageIterator) { + GitHubPageItemIterator(GitHubEndpointPageIterator pageIterator) { this.pageIterator = pageIterator; } @@ -54,8 +54,24 @@ public Item next() { * * @return the list */ + @Deprecated public List 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 - * -

- * } 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

- * type of each page (not the items in the page). - */ -class GitHubPageIterator

, Item> implements Iterator

{ - - static

, Item> GitHubPageIterator ofSingleton(final P page) { - return new GitHubPageIterator<>(page); - } - - private final Consumer itemInitializer; - - /** - * The page that will be returned when {@link #next()} is called. - * - *

- * Will be {@code null} after {@link #next()} is called. - *

- *

- * Will not be {@code null} after {@link #fetchNext()} is called if a new page was fetched. - *

- */ - protected P next; - protected final Class

pageType; - - private GitHubPageIterator(P page) { - this((Class

) page.getClass(), null); - this.next = page; - } - - protected GitHubPageIterator(Class

pageType, Consumer 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

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 @@ > PagedSearchIterable(GitHubEndpointIterable Date: Sun, 4 May 2025 00:25:53 -0700 Subject: [PATCH 03/15] 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 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 { * 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 { * 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 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 { * 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 { */ private static final int GH_FILE_LIMIT_PER_COMMIT_PAGE = 300; - private static GitHubEndpointIterable createEndpointIterable(GHRepository owner, + private static PaginatedEndpoint createEndpointIterable(GHRepository owner, String sha, GHCommit.File[] files) { - GitHubEndpointIterable iterable; + PaginatedEndpoint 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 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 { * 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 pageIterator() { - return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer) { + public PaginatedEndpointPages 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 list(Consumer 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 { * 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 { * 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 { * 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 implements Iterable { - private final GitHubEndpointIterable paginatedEndpoint; + private final PaginatedEndpoint paginatedEndpoint; /** * Instantiates a new git hub page contents iterable. */ - PagedIterable(GitHubEndpointIterable paginatedEndpoint) { + PagedIterable(PaginatedEndpoint paginatedEndpoint) { this.paginatedEndpoint = paginatedEndpoint; } @@ -32,7 +32,7 @@ public PagedIterator _iterator(int pageSize) { @Nonnull public final PagedIterator 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 implements Iterator { - private final GitHubPageItemIterator endpointIterator; + private final PaginatedEndpointItems endpointIterator; /** * Instantiates a new paged iterator. @@ -27,7 +27,7 @@ public class PagedIterator implements Iterator { * @param endpointIterator * the base */ - PagedIterator(GitHubPageItemIterator endpointIterator) { + PagedIterator(PaginatedEndpointItems 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 extends PagedIterable { - private final GitHubEndpointIterable, T> paginatedEndpoint; + private final PaginatedEndpoint, T> paginatedEndpoint; /** * Instantiates a new git hub page contents iterable. */ - > PagedSearchIterable(GitHubEndpointIterable paginatedEndpoint) { + > PagedSearchIterable(PaginatedEndpoint paginatedEndpoint) { super(paginatedEndpoint); this.paginatedEndpoint = paginatedEndpoint; } @@ -33,7 +33,7 @@ > PagedSearchIterable(GitHubEndpointIterable * the type of items on each page */ -class GitHubEndpointIterable, Item> implements Iterable { +class PaginatedEndpoint, Item> implements Iterable { - private static class ArrayIterable extends GitHubEndpointIterable, I> { + private static class ArrayIterable extends PaginatedEndpoint, I> { - private class ArrayIterator extends GitHubEndpointPageIterator, I> { + private class ArrayIterator extends PaginatedEndpointPages, I> { ArrayIterator(GitHubClient client, Class> pageType, @@ -58,7 +58,7 @@ private ArrayIterable(GitHubClient client, } @NotNull @Override - public GitHubEndpointPageIterator, I> pageIterator() { + public PaginatedEndpointPages, I> pages() { return new ArrayIterator(client, pageType, request, pageSize, itemInitializer); } } @@ -91,24 +91,24 @@ public I[] getItems() { } } - static GitHubEndpointIterable, I> ofArrayEndpoint(GitHubClient client, + static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient client, GitHubRequest request, Class receiverType, Consumer itemInitializer) { return new ArrayIterable<>(client, request, receiverType, itemInitializer); } - static GitHubEndpointIterable, I> ofSingleton(I[] array) { + static PaginatedEndpoint, I> ofSingleton(I[] array) { return ofSingleton(new GitHubArrayPage<>(array)); } - static

, I> GitHubEndpointIterable ofSingleton(P page) { + static

, I> PaginatedEndpoint ofSingleton(P page) { Class itemType = (Class) page.getItems().getClass().getComponentType(); - return new GitHubEndpointIterable<>(null, null, (Class

) page.getClass(), itemType, null) { + return new PaginatedEndpoint<>(null, null, (Class

) page.getClass(), itemType, null) { @Nonnull @Override - public GitHubEndpointPageIterator pageIterator() { - return GitHubEndpointPageIterator.ofSingleton(page); + public PaginatedEndpointPages pages() { + return PaginatedEndpointPages.ofSingleton(page); } }; } @@ -138,7 +138,7 @@ public GitHubEndpointPageIterator pageIterator() { * @param itemInitializer * the item initializer */ - GitHubEndpointIterable(GitHubClient client, + PaginatedEndpoint(GitHubClient client, GitHubRequest request, Class pageType, Class itemType, @@ -151,13 +151,13 @@ public GitHubEndpointPageIterator pageIterator() { } @Nonnull - public final GitHubPageItemIterator itemIterator() { - return new GitHubPageItemIterator<>(this.pageIterator()); + public final PaginatedEndpointItems items() { + return new PaginatedEndpointItems<>(this.pages()); } @Nonnull @Override public final Iterator iterator() { - return this.itemIterator(); + return this.items(); } /** @@ -165,8 +165,8 @@ public final Iterator iterator() { * @return */ @Nonnull - public GitHubEndpointPageIterator pageIterator() { - return new GitHubEndpointPageIterator<>(client, pageType, request, pageSize, itemInitializer); + public PaginatedEndpointPages pages() { + return new PaginatedEndpointPages<>(client, pageType, request, pageSize, itemInitializer); } /** @@ -178,7 +178,7 @@ public GitHubEndpointPageIterator 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 toList() throws IOException { - return Collections.unmodifiableList(Arrays.asList(this.toArray())); + return Collections.unmodifiableList(toList(pages(), itemType)); } /** @@ -202,7 +202,7 @@ public final List toList() throws IOException { */ @Nonnull public final Set 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 toSet() throws IOException { * the size * @return the paged iterable */ - public final GitHubEndpointIterable withPageSize(int size) { + public final PaginatedEndpoint 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 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 pages, int totalLength) { * @throws IOException * if an I/O exception occurs. */ - private Item[] toArray(final GitHubEndpointPageIterator iterator, Class itemType) throws IOException { + private List toList(final PaginatedEndpointPages iterator, Class itemType) + throws IOException { try { - ArrayList 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 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 iterator, Cl */ @Nonnull final GitHubResponse toResponse() throws IOException { - GitHubEndpointPageIterator iterator = pageIterator(); - Item[] items = toArray(iterator, itemType); + PaginatedEndpointPages iterator = pages(); + Item[] items = toArray(); GitHubResponse 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, Item> implements Iterator { +class PaginatedEndpointItems, Item> implements Iterator { /** * 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, Item> implements Ite */ private int nextItemIndex; - private final GitHubEndpointPageIterator pageIterator; + private final PaginatedEndpointPages pageIterator; - GitHubPageItemIterator(GitHubEndpointPageIterator pageIterator) { + PaginatedEndpointItems(PaginatedEndpointPages 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

* type of each page (not the items in the page). */ -class GitHubEndpointPageIterator

, Item> implements java.util.Iterator

{ +class PaginatedEndpointPages

, Item> implements java.util.Iterator

{ - protected final Class

pageType; + static

, Item> PaginatedEndpointPages 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

finalResponse = null; private final Consumer itemInitializer; /** * The page that will be returned when {@link #next()} is called. @@ -38,11 +46,6 @@ class GitHubEndpointPageIterator

, Item> implements ja *

*/ 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

finalResponse = null; protected final GitHubClient client; @@ -52,12 +55,14 @@ class GitHubEndpointPageIterator

, Item> implements ja */ protected GitHubRequest nextRequest; - private GitHubEndpointPageIterator(P page) { - this(null, (Class

)page.getClass(), null, 0, null); + protected final Class

pageType; + + private PaginatedEndpointPages(P page) { + this(null, (Class

) page.getClass(), null, 0, null); this.next = page; } - GitHubEndpointPageIterator(GitHubClient client, + PaginatedEndpointPages(GitHubClient client, Class

pageType, GitHubRequest request, int pageSize, @@ -78,10 +83,6 @@ private GitHubEndpointPageIterator(P page) { this.nextRequest = request; } - static

, Item> GitHubEndpointPageIterator ofSingleton(final P page) { - return new GitHubEndpointPageIterator<>(page); - } - /** * On rare occasions the final response from iterating is needed. * @@ -94,6 +95,53 @@ public GitHubResponse

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

nextResponse) { * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is * needed. *

- * 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. *

*

@@ -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

, R> PagedIterable toIterable(Class

pageTyp Class itemType, Consumer 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

, R> PagedIterable toIterable(Class

pageTyp */ public PagedIterable toIterable(Class receiverType, Consumer 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 Date: Mon, 5 May 2025 09:33:55 -0700 Subject: [PATCH 04/15] 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 { - - /** 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 { - - /** - * 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 { - /** - * 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 getAllRepositories() { * app installations accessible to the user access token */ public PagedIterable 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 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 getCheckRuns(String ref, Map 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 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 listTopics() throws IOException { * @return the paged iterable */ public PagedIterable 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 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 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 { - 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 listAllJobs() { * @return the paged iterable */ public PagedIterable 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 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 { - /** - * 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 { - - /** - * 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))); - } -} From c75c9c38de21d7114c443560fe6bacf393c9204c Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Thu, 4 Sep 2025 13:05:08 -0700 Subject: [PATCH 05/15] Add reflection registrations --- .../org/kohsuke/github/PaginatedEndpoint.java | 2 +- .../github-api/reflect-config.json | 136 +++++++++++++++++- .../github-api/serialization-config.json | 27 ++++ 3 files changed, 163 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java index 9d69ae2fdf..b4459daf78 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java @@ -259,7 +259,7 @@ private List toList(final PaginatedEndpointPages iterator, Cla @Nonnull final GitHubResponse toResponse() throws IOException { PaginatedEndpointPages iterator = pages(); - Item[] items = toArray(); + Item[] items = toList(iterator, itemType).toArray((Item[]) Array.newInstance(itemType, 0)); GitHubResponse lastResponse = iterator.finalResponse(); return new GitHubResponse<>(lastResponse, items); } diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json index 30be262b74..e63d96697c 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json @@ -6823,6 +6823,140 @@ "allDeclaredMethods": true, "allPublicClasses": true, "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GHExternalGroupIterable$1$1", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.GitHubPage", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$1", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable$ArrayIterator", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$GitHubArrayPage", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpointItems", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpointPages", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true } - ] diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json index 412aa47e18..55c1d59009 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json @@ -1366,5 +1366,32 @@ }, { "name": "org.kohsuke.github.GitHubBridgeAdapterObject" + }, + { + "name": "org.kohsuke.github.GHExternalGroupIterable$1$1" + }, + { + "name": "org.kohsuke.github.GitHubPage" + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint" + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$1" + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable" + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable$ArrayIterator" + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$GitHubArrayPage" + }, + { + "name": "org.kohsuke.github.PaginatedEndpointItems" + }, + { + "name": "org.kohsuke.github.PaginatedEndpointPages" } ] From dadb0320edf7a9992ced0e63f9eb747e1a0cf3ce Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Fri, 5 Sep 2025 13:08:59 -0700 Subject: [PATCH 06/15] Clean up spotbugs --- .../kohsuke/github/GHCommitFileIterable.java | 2 +- .../java/org/kohsuke/github/GHCompare.java | 3 +- .../org/kohsuke/github/PagedIterable.java | 26 ++++++- .../org/kohsuke/github/PagedIterator.java | 14 +++- .../kohsuke/github/PagedSearchIterable.java | 19 +++++ .../org/kohsuke/github/PaginatedEndpoint.java | 74 +++++++++++-------- .../github/PaginatedEndpointItems.java | 8 +- .../github/PaginatedEndpointPages.java | 16 ++-- .../java/org/kohsuke/github/Requester.java | 1 - 9 files changed, 114 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index 9ec907010c..2cd1ba87b9 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -23,7 +23,7 @@ private static PaginatedEndpoint createEndpointIterable PaginatedEndpoint iterable; if (files != null && files.length < GH_FILE_LIMIT_PER_COMMIT_PAGE) { // create a page iterator that only provides one page - iterable = PaginatedEndpoint.ofSingleton(new GHCommitFilesPage(files)); + iterable = PaginatedEndpoint.ofSinglePage(new GHCommitFilesPage(files), GHCommit.File.class); } else { GitHubRequest request = owner.root() .createRequest() diff --git a/src/main/java/org/kohsuke/github/GHCompare.java b/src/main/java/org/kohsuke/github/GHCompare.java index d9993f54db..40eb011a37 100644 --- a/src/main/java/org/kohsuke/github/GHCompare.java +++ b/src/main/java/org/kohsuke/github/GHCompare.java @@ -261,6 +261,7 @@ public URL getHtmlUrl() { } @Override + @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected behavior") public GHCompare.Commit[] getItems() { return commits; } @@ -345,7 +346,7 @@ public PagedIterable listCommits() { .withPageSize(10); } else { // if not using paginated commits, adapt the returned commits array - return new PagedIterable<>(PaginatedEndpoint.ofSingleton(this.commits)); + return new PagedIterable<>(PaginatedEndpoint.ofSinglePage(this.commits, Commit.class)); } } diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index 951cad5567..e1a9d7b2db 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -6,7 +6,6 @@ import javax.annotation.Nonnull; -// TODO: Auto-generated Javadoc /** * {@link Iterable} that returns {@link PagedIterator}. {@link PagedIterable} is thread-safe but {@link PagedIterator} * is not. Any one instance of {@link PagedIterator} should only be called from a single thread. @@ -19,6 +18,11 @@ public class PagedIterable implements Iterable { private final PaginatedEndpoint paginatedEndpoint; + @Deprecated + public PagedIterable() { + this(null); + } + /** * Instantiates a new git hub page contents iterable. */ @@ -50,11 +54,27 @@ public Set toSet() throws IOException { return paginatedEndpoint.toSet(); } - public PagedIterable withPageSize(int i) { - paginatedEndpoint.withPageSize(i); + /** + * Sets the pagination size. + * + *

+ * When set to non-zero, each API call will retrieve this many entries. + * + * @param size + * the size + * @return the paged iterable + */ + public PagedIterable withPageSize(int size) { + paginatedEndpoint.withPageSize(size); return this; } + @Nonnull + @Deprecated + protected T[] toArray(final PagedIterator iterator) throws IOException { + return paginatedEndpoint.toArray(); + } + GitHubResponse 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 a85bf7128d..7385b3ad67 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.util.Iterator; import java.util.List; @@ -19,7 +21,11 @@ */ public class PagedIterator implements Iterator { - private final PaginatedEndpointItems endpointIterator; + private final PaginatedEndpointItems endpointIterator; + + @Deprecated + @SuppressFBWarnings(value = { "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD" }, justification = "No longer used") + protected Iterator base = null; /** * Instantiates a new paged iterator. @@ -27,7 +33,7 @@ public class PagedIterator implements Iterator { * @param endpointIterator * the base */ - PagedIterator(PaginatedEndpointItems endpointIterator) { + PagedIterator(PaginatedEndpointItems endpointIterator) { this.endpointIterator = endpointIterator; } @@ -49,4 +55,8 @@ public T next() { public List nextPage() { return endpointIterator.nextPage(); } + + @Deprecated + protected void wrapUp(T[] page) { + } } diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 722d0c1bc8..132e144fe6 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -2,6 +2,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.Iterator; + // TODO: Auto-generated Javadoc /** * {@link PagedIterable} enhanced to report search result specific information. @@ -45,4 +47,21 @@ public boolean isIncomplete() { // populate(); return paginatedEndpoint.pages().peek().incompleteResults; } + + /** + * With page size. + * + * @param size + * the size + * @return the paged search iterable + */ + @Override + public PagedSearchIterable withPageSize(int size) { + return (PagedSearchIterable) super.withPageSize(size); + } + + @Deprecated + protected Iterator adapt(final Iterator> base) { + return null; + } } diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java index b4459daf78..0f4f00d5e7 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -22,12 +23,12 @@ */ class PaginatedEndpoint, Item> implements Iterable { - private static class ArrayIterable extends PaginatedEndpoint, I> { + private static class ArrayIterable extends PaginatedEndpoint, I> { - private class ArrayIterator extends PaginatedEndpointPages, I> { + private class ArrayIterator extends PaginatedEndpointPages, I> { ArrayIterator(GitHubClient client, - Class> pageType, + Class> pageType, GitHubRequest request, int pageSize, Consumer itemInitializer) { @@ -35,7 +36,7 @@ private class ArrayIterator extends PaginatedEndpointPages, I> { } @Override - @NotNull protected GitHubResponse> sendNextRequest() throws IOException { + @NotNull protected GitHubResponse> sendNextRequest() throws IOException { GitHubResponse response = client.sendRequest(nextRequest, (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, receiverType)); return new GitHubResponse<>(response, new GitHubArrayPage<>(response.body())); @@ -48,23 +49,21 @@ private class ArrayIterator extends PaginatedEndpointPages, I> { private ArrayIterable(GitHubClient client, GitHubRequest request, Class receiverType, + Class itemType, Consumer itemInitializer) { - super(client, - request, - GitHubArrayPage.getArrayPageClass(receiverType), - (Class) receiverType.getComponentType(), - itemInitializer); + super(client, request, GitHubArrayPage.getArrayPageClass(itemType), itemType, itemInitializer); this.receiverType = receiverType; } - @NotNull @Override - public PaginatedEndpointPages, I> pages() { + @Nonnull + @Override + public PaginatedEndpointPages, I> pages() { return new ArrayIterator(client, pageType, request, pageSize, itemInitializer); } } /** - * Represents the result of a search. + * Represents a page of results * * @author Kohsuke Kawaguchi * @param @@ -72,8 +71,9 @@ public PaginatedEndpointPages, I> pages() { */ private static class GitHubArrayPage implements GitHubPage { - private static

, I> Class

getArrayPageClass(Class receiverType) { - return (Class

) new GitHubArrayPage<>(receiverType).getClass(); + @SuppressFBWarnings(value = { "DM_NEW_FOR_GETCLASS" }, justification = "BUG?") + private static

, I> Class

getArrayPageClass(Class itemType) { + return (Class

) new GitHubArrayPage<>(itemType).getClass(); } private final I[] items; @@ -82,8 +82,8 @@ public GitHubArrayPage(I[] items) { this.items = items; } - private GitHubArrayPage(Class receiverType) { - this.items = (I[]) Array.newInstance(receiverType.getComponentType(), 0); + private GitHubArrayPage(Class itemType) { + this.items = null; } public I[] getItems() { @@ -91,26 +91,39 @@ public I[] getItems() { } } - static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient client, + private static class SinglePage

, I> extends PaginatedEndpoint { + private final P page; + + SinglePage(P page, Class itemType) { + super(null, null, (Class

) page.getClass(), itemType, null); + this.page = page; + } + + @Nonnull + @Override + public PaginatedEndpointPages pages() { + return PaginatedEndpointPages.ofSinglePage(pageType, page); + } + + } + + static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient client, GitHubRequest request, Class receiverType, Consumer itemInitializer) { - return new ArrayIterable<>(client, request, receiverType, itemInitializer); + return new ArrayIterable(client, + request, + (Class) receiverType, + (Class) receiverType.getComponentType(), + itemInitializer); } - static PaginatedEndpoint, I> ofSingleton(I[] array) { - return ofSingleton(new GitHubArrayPage<>(array)); + static PaginatedEndpoint, I> ofSinglePage(I[] array, Class itemType) { + return ofSinglePage(new GitHubArrayPage<>(array), itemType); } - static

, I> PaginatedEndpoint ofSingleton(P page) { - Class itemType = (Class) page.getItems().getClass().getComponentType(); - return new PaginatedEndpoint<>(null, null, (Class

) page.getClass(), itemType, null) { - @Nonnull - @Override - public PaginatedEndpointPages pages() { - return PaginatedEndpointPages.ofSingleton(page); - } - }; + static

, I> PaginatedEndpoint ofSinglePage(P page, Class itemType) { + return new SinglePage<>(page, itemType); } protected final GitHubClient client; @@ -151,9 +164,10 @@ public PaginatedEndpointPages pages() { } @Nonnull - public final PaginatedEndpointItems items() { + public final PaginatedEndpointItems items() { return new PaginatedEndpointItems<>(this.pages()); } + @Nonnull @Override public final Iterator iterator() { diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java index 305a47be56..a0c6b2875d 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java @@ -5,7 +5,7 @@ /** * This class is not thread-safe. Any one instance should only be called from a single thread. */ -class PaginatedEndpointItems, Item> implements Iterator { +class PaginatedEndpointItems implements Iterator { /** * Current batch of items. Each time {@link #next()} is called the next item in this array will be returned. After @@ -14,7 +14,7 @@ class PaginatedEndpointItems, Item> implements Ite * * @see #fetchNext() {@link #fetchNext()} for details on how this field is used. */ - private Page currentPage; + private GitHubPage currentPage; /** * The index of the next item on the page, the item that will be returned when {@link #next()} is called. @@ -23,9 +23,9 @@ class PaginatedEndpointItems, Item> implements Ite */ private int nextItemIndex; - private final PaginatedEndpointPages pageIterator; + private final PaginatedEndpointPages, Item> pageIterator; - PaginatedEndpointItems(PaginatedEndpointPages pageIterator) { + PaginatedEndpointItems(PaginatedEndpointPages, Item> pageIterator) { this.pageIterator = pageIterator; } diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java index a075832a30..22ff87bca0 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java @@ -26,8 +26,9 @@ */ class PaginatedEndpointPages

, Item> implements java.util.Iterator

{ - static

, Item> PaginatedEndpointPages ofSingleton(final P page) { - return new PaginatedEndpointPages<>(page); + static

, Item> PaginatedEndpointPages ofSinglePage(Class

pageType, + final P page) { + return new PaginatedEndpointPages<>(pageType, page); } /** * When done iterating over pages, it is on rare occasions useful to be able to get information from the final @@ -57,8 +58,8 @@ static

, Item> PaginatedEndpointPages ofSing protected final Class

pageType; - private PaginatedEndpointPages(P page) { - this(null, (Class

) page.getClass(), null, 0, null); + private PaginatedEndpointPages(Class

pageType, P page) { + this(null, pageType, null, 0, null); this.next = page; } @@ -75,9 +76,10 @@ private PaginatedEndpointPages(P page) { request = builder.build(); } - if (request != null && !"GET".equals(request.method())) { - throw new IllegalArgumentException("Request method \"GET\" is required for page iterator."); - } + // if (request != null && !"GET".equals(request.method())) { + // throw new IllegalArgumentException("Request method \"GET\" is required for page iterator."); + // } + // assert (request == null || "GET".equals(request.method())); this.client = client; this.nextRequest = request; diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 2a3d80140b..9eaa2d9fe7 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -225,5 +225,4 @@ public PagedIterable toIterable(Class receiverType, Consumer item GitHubRequest request = build(); return new PagedIterable<>(PaginatedEndpoint.ofArrayEndpoint(client, request, receiverType, itemInitializer)); } - } From 0668c5d8109a7254e09721abf2419b55568f004b Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Sat, 6 Sep 2025 03:17:24 -0700 Subject: [PATCH 07/15] Addtional tuning --- .../kohsuke/github/GHCommitFileIterable.java | 2 +- .../java/org/kohsuke/github/GHCompare.java | 2 +- .../org/kohsuke/github/PaginatedEndpoint.java | 22 +++--- .../github/PaginatedEndpointPages.java | 2 +- .../github-api/reflect-config.json | 15 +++++ .../github-api/serialization-config.json | 3 + .../kohsuke/github/AotIntegrationTest.java | 67 ++++++++++++------- 7 files changed, 76 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index 2cd1ba87b9..9196d8ca2c 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -23,7 +23,7 @@ private static PaginatedEndpoint createEndpointIterable PaginatedEndpoint iterable; if (files != null && files.length < GH_FILE_LIMIT_PER_COMMIT_PAGE) { // create a page iterator that only provides one page - iterable = PaginatedEndpoint.ofSinglePage(new GHCommitFilesPage(files), GHCommit.File.class); + iterable = PaginatedEndpoint.fromSinglePage(new GHCommitFilesPage(files), GHCommit.File.class); } else { GitHubRequest request = owner.root() .createRequest() diff --git a/src/main/java/org/kohsuke/github/GHCompare.java b/src/main/java/org/kohsuke/github/GHCompare.java index 40eb011a37..dccb9a6019 100644 --- a/src/main/java/org/kohsuke/github/GHCompare.java +++ b/src/main/java/org/kohsuke/github/GHCompare.java @@ -346,7 +346,7 @@ public PagedIterable listCommits() { .withPageSize(10); } else { // if not using paginated commits, adapt the returned commits array - return new PagedIterable<>(PaginatedEndpoint.ofSinglePage(this.commits, Commit.class)); + return new PagedIterable<>(PaginatedEndpoint.fromSinglePage(this.commits, Commit.class)); } } diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java index 0f4f00d5e7..a2ff5e10ad 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java @@ -91,10 +91,10 @@ public I[] getItems() { } } - private static class SinglePage

, I> extends PaginatedEndpoint { + private static class SinglePageEndpoint

, I> extends PaginatedEndpoint { private final P page; - SinglePage(P page, Class itemType) { + SinglePageEndpoint(P page, Class itemType) { super(null, null, (Class

) page.getClass(), itemType, null); this.page = page; } @@ -102,11 +102,19 @@ private static class SinglePage

, I> extends PaginatedEnd @Nonnull @Override public PaginatedEndpointPages pages() { - return PaginatedEndpointPages.ofSinglePage(pageType, page); + return PaginatedEndpointPages.fromSinglePage(pageType, page); } } + static PaginatedEndpoint, I> fromSinglePage(I[] array, Class itemType) { + return fromSinglePage(new GitHubArrayPage<>(array), itemType); + } + + static

, I> PaginatedEndpoint fromSinglePage(P page, Class itemType) { + return new SinglePageEndpoint<>(page, itemType); + } + static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient client, GitHubRequest request, Class receiverType, @@ -118,14 +126,6 @@ static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient itemInitializer); } - static PaginatedEndpoint, I> ofSinglePage(I[] array, Class itemType) { - return ofSinglePage(new GitHubArrayPage<>(array), itemType); - } - - static

, I> PaginatedEndpoint ofSinglePage(P page, Class itemType) { - return new SinglePage<>(page, itemType); - } - protected final GitHubClient client; protected final Consumer itemInitializer; protected final Class itemType; diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java index 22ff87bca0..e9cf9a034a 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java @@ -26,7 +26,7 @@ */ class PaginatedEndpointPages

, Item> implements java.util.Iterator

{ - static

, Item> PaginatedEndpointPages ofSinglePage(Class

pageType, + static

, Item> PaginatedEndpointPages fromSinglePage(Class

pageType, final P page) { return new PaginatedEndpointPages<>(pageType, page); } diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json index e63d96697c..9ad47e7995 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json @@ -6958,5 +6958,20 @@ "allDeclaredMethods": true, "allPublicClasses": true, "allDeclaredClasses": true + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$SinglePageEndpoint", + "allPublicFields": true, + "allDeclaredFields": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "queryAllPublicMethods": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "allPublicClasses": true, + "allDeclaredClasses": true } ] diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json index 55c1d59009..8ed9d9980f 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json @@ -1393,5 +1393,8 @@ }, { "name": "org.kohsuke.github.PaginatedEndpointPages" + }, + { + "name": "org.kohsuke.github.PaginatedEndpoint$SinglePageEndpoint" } ] diff --git a/src/test/java/org/kohsuke/github/AotIntegrationTest.java b/src/test/java/org/kohsuke/github/AotIntegrationTest.java index a8b458f792..ed4ba33605 100644 --- a/src/test/java/org/kohsuke/github/AotIntegrationTest.java +++ b/src/test/java/org/kohsuke/github/AotIntegrationTest.java @@ -41,41 +41,62 @@ public AotIntegrationTest() { public void testIfAllRequiredClassesAreRegisteredForAot() throws IOException { String artifactId = System.getProperty("test.projectArtifactId", "default"); - Stream providedReflectionConfigStreamOfNames = readAotConfigToStreamOfClassNames( - "./target/classes/META-INF/native-image/org.kohsuke/" + artifactId + "/reflect-config.json"); - Stream providedNoReflectStreamOfNames = Files - .lines(Path.of("./target/test-classes/no-reflect-and-serialization-list")); - Stream providedSerializationStreamOfNames = readAotConfigToStreamOfClassNames( - "./target/classes/META-INF/native-image/org.kohsuke/" + artifactId + "/serialization-config.json"); - Stream providedAotConfigClassNamesPart = Stream - .concat(providedSerializationStreamOfNames, - Stream.concat(providedReflectionConfigStreamOfNames, providedNoReflectStreamOfNames)) - .distinct(); - List providedReflectionAndNoReflectionConfigNames = providedAotConfigClassNamesPart + String reflectConfigPath = "./target/classes/META-INF/native-image/org.kohsuke/" + artifactId + + "/reflect-config.json"; + String noReflectPath = "./target/test-classes/no-reflect-and-serialization-list"; + String serializationConfigPath = "./target/classes/META-INF/native-image/org.kohsuke/" + artifactId + + "/serialization-config.json"; + String generatedReflectConfigPath = "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/" + + artifactId + "/reflect-config.json"; + String generatedSerializationConfigPath = "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/" + + artifactId + "/serialization-config.json"; + + Stream reflectConfigNames = readAotConfigToStreamOfClassNames(reflectConfigPath); + Stream noReflectNames = Files.lines(Path.of(noReflectPath)); + Stream serializationNames = readAotConfigToStreamOfClassNames(serializationConfigPath); + List allConfigClassNames = Stream + .concat(serializationNames, Stream.concat(reflectConfigNames, noReflectNames)) + .distinct() + .sorted() + .collect(Collectors.toList()); + + Stream generatedReflectConfigNames = readAotConfigToStreamOfClassNames(generatedReflectConfigPath); + Stream generatedSerializationNames = readAotConfigToStreamOfClassNames( + generatedSerializationConfigPath); + List allGeneratedConfigClassNames = Stream + .concat(generatedReflectConfigNames, generatedSerializationNames) + .distinct() + .sorted() .collect(Collectors.toList()); - Stream generatedReflectConfigStreamOfClassNames = readAotConfigToStreamOfClassNames( - "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/" + artifactId - + "/reflect-config.json"); - Stream generatedSerializationStreamOfNames = readAotConfigToStreamOfClassNames( - "./target/spring-aot/test/resources/META-INF/native-image/org.kohsuke/" + artifactId - + "/serialization-config.json"); - Stream generatedAotConfigClassNames = Stream.concat(generatedReflectConfigStreamOfClassNames, - generatedSerializationStreamOfNames); + StringBuilder failures = new StringBuilder(); - generatedAotConfigClassNames.forEach(generatedReflectionConfigClassName -> { + allGeneratedConfigClassNames.forEach(generatedReflectionConfigClassName -> { try { - if (!providedReflectionAndNoReflectionConfigNames.contains(generatedReflectionConfigClassName)) { - fail(String.format( + if (!allConfigClassNames.contains(generatedReflectionConfigClassName)) { + failures.append(String.format( Files.readString( Path.of("./target/test-classes/reflection-and-serialization-test-error-message")), - generatedReflectionConfigClassName)); + generatedReflectionConfigClassName)).append('\n'); } } catch (IOException e) { throw new RuntimeException(e); } }); + // Future cleanup + // allConfigClassNames.forEach(reflectionConfigClassName -> { + // if (!allGeneratedConfigClassNames.contains(reflectionConfigClassName)) { + // failures.append( + // String.format("Extra class name found in config files: %1$s\n", reflectionConfigClassName)); + // } + // }); + + // Report all failures at once rather than one at a time + String failureString = failures.toString(); + if (!failureString.isEmpty()) { + fail("\n" + failureString); + } } private Stream readAotConfigToStreamOfClassNames(String reflectionConfig) throws IOException { From 87677f316a5031cd517e8287d54d366c29991b6f Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Sat, 6 Sep 2025 13:20:40 -0700 Subject: [PATCH 08/15] Ignore some missing methods and fields --- pom.xml | 8 ++++++++ .../java/org/kohsuke/github/PagedIterable.java | 15 --------------- .../java/org/kohsuke/github/PagedIterator.java | 10 ---------- .../org/kohsuke/github/PagedSearchIterable.java | 7 ------- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/pom.xml b/pom.xml index 992b1a32cf..e097e5dfcc 100644 --- a/pom.xml +++ b/pom.xml @@ -545,6 +545,14 @@ org.kohsuke.github.internal + + org.kohsuke.github.PagedIterable#_iterator(int) + org.kohsuke.github.PagedIterable#toArray(org.kohsuke.github.PagedIterator) + org.kohsuke.github.PagedIterable#PagedIterable() + org.kohsuke.github.PagedIterator#base + org.kohsuke.github.PagedIterator#wrapUp(java.lang.Object[]) + org.kohsuke.github.PagedSearchIterable#_iterator(int) + org.kohsuke.github.PagedSearchIterable#adapt(java.util.Iterator) diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index e1a9d7b2db..d43d2626cb 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -18,11 +18,6 @@ public class PagedIterable implements Iterable { private final PaginatedEndpoint paginatedEndpoint; - @Deprecated - public PagedIterable() { - this(null); - } - /** * Instantiates a new git hub page contents iterable. */ @@ -30,10 +25,6 @@ public PagedIterable() { this.paginatedEndpoint = paginatedEndpoint; } - public PagedIterator _iterator(int pageSize) { - throw new RuntimeException("No longer used."); - } - @Nonnull public final PagedIterator iterator() { return new PagedIterator<>(paginatedEndpoint.items()); @@ -69,12 +60,6 @@ public PagedIterable withPageSize(int size) { return this; } - @Nonnull - @Deprecated - protected T[] toArray(final PagedIterator iterator) throws IOException { - return paginatedEndpoint.toArray(); - } - GitHubResponse 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 7385b3ad67..817096b1b9 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -1,7 +1,5 @@ package org.kohsuke.github; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import java.util.Iterator; import java.util.List; @@ -23,10 +21,6 @@ public class PagedIterator implements Iterator { private final PaginatedEndpointItems endpointIterator; - @Deprecated - @SuppressFBWarnings(value = { "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD" }, justification = "No longer used") - protected Iterator base = null; - /** * Instantiates a new paged iterator. * @@ -55,8 +49,4 @@ public T next() { public List nextPage() { return endpointIterator.nextPage(); } - - @Deprecated - protected void wrapUp(T[] page) { - } } diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 132e144fe6..9d6fc67439 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -2,8 +2,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.Iterator; - // TODO: Auto-generated Javadoc /** * {@link PagedIterable} enhanced to report search result specific information. @@ -59,9 +57,4 @@ public boolean isIncomplete() { public PagedSearchIterable withPageSize(int size) { return (PagedSearchIterable) super.withPageSize(size); } - - @Deprecated - protected Iterator adapt(final Iterator> base) { - return null; - } } From 7857e897bef030a54cc8ba4ddc611d16d7aa2dc9 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Sun, 7 Sep 2025 00:06:26 -0700 Subject: [PATCH 09/15] Improve code coverage --- pom.xml | 1 + src/main/java/org/kohsuke/github/PagedIterable.java | 5 +++-- src/main/java/org/kohsuke/github/PagedIterator.java | 2 +- src/test/java/org/kohsuke/github/AppTest.java | 2 +- src/test/java/org/kohsuke/github/GitHubTest.java | 3 ++- .../wiremock/searchContent/mappings/6-search_code.json | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index e097e5dfcc..250efb98d9 100644 --- a/pom.xml +++ b/pom.xml @@ -550,6 +550,7 @@ org.kohsuke.github.PagedIterable#toArray(org.kohsuke.github.PagedIterator) org.kohsuke.github.PagedIterable#PagedIterable() org.kohsuke.github.PagedIterator#base + org.kohsuke.github.PagedIterable#iterator() org.kohsuke.github.PagedIterator#wrapUp(java.lang.Object[]) org.kohsuke.github.PagedSearchIterable#_iterator(int) org.kohsuke.github.PagedSearchIterable#adapt(java.util.Iterator) diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index d43d2626cb..89ac1e2114 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import java.io.IOException; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -26,8 +27,8 @@ public class PagedIterable implements Iterable { } @Nonnull - public final PagedIterator iterator() { - return new PagedIterator<>(paginatedEndpoint.items()); + public final Iterator iterator() { + return paginatedEndpoint.iterator(); } @Nonnull diff --git a/src/main/java/org/kohsuke/github/PagedIterator.java b/src/main/java/org/kohsuke/github/PagedIterator.java index 817096b1b9..5c7cfda181 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -43,7 +43,7 @@ public T next() { * Get the next page of items. * * @return a list of the next page of items. - * @deprecated use PagedIterable.pageIterator(). + * @deprecated use PagedIterable.pages(). */ @Deprecated public List nextPage() { diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index cb59f2af62..cb7324e7f5 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -362,7 +362,7 @@ public void testCommit() throws Exception { .getRepository("jenkins") .getCommit("08c1c9970af4d609ae754fbe803e06186e3206f7"); assertThat(commit.getParents().size(), equalTo(1)); - assertThat(commit.listFiles().toList().size(), equalTo(1)); + assertThat(commit.listFiles().withPageSize(50).toList().size(), equalTo(1)); assertThat(commit.getHtmlUrl().toString(), equalTo("https://github.com/jenkinsci/jenkins/commit/08c1c9970af4d609ae754fbe803e06186e3206f7")); assertThat(commit.getLinesAdded(), equalTo(40)); diff --git a/src/test/java/org/kohsuke/github/GitHubTest.java b/src/test/java/org/kohsuke/github/GitHubTest.java index 794eff5297..4cef99ba6a 100644 --- a/src/test/java/org/kohsuke/github/GitHubTest.java +++ b/src/test/java/org/kohsuke/github/GitHubTest.java @@ -294,10 +294,11 @@ public void searchContent() throws Exception { PagedSearchIterable r4 = searchBuilder.list(); - GHContent c4 = r4.iterator().next(); + GHContent c4 = r4.withPageSize(25).iterator().next(); assertThat(c4.getPath(), not(equalTo(c2.getPath()))); assertThat(c4.getPath(), not(equalTo(c3.getPath()))); assertThat(r4.getTotalCount(), equalTo(r2.getTotalCount())); + assertThat(r4.isIncomplete(), equalTo(false)); // Verify qualifier not allowed to be empty IllegalArgumentException e = Assert.assertThrows(IllegalArgumentException.class, diff --git a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/searchContent/mappings/6-search_code.json b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/searchContent/mappings/6-search_code.json index 103b4ae0ea..942a07a309 100644 --- a/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/searchContent/mappings/6-search_code.json +++ b/src/test/resources/org/kohsuke/github/GitHubTest/wiremock/searchContent/mappings/6-search_code.json @@ -2,7 +2,7 @@ "id": "a9b8870b-33d7-4164-a407-310342d68536", "name": "search_code", "request": { - "url": "/search/code?sort=indexed&order=desc&q=addClass+in%3Afile+language%3Ajs+repo%3Ajquery%2Fjquery", + "url": "/search/code?sort=indexed&order=desc&q=addClass+in%3Afile+language%3Ajs+repo%3Ajquery%2Fjquery&per_page=25", "method": "GET", "headers": { "Accept": { From 4948299143bfa1061872aa861fe9394461706929 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Wed, 17 Sep 2025 17:18:08 -0700 Subject: [PATCH 10/15] Rearraging and streamlining --- pom.xml | 2 +- .../java/org/kohsuke/github/GitHubPage.java | 7 + .../github/GitHubPageArrayAdapter.java | 21 +++ .../org/kohsuke/github/PagedIterable.java | 17 +- .../org/kohsuke/github/PagedIterator.java | 5 +- .../kohsuke/github/PagedSearchIterable.java | 23 ++- .../github/PaginatedArrayEndpoint.java | 73 +++++++++ .../org/kohsuke/github/PaginatedEndpoint.java | 145 +++++------------- .../github/PaginatedEndpointItems.java | 21 ++- .../github/PaginatedEndpointPages.java | 115 +++++++------- .../github-api/reflect-config.json | 27 +--- .../github-api/serialization-config.json | 15 +- src/test/java/org/kohsuke/github/AppTest.java | 2 +- .../org/kohsuke/github/GHRepositoryTest.java | 2 +- .../java/org/kohsuke/github/GHUserTest.java | 2 +- .../org/kohsuke/github/GHWorkflowRunTest.java | 9 +- .../kohsuke/github/PaginatedEndpointTest.java | 103 +++++++++++++ 17 files changed, 365 insertions(+), 224 deletions(-) create mode 100644 src/main/java/org/kohsuke/github/GitHubPageArrayAdapter.java create mode 100644 src/main/java/org/kohsuke/github/PaginatedArrayEndpoint.java create mode 100644 src/test/java/org/kohsuke/github/PaginatedEndpointTest.java diff --git a/pom.xml b/pom.xml index 250efb98d9..fee634c580 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,7 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 + 2.20.0 com.github.spotbugs @@ -550,7 +551,6 @@ org.kohsuke.github.PagedIterable#toArray(org.kohsuke.github.PagedIterator) org.kohsuke.github.PagedIterable#PagedIterable() org.kohsuke.github.PagedIterator#base - org.kohsuke.github.PagedIterable#iterator() org.kohsuke.github.PagedIterator#wrapUp(java.lang.Object[]) org.kohsuke.github.PagedSearchIterable#_iterator(int) org.kohsuke.github.PagedSearchIterable#adapt(java.util.Iterator) diff --git a/src/main/java/org/kohsuke/github/GitHubPage.java b/src/main/java/org/kohsuke/github/GitHubPage.java index 0125b911a7..764b099788 100644 --- a/src/main/java/org/kohsuke/github/GitHubPage.java +++ b/src/main/java/org/kohsuke/github/GitHubPage.java @@ -1,5 +1,8 @@ package org.kohsuke.github; +import java.util.Arrays; +import java.util.List; + /** * A page of results from GitHub. * @@ -13,4 +16,8 @@ interface GitHubPage { * @return the items */ I[] getItems(); + + default List getItemsList() { + return Arrays.asList(this.getItems()); + } } diff --git a/src/main/java/org/kohsuke/github/GitHubPageArrayAdapter.java b/src/main/java/org/kohsuke/github/GitHubPageArrayAdapter.java new file mode 100644 index 0000000000..50005f35fc --- /dev/null +++ b/src/main/java/org/kohsuke/github/GitHubPageArrayAdapter.java @@ -0,0 +1,21 @@ +package org.kohsuke.github; + +/** + * Represents a page of results + * + * @author Kohsuke Kawaguchi + * @param + * the generic type + */ +class GitHubPageArrayAdapter implements GitHubPage { + + private final I[] items; + + public GitHubPageArrayAdapter(I[] items) { + this.items = items; + } + + public I[] getItems() { + return items; + } +} diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index 89ac1e2114..8eb3f44b21 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -1,7 +1,6 @@ package org.kohsuke.github; import java.io.IOException; -import java.util.Iterator; import java.util.List; import java.util.Set; @@ -17,18 +16,18 @@ */ public class PagedIterable implements Iterable { - private final PaginatedEndpoint paginatedEndpoint; + private final PaginatedEndpoint, T> paginatedEndpoint; /** * Instantiates a new git hub page contents iterable. */ - PagedIterable(PaginatedEndpoint paginatedEndpoint) { + > PagedIterable(PaginatedEndpoint paginatedEndpoint) { this.paginatedEndpoint = paginatedEndpoint; } @Nonnull - public final Iterator iterator() { - return paginatedEndpoint.iterator(); + public final PagedIterator iterator() { + return new PagedIterator<>(items()); } @Nonnull @@ -61,6 +60,14 @@ public PagedIterable withPageSize(int size) { return this; } + PaginatedEndpointItems, T> items() { + return paginatedEndpoint.items(); + } + + PaginatedEndpointPages, T> pages() { + return paginatedEndpoint.pages(); + } + GitHubResponse 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 5c7cfda181..719bd2102e 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -17,9 +17,10 @@ * @param * the type parameter */ +@Deprecated public class PagedIterator implements Iterator { - private final PaginatedEndpointItems endpointIterator; + private final PaginatedEndpointItems, T> endpointIterator; /** * Instantiates a new paged iterator. @@ -27,7 +28,7 @@ public class PagedIterator implements Iterator { * @param endpointIterator * the base */ - PagedIterator(PaginatedEndpointItems 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 9d6fc67439..468a81d915 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -16,14 +16,13 @@ justification = "Constructed by JSON API") public class PagedSearchIterable extends PagedIterable { - private final PaginatedEndpoint, T> paginatedEndpoint; - + private final PaginatedEndpoint, T> searchPaginatedEndpoint; /** * Instantiates a new git hub page contents iterable. */ > PagedSearchIterable(PaginatedEndpoint paginatedEndpoint) { super(paginatedEndpoint); - this.paginatedEndpoint = paginatedEndpoint; + this.searchPaginatedEndpoint = paginatedEndpoint; } /** @@ -31,9 +30,10 @@ > PagedSearchIterable(PaginatedEndpoint withPageSize(int size) { return (PagedSearchIterable) super.withPageSize(size); } + + @Override + PaginatedEndpointItems, T> items() { + // TODO Auto-generated method stub + return super.items(); + } + + @Override + PaginatedEndpointPages, T> pages() { + // TODO Auto-generated method stub + return super.pages(); + } } diff --git a/src/main/java/org/kohsuke/github/PaginatedArrayEndpoint.java b/src/main/java/org/kohsuke/github/PaginatedArrayEndpoint.java new file mode 100644 index 0000000000..67c28efeee --- /dev/null +++ b/src/main/java/org/kohsuke/github/PaginatedArrayEndpoint.java @@ -0,0 +1,73 @@ +package org.kohsuke.github; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.*; +import java.util.function.Consumer; + +import javax.annotation.Nonnull; + +/** + * {@link PaginatedEndpoint} implementation that take a {@link Consumer} that initializes all the items on each page as + * they are retrieved. + * + * {@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 + * the type of items on each page + */ +class PaginatedArrayEndpoint extends PaginatedEndpoint, Item> { + + private class ArrayPages extends PaginatedEndpointPages, Item> { + + ArrayPages(GitHubClient client, + Class> pageType, + GitHubRequest request, + int pageSize, + Consumer itemInitializer) { + super(client, pageType, request, pageSize, itemInitializer); + } + + @Override + @NotNull protected GitHubResponse> sendNextRequest() throws IOException { + GitHubResponse response = client.sendRequest(nextRequest, + (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, arrayReceiverType)); + return new GitHubResponse<>(response, new GitHubPageArrayAdapter<>(response.body())); + } + } + + /** + * Pretend to get a specific page class type for the sake of the compile time strong typing. + * + * This class never uses {@code pageType}, so it is safe to pass null as the actual class value. + * + * @param + * The type of items in the array page. + * @param itemType + * The class instance for items in the array page. + * @return Always null, but cast to the appropriate class for compile time strong typing. + */ + private static Class> getArrayPageClass(Class itemType) { + return (Class>) null; + } + + private final Class arrayReceiverType; + + PaginatedArrayEndpoint(GitHubClient client, + GitHubRequest request, + Class arrayReceiverType, + Class itemType, + Consumer itemInitializer) { + super(client, request, getArrayPageClass(itemType), itemType, itemInitializer); + this.arrayReceiverType = arrayReceiverType; + } + + @Nonnull + @Override + public PaginatedEndpointPages, Item> pages() { + return new ArrayPages(client, pageType, request, pageSize, itemInitializer); + } +} diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java index a2ff5e10ad..bf5e5bf20c 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java @@ -1,8 +1,5 @@ package org.kohsuke.github; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import org.jetbrains.annotations.NotNull; - import java.io.IOException; import java.lang.reflect.Array; import java.util.*; @@ -23,75 +20,7 @@ */ class PaginatedEndpoint, Item> implements Iterable { - private static class ArrayIterable extends PaginatedEndpoint, I> { - - private class ArrayIterator extends PaginatedEndpointPages, I> { - - ArrayIterator(GitHubClient client, - Class> pageType, - GitHubRequest request, - int pageSize, - Consumer itemInitializer) { - super(client, pageType, request, pageSize, itemInitializer); - } - - @Override - @NotNull protected GitHubResponse> sendNextRequest() throws IOException { - GitHubResponse response = client.sendRequest(nextRequest, - (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, receiverType)); - return new GitHubResponse<>(response, new GitHubArrayPage<>(response.body())); - } - - } - - private final Class receiverType; - - private ArrayIterable(GitHubClient client, - GitHubRequest request, - Class receiverType, - Class itemType, - Consumer itemInitializer) { - super(client, request, GitHubArrayPage.getArrayPageClass(itemType), itemType, itemInitializer); - this.receiverType = receiverType; - } - - @Nonnull - @Override - public PaginatedEndpointPages, I> pages() { - return new ArrayIterator(client, pageType, request, pageSize, itemInitializer); - } - } - - /** - * Represents a page of results - * - * @author Kohsuke Kawaguchi - * @param - * the generic type - */ - private static class GitHubArrayPage implements GitHubPage { - - @SuppressFBWarnings(value = { "DM_NEW_FOR_GETCLASS" }, justification = "BUG?") - private static

, I> Class

getArrayPageClass(Class itemType) { - return (Class

) new GitHubArrayPage<>(itemType).getClass(); - } - - private final I[] items; - - public GitHubArrayPage(I[] items) { - this.items = items; - } - - private GitHubArrayPage(Class itemType) { - this.items = null; - } - - public I[] getItems() { - return items; - } - } - - private static class SinglePageEndpoint

, I> extends PaginatedEndpoint { + static class SinglePageEndpoint

, I> extends PaginatedEndpoint { private final P page; SinglePageEndpoint(P page, Class itemType) { @@ -107,29 +36,53 @@ public PaginatedEndpointPages pages() { } - static PaginatedEndpoint, I> fromSinglePage(I[] array, Class itemType) { - return fromSinglePage(new GitHubArrayPage<>(array), itemType); + static PaginatedEndpoint, I> fromSinglePage(I[] array, Class itemType) { + return fromSinglePage(new GitHubPageArrayAdapter<>(array), itemType); } static

, I> PaginatedEndpoint fromSinglePage(P page, Class itemType) { return new SinglePageEndpoint<>(page, itemType); } - static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient client, + static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient client, GitHubRequest request, Class receiverType, Consumer itemInitializer) { - return new ArrayIterable(client, - request, - (Class) receiverType, - (Class) receiverType.getComponentType(), - itemInitializer); + Class itemType = (Class) receiverType.getComponentType(); + return new PaginatedArrayEndpoint(client, request, receiverType, itemType, itemInitializer); } + /** + * Eagerly walk {@link Iterator} of {@link GitHubPage} and return the result in an array. + * + * @param pageIterator + * the {@link Iterator} of {@link GitHubPage} to read + * @return an array of all elements from the {@link Iterator} of pages + * @throws IOException + * if an I/O exception occurs. + */ + static List toList(final Iterator> pageIterator, Class itemType) + throws IOException { + try { + ArrayList pageList = new ArrayList<>(); + pageIterator.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 + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw e; + } + } + } protected final GitHubClient client; protected final Consumer itemInitializer; - protected final Class itemType; + protected final Class itemType; /** * Page size. 0 is default. */ @@ -164,7 +117,7 @@ static PaginatedEndpoint, I> ofArrayEndpoint(GitHubClient } @Nonnull - public final PaginatedEndpointItems items() { + public final PaginatedEndpointItems items() { return new PaginatedEndpointItems<>(this.pages()); } @@ -234,34 +187,6 @@ public final PaginatedEndpoint withPageSize(int size) { return this; } - /** - * 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 List toList(final PaginatedEndpointPages iterator, Class itemType) - throws IOException { - try { - ArrayList 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 - 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. diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java index a0c6b2875d..8f1bf5bf81 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java @@ -5,7 +5,7 @@ /** * This class is not thread-safe. Any one instance should only be called from a single thread. */ -class PaginatedEndpointItems implements Iterator { +class PaginatedEndpointItems, Item> implements Iterator { /** * Current batch of items. Each time {@link #next()} is called the next item in this array will be returned. After @@ -14,7 +14,7 @@ class PaginatedEndpointItems implements Iterator { * * @see #fetchNext() {@link #fetchNext()} for details on how this field is used. */ - private GitHubPage currentPage; + private Page currentPage; /** * The index of the next item on the page, the item that will be returned when {@link #next()} is called. @@ -23,12 +23,25 @@ class PaginatedEndpointItems implements Iterator { */ private int nextItemIndex; - private final PaginatedEndpointPages, Item> pageIterator; + private final PaginatedEndpointPages pageIterator; - PaginatedEndpointItems(PaginatedEndpointPages, Item> pageIterator) { + PaginatedEndpointItems(PaginatedEndpointPages pageIterator) { this.pageIterator = pageIterator; } + /** + * Get the current page. + * + * If not previously fetched, will attempt fetch the first page. Will still return the last page even after + * hasNext() returns false. + */ + public Page getCurrentPage() { + if (currentPage != null) { + peek(); + } + return currentPage; + } + /** * {@inheritDoc} */ diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java index e9cf9a034a..bb1e43f243 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointPages.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.URL; +import java.util.Iterator; import java.util.NoSuchElementException; import java.util.function.Consumer; @@ -11,30 +12,28 @@ /** * 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 - * -

- * } after iterating completes. + * inside the page). Also exposes {@link #finalResponse()} to allow getting a full {@link GitHubResponse} 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

+ * @param * type of each page (not the items in the page). */ -class PaginatedEndpointPages

, Item> implements java.util.Iterator

{ +class PaginatedEndpointPages, Item> implements Iterator { - static

, Item> PaginatedEndpointPages fromSinglePage(Class

pageType, - final P page) { + static

, I> PaginatedEndpointPages fromSinglePage(Class

pageType, final P page) { return new PaginatedEndpointPages<>(pageType, 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

finalResponse = null; + private GitHubResponse finalResponse = null; private final Consumer itemInitializer; /** * The page that will be returned when {@link #next()} is called. @@ -46,7 +45,7 @@ static

, Item> PaginatedEndpointPages fromSi * Will not be {@code null} after {@link #fetchNext()} is called if a new page was fetched. *

*/ - private P next; + private Page next; protected final GitHubClient client; @@ -56,32 +55,30 @@ static

, Item> PaginatedEndpointPages fromSi */ protected GitHubRequest nextRequest; - protected final Class

pageType; + protected final Class pageType; - private PaginatedEndpointPages(Class

pageType, P page) { + /* + * Constructor for PaginatedEndpointPages for single page + */ + PaginatedEndpointPages(Class pageType, Page page) { this(null, pageType, null, 0, null); this.next = page; } PaginatedEndpointPages(GitHubClient client, - Class

pageType, + Class pageType, GitHubRequest request, int pageSize, Consumer itemInitializer) { this.pageType = pageType; this.itemInitializer = itemInitializer; + this.client = client; 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."); - // } - // assert (request == null || "GET".equals(request.method())); - - this.client = client; this.nextRequest = request; } @@ -90,7 +87,7 @@ private PaginatedEndpointPages(Class

pageType, P page) { * * @return the final response of the iterator. */ - public GitHubResponse

finalResponse() { + public GitHubResponse finalResponse() { if (hasNext()) { throw new GHException("Final response is not available until after iterator is done."); } @@ -108,8 +105,8 @@ public boolean hasNext() { * {@inheritDoc} */ @Nonnull - public P next() { - P result = peek(); + public Page next() { + Page result = peek(); if (result == null) throw new NoSuchElementException(); next = null; @@ -120,9 +117,9 @@ public P next() { * * @return */ - public P peek() { + public Page peek() { if (next == null) { - P result = fetchNext(); + Page result = fetchNext(); if (result != null) { next = result; initializeItems(); @@ -131,6 +128,39 @@ public P peek() { return next; } + /** + * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is + * needed. + *

+ * 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. + *

+ *

+ * 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}. + *

+ */ + private Page fetchNext() { + if (nextRequest == null) + return null; // no more data to fetch + + Page result; + + URL url = nextRequest.url(); + try { + GitHubResponse 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; + } + /** * This method initializes items with local data after they are fetched. It is up to the implementer to decide what * local data to apply. @@ -147,7 +177,7 @@ private void initializeItems() { /** * Locate the next page from the pagination "Link" tag. */ - private void updateNextRequest(GitHubResponse

nextResponse) { + private void updateNextRequest(GitHubResponse nextResponse) { GitHubRequest result = null; String link = nextResponse.header("Link"); if (link != null) { @@ -168,40 +198,7 @@ private void updateNextRequest(GitHubResponse

nextResponse) { } } - /** - * Fetch is called at the start of {@link #hasNext()} or {@link #next()} to fetch another page of data if it is - * needed. - *

- * 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. - *

- *

- * 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}. - *

- */ - protected P fetchNext() { - if (nextRequest == null) - return null; // no more data to fetch - - P result; - - URL url = nextRequest.url(); - try { - GitHubResponse

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

sendNextRequest() throws IOException { + @NotNull protected GitHubResponse sendNextRequest() throws IOException { return client.sendRequest(nextRequest, (connectorResponse) -> GitHubResponse.parseBody(connectorResponse, pageType)); } diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json index 9ad47e7995..951bbc434e 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/reflect-config.json @@ -6870,7 +6870,7 @@ "allDeclaredClasses": true }, { - "name": "org.kohsuke.github.PaginatedEndpoint$1", + "name": "org.kohsuke.github.GitHubPageArrayAdapter", "allPublicFields": true, "allDeclaredFields": true, "queryAllPublicConstructors": true, @@ -6885,22 +6885,7 @@ "allDeclaredClasses": true }, { - "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable", - "allPublicFields": true, - "allDeclaredFields": true, - "queryAllPublicConstructors": true, - "queryAllDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredConstructors": true, - "queryAllPublicMethods": true, - "queryAllDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredMethods": true, - "allPublicClasses": true, - "allDeclaredClasses": true - }, - { - "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable$ArrayIterator", + "name": "org.kohsuke.github.PaginatedEndpointItems", "allPublicFields": true, "allDeclaredFields": true, "queryAllPublicConstructors": true, @@ -6915,7 +6900,7 @@ "allDeclaredClasses": true }, { - "name": "org.kohsuke.github.PaginatedEndpoint$GitHubArrayPage", + "name": "org.kohsuke.github.PaginatedEndpointPages", "allPublicFields": true, "allDeclaredFields": true, "queryAllPublicConstructors": true, @@ -6930,7 +6915,7 @@ "allDeclaredClasses": true }, { - "name": "org.kohsuke.github.PaginatedEndpointItems", + "name": "org.kohsuke.github.PaginatedEndpoint$SinglePageEndpoint", "allPublicFields": true, "allDeclaredFields": true, "queryAllPublicConstructors": true, @@ -6945,7 +6930,7 @@ "allDeclaredClasses": true }, { - "name": "org.kohsuke.github.PaginatedEndpointPages", + "name": "org.kohsuke.github.PaginatedArrayEndpoint", "allPublicFields": true, "allDeclaredFields": true, "queryAllPublicConstructors": true, @@ -6960,7 +6945,7 @@ "allDeclaredClasses": true }, { - "name": "org.kohsuke.github.PaginatedEndpoint$SinglePageEndpoint", + "name": "org.kohsuke.github.PaginatedArrayEndpoint$ArrayPages", "allPublicFields": true, "allDeclaredFields": true, "queryAllPublicConstructors": true, diff --git a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json index 8ed9d9980f..338f148956 100644 --- a/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json +++ b/src/main/resources/META-INF/native-image/org.kohsuke/github-api/serialization-config.json @@ -1377,24 +1377,21 @@ "name": "org.kohsuke.github.PaginatedEndpoint" }, { - "name": "org.kohsuke.github.PaginatedEndpoint$1" + "name": "org.kohsuke.github.GitHubPageArrayAdapter" }, { - "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable" - }, - { - "name": "org.kohsuke.github.PaginatedEndpoint$ArrayIterable$ArrayIterator" + "name": "org.kohsuke.github.PaginatedEndpointItems" }, { - "name": "org.kohsuke.github.PaginatedEndpoint$GitHubArrayPage" + "name": "org.kohsuke.github.PaginatedEndpointPages" }, { - "name": "org.kohsuke.github.PaginatedEndpointItems" + "name": "org.kohsuke.github.PaginatedEndpoint$SinglePageEndpoint" }, { - "name": "org.kohsuke.github.PaginatedEndpointPages" + "name": "org.kohsuke.github.PaginatedArrayEndpoint" }, { - "name": "org.kohsuke.github.PaginatedEndpoint$SinglePageEndpoint" + "name": "org.kohsuke.github.PaginatedArrayEndpoint$ArrayPagest" } ] diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index cb7324e7f5..73769d0913 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -407,7 +407,7 @@ public void testCommit() throws Exception { public void testCommitComment() throws Exception { GHRepository r = gitHub.getUser("jenkinsci").getRepository("jenkins"); PagedIterable comments = r.listCommitComments(); - List batch = comments.iterator().nextPage(); + List batch = comments.pages().next().getItemsList(); for (GHCommitComment comment : batch) { // System.out.println(comment.getBody()); assertThat(r, sameInstance(comment.getOwner())); diff --git a/src/test/java/org/kohsuke/github/GHRepositoryTest.java b/src/test/java/org/kohsuke/github/GHRepositoryTest.java index db5d892f85..b4d7105090 100644 --- a/src/test/java/org/kohsuke/github/GHRepositoryTest.java +++ b/src/test/java/org/kohsuke/github/GHRepositoryTest.java @@ -322,7 +322,7 @@ public void getCheckRuns() throws Exception { .getRepository("github-api") .getCheckRuns("78b9ff49d47daaa158eb373c4e2e040f739df8b9"); // Check if the paging works correctly - assertThat(checkRuns.withPageSize(2).iterator().nextPage(), hasSize(2)); + assertThat(checkRuns.withPageSize(2).items().nextPage(), hasSize(2)); // Check if the checkruns are all succeeded and if we got all of them int checkRunsCount = 0; diff --git a/src/test/java/org/kohsuke/github/GHUserTest.java b/src/test/java/org/kohsuke/github/GHUserTest.java index adeaa294e8..e695ecae6d 100644 --- a/src/test/java/org/kohsuke/github/GHUserTest.java +++ b/src/test/java/org/kohsuke/github/GHUserTest.java @@ -245,7 +245,7 @@ public void verifySuspendedAt() throws IOException { private Set count30(PagedIterable l) { Set users = new HashSet(); - PagedIterator itr = l.iterator(); + Iterator itr = l.iterator(); for (int i = 0; i < 30 && itr.hasNext(); i++) { users.add(itr.next()); } diff --git a/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java b/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java index 960a8e2307..9a23ecb740 100644 --- a/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java +++ b/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java @@ -154,7 +154,7 @@ private static Optional getWorkflowRun(GHRepository repository, .event(GHEvent.PULL_REQUEST) .list() .withPageSize(20) - .iterator() + .items() .nextPage(); for (GHWorkflowRun workflowRun : workflowRuns) { @@ -176,7 +176,7 @@ private static Optional getWorkflowRun(GHRepository repository, .event(GHEvent.WORKFLOW_DISPATCH) .list() .withPageSize(20) - .iterator() + .items() .nextPage(); for (GHWorkflowRun workflowRun : workflowRuns) { @@ -347,8 +347,7 @@ public void testArtifacts() throws IOException { checkArtifactProperties(artifactById, "artifact2"); // Test GHRepository#listArtifacts() as we are sure we have artifacts around - List artifactsFromRepo = new ArrayList<>( - repo.listArtifacts().withPageSize(2).iterator().nextPage()); + List artifactsFromRepo = new ArrayList<>(repo.listArtifacts().withPageSize(2).items().nextPage()); artifactsFromRepo.sort((a1, a2) -> a1.getName().compareTo(a2.getName())); // We have at least the two artifacts we just added @@ -507,7 +506,7 @@ public void testJobs() throws IOException { checkJobProperties(workflowRun.getId(), job1ById, "job1"); // Also test listAllJobs() works correctly - List allJobs = workflowRun.listAllJobs().withPageSize(10).iterator().nextPage(); + List allJobs = workflowRun.listAllJobs().withPageSize(10).items().nextPage(); assertThat(allJobs.size(), greaterThanOrEqualTo(2)); } diff --git a/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java b/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java new file mode 100644 index 0000000000..111185135d --- /dev/null +++ b/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java @@ -0,0 +1,103 @@ +package org.kohsuke.github; + +import org.junit.Test; + +import java.io.*; +import java.util.NoSuchElementException; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThrows; + +/** + * The Class PaginatedEndpointTest. + * + * @author Liam Newman + */ +public class PaginatedEndpointTest extends AbstractGitHubWireMockTest { + + /** + * Test + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void peekNullShouldThrow() throws IOException { + var page = new GitHubPageArrayAdapter<>(new Object[]{}); + var pageType = (Class>) page.getClass(); + + // calling next + var emptyPages = new PaginatedEndpointPages<>(pageType, page); + var emptyPageItems = emptyPages.next().getItems(); + assertThat(emptyPageItems.length, equalTo(0)); + assertThat(emptyPages.hasNext(), equalTo(false)); + // Calling next when hasNext() is false, throws. + assertThrows(NoSuchElementException.class, () -> emptyPages.next()); + + // Calling items.next() on an empty result should throw. + var items = new PaginatedEndpointItems<>(new PaginatedEndpointPages<>(pageType, page)); + assertThat(items.peek(), equalTo(null)); + assertThat(items.hasNext(), equalTo(false)); + assertThrows(NoSuchElementException.class, () -> items.next()); + } + + /** + * Test + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testDeprecatedNextPage() throws IOException { + var page = new GitHubPageArrayAdapter<>(new Object[]{ 1, 2, 3 }); + var pageType = (Class>) page.getClass(); + + var items = new PaginatedEndpointItems<>(new PaginatedEndpointPages<>(pageType, page)); + assertThat(items.next(), equalTo(1)); + // Current Page on a partially read page should return full page + var itemsPage = items.getCurrentPage().getItems(); + assertThat(itemsPage.length, equalTo(3)); + + var partialPage = items.nextPage(); + // Next Page on a partially read page should return + assertThat(partialPage.size(), equalTo(2)); + assertThrows(NoSuchElementException.class, () -> items.next()); + assertThrows(NoSuchElementException.class, () -> items.nextPage()); + } + + /** + * Test + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testNextPageError() throws IOException { + var page = new GitHubPageArrayAdapter<>(new Object[]{}) { + @Override + public Object[] getItems() { + throw new GHException("outer", new Exception("inner")); + } + }; + + var pageIo = new GitHubPageArrayAdapter<>(new Object[]{}) { + @Override + public Object[] getItems() { + throw new GHException("outer", new IOException("inner")); + } + }; + + final var pages = PaginatedEndpoint.fromSinglePage(page, Object.class).pages(); + + var e = assertThrows(GHException.class, () -> pages.finalResponse()); + assertThat(e.getMessage(), equalTo("Final response is not available until after iterator is done.")); + + var ex = assertThrows(GHException.class, () -> PaginatedEndpoint.toList(pages, Object.class)); + assertThat(ex.getMessage(), equalTo("outer")); + + final var pagesIo = PaginatedEndpoint.fromSinglePage(pageIo, Object.class).pages(); + var exIo = assertThrows(IOException.class, () -> PaginatedEndpoint.toList(pagesIo, Object.class)); + assertThat(exIo.getMessage(), equalTo("inner")); + } + +} From 9765cff52f476bedc79e8e1796bb5f9a408700cc Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Thu, 18 Sep 2025 23:56:20 -0700 Subject: [PATCH 11/15] More coverage --- .../kohsuke/github/GHCommitSearchBuilder.java | 21 ++++++++++------ .../kohsuke/github/PagedSearchIterable.java | 10 +++----- src/test/java/org/kohsuke/github/AppTest.java | 12 +++++++++ .../kohsuke/github/PaginatedEndpointTest.java | 25 +++++++++++++++++++ 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java index ecc1042df5..a8ca46ef8a 100644 --- a/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java @@ -173,14 +173,7 @@ public GHCommitSearchBuilder is(String v) { @Override public PagedSearchIterable list() { - return list(item -> { - String repoName = getRepoName(item.url); - try { - GHRepository repo = root().getRepository(repoName); - item.wrapUp(repo); - } catch (IOException ioe) { - } - }); + return list(this::lateBindGHCommit); } /** @@ -294,4 +287,16 @@ public GHCommitSearchBuilder user(String v) { protected String getApiUrl() { return "/search/commits"; } + + /** + * Exposed internally for testing only. + */ + void lateBindGHCommit(GHCommit item) { + String repoName = getRepoName(item.getUrl().toString()); + try { + GHRepository repo = root().getRepository(repoName); + item.wrapUp(repo); + } catch (IOException ioe) { + } + } } diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 468a81d915..0ee52501cc 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -60,14 +60,12 @@ public PagedSearchIterable withPageSize(int size) { } @Override - PaginatedEndpointItems, T> items() { - // TODO Auto-generated method stub - return super.items(); + PaginatedEndpointItems, T> items() { + return new PaginatedEndpointItems<>(pages()); } @Override - PaginatedEndpointPages, T> pages() { - // TODO Auto-generated method stub - return super.pages(); + PaginatedEndpointPages, T> pages() { + return searchPaginatedEndpoint.pages(); } } diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index 73769d0913..0668c226d7 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -432,6 +432,18 @@ public void testCommitSearch() throws IOException { GHCommit firstCommit = r.iterator().next(); assertThat(firstCommit.listFiles().toList(), is(not(empty()))); + + GHCommitSearchBuilder builder = new GHCommitSearchBuilder(GitHub.offline()); + GHException e = Assert.assertThrows(GHException.class, () -> builder.list().iterator().next()); + assertThat(e.getMessage(), equalTo("Failed to retrieve https://api.github.invalid/search/commits?q=")); + + // Verify that this item initalizer does not throw when it otherwise would + builder.lateBindGHCommit(new GHCommit() { + @Override + public URL getUrl() { + return firstCommit.getUrl(); + } + }); } /** diff --git a/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java b/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java index 111185135d..a0bc8191fd 100644 --- a/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java +++ b/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java @@ -65,6 +65,31 @@ public void testDeprecatedNextPage() throws IOException { assertThrows(NoSuchElementException.class, () -> items.nextPage()); } + /** + * Test + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + @Test + public void testIterators() throws IOException { + var page = new GitHubPageArrayAdapter<>(new Object[]{ 1, 2, 3 }); + + var endpoint = PaginatedEndpoint.fromSinglePage(page, Object.class); + + var iterator = endpoint.iterator(); + assertThat(iterator.next(), equalTo(1)); + assertThat(iterator.next(), equalTo(2)); + assertThat(iterator.next(), equalTo(3)); + assertThat(iterator.hasNext(), equalTo(false)); + + var pagedIterator = new PagedIterator<>(endpoint.items()); + assertThat(pagedIterator.next(), equalTo(1)); + var nextPage = pagedIterator.nextPage(); + assertThat(nextPage.size(), equalTo(2)); + assertThat(iterator.hasNext(), equalTo(false)); + } + /** * Test * From d7227096b23d85a19eec33cb673835937cdfbe89 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Fri, 19 Sep 2025 17:24:25 -0700 Subject: [PATCH 12/15] More coverage --- .../github/PaginatedEndpointItems.java | 2 +- .../org/kohsuke/github/GHRepositoryTest.java | 7 +++++ .../org/kohsuke/github/GHWorkflowRunTest.java | 29 +++++++++++++++---- .../org/kohsuke/github/GHWorkflowTest.java | 16 ++++++++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java index 8f1bf5bf81..099213f601 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpointItems.java @@ -36,7 +36,7 @@ class PaginatedEndpointItems, Item> implements Ite * hasNext() returns false. */ public Page getCurrentPage() { - if (currentPage != null) { + if (currentPage == null) { peek(); } return currentPage; diff --git a/src/test/java/org/kohsuke/github/GHRepositoryTest.java b/src/test/java/org/kohsuke/github/GHRepositoryTest.java index b4d7105090..876f49af23 100644 --- a/src/test/java/org/kohsuke/github/GHRepositoryTest.java +++ b/src/test/java/org/kohsuke/github/GHRepositoryTest.java @@ -4,6 +4,7 @@ import com.google.common.collect.Sets; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.io.IOUtils; +import org.assertj.core.util.Objects; import org.junit.Assert; import org.junit.Test; import org.kohsuke.github.GHCheckRun.Conclusion; @@ -321,6 +322,12 @@ public void getCheckRuns() throws Exception { PagedIterable checkRuns = gitHub.getOrganization("hub4j") .getRepository("github-api") .getCheckRuns("78b9ff49d47daaa158eb373c4e2e040f739df8b9"); + + var endpointItems = checkRuns.withPageSize(2).items(); + var currentPage = endpointItems.getCurrentPage(); + var firstPage = Objects.castIfBelongsToType(currentPage, GHCheckRunsPage.class); + assertThat(firstPage.getTotalCount(), equalTo(8)); + // Check if the paging works correctly assertThat(checkRuns.withPageSize(2).items().nextPage(), hasSize(2)); diff --git a/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java b/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java index 6d64e70038..7b5cda128c 100644 --- a/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java +++ b/src/test/java/org/kohsuke/github/GHWorkflowRunTest.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import org.assertj.core.util.Objects; import org.awaitility.Awaitility; import org.junit.Before; import org.junit.Test; @@ -17,10 +18,13 @@ import java.util.List; import java.util.Optional; import java.util.Scanner; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -347,7 +351,12 @@ public void testArtifacts() throws IOException { checkArtifactProperties(artifactById, "artifact2"); // Test GHRepository#listArtifacts() as we are sure we have artifacts around - List artifactsFromRepo = new ArrayList<>(repo.listArtifacts().withPageSize(2).items().nextPage()); + var endpointItems = repo.listArtifacts().withPageSize(2).items(); + var currentPage = endpointItems.getCurrentPage(); + var firstPage = Objects.castIfBelongsToType(currentPage, GHArtifactsPage.class); + assertThat(firstPage.getTotalCount(), equalTo(69)); + + List artifactsFromRepo = new ArrayList<>(endpointItems.nextPage()); artifactsFromRepo.sort((a1, a2) -> a1.getName().compareTo(a2.getName())); // We have at least the two artifacts we just added @@ -523,9 +532,13 @@ public void testJobs() throws IOException { latestPreexistingWorkflowRunId) .orElseThrow(() -> new IllegalStateException("We must have a valid workflow run starting from here")); - List jobs = workflowRun.listJobs() - .toList() - .stream() + var endpointItems = workflowRun.listJobs().items(); + var currentPage = endpointItems.getCurrentPage(); + var firstPage = Objects.castIfBelongsToType(currentPage, GHWorkflowJobsPage.class); + assertThat(firstPage.getTotalCount(), equalTo(2)); + + List jobs = StreamSupport + .stream(Spliterators.spliteratorUnknownSize(endpointItems, Spliterator.ORDERED), false) .sorted((j1, j2) -> j1.getName().compareTo(j2.getName())) .collect(Collectors.toList()); @@ -762,9 +775,13 @@ public void testStartupFailureConclusion() throws IOException { GHWorkflow ghWorkflow = repo.getWorkflow("startup-failure-workflow.yml"); - List ghWorkflowRunList = ghWorkflow.listRuns().toList(); + var endpointItems = ghWorkflow.listRuns().items(); + var currentPage = endpointItems.getCurrentPage(); + var firstPage = Objects.castIfBelongsToType(currentPage, GHWorkflowRunsPage.class); + assertThat(firstPage.getTotalCount(), equalTo(4)); - List list = ghWorkflowRunList.stream() + List list = StreamSupport + .stream(Spliterators.spliteratorUnknownSize(endpointItems, Spliterator.ORDERED), false) .filter(ghWorkflowRun -> ghWorkflowRun.getConclusion().equals(Conclusion.STARTUP_FAILURE)) .collect(Collectors.toList()); diff --git a/src/test/java/org/kohsuke/github/GHWorkflowTest.java b/src/test/java/org/kohsuke/github/GHWorkflowTest.java index 836907ae91..5720457b0b 100644 --- a/src/test/java/org/kohsuke/github/GHWorkflowTest.java +++ b/src/test/java/org/kohsuke/github/GHWorkflowTest.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import org.assertj.core.util.Objects; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -7,13 +8,16 @@ import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import static com.github.tomakehurst.wiremock.client.WireMock.containing; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.notNullValue; // TODO: Auto-generated Javadoc /** @@ -184,7 +188,15 @@ public void testListWorkflowRuns() throws IOException { */ @Test public void testListWorkflows() throws IOException { - List workflows = repo.listWorkflows().toList(); + + var endpointItems = repo.listWorkflows().items(); + var currentPage = endpointItems.getCurrentPage(); + var firstPage = Objects.castIfBelongsToType(currentPage, GHWorkflowsPage.class); + assertThat(firstPage.getTotalCount(), equalTo(1)); + + List workflows = StreamSupport + .stream(Spliterators.spliteratorUnknownSize(endpointItems, Spliterator.ORDERED), false) + .collect(Collectors.toList()); GHWorkflow workflow = workflows.get(0); assertThat(workflow.getId(), equalTo(6817859L)); From 2b4b7a2564011c4ee6fbeb18c0a9dbfb9bb61e8a Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Sat, 20 Sep 2025 00:20:54 -0700 Subject: [PATCH 13/15] Clean up --- .../org/kohsuke/github/GHAppInstallation.java | 9 ++---- .../GHAuthenticatedAppInstallation.java | 9 ++---- .../kohsuke/github/GHCommitFileIterable.java | 12 ++++---- .../java/org/kohsuke/github/GHMyself.java | 8 ++--- .../java/org/kohsuke/github/GHRepository.java | 30 +++++++------------ .../java/org/kohsuke/github/GHWorkflow.java | 8 ++--- .../github/GHWorkflowJobQueryBuilder.java | 3 +- .../org/kohsuke/github/GHWorkflowRun.java | 8 ++--- .../github/GHWorkflowRunQueryBuilder.java | 7 ++--- .../org/kohsuke/github/GitHubRequest.java | 7 +++-- .../kohsuke/github/PagedSearchIterable.java | 2 +- .../org/kohsuke/github/PaginatedEndpoint.java | 9 ++---- .../java/org/kohsuke/github/Requester.java | 11 +++++-- .../java/org/kohsuke/github/GHObjectTest.java | 21 ++++++------- .../kohsuke/github/PaginatedEndpointTest.java | 6 +++- 15 files changed, 65 insertions(+), 85 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHAppInstallation.java b/src/main/java/org/kohsuke/github/GHAppInstallation.java index cbfbef2df0..3aad6326c5 100644 --- a/src/main/java/org/kohsuke/github/GHAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAppInstallation.java @@ -263,11 +263,8 @@ public GHTargetType getTargetType() { */ @Deprecated public PagedSearchIterable listRepositories() { - GitHubRequest request; - - request = root().createRequest().withUrlPath("/installation/repositories").build(); - - return new PagedSearchIterable<>(new PaginatedEndpoint<>(root() - .getClient(), request, GHAppInstallationRepositoryResult.class, GHRepository.class, null)); + return new PagedSearchIterable<>(root().createRequest() + .withUrlPath("/installation/repositories") + .toPaginatedEndpoint(GHAppInstallationRepositoryResult.class, GHRepository.class, null)); } } diff --git a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java index 0eaa1e52ea..813b8f1416 100644 --- a/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java +++ b/src/main/java/org/kohsuke/github/GHAuthenticatedAppInstallation.java @@ -35,12 +35,9 @@ protected GHAuthenticatedAppInstallation(@Nonnull GitHub root) { * @return the paged iterable */ public PagedSearchIterable listRepositories() { - GitHubRequest request; - - request = root().createRequest().withUrlPath("/installation/repositories").build(); - - return new PagedSearchIterable<>(new PaginatedEndpoint<>(root() - .getClient(), request, GHAuthenticatedAppInstallationRepositoryResult.class, GHRepository.class, null)); + return new PagedSearchIterable<>(root().createRequest() + .withUrlPath("/installation/repositories") + .toPaginatedEndpoint(GHAuthenticatedAppInstallationRepositoryResult.class, GHRepository.class, null)); } } diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index 9196d8ca2c..999af46309 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -17,7 +17,7 @@ class GHCommitFileIterable extends PagedIterable { */ private static final int GH_FILE_LIMIT_PER_COMMIT_PAGE = 300; - private static PaginatedEndpoint createEndpointIterable(GHRepository owner, + private static PaginatedEndpoint createEndpoint(GHRepository owner, String sha, GHCommit.File[] files) { PaginatedEndpoint iterable; @@ -25,12 +25,10 @@ private static PaginatedEndpoint createEndpointIterable // create a page iterator that only provides one page iterable = PaginatedEndpoint.fromSinglePage(new GHCommitFilesPage(files), GHCommit.File.class); } else { - GitHubRequest request = owner.root() + iterable = owner.root() .createRequest() .withUrlPath(owner.getApiTailUrl("commits/" + sha)) - .build(); - iterable = new PaginatedEndpoint<>(owner.root() - .getClient(), request, GHCommitFilesPage.class, GHCommit.File.class, null); + .toPaginatedEndpoint(GHCommitFilesPage.class, GHCommit.File.class, null); } return iterable; } @@ -45,8 +43,8 @@ private static PaginatedEndpoint createEndpointIterable * @param files * the list of files initially populated */ - public GHCommitFileIterable(GHRepository owner, String sha, List files) { - super(createEndpointIterable(owner, sha, files != null ? files.toArray(new File[0]) : null)); + GHCommitFileIterable(GHRepository owner, String sha, List files) { + super(createEndpoint(owner, sha, files != null ? files.toArray(new File[0]) : null)); } @Override diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index ba0a8ae410..3436fc8d30 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -113,11 +113,9 @@ public synchronized Map getAllRepositories() { * app installations accessible to the user access token */ public PagedIterable getAppInstallations() { - return new PagedIterable<>(new PaginatedEndpoint<>(root().getClient(), - root().createRequest().withUrlPath(APP_INSTALLATIONS_URL).build(), - GHAppInstallationsPage.class, - GHAppInstallation.class, - null)); + return root().createRequest() + .withUrlPath(APP_INSTALLATIONS_URL) + .toIterable(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 8f5edbbed2..c544136980 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -1189,11 +1189,7 @@ public Map getBranches() throws IOException { * for a specific ref */ public PagedIterable getCheckRuns(String ref) { - GitHubRequest request = root().createRequest() - .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) - .build(); - return new PagedIterable<>(new PaginatedEndpoint<>(root() - .getClient(), request, GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(this))); + return getCheckRuns(ref, null); } /** @@ -1208,12 +1204,10 @@ public PagedIterable getCheckRuns(String ref) { * for a specific ref */ public PagedIterable getCheckRuns(String ref, Map params) { - GitHubRequest request = root().createRequest() + Requester requester = root().createRequest() .withUrlPath(String.format("/repos/%s/%s/commits/%s/check-runs", getOwnerName(), name, ref)) - .with(params) - .build(); - return new PagedIterable<>(new PaginatedEndpoint<>(root() - .getClient(), request, GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(this))); + .with(params); + return requester.toIterable(GHCheckRunsPage.class, GHCheckRun.class, item -> item.wrap(this)); } /** @@ -2554,11 +2548,9 @@ public boolean isVulnerabilityAlertsEnabled() throws IOException { * @return the paged iterable */ public PagedIterable listArtifacts() { - return new PagedIterable<>(new PaginatedEndpoint<>(this.root().getClient(), - root().createRequest().withUrlPath(getApiTailUrl("actions/artifacts")).build(), - GHArtifactsPage.class, - GHArtifact.class, - item -> item.wrapUp(this))); + return root().createRequest() + .withUrlPath(getApiTailUrl("actions/artifacts")) + .toIterable(GHArtifactsPage.class, GHArtifact.class, item -> item.wrapUp(this)); } /** @@ -2966,11 +2958,9 @@ public List listTopics() throws IOException { * @return the paged iterable */ public PagedIterable listWorkflows() { - return new PagedIterable<>(new PaginatedEndpoint<>(root().getClient(), - root().createRequest().withUrlPath(getApiTailUrl("actions/workflows")).build(), - GHWorkflowsPage.class, - GHWorkflow.class, - item -> item.wrapUp(this))); + return root().createRequest() + .withUrlPath(getApiTailUrl("actions/workflows")) + .toIterable(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 2e99fcd826..74e4b0a4b3 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflow.java +++ b/src/main/java/org/kohsuke/github/GHWorkflow.java @@ -153,11 +153,9 @@ public String getState() { * @return the paged iterable */ public PagedIterable listRuns() { - return new PagedIterable<>(new PaginatedEndpoint<>(owner.root().getClient(), - root().createRequest().withUrlPath(getApiRoute(), "runs").build(), - GHWorkflowRunsPage.class, - GHWorkflowRun.class, - item -> item.wrapUp(owner))); + return root().createRequest() + .withUrlPath(getApiRoute(), "runs") + .toIterable(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 7556737e03..a7656f3962 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowJobQueryBuilder.java @@ -48,7 +48,6 @@ public GHWorkflowJobQueryBuilder latest() { */ @Override public PagedIterable list() { - return new PagedIterable<>(new PaginatedEndpoint<>(repo.root() - .getClient(), req.build(), GHWorkflowJobsPage.class, GHWorkflowJob.class, item -> item.wrapUp(repo))); + return req.toIterable(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 e0ac93a612..bfd20136d8 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRun.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRun.java @@ -579,11 +579,9 @@ public PagedIterable listAllJobs() { * @return the paged iterable */ public PagedIterable listArtifacts() { - return new PagedIterable<>(new PaginatedEndpoint<>(owner.root().getClient(), - root().createRequest().withUrlPath(getApiRoute(), "artifacts").build(), - GHArtifactsPage.class, - GHArtifact.class, - item -> item.wrapUp(owner))); + return root().createRequest() + .withUrlPath(getApiRoute(), "artifacts") + .toIterable(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 60c891e69b..97981663a6 100644 --- a/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHWorkflowRunQueryBuilder.java @@ -133,11 +133,8 @@ public GHWorkflowRunQueryBuilder headSha(String headSha) { */ @Override public PagedIterable list() { - return new PagedIterable<>(new PaginatedEndpoint<>(repo.root().getClient(), - req.withUrlPath(repo.getApiTailUrl("actions/runs")).build(), - GHWorkflowRunsPage.class, - GHWorkflowRun.class, - item -> item.wrapUp(repo))); + return req.withUrlPath(repo.getApiTailUrl("actions/runs")) + .toIterable(GHWorkflowRunsPage.class, GHWorkflowRun.class, item -> item.wrapUp(repo)); } /** diff --git a/src/main/java/org/kohsuke/github/GitHubRequest.java b/src/main/java/org/kohsuke/github/GitHubRequest.java index 20cf1462a4..51eec456ec 100644 --- a/src/main/java/org/kohsuke/github/GitHubRequest.java +++ b/src/main/java/org/kohsuke/github/GitHubRequest.java @@ -323,10 +323,11 @@ public B with(@WillClose InputStream body) throws IOException { * @return the request builder */ public B with(Map map) { - for (Map.Entry entry : map.entrySet()) { - with(entry.getKey(), entry.getValue()); + if (map != null) { + for (Map.Entry entry : map.entrySet()) { + with(entry.getKey(), entry.getValue()); + } } - return (B) this; } diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 0ee52501cc..6531f449bc 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -18,7 +18,7 @@ public class PagedSearchIterable extends PagedIterable { private final PaginatedEndpoint, T> searchPaginatedEndpoint; /** - * Instantiates a new git hub page contents iterable. + * Instantiates a new paged search iterable. */ > PagedSearchIterable(PaginatedEndpoint paginatedEndpoint) { super(paginatedEndpoint); diff --git a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java index bf5e5bf20c..ef9428c966 100644 --- a/src/main/java/org/kohsuke/github/PaginatedEndpoint.java +++ b/src/main/java/org/kohsuke/github/PaginatedEndpoint.java @@ -18,7 +18,7 @@ * @param * the type of items on each page */ -class PaginatedEndpoint, Item> implements Iterable { +class PaginatedEndpoint, Item> { static class SinglePageEndpoint

, I> extends PaginatedEndpoint { private final P page; @@ -79,6 +79,7 @@ static List toList(final Iterator> pageIterator, } } } + protected final GitHubClient client; protected final Consumer itemInitializer; @@ -121,12 +122,6 @@ public final PaginatedEndpointItems items() { return new PaginatedEndpointItems<>(this.pages()); } - @Nonnull - @Override - public final Iterator iterator() { - return this.items(); - } - /** * * @return diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 9eaa2d9fe7..5228ab50b3 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -202,8 +202,7 @@ public void sendGraphQL() throws IOException { public

, R> PagedIterable toIterable(Class

pageType, Class itemType, Consumer itemInitializer) { - GitHubRequest request = build(); - return new PagedIterable<>(new PaginatedEndpoint<>(client, request, pageType, itemType, itemInitializer)); + return new PagedIterable<>(toPaginatedEndpoint(pageType, itemType, itemInitializer)); } /** @@ -225,4 +224,12 @@ public PagedIterable toIterable(Class receiverType, Consumer item GitHubRequest request = build(); return new PagedIterable<>(PaginatedEndpoint.ofArrayEndpoint(client, request, receiverType, itemInitializer)); } + +

, R> PaginatedEndpoint toPaginatedEndpoint(Class

pageType, + Class itemType, + Consumer itemInitializer) { + GitHubRequest request = build(); + return new PaginatedEndpoint<>(client, request, pageType, itemType, itemInitializer); + } + } diff --git a/src/test/java/org/kohsuke/github/GHObjectTest.java b/src/test/java/org/kohsuke/github/GHObjectTest.java index 04cb3ac365..5e06a2a08e 100644 --- a/src/test/java/org/kohsuke/github/GHObjectTest.java +++ b/src/test/java/org/kohsuke/github/GHObjectTest.java @@ -25,16 +25,17 @@ public GHObjectTest() { @Test public void test_toString() throws Exception { GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG); - assertThat(org.toString(), containsString("login=hub4j-test-org")); - assertThat(org.toString(), containsString("location=")); - assertThat(org.toString(), containsString("blog=")); - assertThat(org.toString(), containsString("email=")); - assertThat(org.toString(), containsString("bio=")); - assertThat(org.toString(), containsString("name=")); - assertThat(org.toString(), containsString("company=")); - assertThat(org.toString(), containsString("type=Organization")); - assertThat(org.toString(), containsString("followers=0")); - assertThat(org.toString(), containsString("hireable=false")); + String orgString = org.toString(); + assertThat(orgString, containsString("login=hub4j-test-org")); + assertThat(orgString, containsString("location=")); + assertThat(orgString, containsString("blog=")); + assertThat(orgString, containsString("email=")); + assertThat(orgString, containsString("bio=")); + assertThat(orgString, containsString("name=")); + assertThat(orgString, containsString("company=")); + assertThat(orgString, containsString("type=Organization")); + assertThat(orgString, containsString("followers=0")); + assertThat(orgString, containsString("hireable=false")); // getResponseHeaderFields is deprecated but we should not break it. assertThat(org.getResponseHeaderFields(), notNullValue()); diff --git a/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java b/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java index a0bc8191fd..2aaa189b4f 100644 --- a/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java +++ b/src/test/java/org/kohsuke/github/PaginatedEndpointTest.java @@ -77,7 +77,11 @@ public void testIterators() throws IOException { var endpoint = PaginatedEndpoint.fromSinglePage(page, Object.class); - var iterator = endpoint.iterator(); + // Removed Iterable from PaginatedEndpoint for simplicity. + // Can be added later if desired, swap in this line to cover iterator() method. + // var iterator = endpoint.iterator(); + var iterator = endpoint.items(); + assertThat(iterator.next(), equalTo(1)); assertThat(iterator.next(), equalTo(2)); assertThat(iterator.next(), equalTo(3)); From f6c70d74a0b30f4d3f1419f5ed5680471f3c3b50 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Sat, 20 Sep 2025 00:34:29 -0700 Subject: [PATCH 14/15] Name change --- .../java/org/kohsuke/github/GHCommitFileIterable.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index 999af46309..a514268b41 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -20,17 +20,17 @@ class GHCommitFileIterable extends PagedIterable { private static PaginatedEndpoint createEndpoint(GHRepository owner, String sha, GHCommit.File[] files) { - PaginatedEndpoint iterable; + PaginatedEndpoint endpoint; if (files != null && files.length < GH_FILE_LIMIT_PER_COMMIT_PAGE) { - // create a page iterator that only provides one page - iterable = PaginatedEndpoint.fromSinglePage(new GHCommitFilesPage(files), GHCommit.File.class); + // create an endpoint that only reads one already loaded page + endpoint = PaginatedEndpoint.fromSinglePage(new GHCommitFilesPage(files), GHCommit.File.class); } else { - iterable = owner.root() + endpoint = owner.root() .createRequest() .withUrlPath(owner.getApiTailUrl("commits/" + sha)) .toPaginatedEndpoint(GHCommitFilesPage.class, GHCommit.File.class, null); } - return iterable; + return endpoint; } /** From 8b81765b95bb52c911afaa1745a9ac396cb98b4b Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Sat, 20 Sep 2025 00:50:42 -0700 Subject: [PATCH 15/15] One more --- .../java/org/kohsuke/github/GHCommitFileIterable.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java index a514268b41..6332323fbe 100644 --- a/src/main/java/org/kohsuke/github/GHCommitFileIterable.java +++ b/src/main/java/org/kohsuke/github/GHCommitFileIterable.java @@ -19,11 +19,12 @@ class GHCommitFileIterable extends PagedIterable { private static PaginatedEndpoint createEndpoint(GHRepository owner, String sha, - GHCommit.File[] files) { + List files) { PaginatedEndpoint endpoint; - if (files != null && files.length < GH_FILE_LIMIT_PER_COMMIT_PAGE) { + if (files != null && files.size() < GH_FILE_LIMIT_PER_COMMIT_PAGE) { // create an endpoint that only reads one already loaded page - endpoint = PaginatedEndpoint.fromSinglePage(new GHCommitFilesPage(files), GHCommit.File.class); + endpoint = PaginatedEndpoint.fromSinglePage(new GHCommitFilesPage(files.toArray(new File[0])), + GHCommit.File.class); } else { endpoint = owner.root() .createRequest() @@ -44,7 +45,7 @@ private static PaginatedEndpoint createEndpoint(GHRepos * the list of files initially populated */ GHCommitFileIterable(GHRepository owner, String sha, List files) { - super(createEndpoint(owner, sha, files != null ? files.toArray(new File[0]) : null)); + super(createEndpoint(owner, sha, files)); } @Override