public class Batch {

  private final List<MutationStatement> statements = Lists.newArrayList();

  /**
   * Adds a statement to this batch.
   *
   * @param statement the mutation statement to add.
   * @return this batch statement.
   */
  public Batch add(final MutationStatement statement) {
    statements.add(statement);
    return this;
  }

  public List<Mutation> getPb(final String namespace) {
    return statements
        .stream()
        .map(m -> m.getPb(namespace))
        .collect(Collectors.toList());
  }

} public interface Datastore extends Closeable {

  static Datastore create(final DatastoreConfig config) {
    return new DatastoreImpl(config);
  }

  /**
   * Start a new transaction.
   *
   * The returned {@code TransactionResult} contains the transaction if the
   * request is successful.
   *
   * @return the result of the transaction request.
   */
  TransactionResult transaction() throws DatastoreException;

  /**
   * Start a new transaction.
   *
   * The returned {@code TransactionResult} contains the transaction if the
   * request is successful.
   *
   * @return the result of the transaction request.
   */
  ListenableFuture<TransactionResult> transactionAsync();

  /**
   * Rollback a given transaction.
   *
   * You normally rollback a transaction in the event of d Datastore failure.
   *
   * @param txn the transaction.
   * @return the result of the rollback request.
   */
  RollbackResult rollback(final TransactionResult txn) throws DatastoreException;

  /**
   * Rollback a given transaction.
   *
   * You normally rollback a transaction in the event of d Datastore failure.
   *
   * @param txn the transaction.
   * @return the result of the rollback request.
   */
  ListenableFuture<RollbackResult> rollbackAsync(final ListenableFuture<TransactionResult> txn);

  /**
   * Commit a given transaction.
   *
   * You normally manually commit a transaction after performing read-only
   * operations without mutations.
   *
   * @param txn the transaction.
   * @return the result of the commit request.
   */
  MutationResult commit(final TransactionResult txn) throws DatastoreException;

  /**
   * Commit a given transaction.
   *
   * You normally manually commit a transaction after performing read-only
   * operations without mutations.
   *
   * @param txn the transaction.
   * @return the result of the commit request.
   */
  ListenableFuture<MutationResult> commitAsync(final ListenableFuture<TransactionResult> txn);

  /**
   * Execute a allocate ids statement.
   *
   * @param statement the statement to execute.
   * @return the result of the allocate ids request.
   */
  AllocateIdsResult execute(final AllocateIds statement) throws DatastoreException;

  /**
   * Execute a allocate ids statement.
   *
   * @param statement the statement to execute.
   * @return the result of the allocate ids request.
   */
  ListenableFuture<AllocateIdsResult> executeAsync(final AllocateIds statement);

  /**
   * Execute a keyed query statement.
   *
   * @param statement the statement to execute.
   * @return the result of the query request.
   */
  QueryResult execute(final KeyQuery statement) throws DatastoreException;

  /**
   * Execute a multi-keyed query statement.
   *
   * @param statements the statements to execute.
   * @return the result of the query request.
   */
  QueryResult execute(final List<KeyQuery> statements) throws DatastoreException;

  /**
   * Execute a keyed query statement.
   *
   * @param statement the statement to execute.
   * @return the result of the query request.
   */
  ListenableFuture<QueryResult> executeAsync(final KeyQuery statement);

  /**
   * Execute a multi-keyed query statement.
   *
   * @param statements the statements to execute.
   * @return the result of the query request.
   */
  ListenableFuture<QueryResult> executeAsync(final List<KeyQuery> statements);

  /**
   * Execute a keyed query statement in a given transaction.
   *
   * @param statement the statement to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the query request.
   */
  QueryResult execute(final KeyQuery statement, final TransactionResult txn) throws DatastoreException;

  /**
   * Execute a multi-keyed query statement in a given transaction.
   *
   * @param statements the statements to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the query request.
   */
  QueryResult execute(final List<KeyQuery> statements, final TransactionResult txn) throws DatastoreException;

  /**
   * Execute a keyed query statement in a given transaction.
   *
   * @param statement the statement to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the query request.
   */
  ListenableFuture<QueryResult> executeAsync(final KeyQuery statement, final ListenableFuture<TransactionResult> txn);

  /**
   * Execute a multi-keyed query statement in a given transaction.
   *
   * @param statements the statements to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the query request.
   */
  ListenableFuture<QueryResult> executeAsync(final List<KeyQuery> statements, final ListenableFuture<TransactionResult> txn);

  /**
   * Execute a mutation query statement.
   *
   * @param statement the statement to execute.
   * @return the result of the mutation request.
   */
  MutationResult execute(final MutationStatement statement) throws DatastoreException;

  /**
   * Execute a mutation query statement.
   *
   * @param statement the statement to execute.
   * @return the result of the mutation request.
   */
  ListenableFuture<MutationResult> executeAsync(final MutationStatement statement);

  /**
   * Execute a mutation query statement in a given transaction.
   *
   * @param statement the statement to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the mutation request.
   */
  MutationResult execute(final MutationStatement statement, final TransactionResult txn) throws DatastoreException;

  /**
   * Execute a mutation query statement in a given transaction.
   *
   * @param statement the statement to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the mutation request.
   */
  ListenableFuture<MutationResult> executeAsync(final MutationStatement statement, final ListenableFuture<TransactionResult> txn);

  /**
   * Execute a batch mutation query statement.
   *
   * @param batch to execute.
   * @return the result of the mutation request.
   */
  MutationResult execute(final Batch batch) throws DatastoreException;

  /**
   * Execute a batch mutation query statement.
   *
   * @param batch to execute.
   * @return the result of the mutation request.
   */
  ListenableFuture<MutationResult> executeAsync(final Batch batch);

  /**
   * Execute a batch mutation query statement in a given transaction.
   *
   * @param batch to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the mutation request.
   */
  MutationResult execute(final Batch batch, final TransactionResult txn) throws DatastoreException;

  /**
   * Execute a batch mutation query statement in a given transaction.
   *
   * @param batch to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the mutation request.
   */
  ListenableFuture<MutationResult> executeAsync(final Batch batch, final ListenableFuture<TransactionResult> txn);

  /**
   * Execute a query statement.
   *
   * @param statement the statement to execute.
   * @return the result of the query request.
   */
  QueryResult execute(final Query statement) throws DatastoreException;

  /**
   * Execute a query statement.
   *
   * @param statement the statement to execute.
   * @return the result of the query request.
   */
  ListenableFuture<QueryResult> executeAsync(final Query statement);

  /**
   * Execute a query statement in a given transaction.
   *
   * @param statement the statement to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the query request.
   */
  QueryResult execute(final Query statement, final TransactionResult txn) throws DatastoreException;

  /**
   * Execute a query statement in a given transaction.
   *
   * @param statement the statement to execute.
   * @param txn the transaction to execute the query.
   * @return the result of the query request.
   */
  ListenableFuture<QueryResult> executeAsync(final Query statement, final ListenableFuture<TransactionResult> txn);
} public final class DatastoreConfig {

  public static final List<String> SCOPES = ImmutableList.of(
    "https://www.googleapis.com/auth/datastore");

  private static final Integer DEFAULT_CONNECT_TIMEOUT = 5000;
  private static final Integer DEFAULT_MAX_CONNECTIONS = -1;
  private static final Integer DEFAULT_REQUEST_TIMEOUTS = 5000;
  private static final Integer DEFAULT_REQUEST_RETRIES = 5;
  private static final String DEFAULT_HOST = "https://datastore.googleapis.com";
  private static final String DEFAULT_VERSION = "v1";

  private final int connectTimeout;
  private final int maxConnections;
  private final int requestTimeout;
  private final int requestRetry;
  private final Credential credential;
  private final String project;
  private final String namespace;
  private final String host;
  private final String version;

  private DatastoreConfig(final Integer connectTimeout,
                          final Integer maxConnections,
                          final Integer requestTimeout,
                          final Integer requestRetry,
                          final Credential credential,
                          final String project,
                          final String namespace,
                          final String host,
                          final String version) {
    this.connectTimeout = firstNonNull(connectTimeout, DEFAULT_CONNECT_TIMEOUT);
    this.maxConnections = firstNonNull(maxConnections, DEFAULT_MAX_CONNECTIONS);
    this.requestTimeout = firstNonNull(requestTimeout, DEFAULT_REQUEST_TIMEOUTS);
    this.requestRetry = firstNonNull(requestRetry, DEFAULT_REQUEST_RETRIES);
    this.credential = credential;
    this.project = project;
    this.namespace = namespace;
    this.host = firstNonNull(host, DEFAULT_HOST);
    this.version = firstNonNull(version, DEFAULT_VERSION);
  }

  public static final class Builder {
    private Integer connectTimeout;
    private Integer maxConnections;
    private Integer requestTimeout;
    private Integer requestRetry;
    private Credential credential;
    private String project;
    private String namespace;
    private String host;
    private String version;

    private Builder() {}

    /**
     * Creates a new {@code DatastoreConfig}.
     *
     * @return an immutable config.
     */
    public DatastoreConfig build() {
      return new DatastoreConfig(connectTimeout,
                                 maxConnections,
                                 requestTimeout,
                                 requestRetry,
                                 credential,
                                 project,
                                 namespace,
                                 host,
                                 version);
    }

    /**
     * Set the maximum time in milliseconds the client will can wait
     * when connecting to a remote host.
     *
     * @param connectTimeout the maximum time in milliseconds.
     * @return this config builder.
     */
    public Builder connectTimeout(final int connectTimeout) {
      this.connectTimeout = connectTimeout;
      return this;
    }

    /**
     * Set the maximum number of connections client will handle.
     *
     * @param maxConnections the maximum number of connections.
     * @return this config builder.
     */
    public Builder maxConnections(final int maxConnections) {
      this.maxConnections = maxConnections;
      return this;
    }

    /**
     * Set the maximum time in milliseconds the client waits until
     * the response is completed.
     *
     * @param requestTimeout the maximum time in milliseconds.
     * @return this config builder.
     */
    public Builder requestTimeout(final int requestTimeout) {
      this.requestTimeout = requestTimeout;
      return this;
    }

    /**
     * Set the number of times a request will be retried when an
     * {@link java.io.IOException} occurs because of a Network exception.
     *
     * @param requestRetry the number of times a request will be retried.
     * @return this config builder.
     */
    public Builder requestRetry(final int requestRetry) {
      this.requestRetry = requestRetry;
      return this;
    }

    /**
     * Set Datastore credentials to ues when requesting an access token.
     * <p>
     * Credentials can be generated by calling
     * {@code DatastoreHelper.getComputeEngineCredential} or
     * {@code DatastoreHelper.getServiceAccountCredential}
     *
     * @param credential the credentials used to authenticate.
     * @return this config builder.
     */
    public Builder credential(final Credential credential) {
      this.credential = credential;
      return this;
    }

    /**
     * Set project id to use when querying Datastore.
     *
     * @param project the project id.
     * @return this config builder.
     */
    public Builder project(final String project) {
      this.project = project;
      return this;
    }

    /**
     * An optional namespace may be specified to further partition data in
     * your project.
     *
     * @param namespace the namespace.
     * @return this config builder.
     */
    public Builder namespace(final String namespace) {
      this.namespace = namespace;
      return this;
    }

    /**
     * The Datastore host to connect to. By default, this is the Google
     * Datastore provider, however you may run a local Developer Server.
     *
     * @param host the host to connect to.
     * @return this config builder.
     */
    public Builder host(final String host) {
      this.host = host;
      return this;
    }

    /**
     * The Datastore API version to use.
     *
     * @param version the version to use.
     * @return this config builder.
     */
    public Builder version(final String version) {
      this.version = version;
      return this;
    }
  }

  public static DatastoreConfig.Builder builder() {
    return new DatastoreConfig.Builder();
  }

  public int getConnectTimeout() {
    return connectTimeout;
  }

  public int getMaxConnections() {
    return maxConnections;
  }

  public int getRequestTimeout() {
    return requestTimeout;
  }

  public int getRequestRetry() {
    return requestRetry;
  }

  public Credential getCredential() {
    return credential;
  }

  public String getProject() {
    return project;
  }

  public String getNamespace() {
    return namespace;
  }

  public String getHost() {
    return host;
  }

  public String getVersion() {
    return version;
  }
} public final class Entity {

  private final com.google.datastore.v1.Entity entity;
  private Map<String, Value> properties;

  private Entity(final com.google.datastore.v1.Entity entity) {
    this.entity = entity;
    this.properties = null;
  }

  public static final class Builder {

    private com.google.datastore.v1.Entity.Builder entity;
    private Map<String, Value> properties;

    private Builder() {
      this.entity = com.google.datastore.v1.Entity.newBuilder();
      this.properties = Maps.newHashMap();
    }

    private Builder(final Key key) {
      this.entity = com.google.datastore.v1.Entity.newBuilder().setKey(key.getPb());
      this.properties = Maps.newHashMap();
    }

    private Builder(final Entity entity) {
      this(entity.getPb());
    }

    private Builder(final com.google.datastore.v1.Entity entity) {
      this.entity = com.google.datastore.v1.Entity.newBuilder(entity);
      this.properties = entity.getProperties().entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getKey, e -> Value.builder(e.getValue()).build()));
    }

    /**
     * Creates a new {@code Entity}.
     *
     * @return an immutable entity.
     */
    public Entity build() {
      entity.getMutableProperties().clear();
      entity.putAllProperties(
          properties
              .entrySet()
              .stream()
              .collect(Collectors.toMap(
                  Map.Entry::getKey, e -> e.getValue().getPb().toBuilder().build())));

      return new Entity(entity.build());
    }

    /**
     * Set the key for this entity.
     *
     * @param key the key to set for this entity.
     * @return this entity builder.
     */
    public Builder key(final Key key) {
      entity.setKey(key.getPb());
      return this;
    }

    /**
     * Set property and its value for this entity.
     *
     * @param name the property name to set.
     * @param value the property value.
     * @return this entity builder.
     */
    public Builder property(final String name, final Value value) {
      properties.put(name, value);
      return this;
    }

    /**
     * Set property and its value for this entity.
     *
     * @param name the property name to set.
     * @param value the property value.
     * @return this entity builder.
     */
    public Builder property(final String name, final Object value) {
      properties.put(name, Value.builder(value).build());
      return this;
    }

    /**
     * Set property and its value for this entity.
     *
     * @param name the property name to set.
     * @param value the property value.
     * @param indexed indicates whether the value should be indexed or not.
     * @return this entity builder.
     */
    public Builder property(final String name, final Object value, final boolean indexed) {
      properties.put(name, Value.builder(value).indexed(indexed).build());
      return this;
    }

    /**
     * Set property and a list of value for this entity.
     *
     * @param name the property name to set.
     * @param values a list of value.
     * @return this entity builder.
     */
    public Builder property(final String name, final List<Object> values) {
      properties.put(name, Value.builder(values).build());
      return this;
    }

    /**
     * Remove a property from this entity.
     *
     * @param name the property name to remove.
     * @return this entity builder.
     */
    public Builder remove(final String name) {
      properties.remove(name);
      return this;
    }
  }

  /**
   * Creates a new empty {@code Entity} builder.
   *
   * @return an entity builder.
   */
  public static Entity.Builder builder() {
    return new Entity.Builder();
  }

  /**
   * Creates a new {@code Entity} builder for a given kind.
   *
   * This is a shortcut for {@code Entity.builder().key(Key.builder(kind).build())}
   *
   * @param kind the kind of entity.
   * @return an entity builder.
   */
  public static Entity.Builder builder(final String kind) {
    return new Entity.Builder(Key.builder(kind).build());
  }

  /**
   * Creates a new {@code Entity} builder for a given kind and key id.
   *
   * This is a shortcut for {@code Entity.builder().key(Key.builder(kind, id).build())}
   *
   * @param kind the kind of entity.
   * @param id the key id.
   * @return an entity builder.
   */
  public static Entity.Builder builder(final String kind, final long id) {
    return new Entity.Builder(Key.builder(kind, id).build());
  }

  /**
   * Creates a new {@code Entity} builder for a given kind and key name.
   *
   * This is a shortcut for {@code Entity.builder().key(Key.builder(kind, name).build())}
   *
   * @param kind the kind of entity.
   * @param name the key name.
   * @return an entity builder.
   */
  public static Entity.Builder builder(final String kind, final String name) {
    return new Entity.Builder(Key.builder(kind, name).build());
  }

  /**
   * Creates a new {@code Entity} builder for a given key.
   *
   * This is a shortcut for {@code Entity.builder().key(key).build())}
   *
   * @param key the key for this entity.
   * @return an entity builder.
   */
  public static Entity.Builder builder(final Key key) {
    return new Entity.Builder(key);
  }

  /**
   * Creates a new {@code Entity} builder based on an existing entity.
   *
   * @param entity the entity to use as a base.
   * @return an entity builder.
   */
  public static Entity.Builder builder(final Entity entity) {
    return new Entity.Builder(entity);
  }

  static Entity.Builder builder(final com.google.datastore.v1.Entity entity) {
    return new Entity.Builder(entity);
  }

  /**
   * Return the key for this entity.
   *
   * @return a {@code Key}.
   */
  public Key getKey() {
    return Key.builder(entity.getKey()).build();
  }

  /**
   * Return the value for a given property as a string, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public String getString(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getString();
  }

  /**
   * Return the value for a given property as an integer, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public Long getInteger(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getInteger();
  }

  /**
   * Return the value for a given property as a boolean, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public Boolean getBoolean(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getBoolean();
  }

  /**
   * Return the value for a given property as a double, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public Double getDouble(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getDouble();
  }

  /**
   * Return the value for a given property as a date, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public Date getDate(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getDate();
  }

  /**
   * Return the value for a given property as a blob, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public ByteString getBlob(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getBlob();
  }

  /**
   * Return the value for a given property as an entity, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public Entity getEntity(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getEntity();
  }

  /**
   * Return the value for a given property as a key, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a property value.
   */
  public Key getKey(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getKey();
  }

  /**
   * Return the value for a given property as a list of {@code Key}, or null
   * if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @return a list of property values.
   */
  public List<Value> getList(final String name) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getList();
  }

  /**
   * Return the value for a given property as a list of values that are cast
   * to a given type, or null if the property doesn't exist.
   *
   * @param name the name of the property to get.
   * @param clazz the type of class to cast values to.
   * @return a list of property values.
   */
  public <T> List<T> getList(final String name, final Class<T> clazz) {
    final Value value = getProperties().get(name);
    return value == null ? null : value.getList(clazz);
  }

  /**
   * Return a map of properties to their values for this entity.
   *
   * @return a map of property values.
   */
  public Map<String, Value> getProperties() {
    if (properties == null) {
      properties = entity
          .getProperties()
          .entrySet()
          .stream()
          .collect(Collectors.toMap(Map.Entry::getKey, e -> Value.builder(e.getValue()).build()));
    }
    return properties;
  }

  /**
   * Get the value for given property, or empty if none exists.
   *
   * @param name name of the property to get.
   * @return an optional containing a property value, or empty.
   */
  public Optional<Value> get(final String name) {
      return Optional.ofNullable(getProperties().get(name));
  }

  /**
   * Return whether a given property name exists in this entity.
   *
   * @param name the name of the property.
   * @return true if the property exists.
   */
  public boolean contains(final String name) {
    return getProperties().containsKey(name);
  }

  @Override
  public String toString() {
    return "{" + getProperties().entrySet().stream()
        .map(property -> property.getKey() + ":" + property.getValue())
        .collect(Collectors.joining(", ")) + "}";
  }

  @Override
  public int hashCode() {
    return entity.hashCode();
  }

  @Override
  public boolean equals(final Object obj) {
    return obj.getClass() == this.getClass() || (obj instanceof Entity && Objects.equals(entity, ((Entity) obj).entity));
  }

  com.google.datastore.v1.Entity getPb() {
    return entity;
  }

  com.google.datastore.v1.Entity getPb(final String namespace) {
    final Map<String, com.google.datastore.v1.Value> propertiesLocal = entity.getProperties()
        .entrySet()
        .stream()
        .collect(Collectors.toMap(
            Map.Entry::getKey, e -> Value.builder(e.getValue()).build().getPb(namespace)));

    return com.google.datastore.v1.Entity
        .newBuilder()
        .putAllProperties(propertiesLocal)
        .setKey(getKey().getPb(namespace))
        .build();
  }
} public class Insert extends KeyedStatement implements MutationStatement {

  protected Entity.Builder entity;

  Insert(final Key key) {
    super(key);
    this.entity = Entity.builder(key);
  }

  Insert(final Entity entity) {
    super(entity.getKey());
    this.entity = Entity.builder(entity);
  }

  /**
   * Adds a property name and value to the entity to before inserting.
   *
   * @param name the property name.
   * @param value the property {@code Value}.
   * @return this insert statement.
   */
  public Insert value(final String name, final Object value) {
    entity.property(name, value);
    return this;
  }

  /**
   * Adds a property name and value to the entity before inserting.
   *
   * @param name the property name.
   * @param value the property {@code Value}.
   * @param indexed indicates whether the {@code Value} should be indexed or not.
   * @return this insert statement.
   */
  public Insert value(final String name, final Object value, final boolean indexed) {
    entity.property(name, value, indexed);
    return this;
  }

  /**
   * Adds a property name and list of values to the entity before inserting.
   *
   * @param name the property name.
   * @param values a list of property {@code Value}s.
   * @return this insert statement.
   */
  public Insert value(final String name, final List<Object> values) {
    entity.property(name, values);
    return this;
  }

  @Override
  public Mutation getPb(final String namespace) {
    final com.google.datastore.v1.Mutation.Builder mutation =
      com.google.datastore.v1.Mutation.newBuilder();

    return mutation.setInsert(entity.build().getPb(namespace)).build();
  }
} public class KeyQuery extends KeyedStatement {

  KeyQuery(final Key key) {
    super(key);
  }
} public final class MutationResult implements Result {

  private final List<com.google.datastore.v1.MutationResult> result;
  private final int indexUpdates;

  private MutationResult(final com.google.datastore.v1.MutationResult result, int indexUpdates) {
    this.result = ImmutableList.of(result);
    this.indexUpdates = indexUpdates;
  }

  private MutationResult(final List<com.google.datastore.v1.MutationResult> results, int indexUpdates) {
    this.result = ImmutableList.copyOf(results);
    this.indexUpdates = indexUpdates;
  }

  static MutationResult build(final com.google.datastore.v1.CommitResponse response) {
    return new MutationResult(response.getMutationResultsList(), response.getIndexUpdates());
  }

  /**
   * Build an empty result.
   *
   * @return a new empty mutation result.
   */
  public static MutationResult build() {
    return new MutationResult(com.google.datastore.v1.MutationResult.getDefaultInstance(), 0);
  }

  /**
   * Return the first entity key that was inserted or null if empty.
   *
   * This is a shortcut for {@code getInsertKeys().get(0)}
   *
   * @return a key that describes the newly inserted entity.
   */
  public Key getInsertKey() {
    if (result.isEmpty()) {
      return null;
    }
    return Key.builder(result.get(0).getKey()).build();
  }

  /**
   * Return all entity keys that were inserted for automatically generated
   * key ids.
   *
   * @return a list of keys that describe the newly inserted entities.
   */
  public List<Key> getInsertKeys() {
    return ImmutableList.copyOf(result.stream()
        .map(r -> Key.builder(r.getKey()).build())
        .collect(Collectors.toList()));
  }

  /**
   * Return the number of indexes updated during the mutation operation.
   *
   * @return the number of index updates.
   */
  public int getIndexUpdates() {
    return indexUpdates;
  }
} public class Query implements Statement {

  private final com.google.datastore.v1.Query.Builder query;
  private final List<Filter> filters;

  Query() {
    query = com.google.datastore.v1.Query.newBuilder();
    filters = Lists.newArrayList();
  }

  /**
   * Specifies that only entity keys should be returned and not all
   * properties.
   *
   * @return this query statement.
   */
  public Query keysOnly() {
    query.addProjection(
        Projection
            .newBuilder()
            .setProperty(PropertyReference.newBuilder().setName("__key__").build()));
    return this;
  }

  /**
   * Only return a given set of properties, otherwise known as a Projection Query.
   *
   * @param properties one or more property names to return.
   * @return this query statement.
   */
  public Query properties(final String... properties) {
    return properties(Arrays.asList(properties));
  }

  /**
   * Only return a given set of properties, otherwise known as a Projection Query.
   *
   * @param properties one or more property names to return.
   * @return this query statement.
   */
  public Query properties(final List<String> properties) {
    query.addAllProjection(properties.stream()
                               .map(property -> Projection.newBuilder()
                                   .setProperty(com.google.datastore.v1.PropertyReference
                                     .newBuilder()
                                     .setName(property))
                                  .build())
                               .collect(Collectors.toList()));
    return this;
  }

  /**
   * Query entities of a given kind.
   *
   * @param kind the kind of entity to query.
   * @return this query statement.
   */
  public Query kindOf(final String kind) {
    query.addKind(com.google.datastore.v1.KindExpression.newBuilder().setName(kind));
    return this;
  }

  /**
   * Apply a given filter to the query.
   *
   * @param filter the query filter to apply.
   * @return this query statement.
   */
  public Query filterBy(final Filter filter) {
    filters.add(filter);
    return this;
  }

  /**
   * Apply a given order to the query.
   *
   * @param order the query order to apply.
   * @return this query statement.
   */
  public Query orderBy(final Order order) {
    query.addOrder(order.getPb());
    return this;
  }

  /**
   * Apply a given group to the query.
   *
   * @param group the query group to apply.
   * @return this query statement.
   */
  public Query groupBy(final Group group) {
    query.addDistinctOn(group.getPb());
    return this;
  }

  /**
   * Tell Datastore to begin returning entities from a given cursor
   * position. This is used to page results; the last cursor position
   * is returned in {@code QueryResult}.
   *
   * @param cursor the last query cursor position.
   * @return this query statement.
   */
  public Query fromCursor(final ByteString cursor) {
    query.setStartCursor(cursor);
    return this;
  }

  /**
   * Limit the number of entities returned in this query. The last
   * cursor position will be returned in {@code QueryResult} if more
   * entities are required.
   *
   * @param limit the maximum number of entities to return.
   * @return this query statement.
   */
  public Query limit(final int limit) {
    query.setLimit(Int32Value.newBuilder().setValue(limit));
    return this;
  }

  com.google.datastore.v1.Query getPb(String namespace) {
    if (filters.size() == 1) {
      query.setFilter(filters.get(0).getPb(namespace));
    } else if (filters.size() > 1) {
      query.setFilter(com.google.datastore.v1.Filter.newBuilder()
                          .setCompositeFilter(
                              com.google.datastore.v1.CompositeFilter.newBuilder()
                                  .addAllFilters(filters.stream().map(f -> f.getPb(namespace)).collect(Collectors.toList()))
                                  .setOp(com.google.datastore.v1.CompositeFilter.Operator.AND)));
    }
    return query.build();
  }
} public final class QueryBuilder {

  private QueryBuilder(){
  }

  /**
   * Start building a new INSERT query.
   *
   * @param kind the kind of entity to insert.
   * @return an in-construction INSERT query.
   */
  public static Insert insert(final String kind) {
    return new Insert(Key.builder(kind).build());
  }

  /**
   * Start building a new INSERT query.
   *
   * @param kind the kind of entity to insert.
   * @param id the key id of this entity to insert.
   * @return an in-construction INSERT query.
   */
  public static Insert insert(final String kind, final long id) {
    return new Insert(Key.builder(kind, id).build());
  }

  /**
   * Start building a new INSERT query.
   *
   * @param kind the kind of entity to insert.
   * @param name the key name of this entity to insert.
   * @return an in-construction INSERT query.
   */
  public static Insert insert(final String kind, final String name) {
    return new Insert(Key.builder(kind, name).build());
  }

  /**
   * Start building a new INSERT query.
   *
   * @param key the key of this entity to insert.
   * @return an in-construction INSERT query.
   */
  public static Insert insert(final Key key) {
    return new Insert(key);
  }

  /**
   * Start building a new INSERT query.
   *
   * @param entity the entity to insert.
   * @return an in-construction INSERT query.
   */
  public static Insert insert(final Entity entity) {
    return new Insert(entity);
  }

  /**
   * Start building a new UPDATE query.
   *
   * @param kind the kind of entity to update.
   * @return an in-construction UPDATE query.
   */
  public static Update update(final String kind) {
    return new Update(Key.builder().path(kind).build());
  }

  /**
   * Start building a new UPDATE query.
   *
   * @param kind the kind of entity to update.
   * @param id the key id of this entity to update.
   * @return an in-construction UPDATE query.
   */
  public static Update update(final String kind, final long id) {
    return new Update(Key.builder().path(kind, id).build());
  }

  /**
   * Start building a new UPDATE query.
   *
   * @param kind the kind of entity to update.
   * @param name the key name of the entity to update.
   * @return an in-construction UPDATE query.
   */
  public static Update update(final String kind, final String name) {
    return new Update(Key.builder().path(kind, name).build());
  }

  /**
   * Start building a new UPDATE query.
   *
   * @param key the key of the entity to update.
   * @return an in-construction UPDATE query.
   */
  public static Update update(final Key key) {
    return new Update(key);
  }

  /**
   * Start building a new UPDATE query.
   *
   * @param entity the entity to update.
   * @return an in-construction UPDATE query.
   */
  public static Update update(final Entity entity) {
    return new Update(entity);
  }

  /**
   * Start building a new DELETE query.
   *
   * @param kind the kind of entity to delete.
   * @param id the key id of this entity to delete.
   * @return an in-construction DELETE query.
   */
  public static Delete delete(final String kind, final long id) {
    return new Delete(Key.builder().path(kind, id).build());
  }

  /**
   * Start building a new DELETE query.
   *
   * @param kind the kind of entity to delete.
   * @param name the key name of the entity to delete.
   * @return an in-construction DELETE query.
   */
  public static Delete delete(final String kind, final String name) {
    return new Delete(Key.builder().path(kind, name).build());
  }

  /**
   * Start building a new DELETE query.
   *
   * @param key the key of the entity to delete.
   * @return an in-construction DELETE query.
   */
  public static Delete delete(final Key key) {
    return new Delete(key);
  }

  /**
   * Start building a new ALLOCATE query.
   *
   * @param keys list of partial keys to allocate.
   * @return an in-construction ALLOCATE query.
   */
  public static AllocateIds allocate(final List<Key> keys) {
    return new AllocateIds(keys);
  }

  /**
   * Start building a new ALLOCATE query.
   *
   * @return an in-construction ALLOCATE query.
   */
  public static AllocateIds allocate() {
    return new AllocateIds();
  }

  /**
   * Start building a new BATCH query.
   * <p>
   * This method will build a query for batching operations into a single
   * call to Datastore.
   * <p>
   * NOTE: Use a transaction to make this batch operation atomic, otherwise
   * and error might mean that some, none, or all of the requested operations
   * have been performed.
   *
   * @return an in-construction BATCH query.
   */
  public static Batch batch() {
    return new Batch();
  }

  /**
   * Start building a new KEYQUERY query.
   * <p>
   * You should use a {@code KeyQuery} to retrieve a single entity based
   * on its key.
   *
   * @param kind the kind of entity to retrieve.
   * @param id the key id of this entity to retrieve.
   * @return an in-construction KEYQUERY query.
   */
  public static KeyQuery query(final String kind, final long id) {
    return new KeyQuery(Key.builder().path(kind, id).build());
  }

  /**
   * Start building a new KEYQUERY query.
   * <p>
   * You should use a {@code KeyQuery} to retrieve a single entity based
   * on its key.
   *
   * @param kind the kind of entity to retrieve.
   * @param name the key name of this entity to retrieve.
   * @return an in-construction KEYQUERY query.
   */
  public static KeyQuery query(final String kind, final String name) {
    return new KeyQuery(Key.builder().path(kind, name).build());
  }

  /**
   * Start building a new KEYQUERY query.
   * <p>
   * Use a {@code KeyQuery} to retrieve a single entity based on its key.
   *
   * @param key the key of entity to retrieve.
   * @return an in-construction KEYQUERY query.
   */
  public static KeyQuery query(final Key key) {
    return new KeyQuery(key);
  }

  /**
   * Start building a new QUERY query.
   * <p>
   * Use a {@code Query} to retrieve one or more entities that satisfy a
   * given criteria and order.
   *
   * @return an in-construction QUERY query.
   */
  public static Query query() {
    return new Query();
  }

  /**
   * Creates an "equal" {@code Filter} stating the provided property
   * must be equal to a given value.
   *
   * @param name the property name.
   * @param value the value.
   * @return a query filter.
   */
  public static Filter eq(final String name, final Object value) {
    return new Filter(name, Filter.Operator.EQUAL, Value.builder().value(value).build());
  }

  /**
   * Creates an "less than" {@code Filter} stating the provided property
   * must be less than a given value.
   *
   * @param name the property name.
   * @param value the value.
   * @return a query filter.
   */
  public static Filter lt(final String name, final Object value) {
    return new Filter(name, Filter.Operator.LESS_THAN, Value.builder().value(value).build());
  }

  /**
   * Creates an "less than or equal" {@code Filter} stating the provided
   * property must be less than or equal to a given value.
   *
   * @param name the property name.
   * @param value the value.
   * @return a query filter.
   */
  public static Filter lte(final String name, final Object value) {
    return new Filter(name, Filter.Operator.LESS_THAN_OR_EQUAL, Value.builder().value(value).build());
  }

  /**
   * Creates an "greater than" {@code Filter} stating the provided property
   * must be greater than a given value.
   *
   * @param name the property name.
   * @param value the value.
   * @return a query filter.
   */
  public static Filter gt(final String name, final Object value) {
    return new Filter(name, Filter.Operator.GREATER_THAN, Value.builder().value(value).build());
  }

  /**
   * Creates an "greater than or equal" {@code Filter} stating the provided
   * property must be greater than or equal to a given value.
   *
   * @param name the property name.
   * @param value the value.
   * @return a query filter.
   */
  public static Filter gte(final String name, final Object value) {
    return new Filter(name, Filter.Operator.GREATER_THAN_OR_EQUAL, Value.builder().value(value).build());
  }

  /**
   * Creates an "ancestor" {@code Filter} stating the provided key must be
   * an ancestor of the queried entities.
   *
   * @param kind the ancestor kind.
   * @param id the ancestor key id.
   * @return a query filter.
   */
  public static Filter ancestor(final String kind, final long id) {
    return new Filter("__key__", Filter.Operator.HAS_ANCESTOR, Value.builder().value(Key.builder(kind, id).build()).build());
  }

  /**
   * Creates an "ancestor" {@code Filter} stating the provided key must be
   * an ancestor of the queried entities.
   *
   * @param kind the ancestor kind.
   * @param name the ancestor key name.
   * @return a query filter.
   */
  public static Filter ancestor(final String kind, final String name) {
    return new Filter("__key__", Filter.Operator.HAS_ANCESTOR, Value.builder().value(Key.builder(kind, name).build()).build());
  }

  /**
   * Creates an "ancestor" {@code Filter} stating the provided key must be
   * an ancestor of the queried entities.
   *
   * @param key the ancestor key.
   * @return a query filter.
   */
  public static Filter ancestor(final Key key) {
    return new Filter("__key__", Filter.Operator.HAS_ANCESTOR, Value.builder().value(key).build());
  }

  /**
   * Ascending ordering for the provided property name.
   *
   * @param name the property name.
   * @return the query ordering.
   */
  public static Order asc(final String name) {
    return new Order(name, Order.Direction.ASCENDING);
  }

  /**
   * Descending ordering for the provided property name.
   *
   * @param name the property name.
   * @return the query ordering.
   */
  public static Order desc(final String name) {
    return new Order(name, Order.Direction.DESCENDING);
  }

  /**
   * Creates a {@code Group} stating the query should be grouped
   * by the provided property name.
   *
   * @param name the name of the property to group by.
   * @return a query grouping.
   */
  public static Group group(final String name) {
    return new Group(name);
  }
} public final class QueryResult implements Result, Iterable<Entity> {

  private final List<Entity> entities;
  private final ByteString cursor;

  private QueryResult(final List<Entity> entities) {
    this.entities = entities;
    this.cursor = null;
  }

  private QueryResult(final List<Entity> entities, final ByteString cursor) {
    this.entities = entities;
    this.cursor = cursor;
  }

  static QueryResult build(final com.google.datastore.v1.LookupResponse response) {
    return new QueryResult(ImmutableList.copyOf(
        response.getFoundList().stream()
            .map(entity -> Entity.builder(entity.getEntity()).build())
            .collect(Collectors.toList())));
  }

  static QueryResult build(final com.google.datastore.v1.RunQueryResponse response) {
    final com.google.datastore.v1.QueryResultBatch batch = response.getBatch();
    return new QueryResult(ImmutableList.copyOf(
        response.getBatch().getEntityResultsList().stream()
            .map(entity -> Entity.builder(entity.getEntity()).build())
            .collect(Collectors.toList())),
        batch.getEndCursor());
  }

  /**
   * Build an empty result.
   *
   * @return a new empty query result.
   */
  public static QueryResult build() {
    return new QueryResult(ImmutableList.of());
  }

  /**
   * Return the first entity returned from the query or null if not found.
   *
   * This is a shortcut for {@code getAll().get(0)}
   *
   * @return an entity returned from the Datastore.
   */
  public Entity getEntity() {
    return Iterables.getFirst(entities, null);
  }

  /**
   * Return all entities returned from the query.
   *
   * @return a list of entities returned from the query.
   */
  public List<Entity> getAll() {
    return entities;
  }

  /**
   * An iterator for all entities returned from the query.
   *
   * @return an entity iterator.
   */
  @Override
  public Iterator<Entity> iterator() {
    return entities.iterator();
  }

  /**
   * The last cursor position after returning all entities for this batch.
   *
   * This is useful for paging; if you supply a {@code limit()}, then the
   * cursor can be used when retrieving the next batch: {@code QueryfromCursor()}.
   *
   * @return the last cursor position.
   */
  public ByteString getCursor() {
    return cursor;
  }
} public final class TransactionResult implements Result {

  private final ByteString transaction;

  private TransactionResult(final ByteString transaction) {
    this.transaction = transaction;
  }

  static TransactionResult build(final com.google.datastore.v1.BeginTransactionResponse response) {
    return new TransactionResult(response.getTransaction());
  }

  /**
   * Build an empty result.
   *
   * @return a new empty transaction result.
   */
  public static TransactionResult build() {
    return new TransactionResult(null);
  }

  ByteString getTransaction() {
    return transaction;
  }
} public static Order asc(final String name) {
  return new Order(name, Order.Direction.ASCENDING);
} public static Filter eq(final String name, final Object value) {
  return new Filter(name, Filter.Operator.EQUAL, Value.builder().value(value).build());
}
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.spotify.asyncdatastoreclient.Batch;
import com.spotify.asyncdatastoreclient.Datastore;
import com.spotify.asyncdatastoreclient.DatastoreConfig;
import com.spotify.asyncdatastoreclient.Entity;
import com.spotify.asyncdatastoreclient.Insert;
import com.spotify.asyncdatastoreclient.KeyQuery;
import com.spotify.asyncdatastoreclient.MutationResult;
import com.spotify.asyncdatastoreclient.Query;
import com.spotify.asyncdatastoreclient.QueryBuilder;
import com.spotify.asyncdatastoreclient.QueryResult;
import com.spotify.asyncdatastoreclient.TransactionResult;
import java.util.Date;
import java.util.List;
import static com.spotify.asyncdatastoreclient.QueryBuilder.asc;
import static com.spotify.asyncdatastoreclient.QueryBuilder.eq;
/*
 * Copyright (c) 2011-2015 Spotify AB
 *
 * 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.spotify.asyncdatastoreclient.example;




/**
 * Some simple asynchronous examples that should help you get started.
 */
public final class ExampleAsync {

  private ExampleAsync() {
  }

  private static ListenableFuture<MutationResult> addData(final Datastore datastore) {
    final Insert insert = QueryBuilder.insert("employee", 1234567L)
        .value("fullname", "Fred Blinge")
        .value("inserted", new Date())
        .value("age", 40);
    return datastore.executeAsync(insert);
  }

  private static ListenableFuture<MutationResult> addDataInTransaction(final Datastore datastore) {
    final ListenableFuture<TransactionResult> txn = datastore.transactionAsync();

    final KeyQuery get = QueryBuilder.query("employee", 2345678L);

    return Futures.transformAsync(datastore.executeAsync(get, txn), result -> {
      if (result.getEntity() == null) {
        datastore.rollbackAsync(txn); // fire and forget
        return Futures.immediateFuture(MutationResult.build());
      }

      final Insert insert = QueryBuilder.insert("employee", 2345678L)
          .value("fullname", "Fred Blinge")
          .value("inserted", new Date())
          .value("age", 40);
      return datastore.executeAsync(insert);
    }, MoreExecutors.directExecutor());
  }

  private static ListenableFuture<QueryResult> queryData(final Datastore datastore) {