Skip to content

Commit 9f96966

Browse files
authored
Java 8 Stream Documentation improvements (#298)
* Added test for lazy Stream evaluation (#295). * Cleaned up Stream support documentaion (#295).
1 parent ce32db4 commit 9f96966

File tree

5 files changed

+137
-68
lines changed

5 files changed

+137
-68
lines changed

README.md

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ To utilize the GitLab API for Java in your project, simply add the following dep
1111
```java
1212
dependencies {
1313
...
14-
compile group: 'org.gitlab4j', name: 'gitlab4j-api', version: '4.9.15'
14+
compile group: 'org.gitlab4j', name: 'gitlab4j-api', version: '4.9.16'
1515
}
1616
```
1717

@@ -22,7 +22,7 @@ dependencies {
2222
<dependency>
2323
<groupId>org.gitlab4j</groupId>
2424
<artifactId>gitlab4j-api</artifactId>
25-
<version>4.9.15</version>
25+
<version>4.9.16</version>
2626
</dependency>
2727
```
2828

@@ -141,34 +141,41 @@ List<Project> allProjects = projectPager.all();
141141

142142
---
143143
## Java 8 Stream Support
144-
As of GitLab4J-API 4.9.2, you can also stream list based items in a Java 8 Stream using a Pager instance as follows:
145-
```java
146-
// Get a Pager instance to get a Stream<Project> instance.
147-
Pager<Project> projectPager = gitlabApi.getProjectsApi().getProjects(10);
144+
As of GitLab4J-API 4.9.2, all GitLabJ-API methods that return a List result also similarlly named method returns a Java 8 Stream. The Stream returning methods use the following naming convention: ```getXxxxxStream()```.
145+
146+
147+
**IMPORTANT**
148+
The built-in methods that return a Stream do so using ___eager evaluation___, meaning all items are pre-fetched from the GitLab server and a Stream is returned which will stream those items. **Eager evaluation does NOT support paralell reading of data from ther server, it does however allow for paralell processing of the Stream post data fetch.**
149+
150+
To stream using ___lazy evaluation___, use the GitLab4J-API methods that return a ```Pager``` instance, and then call the ```lazyStream()``` method on the ```Pager``` instance to create a lazy evaluation Stream. The Stream utilizes the ```Pager``` instance to page through the available items. **A lazy Stream does NOT support parallel operations or skipping.**
151+
152+
153+
**Eager evaluation example usage:**
148154

149-
// Stream the Projects printing out the project name.
150-
projectPager.stream().map(Project::getName).forEach(name -> System.out.println(name));
151-
```
152-
The following API classes also include ```getXxxxxStream()``` methods which return a Java 8 Stream:
153-
```
154-
GroupApi
155-
IssuesApi
156-
NotesApi
157-
ProjectApi
158-
RepositoryApi
159-
TagsApi
160-
UserApi
161-
```
162-
Example usage:
163155
```java
164-
// Stream the visible Projects printing out the project name.
165-
gitlabApi.getProjectsApi().getProjectsStream().map(Project::getName).forEach(name -> System.out.println(name));
156+
// Stream the visible projects printing out the project name.
157+
Stream<Project> projectStream = gitlabApi.getProjectApi().getProjectsStream();
158+
projectStream.map(Project::getName).forEach(name -> System.out.println(name));
166159

167160
// Operate on the stream in parallel, this example sorts User instances by username
161+
// NOTE: Fetching of the users is not done in paralell,
162+
// only the soprting of the users is a paralell operation.
168163
Stream<User> stream = new UserApi(gitLabApi).getUsersStream();
169-
List<User> sortedUsers = stream.parallel().sorted(comparing(User::getUsername)).collect(toList());
164+
List<User> users = stream.parallel().sorted(comparing(User::getUsername)).collect(toList());
165+
```
166+
167+
**Lazy evaluation example usage:**
168+
169+
```java
170+
// Get a Pager instance to that will be used to lazily stream Project instances.
171+
// In this example, 10 Projects per page will be pre-fetched.
172+
Pager<Project> projectPager = gitlabApi.getProjectApi().getProjects(10);
173+
174+
// Lazily stream the Projects, printing out each project name, limit the output to 5 project names
175+
projectPager.lazyStream().limit(5).map(Project::getName).forEach(name -> System.out.println(name));
170176
```
171177

178+
172179
---
173180
## Java 8 Optional&lt;T&gt; Support
174181
GitLab4J-API supports Java 8 Optional&lt;T&gt; for API calls that result in the return of a single item. Here is an example on how to use the Java 8 Optional&lt;T&gt; API calls:

src/main/java/org/gitlab4j/api/GitLabApi.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class GitLabApi {
2727
private final static Logger LOGGER = Logger.getLogger(GitLabApi.class.getName());
2828

2929
/** GitLab4J default per page. GitLab will ignore anything over 100. */
30-
public static final int DEFAULT_PER_PAGE = 100;
30+
public static final int DEFAULT_PER_PAGE = 96;
3131

3232
/** Specifies the version of the GitLab API to communicate with. */
3333
public enum ApiVersion {
@@ -1393,7 +1393,7 @@ public static final <T> T orElseThrow(Optional<T> optional) throws GitLabApiExce
13931393

13941394
return (optional.get());
13951395
}
1396-
1396+
13971397
/**
13981398
* Gets the SnippetsApi instance owned by this GitLabApi instance. The SnippetsApi is used
13991399
* to perform all snippet related API calls.

src/main/java/org/gitlab4j/api/Pager.java

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
import com.fasterxml.jackson.databind.ObjectMapper;
1919

2020
/**
21-
* <p>This class defines an Iterator implementation that is used as a paging iterator for all API methods that
21+
* <p>This class defines an Iterator implementation that is used as a paging iterator for all API methods that
2222
* return a List of objects. It hides the details of interacting with the GitLab API when paging is involved
2323
* simplifying accessing large lists of objects.</p>
24-
*
24+
*
2525
* <p>Example usage:</p>
26-
*
26+
*
2727
* <pre>
2828
* // Get a Pager instance that will page through the projects with 10 projects per page
2929
* Pager&lt;Project&gt; projectPager = gitlabApi.getProjectsApi().getProjectsPager(10);
@@ -35,8 +35,8 @@
3535
* System.out.println(project.getName() + " : " + project.getDescription());
3636
* }
3737
* }
38-
* </pre>
39-
*
38+
* </pre>
39+
*
4040
* @param <T> the GitLab4J type contained in the List.
4141
*/
4242
public class Pager<T> implements Iterator<List<T>>, Constants {
@@ -48,6 +48,7 @@ public class Pager<T> implements Iterator<List<T>>, Constants {
4848

4949
private List<String> pageParam = new ArrayList<>(1);
5050
private List<T> currentItems;
51+
private Stream<T> pagerStream = null;
5152

5253
private AbstractApi api;
5354
private MultivaluedMap<String, String> queryParams;
@@ -59,7 +60,7 @@ public class Pager<T> implements Iterator<List<T>>, Constants {
5960

6061
/**
6162
* Creates a Pager instance to access the API through the specified path and query parameters.
62-
*
63+
*
6364
* @param api the AbstractApi implementation to communicate through
6465
* @param type the GitLab4J type that will be contained in the List
6566
* @param itemsPerPage items per page
@@ -114,7 +115,7 @@ public class Pager<T> implements Iterator<List<T>>, Constants {
114115

115116
/**
116117
* Get the specified integer header value from the Response instance.
117-
*
118+
*
118119
* @param response the Response instance to get the value from
119120
* @param key the HTTP header key to get the value for
120121
* @return the specified integer header value from the Response instance
@@ -136,7 +137,7 @@ private int getHeaderValue(Response response, String key) throws GitLabApiExcept
136137

137138
/**
138139
* Sets the "page" query parameter.
139-
*
140+
*
140141
* @param page the value for the "page" query parameter
141142
*/
142143
private void setPageParam(int page) {
@@ -204,7 +205,7 @@ public List<T> next() {
204205

205206
/**
206207
* This method is not implemented and will throw an UnsupportedOperationException if called.
207-
*
208+
*
208209
* @throws UnsupportedOperationException when invoked
209210
*/
210211
@Override
@@ -289,7 +290,7 @@ public List<T> page(int pageNumber) {
289290
throw new RuntimeException(e);
290291
}
291292
}
292-
293+
293294
/**
294295
* Gets all the items from each page as a single List instance.
295296
*
@@ -312,34 +313,62 @@ public List<T> all() throws GitLabApiException {
312313
}
313314

314315
/**
315-
* Builds and returns a Stream instance for streaming all the items from each page.
316+
* Builds and returns a Stream instance which is pre-populated with all items from all pages.
316317
*
317-
* @return a Stream instance for streaming all the items from each pag
318-
* @throws GitLabApiException if any error occurs
318+
* @return a Stream instance which is pre-populated with all items from all pages
319+
* @throws IllegalStateException if Stream has already been issued
320+
* @throws GitLabApiException if any other error occurs
319321
*/
320-
public Stream<T> stream() throws GitLabApiException {
322+
public Stream<T> stream() throws GitLabApiException, IllegalStateException {
321323

322-
// Make sure that current page is 0, this will ensure the whole list is streamed
323-
// regardless of what page the instance is currently on.
324-
currentPage = 0;
324+
if (pagerStream == null) {
325+
synchronized (this) {
326+
if (pagerStream == null) {
325327

326-
// Create a Stream.Builder to contain all the items. This is more efficient than
327-
// getting a List with all() and streaming that List
328-
Stream.Builder<T> streamBuilder = Stream.builder();
328+
// Make sure that current page is 0, this will ensure the whole list is streamed
329+
// regardless of what page the instance is currently on.
330+
currentPage = 0;
329331

330-
// Iterate through the pages and append each page of items to the stream builder
331-
while (hasNext()) {
332-
next().forEach(streamBuilder);
333-
}
332+
// Create a Stream.Builder to contain all the items. This is more efficient than
333+
// getting a List with all() and streaming that List
334+
Stream.Builder<T> streamBuilder = Stream.builder();
334335

335-
return (streamBuilder.build());
336+
// Iterate through the pages and append each page of items to the stream builder
337+
while (hasNext()) {
338+
next().forEach(streamBuilder);
339+
}
340+
341+
pagerStream = streamBuilder.build();
342+
return (pagerStream);
343+
}
344+
}
345+
}
346+
347+
throw new IllegalStateException("Stream already issued");
336348
}
337349

338-
public Stream<T> lazyStream() {
339-
// Make sure that current page is 0, this will ensure the whole list is streamed
340-
// regardless of what page the instance is currently on.
341-
currentPage = 0;
350+
/**
351+
* Creates a Stream instance for lazily streaming items from the GitLab server.
352+
*
353+
* @return a Stream instance for lazily streaming items from the GitLab server
354+
* @throws IllegalStateException if Stream has already been issued
355+
*/
356+
public Stream<T> lazyStream() throws IllegalStateException {
357+
358+
if (pagerStream == null) {
359+
synchronized (this) {
360+
if (pagerStream == null) {
361+
362+
// Make sure that current page is 0, this will ensure the whole list is streamed
363+
// regardless of what page the instance is currently on.
364+
currentPage = 0;
365+
366+
pagerStream = StreamSupport.stream(new PagerSpliterator<T>(this), false);
367+
return (pagerStream);
368+
}
369+
}
370+
}
342371

343-
return StreamSupport.stream(new PagerSpliterator<T>(this), false);
372+
throw new IllegalStateException("Stream already issued");
344373
}
345374
}

src/main/java/org/gitlab4j/api/ProjectApi.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -818,15 +818,15 @@ public Project createProject(Project project, String importUrl) throws GitLabApi
818818
if (isApiVersion(ApiVersion.V3)) {
819819
boolean isPublic = (project.getPublic() != null ? project.getPublic() : project.getVisibility() == Visibility.PUBLIC);
820820
formData.withParam("public", isPublic);
821-
821+
822822
if (project.getTagList() != null && !project.getTagList().isEmpty()) {
823823
throw new IllegalArgumentException("GitLab API v3 does not support tag lists when creating projects");
824824
}
825825
} else {
826826
Visibility visibility = (project.getVisibility() != null ? project.getVisibility() :
827827
project.getPublic() == Boolean.TRUE ? Visibility.PUBLIC : null);
828828
formData.withParam("visibility", visibility);
829-
829+
830830
if (project.getTagList() != null && !project.getTagList().isEmpty()) {
831831
formData.withParam("tag_list", String.join(",", project.getTagList()));
832832
}
@@ -1057,15 +1057,15 @@ public Project updateProject(Project project) throws GitLabApiException {
10571057
formData.withParam("visibility_level", project.getVisibilityLevel());
10581058
boolean isPublic = (project.getPublic() != null ? project.getPublic() : project.getVisibility() == Visibility.PUBLIC);
10591059
formData.withParam("public", isPublic);
1060-
1060+
10611061
if (project.getTagList() != null && !project.getTagList().isEmpty()) {
10621062
throw new IllegalArgumentException("GitLab API v3 does not support tag lists when updating projects");
10631063
}
10641064
} else {
10651065
Visibility visibility = (project.getVisibility() != null ? project.getVisibility() :
10661066
project.getPublic() == Boolean.TRUE ? Visibility.PUBLIC : null);
10671067
formData.withParam("visibility", visibility);
1068-
1068+
10691069
if (project.getTagList() != null && !project.getTagList().isEmpty()) {
10701070
formData.withParam("tag_list", String.join(",", project.getTagList()));
10711071
}
@@ -1090,7 +1090,7 @@ public void deleteProject(Object projectIdOrPath) throws GitLabApiException {
10901090

10911091
/**
10921092
* Forks a project into the user namespace of the authenticated user or the one provided.
1093-
* The forking operation for a project is asynchronous and is completed in a background job.
1093+
* The forking operation for a project is asynchronous and is completed in a background job.
10941094
* The request will return immediately.
10951095
*
10961096
* <pre><code>POST /projects/:id/fork</code></pre>
@@ -1109,7 +1109,7 @@ public Project forkProject(Object projectIdOrPath, String namespace) throws GitL
11091109

11101110
/**
11111111
* Forks a project into the user namespace of the authenticated user or the one provided.
1112-
* The forking operation for a project is asynchronous and is completed in a background job.
1112+
* The forking operation for a project is asynchronous and is completed in a background job.
11131113
* The request will return immediately.
11141114
*
11151115
* <pre><code>POST /projects/:id/fork</code></pre>
@@ -1130,7 +1130,7 @@ public Project forkProject(Object projectIdOrPath, Integer namespaceId) throws G
11301130
* Create a forked from/to relation between existing projects.
11311131
*
11321132
* <pre><code>POST /projects/:id/fork/:forkFromId</code></pre>
1133-
*
1133+
*
11341134
*
11351135
* @param projectIdOrPath projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance
11361136
* @param forkedFromId the ID of the project that was forked from
@@ -1455,7 +1455,7 @@ public List<ProjectUser> getProjectUsers(Object projectIdOrPath, String search)
14551455
}
14561456

14571457
/**
1458-
* Get a Pager of project users matching the specified search string. This Pager includes
1458+
* Get a Pager of project users matching the specified search string. This Pager includes
14591459
* all project members and all users assigned to project parent groups.
14601460
*
14611461
* <pre><code>GET /projects/:id/users</code></pre>
@@ -1644,7 +1644,7 @@ public Optional<ProjectHook> getOptionalHook(Object projectIdOrPath, Integer hoo
16441644
* @return the added ProjectHook instance
16451645
* @throws GitLabApiException if any exception occurs
16461646
*/
1647-
public ProjectHook addHook(String projectName, String url, ProjectHook enabledHooks, boolean enableSslVerification, String secretToken)
1647+
public ProjectHook addHook(String projectName, String url, ProjectHook enabledHooks, boolean enableSslVerification, String secretToken)
16481648
throws GitLabApiException {
16491649

16501650
if (projectName == null) {
@@ -2266,9 +2266,9 @@ public void deletePushRules(Object projectIdOrPath) throws GitLabApiException {
22662266

22672267
/**
22682268
* Get a list of projects that were forked from the specified project.
2269-
*
2269+
*
22702270
* <pre><code>GET /projects/:id/forks</code></pre>
2271-
*
2271+
*
22722272
* @param projectIdOrPath projectIdOrPath the project in the form of an Integer(ID), String(path), or Project instance, required
22732273
* @return a List of forked projects
22742274
* @throws GitLabApiException if any exception occurs
@@ -2382,7 +2382,7 @@ public Project transferProject(Object projectIdOrPath, String namespace) throws
23822382
}
23832383

23842384
/**
2385-
* Uploads and sets the project avatar for the specified project.
2385+
* Uploads and sets the project avatar for the specified project.
23862386
*
23872387
* <pre><code>PUT /projects/:id/uploads</code></pre>
23882388
*
@@ -2395,4 +2395,4 @@ public Project setProjectAvatar(Object projectIdOrPath, File avatarFile) throws
23952395
Response response = putUpload(Response.Status.OK, "avatar", avatarFile, "projects", getProjectIdOrPath(projectIdOrPath));
23962396
return (response.readEntity(Project.class));
23972397
}
2398-
}
2398+
}

0 commit comments

Comments
 (0)