@Override
  protected DerivedViewQuery complete(ViewQuery criteria, Sort sort) {
    boolean descending = false;

    if (sort != null) {
      int sortCount = 0;
      Iterator<Sort.Order> it = sort.iterator();
      while (it.hasNext()) {
        sortCount++;
        if (!it.next().isAscending()) {
          descending = true;
        }
      }
      if (sortCount > 1) {
        throw new IllegalArgumentException(
            "Detected " + sortCount + " sort instructions, maximum one supported");
      }
      query.descending(descending);
    }

    if (tree.isLimiting()) {
      query.limit(tree.getMaxResults());
    }

    boolean isCount = tree.isCountProjection() == Boolean.TRUE;
    boolean isExplicitReduce = viewAnnotation != null && viewAnnotation.reduce();
    if (isCount || isExplicitReduce) {
      query.reduce();
    }

    return new DerivedViewQuery(query, tree.isLimiting(), isCount || isExplicitReduce);
  }
  @Override
  protected ViewQuery create(Part part, Iterator<Object> objectIterator) {
    ConvertingIterator iterator = new ConvertingIterator(objectIterator, converter);

    switch (part.getType()) {
      case GREATER_THAN_EQUAL:
        startKey(iterator);
        break;
      case LESS_THAN_EQUAL:
        query.inclusiveEnd(true);
      case BEFORE:
      case LESS_THAN: // fall-through on purpose here
        endKey(iterator);
        break;
      case BETWEEN:
        startKey(iterator);
        endKey(iterator);
        break;
      case STARTING_WITH: // starting_with only supports String keys
        String nameStart = nextString(iterator);
        query.startKey(nameStart).endKey(nameStart + "\uefff");
        query.inclusiveEnd(false);
        break;
      case SIMPLE_PROPERTY:
        key(iterator);
        break;
      case IN:
        query.keys(in(iterator));
        break;
      default:
        throw new IllegalArgumentException(
            "Unsupported keyword in View query derivation: " + part.toString());
    }
    return query;
  }
  /**
   * Queries all documents of a view with an optional range parameter
   *
   * @param designDocName
   * @param viewName
   * @param startKey
   * @param endKey
   * @return
   */
  public static ViewResult query(
      String designDocName, String viewName, String startKey, String endKey) {
    ViewResult result;

    // Perform the query
    ViewQuery query = ViewQuery.from(designDocName, viewName).inclusiveEnd(true).stale(Stale.FALSE);

    if (startKey != null) {
      query = query.startKey(startKey);
    }

    if (endKey != null) {
      query = query.endKey(endKey);
    }

    result = client.query(query);

    return result;
  }
  private void key(Iterator<Object> iterator) {
    if (!iterator.hasNext() && treeCount > 1) {
      throw new IllegalArgumentException("Not enough parameters for key");
    } else if (!iterator.hasNext()) {
      // probably pattern like findAllByUsername(), just apply query without parameters
      return;
    }

    Object next = iterator.next();
    if (next instanceof String) {
      query.key((String) next);
    } else if (next instanceof Boolean) {
      query.key((Boolean) next);
    } else if (next instanceof Double) {
      query.key((Double) next);
    } else if (next instanceof Integer) {
      query.key((Integer) next);
    } else if (next instanceof Long) {
      query.key((Long) next);
    } else if (next instanceof Collection) {
      // when creating a JsonArray, the from(List) method is preferred because it will convert
      // internal
      // Lists and Maps to JsonObject and JsonArray respectively
      List<Object> arrayContent = new ArrayList<Object>((Collection) next);
      query.key(JsonArray.from(arrayContent));
    } else if (next.getClass().isArray()) {
      List<Object> arrayContent = Arrays.asList((Object[]) next);
      query.key(JsonArray.from(arrayContent));
    } else if (next
        instanceof
        JsonArray) { // discouraged, since it's leaking store-specifics in the method signature
      query.key((JsonArray) next);
    } else if (next
        instanceof
        JsonObject) { // discouraged, since it's leaking store-specifics in the method signature
      query.key((JsonObject) next);
    } else {
      throw new IllegalArgumentException("Unsupported parameter type for key: " + next.getClass());
    }
  }