main.java.com.cloudant.client.internal.views.ViewResponseImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cloudant-client Show documentation
Show all versions of cloudant-client Show documentation
Official Cloudant client for Java
/*
* Copyright (c) 2015 IBM Corp. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
package com.cloudant.client.internal.views;
import com.cloudant.client.api.model.Document;
import com.cloudant.client.api.views.ViewResponse;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
class ViewResponseImpl implements ViewResponse {
private final ViewQueryParameters initialQueryParameters;
private final boolean hasPrevious;
private final boolean hasNext;
private final long pageNumber;
private PageMetadata nextPageMetadata;
private PageMetadata previousPageMetadata;
private final long totalRows;
private final long resultFrom;
private final long resultTo;
private final List> rows = new ArrayList>();
private List keys = null;
private List values = null;
private List docs = null;
ViewResponseImpl(ViewQueryParameters viewQueryParameters, JsonObject response) {
this(viewQueryParameters, response, null);
}
ViewResponseImpl(ViewQueryParameters initialQueryParameters, JsonObject response,
PageMetadata pageMetadata) {
this.initialQueryParameters = initialQueryParameters;
PageMetadata.PagingDirection thisPageDirection;
if (pageMetadata == null) {
previousPageMetadata = null;
pageNumber = 1l;
//from a first page we can only page FORWARD
thisPageDirection = PageMetadata.PagingDirection.FORWARD;
} else {
this.pageNumber = pageMetadata.pageNumber;
thisPageDirection = pageMetadata.direction;
}
//build the rows from the response
JsonArray rowsArray = response.getAsJsonArray("rows");
if (rowsArray != null) {
for (JsonElement row : rowsArray) {
rows.add(new RowImpl(initialQueryParameters, row));
}
}
int resultRows = rows.size();
JsonElement totalRowsElement = response.get("total_rows");
if (totalRowsElement != null) {
totalRows = totalRowsElement.getAsLong();
} else {
//if there is no total rows element, use the rows size
totalRows = rows.size();
}
long rowsPerPage = (initialQueryParameters.getRowsPerPage() != null) ?
initialQueryParameters.getRowsPerPage().longValue() : totalRows;
//we expect limit = rowsPerPage + 1 results, if we have rowsPerPage or less we are on the
// last page
hasNext = resultRows > rowsPerPage;
if (PageMetadata.PagingDirection.BACKWARD == thisPageDirection) {
//Result needs reversing because to implement backward paging the view reading
// order is reversed
Collections.reverse(rows);
}
//set previous page links if not the first page
if (this.pageNumber > 1) {
hasPrevious = true;
// Construct the previous page metadata (i.e. paging backward)
// Decrement the page number by 1
// The startKey of this page is also the start key of the previous page, but using a
// descending lookup indicated by the paging direction.
previousPageMetadata = new PageMetadata(PageMetadata.PagingDirection
.BACKWARD, this.pageNumber - 1l, PageMetadata.reversePaginationQueryParameters
(initialQueryParameters, rows.get(0).getKey(), rows.get(0).getId()));
} else {
hasPrevious = false;
}
// If we are not on the last page, we need to use the last
// result as the start key for the next page and therefore
// we don't return it to the user.
// If we are on the last page, the final row should be returned
// to the user.
int lastIndex = resultRows - 1;
if (hasNext) {
// Construct the next page metadata (i.e. paging forward)
// Increment the page number by 1
// The last element is the start of the next page so use the key and ID from that
// element for creating the next page query parameters.
nextPageMetadata = new PageMetadata(PageMetadata.PagingDirection
.FORWARD, this.pageNumber + 1l, PageMetadata.forwardPaginationQueryParameters
(initialQueryParameters, rows.get(lastIndex).getKey(), rows.get(lastIndex)
.getId()));
// The final element is the first element of the next page, so remove from the list that
// will be returned.
rows.remove(lastIndex);
} else {
nextPageMetadata = null;
}
// calculate paging display info
long offset = (this.pageNumber - 1) * rowsPerPage;
resultFrom = offset + 1;
resultTo = offset + (hasNext ? rowsPerPage : resultRows);
}
@Override
public List> getRows() {
return Collections.unmodifiableList(rows);
}
@Override
public List getKeys() {
if (keys == null) {
populateKV();
}
return keys;
}
@Override
public List getValues() {
if (values == null) {
populateKV();
}
return values;
}
@Override
public List getDocs() {
if (initialQueryParameters.getIncludeDocs()) {
if (docs == null) {
docs = new ArrayList();
for (Row row : getRows()) {
docs.add(row.getDocument());
}
}
return docs;
} else {
throw new IllegalStateException("Cannot getDocs() when include_docs is false.");
}
}
@Override
public List getDocsAs(Class docType) {
if (initialQueryParameters.getIncludeDocs()) {
List documents = new ArrayList();
for (Row row : getRows()) {
documents.add(row.getDocumentAsType(docType));
}
return documents;
} else {
throw new IllegalStateException("Cannot getDocs() when include_docs is false.");
}
}
@Override
public boolean hasNextPage() {
return hasNext;
}
@Override
public boolean hasPreviousPage() {
return hasPrevious;
}
@Override
public ViewResponse nextPage() throws IOException {
if (hasNext) {
JsonObject response = ViewRequester.getResponseAsJson(nextPageMetadata
.pageRequestParameters);
return new ViewResponseImpl(initialQueryParameters, response, nextPageMetadata);
} else {
return null;
}
}
@Override
public ViewResponse previousPage() throws IOException {
if (hasPrevious) {
JsonObject response = ViewRequester.getResponseAsJson(previousPageMetadata
.pageRequestParameters);
return new ViewResponseImpl(initialQueryParameters, response,
previousPageMetadata);
} else {
return null;
}
}
@Override
public String getNextPageToken() {
if (hasNext) {
return PaginationToken.tokenize(nextPageMetadata);
}
return null;
}
@Override
public String getPreviousPageToken() {
if (hasPrevious) {
return PaginationToken.tokenize(previousPageMetadata);
}
return null;
}
@Override
public Long getPageNumber() {
return this.pageNumber;
}
@Override
public Long getFirstRowCount() {
return resultFrom;
}
@Override
public Long getLastRowCount() {
return resultTo;
}
@Override
public Long getTotalRowCount() {
return totalRows;
}
@Override
public Iterator> iterator() {
return new Iterator>() {
//initially true to allow next() to get the first element
boolean hasNext = true;
//hold a ref to the previous page so we don't have to make the GET for the next page
// until next() is called
ViewResponse previousPage = null;
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public ViewResponse next() {
try {
if (hasNext) {
ViewResponse pageToReturn = (previousPage == null) ?
ViewResponseImpl.this : previousPage.nextPage();
hasNext = pageToReturn.hasNextPage();
previousPage = pageToReturn;
return pageToReturn;
} else {
throw new NoSuchElementException("No more pages");
}
} catch (IOException e) {
//iterators can't throw a checked exception, so wrap in a runtime
throw new RuntimeException(e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private void populateKV() {
keys = new ArrayList();
values = new ArrayList();
for (Row row : getRows()) {
keys.add(row.getKey());
values.add(row.getValue());
}
}
}