org.spongepowered.api.data.DataTransactionResult Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spongeapi Show documentation
Show all versions of spongeapi Show documentation
A plugin API for Minecraft: Java Edition
The newest version!
/*
* This file is part of SpongeAPI, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.api.data;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.api.data.value.Value;
import org.spongepowered.api.util.CopyableBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
/**
* Represents a transaction taking place where a {@link DataHolder.Mutable} is
* accepting {@link Value}s.
*/
public final class DataTransactionResult {
private static final DataTransactionResult SUCCESS_NODATA = DataTransactionResult.builder().result(Type.SUCCESS).build();
private static final DataTransactionResult FAIL_NODATA = DataTransactionResult.builder().result(Type.FAILURE).build();
private static final Collector COLLECTOR = new Collector() {
@Override
public Supplier supplier() {
return DataTransactionResult::builder;
}
@Override
public BiConsumer accumulator() {
return Builder::absorbResult;
}
@Override
public BinaryOperator combiner() {
return (left, right) -> {
left.absorbResult(right.build());
return left;
};
}
@Override
public Function finisher() {
return DataTransactionResult.Builder::build;
}
@Override
public Set characteristics() {
return ImmutableSet.of();
}
};
public static Collector toTransaction() {
return DataTransactionResult.COLLECTOR;
}
/**
* Gets a new {@link Builder} to build a new
* {@link DataTransactionResult}.
*
* @return The new builder, for chaining
*/
public static Builder builder() {
return new Builder();
}
/**
* Creates a {@link DataTransactionResult} with no data successfully added,
* removed, or rejected, and with the
* {@link Type} of
* {@link Type#SUCCESS}
* result type.
*
* @return A clean and empty data transaction
*/
public static DataTransactionResult successNoData() {
return DataTransactionResult.SUCCESS_NODATA;
}
/**
* Creates a new {@link DataTransactionResult} with the provided
* {@link Value.Immutable} being the successful addition. The result type is
* still {@link Type#SUCCESS}. If a {@link Value.Mutable} is
* necessary, use {@link Value.Mutable}#asImmutable()} to use this method. A
* {@link DataTransactionResult} is always immutable once created, and any
* {@link Value}s should be provided as {@link Value.Immutable}s or
* transformed into {@link Value.Immutable}s.
*
* @param value The successfully added immutable value
* @return The new data transaction result
*/
public static DataTransactionResult successResult(final Value.Immutable> value) {
return DataTransactionResult.builder().success(value).result(Type.SUCCESS).build();
}
/**
* Creates a new {@link DataTransactionResult} with the provided
* {@link Value.Immutable} being the successful addition. The result type is
* still {@link Type#SUCCESS}. If a {@link Value.Mutable} is
* necessary, use {@link Value.Mutable}#asImmutable()} to use this method. A
* {@link DataTransactionResult} is always immutable once created, and any
* {@link Value}s should be provided as {@link Value.Immutable}s or
* transformed into {@link Value.Immutable}s.
*
* @param successful The successfully added immutable value
* @param replaced The replaced value
* @return The new data transaction result
*/
public static DataTransactionResult successReplaceResult(final Value.Immutable> successful, final Value.Immutable> replaced) {
return DataTransactionResult.builder().result(Type.SUCCESS).success(successful).replace(replaced).build();
}
/**
* Creates a new {@link DataTransactionResult} with the provided
* {@link Value.Immutable}s being the successful additions and
* the provided {@link Value.Immutable}s that were replaced. The result type
* is still {@link Type#SUCCESS}. If a {@link Value.Mutable}
* is necessary, use {@link Value.Mutable}#asImmutable()} to use this method. A
* {@link DataTransactionResult} is always immutable once created, and any
* {@link Value}s should be provided as {@link Value.Immutable}s or
* transformed into {@link Value.Immutable}s.
*
* @param successful The successfully added immutable values
* @param replaced The successfully replaced immutable values
* @return The new data transaction result
*/
public static DataTransactionResult successReplaceResult(final Collection> successful, final Collection> replaced) {
return DataTransactionResult.builder().success(successful).replace(replaced).result(Type.SUCCESS).build();
}
/**
* Creates a {@link DataTransactionResult} with the provided
* {@link Value.Immutable}s being successfully removed. The result type is
* still {@link Type#SUCCESS}. If a {@link Value.Mutable} is necessary, use
* {@link Value.Mutable}#asImmutable()} to use this method. A {@link DataTransactionResult}
* is always immutable once created, and any {@link Value}s should be provided
* as {@link Value.Immutable}s or transformed into {@link Value.Immutable}s.
*
* @param removed The successfully removed values
* @return The new data transaction result
*/
public static DataTransactionResult successRemove(final Collection> removed) {
return DataTransactionResult.builder().replace(removed).result(Type.SUCCESS).build();
}
/**
* Creates a {@link DataTransactionResult} with the provided
* {@link Value.Immutable} being successfully removed. The result type is
* still {@link Type#SUCCESS}. If a {@link Value.Mutable} is necessary, use
* {@link Value.Mutable}#asImmutable()} to use this method. A
* {@link DataTransactionResult} is always immutable once created, and a
* {@link Value} should be provided as an {@link Value.Immutable} or
* transformed into an {@link Value.Immutable}.
*
* @param removed The successfully removed value
* @return The new data transaction result
*/
public static DataTransactionResult successRemove(final Value.Immutable> removed) {
return DataTransactionResult.builder().replace(removed).result(Type.SUCCESS).build();
}
/**
* Creates a new {@link DataTransactionResult} that ends in failure. The
* provided {@link Value.Immutable} is considered "rejected" and was not
* successfully added.
*
* @param value The value that was rejected
* @return The new data transaction result
*/
public static DataTransactionResult failResult(final Value.Immutable> value) {
return DataTransactionResult.builder().reject(value).result(Type.FAILURE).build();
}
/**
* Creates a new {@link DataTransactionResult} that ends in failure. The
* provided {@link Value.Immutable}s are considered "rejected" and were not
* successfully added.
*
* @param values The values that were rejected
* @return The new data transaction result
*/
public static DataTransactionResult failResult(final Iterable> values) {
return DataTransactionResult.builder().reject(values).result(Type.FAILURE).build();
}
/**
* Creates a new {@link DataTransactionResult} that ends in failure. There
* is no additional data to include.
*
* @return The new data transaction result
*/
public static DataTransactionResult failNoData() {
return DataTransactionResult.FAIL_NODATA;
}
/**
* Creates a new {@link DataTransactionResult} that ends in failure. The
* provided {@link Value.Immutable} is considered "incompatible" and was not
* successfully added.
*
* @param value The value that was incompatible or errored
* @return The new data transaction result
*/
public static DataTransactionResult errorResult(final Value.Immutable> value) {
return DataTransactionResult.builder().result(Type.ERROR).reject(value).build();
}
/**
* The type of transaction result.
*/
public enum Type {
/**
* The actual result of the operation is undefined, this probably
* indicates that something went wrong with the operation that the
* {@link Value} couldn't handle or didn't expect. The
* state of the {@link Value} is undefined.
*/
UNDEFINED,
/**
* The item data operation succeeded.
*/
SUCCESS,
/**
* The {@link Value} operation failed for an
* expected reason (such as the {@link Value} being
* incompatible with the {@link DataHolder}. The condition of the
* {@link Value} is unchanged.
*/
FAILURE,
/**
* The {@link Value} operation failed because an
* unexpected condition occurred. The state of the
* {@link Value} is undefined.
*/
ERROR,
/**
* An operation was cancelled by a third party (eg. a
* {@link Value} event was cancelled). The condition of the
* {@link Value} is unchanged.
*/
CANCELLED,
;
}
final Type type;
private final ImmutableList> rejected;
private final ImmutableList> replaced;
private final ImmutableList> success;
DataTransactionResult(final Builder builder) {
this.type = builder.resultType;
if (builder.rejected != null) {
this.rejected = ImmutableList.copyOf(builder.rejected);
} else {
this.rejected = ImmutableList.of();
}
if (builder.replaced != null) {
this.replaced = ImmutableList.copyOf(builder.replaced);
} else {
this.replaced = ImmutableList.of();
}
if (builder.successful != null) {
this.success = ImmutableList.copyOf(builder.successful);
} else {
this.success = ImmutableList.of();
}
}
/**
* Gets the type of result.
*
* @return the type of result
*/
public Type type() {
return this.type;
}
/**
* Gets whether this {@link DataTransactionResult} was successful or not.
*
* @return True if this result was successful
*/
public boolean isSuccessful() {
return this.type() == Type.SUCCESS;
}
/**
* If any {@link Value}s applied onto a {@link DataHolder} were
* successful, they'll be stored in the given list.
*
* @return An immutable list of the values successfully offered
*/
public List> successfulData() {
return this.success;
}
/**
* Gets the successfully applied {@link Value} based on the provided {@link Key}.
*
* @param key The key
* @param The data type
* @param The value type
* @return The value, if available
*/
@SuppressWarnings("unchecked")
public > Optional> successfulValue(final Key key) {
for (final Value.Immutable> value : this.successfulData()) {
if (value.key() == key) {
return Optional.of((Value.Immutable) value);
}
}
return Optional.empty();
}
/**
* If {@link Value.Mutable}s were supplied to the operation, this
* collection will return any {@link Value.Immutable}s which were rejected
* by the target {@link DataHolder}.
*
* @return Any data that was rejected from the operation
*/
public List> rejectedData() {
return this.rejected;
}
/**
* Gets the rejected {@link Value} based on the provided {@link Key}.
*
* @param key The key
* @param The data type
* @param The value type
* @return The value, if available
*/
@SuppressWarnings("unchecked")
public > Optional> rejectedValue(final Key key) {
for (final Value.Immutable> value : this.rejectedData()) {
if (value.key() == key) {
return Optional.of((Value.Immutable) value);
}
}
return Optional.empty();
}
/**
* If the operation replaced any {@link Value.Mutable}s, this returns a collection
* of the replaced {@link Value.Immutable}s.
*
* @return Any data that was replaced
*/
public List> replacedData() {
return this.replaced;
}
/**
* Gets the replaced {@link Value} based on the provided {@link Key}.
*
* @param key The key
* @param The data type
* @param The value type
* @return The value, if available
*/
@SuppressWarnings("unchecked")
public > Optional> replacedValue(final Key key) {
for (final Value.Immutable> value : this.replacedData()) {
if (value.key() == key) {
return Optional.of((Value.Immutable) value);
}
}
return Optional.empty();
}
/**
* If this result of {@link #isSuccessful()} returns {@code true},
* the provided {@link Consumer} is called provided a list of all
* "successful" data as retrieved from {@link #successfulData()}.
*
* @param consumer The consumer to call
*/
public void ifSuccessful(final Consumer>> consumer) {
if (this.isSuccessful()) {
consumer.accept(this.success);
}
}
/**
* Used to call a {@link Supplier} for an {@link Exception} of type
* {@code E} such that if this transaction's {@link #isSuccessful()}
* returns {@code false}, the supplier's exception is thrown.
*
* @param supplier The supplier of the exception to throw
* @param The type of exception
* @throws E The exception to throw if this transaction is not successful
*/
public void ifNotSuccessful(final Supplier supplier) throws E {
if (!this.isSuccessful()) {
throw supplier.get();
}
}
@Override
public String toString() {
return new StringJoiner(", ", DataTransactionResult.class.getSimpleName() + "[", "]")
.add("type=" + this.type)
.add("rejected=" + this.rejected)
.add("replaced=" + this.replaced)
.add("success=" + this.success)
.toString();
}
@Override
public boolean equals(final @Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final DataTransactionResult that = (DataTransactionResult) o;
return this.type == that.type
&& Objects.equals(this.rejected, that.rejected)
&& Objects.equals(this.replaced, that.replaced)
&& Objects.equals(this.success, that.success);
}
@Override
public int hashCode() {
return Objects.hash(this.type, this.rejected, this.replaced, this.success);
}
/**
* A type of builder for building {@link DataTransactionResult}s. The common
* use is for both implementations of {@link DataHolder}s, and various
* {@link org.spongepowered.api.event.data.ChangeDataHolderEvent.ValueChange}s.
*/
public static final class Builder implements org.spongepowered.api.util.Builder, CopyableBuilder {
@MonotonicNonNull List> rejected;
@MonotonicNonNull List> replaced;
@MonotonicNonNull List> successful;
@MonotonicNonNull Type resultType;
Builder() {
}
/**
* Sets the expectant {@link Type} to the provided
* type. A {@link DataTransactionResult} must always have a type to mark
* the transaction a "success" or "failure".
*
* @param type The type of the transaction result
* @return This builder, for chaining
*/
public Builder result(final Type type) {
this.resultType = Objects.requireNonNull(type);
return this;
}
/**
* Adds the provided {@link Value.Immutable} to the {@link List} of
* "replaced" {@link Value.Immutable}s. The replaced values are always
* copied for every {@link DataTransactionResult} for referencing.
*
* @param value The value to replace
* @return This builder, for chaining
*/
public Builder replace(final Value.Immutable> value) {
if (this.replaced == null) {
this.replaced = new ArrayList<>();
}
this.replaced.add(Objects.requireNonNull(value));
return this;
}
/**
* Adds the provided {@link Value.Immutable}s to the {@link List} of
* "replaced" {@link Value.Immutable}s. The replaced values are always
* copied for every {@link DataTransactionResult} for referencing.
*
* @param values The values to replace
* @return This builder, for chaining
*/
public Builder replace(final Iterable> values) {
for (final Value.Immutable> value : values) {
this.replace(Objects.requireNonNull(value));
}
return this;
}
/**
* Adds the provided {@link Value.Immutable} to the {@link List} of
* "rejected" {@link Value.Immutable}s. The rejected values are always
* copied for every {@link DataTransactionResult} for referencing.
*
* @param value The values to reject
* @return This builder, for chaining
*/
public Builder reject(final Value.Immutable> value) {
if (this.rejected == null) {
this.rejected = new ArrayList<>();
}
this.rejected.add(Objects.requireNonNull(value));
return this;
}
/**
* Adds the provided {@link Value.Immutable}s to the {@link List} of
* "rejected" {@link Value.Immutable}s. The rejected values are always
* copied for every {@link DataTransactionResult} for referencing.
*
* @param values The values to reject
* @return This builder, for chaining
*/
public Builder reject(final Iterable> values) {
for (final Value.Immutable> value : values) {
this.reject(Objects.requireNonNull(value));
}
return this;
}
/**
* Adds the provided {@link Value.Immutable} to the {@link List} of
* "successful" {@link Value.Immutable}s. The successful values are always
* copied for every {@link DataTransactionResult} for referencing.
*
* @param value The value that was successfully provided
* @return This builder, for chaining
*/
public Builder success(final Value.Immutable> value) {
if (this.successful == null) {
this.successful = new ArrayList<>();
}
this.successful.add(Objects.requireNonNull(value));
return this;
}
/**
* Adds the provided {@link Value.Immutable}s to the {@link List} of
* "successful" {@link Value.Immutable}s. The rejected values are always
* copied for every {@link DataTransactionResult} for referencing.
*
* @param values The values that were successfully provided
* @return This builder, for chaining
*/
public Builder success(final Iterable> values) {
for (final Value.Immutable> value : values) {
this.success(Objects.requireNonNull(value));
}
return this;
}
/**
* Combines the currently building {@link DataTransactionResult} with the
* one provided. Usually, this means that there is some merging of the
* {@link Value.Immutable}s based on {@link Key}. If this builder already
* has an {@link Value.Immutable} as being successfully offered, and the
* provided result shows the same key as being rejected, the rejected
* {@link Value.Immutable} will remain in the final result.
*
* @param result The result to merge
* @return This builder, for chaining
*/
public Builder absorbResult(final DataTransactionResult result) {
// First, let's handle the type:
if (this.resultType == null) {
this.resultType = result.type();
} else {
if (this.resultType.compareTo(result.type()) < 0) {
this.resultType = result.type();
}
}
final List> newSuccessful = new ArrayList<>();
final List> newReplaced = new ArrayList<>();
final List> newRejected = new ArrayList<>();
// Now let's handle the successful data
if (this.successful != null) {
dance:
for (final Value.Immutable> value : this.successful) {
for (final Value.Immutable> rejected : result.rejectedData()) {
if (value.key().equals(rejected.key())) {
newRejected.add(rejected);
continue dance;
}
}
for (final Value.Immutable> replaced : result.replacedData()) {
if (value.key().equals(replaced.key())) {
newReplaced.add(value);
continue dance;
}
}
for (final Value.Immutable> successful : result.successfulData()) {
if (value.key().equals(successful.key())) {
newSuccessful.add(successful);
continue dance;
}
}
newSuccessful.add(value);
}
}
if (this.replaced != null) {
dance:
for (final Value.Immutable> value : this.replaced) {
for (final Value.Immutable> rejected : result.rejectedData()) {
if (value.key().equals(rejected.key())) {
newRejected.add(rejected);
continue dance;
}
}
for (final Value.Immutable> replaced : result.replacedData()) {
if (value.key().equals(replaced.key())) {
newReplaced.add(value);
continue dance;
}
}
for (final Value.Immutable> successful : result.successfulData()) {
if (value.key().equals(successful.key())) {
newSuccessful.add(successful);
continue dance;
}
}
newReplaced.add(value);
}
}
if (this.rejected != null) {
dance:
for (final Value.Immutable> value : this.rejected) {
for (final Value.Immutable> rejected : result.rejectedData()) {
if (value.key().equals(rejected.key())) {
newRejected.add(rejected);
continue dance;
}
}
for (final Value.Immutable> replaced : result.replacedData()) {
if (value.key().equals(replaced.key())) {
newReplaced.add(value);
continue dance;
}
}
for (final Value.Immutable> successful : result.successfulData()) {
if (value.key().equals(successful.key())) {
newSuccessful.add(successful);
continue dance;
}
}
newRejected.add(value);
}
}
dance:
for (final Value.Immutable> value : result.successfulData()) {
for (final Value.Immutable> rejected : newRejected) {
if (value.key().equals(rejected.key())) {
continue dance;
}
}
for (final Value.Immutable> replaced : newReplaced) {
if (value.key().equals(replaced.key())) {
continue dance;
}
}
for (final Value.Immutable> successful : newSuccessful) {
if (value.key().equals(successful.key())) {
continue dance;
}
}
newSuccessful.add(value);
}
dance:
for (final Value.Immutable> value : result.rejectedData()) {
for (final Value.Immutable> rejected : newRejected) {
if (value.key().equals(rejected.key())) {
continue dance;
}
}
for (final Value.Immutable> replaced : newReplaced) {
if (value.key().equals(replaced.key())) {
continue dance;
}
}
for (final Value.Immutable> successful : newSuccessful) {
if (value.key().equals(successful.key())) {
continue dance;
}
}
newRejected.add(value);
}
dance:
for (final Value.Immutable> value : result.replacedData()) {
for (final Value.Immutable> rejected : newRejected) {
if (value.key().equals(rejected.key())) {
continue dance;
}
}
for (final Value.Immutable> replaced : newReplaced) {
if (value.key().equals(replaced.key())) {
continue dance;
}
}
for (final Value.Immutable> successful : newSuccessful) {
if (value.key().equals(successful.key())) {
continue dance;
}
}
newReplaced.add(value);
}
this.replaced = newReplaced;
this.rejected = newRejected;
this.successful = newSuccessful;
return this;
}
/**
* Builds a new {@link DataTransactionResult} with the providing
* {@link List}s of {@link Value.Immutable}s that are successfully
* offered, {@link Value.Immutable}s that were replaced, and
* {@link Value.Immutable}s that were rejected.
*
* @return The newly created transaction result
*/
@Override
public DataTransactionResult build() {
if (this.resultType == null) {
throw new IllegalStateException("ResultType must be set!");
}
return new DataTransactionResult(this);
}
@Override
public Builder from(final DataTransactionResult value) {
this.resultType = value.type;
this.rejected = new ArrayList<>(value.rejectedData());
this.replaced = new ArrayList<>(value.replacedData());
this.successful = new ArrayList<>(value.successfulData());
return this;
}
@Override
public Builder reset() {
this.rejected = null;
this.replaced = null;
this.successful = null;
this.resultType = null;
return this;
}
}
}