Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.datacomponent.item.ResolvableProfile;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.persistence.PersistentDataKey;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
import io.papermc.paper.world.damagesource.CombatEntry;
import io.papermc.paper.world.damagesource.FallLocationType;
Expand All @@ -13,6 +14,7 @@
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.util.Services;
Expand All @@ -31,6 +33,7 @@
import org.bukkit.entity.Pose;
import org.bukkit.entity.SpawnCategory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
Expand Down Expand Up @@ -121,4 +124,6 @@ default VersionFetcher getVersionFetcher() {
Component resolveWithContext(Component component, @Nullable CommandSender context, @Nullable Entity scoreboardSubject, boolean bypassPermissions) throws IOException;

ComponentFlattener componentFlattener();

<C> PersistentDataKey<C> createPersistentDataKey(Key key, PersistentDataType<?, C> type);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.papermc.paper.persistence;

import java.util.Set;
import com.google.common.base.Preconditions;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer;
Expand Down Expand Up @@ -66,6 +67,32 @@ public interface PersistentDataContainerView {
*/
boolean has(NamespacedKey key);

/**
* Returns if the persistent metadata provider has metadata registered
* matching the provided parameters.
* <p>
* This method will only return true if the found value has the same primitive
* data type as the provided data key.
* <p>
* Storing a value using a custom {@link PersistentDataType} implementation
* will not store the complex data type. Therefore, storing a UUID (by
* storing a byte[]) will match has("key", {@link PersistentDataType#BYTE_ARRAY}).
* Likewise, a stored byte[] will always match your UUID {@link PersistentDataType}
* even if it is not 16 bytes long.
* <p>
* This method is only usable for custom object keys. Overwriting existing
* tags, like the display name, will not work as the values are stored
* using your namespace.
*
* @param dataKey the data key the value is stored under
* @return if a value with the provided key and type exists
* @throws IllegalArgumentException if the key to look up is null
*/
default boolean has(final PersistentDataKey<?> dataKey) {
Preconditions.checkArgument(dataKey != null, "The data key cannot be null");
return this.has(dataKey.key(), dataKey.dataType());
}

/**
* Returns the metadata value that is stored on the
* {@link PersistentDataHolder} instance.
Expand Down Expand Up @@ -110,6 +137,46 @@ public interface PersistentDataContainerView {
*/
<P, C> C getOrDefault(NamespacedKey key, PersistentDataType<P, C> type, C defaultValue);

/**
* Returns the metadata value that is stored on the
* {@link PersistentDataHolder} instance.
*
* @param dataKey the data key to look up in the custom tag map
* @param <C> the generic type of the eventually created complex object
* @return the value or {@code null} if no value was mapped under the given
* value
* @throws IllegalArgumentException if the data key to look up is null
* @throws IllegalArgumentException if a value exists under the given key,
* but cannot be accessed using the given type
* @throws IllegalArgumentException if no suitable adapter was found for
* the {@link PersistentDataType#getPrimitiveType()}
*/
default <C> @Nullable C get(final PersistentDataKey<C> dataKey) {
Preconditions.checkArgument(dataKey != null, "The data key cannot be null");
return this.get(dataKey.key(), dataKey.dataType());
}

/**
* Returns the metadata value that is stored on the
* {@link PersistentDataHolder} instance. If the value does not exist in the
* container, the default value provided is returned.
*
* @param dataKey the data key to look up in the custom tag map
* @param defaultValue the default value to return if no value was found for
* the provided key
* @param <C> the generic type of the eventually created complex object
* @return the value or the default value if no value was mapped under the given key
* @throws IllegalArgumentException if the data key to look up is null
* @throws IllegalArgumentException if a value exists under the given key,
* but cannot be accessed using the given type
* @throws IllegalArgumentException if no suitable adapter was found for
* the {@link PersistentDataType#getPrimitiveType()}
*/
default <C> C getOrDefault(final PersistentDataKey<C> dataKey, final C defaultValue) {
Preconditions.checkArgument(dataKey != null, "The data key cannot be null");
return this.getOrDefault(dataKey.key(), dataKey.dataType(), defaultValue);
}

/**
* Get the set of keys present on this {@link PersistentDataContainer}
* instance.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.papermc.paper.persistence;

import io.papermc.paper.InternalAPIBridge;
import net.kyori.adventure.key.Key;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jspecify.annotations.NullMarked;

/**
* This class represents the union of a {@link Key} and a
* {@link PersistentDataType}.
* <p>
* The advantage of this class is more direct, typed access to data stored in a {@link PersistentDataContainer}.
* Instead of having to keep track of the {@link NamespacedKey} and {@link PersistentDataType} separately
* when working with PDC, this class allows you to store both together.
* <p>
* <h2>Example usage</h2>
* <pre>{@code
* public class CustomLogic {
* public static final PersistentDataKey<Integer> TRACKED_VALUE = PersistentDataKey.of(
* Key.key("custom:tracked_value"),
* PersistentDataType.INTEGER
* );
* public static final PersistentDataKey<UUID> OWNING_PLAYER = PersistentDataKey.of(
* Key.key("custom:owning_player"),
* new UUIDTagType() // Custom type
* );
*
* // Checks if the stored PDC value for OWNING_PLAYER matches the executor UUID and adds
* // 1 to the TRACKED_VALUE, if it does.
* public static void incrementIfOwner(PersistentDataContainer pdc, UUID executor) {
* if (!Objects.equals(pdc.get(OWNING_PLAYER), executor) {
* return;
* }
*
* pdc.set(TRACKED_VALUE, pdc.getOrDefault(TRACKED_VALUE, 0) + 1);
* }
* }
* }</pre>
*
* @param <C> the retrieved object type when applying this tag type
*/
@NullMarked
public interface PersistentDataKey<C> {

/**
* Creates a new {@link PersistentDataKey} with the given {@link Key} and {@link PersistentDataType}.
*
* @param key the access key to store
* @param type the persistent data type to reference
* @param <C> the retrieved object type when applying this tag type
* @return a new {@link PersistentDataKey} of the given key and type
* @throws IllegalArgumentException if either key or type are null
*/
static <C> PersistentDataKey<C> of(final Key key, final PersistentDataType<?, C> type) {
return InternalAPIBridge.get().createPersistentDataKey(key, type);
}

/**
* {@return the access key of this {@link PersistentDataKey}}
*/
NamespacedKey key();

/**
* {@return the data type of this {@link PersistentDataKey}}
*/
PersistentDataType<?, C> dataType();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.bukkit.persistence;

import com.google.common.base.Preconditions;
import io.papermc.paper.persistence.PersistentDataKey;
import org.bukkit.NamespacedKey;
import org.jetbrains.annotations.NotNull;

Expand All @@ -26,11 +28,34 @@ public interface PersistentDataContainer extends io.papermc.paper.persistence.Pe
* @throws IllegalArgumentException if the key is null
* @throws IllegalArgumentException if the type is null
* @throws IllegalArgumentException if the value is null. Removing a tag should
* be done using {@link #remove(NamespacedKey)}
* be done using {@link #remove(NamespacedKey)} or {@link #remove(PersistentDataKey)}
* @throws IllegalArgumentException if no suitable adapter was found for
* the {@link PersistentDataType#getPrimitiveType()}
*/
<P, C> void set(@NotNull NamespacedKey key, @NotNull PersistentDataType<P, C> type, @NotNull C value);

/**
* Stores a metadata value on the {@link PersistentDataHolder} instance.
* <p>
* This API cannot be used to manipulate minecraft data, as the values will
* be stored using your namespace. This method will override any existing
* value the {@link PersistentDataHolder} may have stored under the provided
* key.
*
* @param dataKey the data key this value will be stored under
* @param value the value to store in the tag
* @param <C> the generic type of the object to store
*
* @throws IllegalArgumentException if the data key is null
* @throws IllegalArgumentException if the value is null. Removing a tag should
* be done using {@link #remove(NamespacedKey)} or {@link #remove(PersistentDataKey)}
* @throws IllegalArgumentException if no suitable adapter was found for
* the {@link PersistentDataType#getPrimitiveType()}
*/
default <C> void set(final @NotNull PersistentDataKey<C> dataKey, final @NotNull C value) {
Preconditions.checkArgument(dataKey != null, "The data key cannot be null");
this.set(dataKey.key(), dataKey.dataType(), value);
}
// Paper - move to PersistentDataContainerView

/**
Expand All @@ -41,6 +66,17 @@ public interface PersistentDataContainer extends io.papermc.paper.persistence.Pe
* @throws IllegalArgumentException if the provided key is null
*/
void remove(@NotNull NamespacedKey key);

/**
* Removes a custom key from the {@link PersistentDataHolder} instance.
*
* @param dataKey the data key to remove
* @throws IllegalArgumentException if the provided data key is null
*/
default void remove(final @NotNull PersistentDataKey<?> dataKey) {
Preconditions.checkArgument(dataKey != null, "The data key cannot be null");
this.remove(dataKey.key());
}
// Paper - move to PersistentDataContainerView

// Paper start - byte array serialization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import io.papermc.paper.datacomponent.item.ResolvableProfile;
import io.papermc.paper.entity.poi.PaperPoiType;
import io.papermc.paper.entity.poi.PoiType;
import io.papermc.paper.persistence.PaperPersistentDataKey;
import io.papermc.paper.persistence.PersistentDataKey;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager;
import io.papermc.paper.util.MCUtil;
Expand All @@ -26,6 +28,7 @@
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.minecraft.Optionull;
Expand Down Expand Up @@ -69,6 +72,7 @@
import org.bukkit.entity.Pose;
import org.bukkit.entity.SpawnCategory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jspecify.annotations.NullMarked;
Expand Down Expand Up @@ -257,4 +261,12 @@ public Component resolveWithContext(final Component component, final @Nullable C
public ComponentFlattener componentFlattener() {
return PaperAdventure.FLATTENER;
}

@Override
public <C> PersistentDataKey<C> createPersistentDataKey(final Key key, final PersistentDataType<?, C> type) {
final NamespacedKey namespacedKey = key instanceof NamespacedKey thisKey
? thisKey
: new NamespacedKey(key.namespace(), key.value());
return new PaperPersistentDataKey<>(namespacedKey, type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.papermc.paper.persistence;

import com.google.common.base.Preconditions;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType;
import org.jspecify.annotations.NullMarked;

@NullMarked
public record PaperPersistentDataKey<C>(
NamespacedKey key,
PersistentDataType<?, C> dataType
) implements PersistentDataKey<C> {

public PaperPersistentDataKey {
Preconditions.checkArgument(key != null, "The key cannot be null");
Preconditions.checkArgument(dataType != null, "The type cannot be null");
}
}
Loading