/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* 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.lanternpowered.server.data.value.processor;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import org.lanternpowered.server.data.value.ElementHolder;
import org.lanternpowered.server.data.value.IValueContainer;
import org.lanternpowered.server.data.value.ValueHelper;
import org.lanternpowered.server.util.functions.TriFunction;
import org.spongepowered.api.data.DataTransactionResult;
import org.spongepowered.api.data.key.Key;
import org.spongepowered.api.data.value.BaseValue;
import org.spongepowered.api.data.value.immutable.ImmutableValue;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import javax.annotation.Nullable;
public class SimpleValueProcessor<V extends BaseValue<E>, E> implements ValueProcessor<V, E> {
private BiFunction<Key<? extends V>, IValueContainer<?>, DataTransactionResult> removeHandler;
private TriFunction<Key<? extends V>, IValueContainer<?>, E, DataTransactionResult> offerHandler;
private BiFunction<IValueContainer<?>, V, DataTransactionResult> valueOfferHandler;
private BiFunction<Key<? extends V>, IValueContainer<?>, Optional<E>> retrieveHandler;
private BiFunction<Key<? extends V>, IValueContainer<?>, Optional<V>> valueRetrieveHandler;
private final TriFunction<Key<? extends V>, IValueContainer<?>, E, V> valueBuilder;
private BiPredicate<Key<? extends V>, IValueContainer<?>> applicableTester;
public SimpleValueProcessor(
BiFunction<Key<? extends V>, IValueContainer<?>, DataTransactionResult> removeHandler,
TriFunction<Key<? extends V>, IValueContainer<?>, E, DataTransactionResult> offerHandler,
BiFunction<Key<? extends V>, IValueContainer<?>, Optional<E>> retrieveHandler,
BiFunction<Key<? extends V>, IValueContainer<?>, Optional<V>> valueRetrieveHandler,
TriFunction<Key<? extends V>, IValueContainer<?>, E, V> valueBuilder,
BiPredicate<Key<? extends V>, IValueContainer<?>> applicableTester) {
this.removeHandler = removeHandler;
this.offerHandler = offerHandler;
this.valueBuilder = valueBuilder;
this.applicableTester = applicableTester;
this.retrieveHandler = retrieveHandler;
this.valueRetrieveHandler = valueRetrieveHandler;
}
@Override
public BiPredicate<Key<? extends V>, IValueContainer<?>> getApplicableTester() {
return this.applicableTester;
}
@Override
public BiFunction<Key<? extends V>, IValueContainer<?>, DataTransactionResult> getRemoveHandler() {
return this.removeHandler;
}
@Override
public TriFunction<Key<? extends V>, IValueContainer<?>, E, DataTransactionResult> getOfferHandler() {
return this.offerHandler;
}
@Override
public BiFunction<Key<? extends V>, IValueContainer<?>, Optional<E>> getRetrieveHandler() {
return this.retrieveHandler;
}
@Override
public BiFunction<Key<? extends V>, IValueContainer<?>, Optional<V>> getValueRetrieveHandler() {
return this.valueRetrieveHandler;
}
@Override
public BiFunction<IValueContainer<?>, V, DataTransactionResult> getValueOfferHandler() {
return this.valueOfferHandler;
}
@Override
public TriFunction<Key<? extends V>, IValueContainer<?>, E, V> getValueBuilder() {
return this.valueBuilder;
}
@SuppressWarnings("unchecked")
public static class BuilderBase<V extends BaseValue<E>, E, B extends ValueProcessor.BuilderBase<V, E, B>>
implements ValueProcessor.BuilderBase<V, E, B> {
@Nullable protected BiFunction<Key<? extends V>, IValueContainer<?>, DataTransactionResult> removeHandler;
@Nullable protected TriFunction<Key<? extends V>, IValueContainer<?>, E, DataTransactionResult> offerHandler;
@Nullable protected BiFunction<IValueContainer<?>, V, DataTransactionResult> valueOfferHandler;
@Nullable protected TriFunction<Key<? extends V>, IValueContainer<?>, E, V> valueBuilder;
@Nullable protected BiFunction<Key<? extends V>, IValueContainer<?>, Optional<E>> retrieveHandler;
@Nullable protected BiFunction<Key<? extends V>, IValueContainer<?>, Optional<V>> valueRetrieveHandler;
@Nullable protected BiPredicate<Key<? extends V>, IValueContainer<?>> applicableTester;
BuilderBase() {
this.reset();
}
@Override
public B applicableTester(BiPredicate<Key<? extends V>, IValueContainer<?>> tester) {
this.applicableTester = checkNotNull(tester, "tester");
return (B) this;
}
@Override
public B removeHandler(BiFunction<Key<? extends V>, IValueContainer<?>, DataTransactionResult> handler) {
this.removeHandler = checkNotNull(handler, "handler");
return (B) this;
}
@Override
public B valueOfferHandler(BiFunction<IValueContainer<?>, V, DataTransactionResult> handler) {
this.valueOfferHandler = checkNotNull(handler, "handler");
return (B) this;
}
@Override
public B offerHandler(TriFunction<Key<? extends V>, IValueContainer<?>, E, DataTransactionResult> handler) {
this.offerHandler = checkNotNull(handler, "handler");
return (B) this;
}
@Override
public B retrieveHandler(BiFunction<Key<? extends V>, IValueContainer<?>, Optional<E>> handler) {
this.retrieveHandler = checkNotNull(handler, "handler");
return (B) this;
}
@Override
public B valueRetrieveHandler(BiFunction<Key<? extends V>, IValueContainer<?>, Optional<V>> handler) {
this.valueRetrieveHandler = checkNotNull(handler, "handler");
return (B) this;
}
@Override
public B valueBuilder(TriFunction<Key<? extends V>, IValueContainer<?>, E, V> builder) {
this.valueBuilder = checkNotNull(builder, "builder");
return (B) this;
}
@SuppressWarnings("unchecked")
@Override
public SimpleValueProcessor<V, E> build() {
checkState(this.offerHandler != null || this.valueOfferHandler != null, "The offer handler must be set.");
checkState(this.retrieveHandler != null || this.valueRetrieveHandler != null, "The retrieve handler must be set.");
SimpleValueProcessor<V, E> valueProcessor = new SimpleValueProcessor<>(this.removeHandler == null ? RemoveHandlers.failAlways() :
this.removeHandler, this.offerHandler, this.retrieveHandler, this.valueRetrieveHandler, this.valueBuilder == null ?
ValueBuilders.def() : this.valueBuilder, this.applicableTester == null ? (k, c) -> true : this.applicableTester);
if (this.offerHandler == null) {
valueProcessor.offerHandler = (key, valueContainer, element) -> valueProcessor.getValueOfferHandler()
.apply(valueContainer, valueProcessor.getValueBuilder().apply(key, valueContainer, element));
} else if (this.valueOfferHandler == null) {
valueProcessor.valueOfferHandler = (valueContainer, value) -> valueProcessor.getOfferHandler()
.apply((Key) value.getKey(), valueContainer, value.get());
}
if (this.retrieveHandler == null) {
valueProcessor.retrieveHandler = (key, valueContainer) -> valueProcessor.getValueRetrieveHandler()
.apply(key, valueContainer).flatMap(value -> Optional.of(value.get()));
} else if (this.valueRetrieveHandler == null) {
valueProcessor.valueRetrieveHandler = (key, valueContainer) -> valueProcessor.getRetrieveHandler().apply(key, valueContainer)
.flatMap(element -> Optional.of(valueProcessor.getValueBuilder().apply(key, valueContainer, element)));
}
return valueProcessor;
}
@Override
public B from(ValueProcessor<V, E> value) {
this.offerHandler = value.getOfferHandler();
this.valueOfferHandler = value.getValueOfferHandler();
this.removeHandler = value.getRemoveHandler();
this.retrieveHandler = value.getRetrieveHandler();
this.valueRetrieveHandler = value.getValueRetrieveHandler();
this.valueBuilder = value.getValueBuilder();
this.applicableTester = value.getApplicableTester();
return (B) this;
}
@Override
public B reset() {
this.offerHandler = null;
this.valueOfferHandler = null;
this.removeHandler = null;
this.retrieveHandler = null;
this.valueRetrieveHandler = null;
this.valueBuilder = null;
this.applicableTester = null;
return (B) this;
}
}
public static class Builder<V extends BaseValue<E>, E> extends BuilderBase<V, E, ValueProcessor.Builder<V, E>>
implements ValueProcessor.Builder<V, E> {
Builder() {
}
}
public static class AttachedElementBuilder<V extends BaseValue<E>, E> extends BuilderBase<V, E, ValueProcessor.AttachedElementBuilder<V, E>>
implements ValueProcessor.AttachedElementBuilder<V, E> {
/**
* The default {@link SimpleValueProcessor} for attached elements.
*/
static SimpleValueProcessor DEFAULT;
/**
* The shared {@link SimpleValueProcessor} that will only make the attached elements unremovable.
*/
static SimpleValueProcessor NON_REMOVABLE;
private static boolean initialized = false;
static {
DEFAULT = new AttachedElementBuilder().build();
NON_REMOVABLE = (SimpleValueProcessor) new AttachedElementBuilder().failAlwaysRemoveHandler().build();
initialized = true;
}
AttachedElementBuilder() {
}
@Override
public AttachedElementBuilder<V, E> from(ValueProcessor<V, E> value) {
if (value == DEFAULT || value == NON_REMOVABLE) {
this.reset();
if (value == NON_REMOVABLE) {
this.removeHandler = value.getRemoveHandler();
}
} else {
super.from(value);
}
return this;
}
@SuppressWarnings("unchecked")
@Override
public SimpleValueProcessor<V, E> build() {
// initialized -> Make sure that the cached fields are initialized before we can use them
// otherwise will the cached fields never be non-null
if (initialized && this.applicableTester == null && this.offerHandler == null && this.valueOfferHandler == null &&
this.retrieveHandler == null && this.valueRetrieveHandler == null && this.valueBuilder == null) {
if (this.removeHandler == null) {
return DEFAULT;
} else if (this.removeHandler.equals(RemoveHandlers.failAlways())) {
return NON_REMOVABLE;
}
}
SimpleValueProcessor<V, E> valueProcessor = new SimpleValueProcessor<>(this.removeHandler, this.offerHandler, this.retrieveHandler,
this.valueRetrieveHandler, this.valueBuilder == null ? ValueBuilders.def() : this.valueBuilder, applicableTester);
if (this.applicableTester == null) {
valueProcessor.applicableTester = (key, container) -> container.getElementHolder(key) != null;
} else {
BiPredicate<Key<? extends V>, IValueContainer<?>> applicableTester = this.applicableTester;
valueProcessor.applicableTester = (key, container) -> container.getElementHolder(key) != null && applicableTester.test(key, container);
}
if (this.retrieveHandler == null) {
if (this.valueRetrieveHandler == null) {
valueProcessor.retrieveHandler = (key, valueContainer) -> {
ElementHolder<E> iValue = checkNotNull(valueContainer.getElementHolder(key));
return Optional.ofNullable(iValue.get());
};
} else {
valueProcessor.retrieveHandler = (key, valueContainer) -> valueProcessor.getValueRetrieveHandler()
.apply(key, valueContainer).flatMap(value -> Optional.of(value.get()));
}
}
if (this.valueRetrieveHandler == null) {
if (this.retrieveHandler == null) {
valueProcessor.valueRetrieveHandler = (key, valueContainer) -> {
ElementHolder<E> iValue = checkNotNull(valueContainer.getElementHolder(key));
E element = iValue.get();
return element == null ? Optional.empty() : Optional.of(valueProcessor.getValueBuilder().apply(key, valueContainer, element));
};
} else {
valueProcessor.valueRetrieveHandler = (key, valueContainer) -> valueProcessor.getRetrieveHandler().apply(key, valueContainer)
.flatMap(element -> Optional.of(valueProcessor.getValueBuilder().apply(key, valueContainer, element)));
}
}
if (this.offerHandler == null) {
if (this.valueOfferHandler == null) {
valueProcessor.offerHandler = (key, valueContainer, element) -> {
ElementHolder<E> elementHolder = checkNotNull(valueContainer.getElementHolder(key));
E oldElement = elementHolder.set(element);
ImmutableValue<E> newValue = ValueHelper.toImmutable(valueProcessor.getValueBuilder().apply(key, valueContainer, element));
if (oldElement != null) {
ImmutableValue<E> oldValue =
ValueHelper.toImmutable(valueProcessor.getValueBuilder().apply(key, valueContainer, oldElement));
return DataTransactionResult.successReplaceResult(newValue, oldValue);
}
return DataTransactionResult.successResult(newValue);
};
} else {
valueProcessor.offerHandler = (key, valueContainer, element) -> valueProcessor.getValueOfferHandler()
.apply(valueContainer, valueProcessor.getValueBuilder().apply(key, valueContainer, element));
}
}
if (this.valueOfferHandler == null) {
if (this.offerHandler == null) {
valueProcessor.valueOfferHandler = (valueContainer, value) -> {
Key key = value.getKey();
E element = value.get();
ElementHolder<E> elementHolder = checkNotNull(valueContainer.getElementHolder(key));
E oldElement = elementHolder.set(element);
ImmutableValue<E> newValue = ValueHelper.toImmutable(value);
if (oldElement != null) {
ImmutableValue<E> oldValue =
ValueHelper.toImmutable(valueProcessor.getValueBuilder().apply(key, valueContainer, oldElement));
return DataTransactionResult.successReplaceResult(newValue, oldValue);
}
return DataTransactionResult.successResult(newValue);
};
} else {
valueProcessor.valueOfferHandler = (valueContainer, value) -> valueProcessor.getOfferHandler()
.apply((Key) value.getKey(), valueContainer, value.get());
}
}
if (this.removeHandler == null) {
valueProcessor.removeHandler = (key, valueContainer) -> {
ElementHolder<E> elementHolder = checkNotNull(valueContainer.getElementHolder(key));
E oldElement = elementHolder.set(null);
if (oldElement != null) {
ImmutableValue<E> oldValue = ValueHelper.toImmutable(valueProcessor.getValueBuilder().apply(key, valueContainer, oldElement));
return DataTransactionResult.successRemove(oldValue);
}
return DataTransactionResult.failNoData();
};
}
return valueProcessor;
}
}
}