Skip to content
Draft
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
3 changes: 3 additions & 0 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ dependencies {
// test
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.gson)
testImplementation(libs.guava)
testImplementation(libs.adventure.api)
}

tasks.test {
Expand Down
3 changes: 2 additions & 1 deletion api/src/main/java/dev/jsinco/brewery/api/brew/Brew.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.jsinco.brewery.api.brew;

import com.google.errorprone.annotations.Immutable;
import dev.jsinco.brewery.api.meta.MetaContainer;
import dev.jsinco.brewery.api.recipe.Recipe;
import dev.jsinco.brewery.api.recipe.RecipeRegistry;
import org.jetbrains.annotations.NotNull;
Expand All @@ -12,7 +13,7 @@
import java.util.function.Supplier;

@Immutable
public interface Brew {
public interface Brew extends MetaContainer<Brew> {

/**
* @param registry A registry with all available recipes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.jsinco.brewery.api.meta;

/**
* A convenience type for boolean metadata, stored internally as a byte, where 0 is false and everything else is true.
*/
public class BooleanMetaDataType implements MetaDataType<Byte, Boolean> {

public static final BooleanMetaDataType INSTANCE = new BooleanMetaDataType();

@Override
public Class<Byte> getPrimitiveType() {
return Byte.class;
}

@Override
public Class<Boolean> getComplexType() {
return Boolean.class;
}

@Override
public Byte toPrimitive(Boolean complex) {
return complex ? (byte) 1 : (byte) 0;
}

@Override
public Boolean toComplex(Byte primitive) {
return primitive != 0;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.jsinco.brewery.api.meta;

import com.google.common.collect.Lists;

import java.util.List;

public class ListMetaDataType<P, C> implements MetaDataType<List<P>, List<C>> {

private final MetaDataType<P, C> elementType;

private ListMetaDataType(MetaDataType<P, C> elementType) {
this.elementType = elementType;
}

public static <P, C> ListMetaDataType<P, C> from(MetaDataType<P, C> type) {
return new ListMetaDataType<>(type);
}

@Override
@SuppressWarnings("unchecked")
public Class<List<P>> getPrimitiveType() {
return (Class<List<P>>) (Object) List.class;
}

@Override
@SuppressWarnings("unchecked")
public Class<List<C>> getComplexType() {
return (Class<List<C>>) (Object) List.class;
}

@Override
public List<P> toPrimitive(List<C> complex) {
return Lists.transform(complex, elementType::toPrimitive);
}

@Override
public List<C> toComplex(List<P> primitive) {
return Lists.transform(primitive, elementType::toComplex);
}

public MetaDataType<P, C> getElementDataType() {
return elementType;
}

}
36 changes: 36 additions & 0 deletions api/src/main/java/dev/jsinco/brewery/api/meta/MetaContainer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dev.jsinco.brewery.api.meta;

import net.kyori.adventure.key.Key;

public interface MetaContainer<SELF extends MetaContainer<SELF>> {

/**
* Creates a new meta container with the provided metadata added. The old container is unchanged.
* @param key The key the value will be stored under
* @param type The type of the value
* @param value The value to store
* @return A new meta container
* @param <P> The primitive type the value will be stored as when serialized
* @param <C> The type of the value to store
*/
<P, C> SELF withMeta(Key key, MetaDataType<P, C> type, C value);

/**
* Creates a new meta container with the provided metadata removed. The old container is unchanged.
* @param key The key to remove
* @return A new meta container
*/
SELF withoutMeta(Key key);

/**
* Gets metadata under the provided key.
* @param key The key to look up
* @param type The type of the metadata value
* @return The metadata value, or null if the key is not present
* @param <P> The primitive type the value is stored as when serialized
* @param <C> The type of the value to retrieve
* @throws IllegalArgumentException If the value is not of the expected type
*/
<P, C> C meta(Key key, MetaDataType<P, C> type);

}
86 changes: 86 additions & 0 deletions api/src/main/java/dev/jsinco/brewery/api/meta/MetaData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package dev.jsinco.brewery.api.meta;

import net.kyori.adventure.key.Key;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* A basic metadata container, and the primitive type for nested metadata containers ({@link MetaDataType#CONTAINER}).
*/
public final class MetaData implements MetaContainer<MetaData> {

private final Map<Key, Object> meta;

/**
* Creates an empty metadata container.
*/
public MetaData() {
this(Collections.emptyMap());
}
private MetaData(Map<Key, Object> meta) {
this.meta = meta;
}

@Override
public <P, C> MetaData withMeta(Key key, MetaDataType<P, C> type, C value) {
return new MetaData(Stream.concat(
meta.entrySet().stream(),
Stream.of(Map.entry(key, type.toPrimitive(value)))
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
}

@Override
public MetaData withoutMeta(Key key) {
return new MetaData(meta.entrySet().stream()
.filter(entry -> !entry.getKey().equals(key))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
}

@Override
public <P, C> C meta(Key key, MetaDataType<P, C> type) {
Object value = meta.get(key);
if (value == null) {
return null;
}
if (type.getPrimitiveType().isInstance(value)) {
if (type instanceof ListMetaDataType<?,?> listType) {
List<?> list = (List<?>) value;
if (!list.isEmpty() && !listType.getElementDataType().getPrimitiveType().isInstance(list.getFirst())) {
throw new IllegalArgumentException("Meta for " + key + " is not of List with element type " +
listType.getElementDataType().getPrimitiveType().getSimpleName());
}
}
return type.toComplex(type.getPrimitiveType().cast(value));
}
throw new IllegalArgumentException("Meta for " + key + " is not of type " + type.getPrimitiveType().getSimpleName());
}

/**
* @return An unmodifiable map of keys to metadata values as their primitive types
*/
public Map<Key, Object> primitiveMap() {
return meta;
}

@Override
public boolean equals(Object o) {
return o instanceof MetaData metaData && Objects.equals(meta, metaData.meta);
}
@Override
public int hashCode() {
return Objects.hashCode(meta);
}

@Override
public String toString() {
return meta.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining(", ", "MetaData{", "}"));
}

}
104 changes: 104 additions & 0 deletions api/src/main/java/dev/jsinco/brewery/api/meta/MetaDataType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package dev.jsinco.brewery.api.meta;

import net.kyori.adventure.key.Key;

/**
* Represents a type that a metadata value can have.
* <p>
* All static variables of this interface are <strong>primitive</strong> metadata types.
* Primitive types are stored when the metadata is serialized.
* </p>
* <p>
* Users can implement this interface to store custom, more complex types.
* See {@link BooleanMetaDataType} for an example.
* </p>
* @param <P> The primitive Java type that is stored when serialized,
* <strong>must absolutely be one of the primitive types listed in this interface!</strong>
* @param <C> The Java type that is retrieved when using {@link MetaContainer#meta(Key, MetaDataType)}
*/
public interface MetaDataType<P, C> {

MetaDataType<Byte, Byte> BYTE = new Primitive<>(Byte.class);
MetaDataType<Short, Short> SHORT = new Primitive<>(Short.class);
MetaDataType<Integer, Integer> INTEGER = new Primitive<>(Integer.class);
MetaDataType<Long, Long> LONG = new Primitive<>(Long.class);
MetaDataType<Float, Float> FLOAT = new Primitive<>(Float.class);
MetaDataType<Double, Double> DOUBLE = new Primitive<>(Double.class);
MetaDataType<String, String> STRING = new Primitive<>(String.class);

MetaDataType<byte[], byte[]> BYTE_ARRAY = new Primitive<>(byte[].class);
MetaDataType<int[], int[]> INTEGER_ARRAY = new Primitive<>(int[].class);
MetaDataType<long[], long[]> LONG_ARRAY = new Primitive<>(long[].class);

/**
* The metadata type for nested metadata.
*/
MetaDataType<MetaData, MetaData> CONTAINER = new Primitive<>(MetaData.class);

ListMetaDataType<Byte, Byte> BYTE_LIST = ListMetaDataType.from(MetaDataType.BYTE);
ListMetaDataType<Short, Short> SHORT_LIST = ListMetaDataType.from(MetaDataType.SHORT);
ListMetaDataType<Integer, Integer> INTEGER_LIST = ListMetaDataType.from(MetaDataType.INTEGER);
ListMetaDataType<Long, Long> LONG_LIST = ListMetaDataType.from(MetaDataType.LONG);
ListMetaDataType<Float, Float> FLOAT_LIST = ListMetaDataType.from(MetaDataType.FLOAT);
ListMetaDataType<Double, Double> DOUBLE_LIST = ListMetaDataType.from(MetaDataType.DOUBLE);
ListMetaDataType<String, String> STRING_LIST = ListMetaDataType.from(MetaDataType.STRING);

ListMetaDataType<byte[], byte[]> BYTE_ARRAY_LIST = ListMetaDataType.from(MetaDataType.BYTE_ARRAY);
ListMetaDataType<int[], int[]> INTEGER_ARRAY_LIST = ListMetaDataType.from(MetaDataType.INTEGER_ARRAY);
ListMetaDataType<long[], long[]> LONG_ARRAY_LIST = ListMetaDataType.from(MetaDataType.LONG_ARRAY);

ListMetaDataType<MetaData, MetaData> CONTAINER_LIST = ListMetaDataType.from(MetaDataType.CONTAINER);

/**
* @return The class of the primitive Java type
*/
Class<P> getPrimitiveType();

/**
* @return The class of the complex Java type
*/
Class<C> getComplexType();

/**
* Reduces a complex object to this type's primitive, which can later be passed to {@link #toComplex(Object)}
* to reconstruct the original object.
* @param complex The complex object
* @return The primitive value
*/
P toPrimitive(C complex);

/**
* Reconstructs a complex object from its primitive form created from {@link #toPrimitive(Object)}.
* This method may assume the primitive is valid and throw exceptions if this assumption does not hold.
* @param primitive The primitive value
* @return The complex object
*/
C toComplex(P primitive);

class Primitive<P> implements MetaDataType<P, P> {
private final Class<P> primitiveClass;

private Primitive(Class<P> primitiveClass) {
this.primitiveClass = primitiveClass;
}

@Override
public Class<P> getPrimitiveType() {
return primitiveClass;
}
@Override
public Class<P> getComplexType() {
return primitiveClass;
}

@Override
public P toPrimitive(P complex) {
return complex;
}
@Override
public P toComplex(P primitive) {
return primitive;
}
}

}
Loading