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
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public String getReadableFields() {
attributes.add(accessor.isOptional() ? "optional" : "required");
if (accessor.getter() == null) attributes.add("creation-only");
String names = accessor.name() + (accessor.aliases().length == 0 ? "" : "|" + String.join("|", accessor.aliases()));
fieldStrings.add("- `" + names + ": " + typeName(accessor.expectedType()) + "` ("
fieldStrings.add("- `" + names + ": " + fieldTypeName(accessor.nativeType(), accessor.expectedType()) + "` ("
+ String.join("; ", attributes) + ")");
Class<?> valueType = accessor.expectedType().isArray()
? accessor.expectedType().getComponentType() : accessor.expectedType();
Expand All @@ -88,6 +88,27 @@ public String getReadableFields() {
return String.join("\n", fieldStrings);
}

private static String fieldTypeName(java.lang.reflect.Type nativeType, Class<?> expectedType) {
var raw = org.crebs.skpackets.elements.general.field.PacketArguments.raw(nativeType);
if (raw == java.util.BitSet.class) return "bit indices (numbers)";
if (raw == byte[][].class) return "byte arrays";
if (raw == java.util.Optional.class)
return fieldTypeName(org.crebs.skpackets.elements.general.field.PacketArguments.argument(nativeType, 0), expectedType);
if (java.util.Map.class.isAssignableFrom(raw)) {
var key = org.crebs.skpackets.elements.general.field.PacketArguments.argument(nativeType, 0);
var value = org.crebs.skpackets.elements.general.field.PacketArguments.argument(nativeType, 1);
return "map of " + fieldTypeName(key, org.crebs.skpackets.elements.general.field.PacketArguments.expressionType(key))
+ " to " + fieldTypeName(value, org.crebs.skpackets.elements.general.field.PacketArguments.expressionType(value));
}
if (expectedType == org.crebs.skpackets.elements.general.field.PacketValue.class)
return org.crebs.skpackets.elements.general.field.PacketValueTypes.label(raw);
if (expectedType.isArray() && expectedType.getComponentType() == org.crebs.skpackets.elements.general.field.PacketValue.class)
return org.crebs.skpackets.elements.general.field.PacketValueTypes.label(
org.crebs.skpackets.elements.general.field.PacketArguments.raw(
org.crebs.skpackets.elements.general.field.PacketRecords.elementType(nativeType))) + " values";
return typeName(expectedType);
}

private static String typeName(Class<?> type) {
if (type.isArray()) return pluralTypeName(type.getComponentType());
if (type == Boolean.class || type == boolean.class) return "boolean";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@

public static Class<?> expressionType(Type type) {
Class<?> raw = raw(type);
if (raw == BitSet.class) return Number[].class;
if (raw == byte[][].class) return PacketValue[].class;
if (raw == Optional.class) return expressionType(argument(type, 0));
if (Map.class.isAssignableFrom(raw)) return PacketValue[].class;
if (raw.isArray()) return arrayType(expressionType(raw.getComponentType()));
if (Collection.class.isAssignableFrom(raw)) return arrayType(expressionType(argument(type, 0)));
return PUBLIC_TYPES.computeIfAbsent(raw, PacketArguments::scalarType);
Expand Down Expand Up @@ -59,6 +62,17 @@
}
if (value instanceof PacketRecord record) value = PacketRecords.construct(record, type, name);
if (value == null) throw invalid(name, type, null);
if (target == BitSet.class) {
if (value instanceof BitSet bits) return bits.clone();
BitSet bits = new BitSet();
for (Object index : elements(value)) {
if (!(index instanceof Number number)) throw invalid(name, type, index);
int bit = (int) PacketNumbers.convert(number, int.class, name);
if (bit < 0 || bit > 1048575) throw new IllegalArgumentException("Bit index must be between 0 and 1048575: " + name);
bits.set(bit);
}
return bits;
}
if (target.isArray()) return convertArray(value, type, name);
if (Collection.class.isAssignableFrom(target)) return convertCollection(value, type, name);
if (Map.class.isAssignableFrom(target)) return convertMap(value, type, name);
Expand Down Expand Up @@ -104,7 +118,22 @@
}

private static Object convertMap(Object value, Type type, String name) {
if (!(value instanceof Map<?, ?> input)) throw invalid(name, type, value);
Map<?, ?> input;
if (value instanceof Map<?, ?> map) input = map;
else {
Map<Object, Object> entries = new LinkedHashMap<>();
for (Object entry : elements(value)) {
if (entry instanceof PacketValue wrapped) entry = wrapped.value();
if (!(entry instanceof PacketRecord record)) throw invalid(name, type, entry);
if (!record.fields().keySet().equals(Set.of("key", "value")))
throw new IllegalArgumentException("Map entry '" + name + "' requires key and value.");
Object key = convert(record.fields().get("key"), argument(type, 0), name + " key");
Object item = convert(record.fields().get("value"), argument(type, 1), name + " value");
if (entries.putIfAbsent(key, item) != null)
throw new IllegalArgumentException("Duplicate map key in '" + name + "': " + key);
}
return entries;
}
Map<Object, Object> result = new LinkedHashMap<>();
input.forEach((key, entry) -> result.put(convert(key, argument(type, 0), name + " key"),
convert(entry, argument(type, 1), name + " value")));
Expand All @@ -125,6 +154,11 @@
static Object toSkript(Object value) {
if (value == null) return null;
if (value instanceof Optional<?> optional) return toSkript(optional.orElse(null));
if (value instanceof BitSet bits) return bits.stream().mapToObj(Integer::valueOf).toArray(Number[]::new);

Check warning on line 157 in src/main/java/org/crebs/skpackets/elements/general/field/PacketArguments.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Stream API call chain can be simplified

Can be replaced with 'boxed'
if (value instanceof byte[][] rows) return Arrays.stream(rows).map(PacketValue::new).toArray(PacketValue[]::new);
if (value instanceof Map<?, ?> map) return map.entrySet().stream()
.map(entry -> new PacketValue(new PacketRecord(Map.of(
"key", toSkript(entry.getKey()), "value", toSkript(entry.getValue()))))).toArray(PacketValue[]::new);
if (value instanceof Collection<?> collection) return collection.stream().map(PacketArguments::toSkript).toArray();
if (value.getClass().isArray()) return Arrays.stream(elements(value)).map(PacketArguments::toSkript).toArray();
var codec = PacketValueCodecs.find(value.getClass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ PacketWrapper<?> create(ConstructionContext<PacketTypeCommon, ?> values, Map<Str
}
}

private static boolean nullable(MethodNode model, int index) {
static boolean nullable(MethodNode model, int index) {
return Stream.of(model.invisibleTypeAnnotations, model.visibleTypeAnnotations)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ final class PacketFieldRules {
entry(WrapperPlayServerEntityStatus.class, Map.of("status", DEFAULT.adapting(STATUS_BYTE))),
entry(WrapperPlayServerGameTestHighlightPos.class, Map.of("relativepos", DEFAULT.defaulting(Vector::new))),
entry(WrapperPlayServerEntityMetadata.class, Map.of("entitymetadata", DEFAULT.adapting(ENTITY_META))),
entry(WrapperPlayServerOpenSignEditor.class, Map.of("isfronttext", named("sign side").adapting(SIGN_SIDE))),
entry(WrapperPlayServerOpenSignEditor.class, Map.of(
"position", named("block position"),
"isfronttext", named("sign side").adapting(SIGN_SIDE))),
entry(WrapperPlayServerCloseWindow.class, Map.of("id", DEFAULT.defaulting(() -> 0))),
entry(WrapperPlayClientUpdateSign.class, Map.of("isfronttext", named("sign side").adapting(SIGN_SIDE))),
entry(WrapperPlayClientInteractEntity.class, Map.of("sneaking", DEFAULT.adapting(SNEAKING))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,28 @@ private static Expression<?> nested(Type type, SectionNode section, String name)
error("Duplicate list entry in '" + name + "': " + label); return null;
}
Map<String, Expression<?>> record = new LinkedHashMap<>();
if (!parse(shape.fields(), child, record, name + "." + label)) return null;
if (!parseSelected(shape, child, record, name + "." + label)) return null;
records.add(record);
}
} else {
Map<String, Expression<?>> record = new LinkedHashMap<>();
if (!parse(shape.fields(), section, record, name)) return null;
if (!parseSelected(shape, section, record, name)) return null;
records.add(record);
}
return new RecordsExpression(shape, records, !PacketRecords.isList(type));
}

private static boolean parseSelected(PacketRecords.Shape shape, SectionNode section,
Map<String, Expression<?>> record, String name) {
Set<String> supplied = new HashSet<>();
for (var node : section) if (!node.isVoid() && node.getKey() != null) supplied.add(node.getKey());
try {
return parse(shape.select(supplied, name).fields(), section, record, name);
} catch (IllegalArgumentException exception) {
return error(exception.getMessage());
}
}

private static boolean error(String message) { Skript.error(message); return false; }

private static final class RecordsExpression extends SimpleExpression<PacketValue> {
Expand All @@ -103,15 +114,16 @@ private static final class RecordsExpression extends SimpleExpression<PacketValu
@Override public boolean init(Expression<?>[] expressions, int pattern, Kleenean delayed, SkriptParser.ParseResult result) { return true; }
@Override protected PacketValue[] get(Event event) {
List<PacketValue> values = new ArrayList<>();
var schema = new FieldSchema<String, Object>("nested packet value", shape.fields(), context -> {
Map<String, Object> fields = new LinkedHashMap<>();
for (var field : shape.fields()) {
Object value = context.getOptional(field.name(), Object.class);
if (value != null) fields.put(field.name(), value);
}
return new PacketValue(new PacketRecord(fields));
});
for (var record : records) {
var selected = shape.select(record.keySet(), "nested packet value");
var schema = new FieldSchema<String, Object>("nested packet value", selected.fields(), context -> {
Map<String, Object> fields = new LinkedHashMap<>();
for (var field : selected.fields()) {
Object value = context.getOptional(field.name(), Object.class);
if (value != null) fields.put(field.name(), value);
}
return new PacketValue(new PacketRecord(fields));
});
Object value = FieldSections.create(schema, "nested packet value", record, event, Map.of());
if (value == null) throw new IllegalArgumentException("A required nested packet entry evaluated to no value.");
values.add((PacketValue)value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@
public final class PacketRecords {
private PacketRecords() {}
public record Shape(List<FieldAccessor<Object, ?>> fields, Map<String, Object> defaults,
Function<Map<String, Object>, Object> factory) {}
Function<Map<String, Object>, Object> factory, List<Shape> alternatives) {
public Shape(List<FieldAccessor<Object, ?>> fields, Map<String, Object> defaults,
Function<Map<String, Object>, Object> factory) {
this(fields, defaults, factory, List.of());
}
Shape select(Set<String> supplied, String name) {
return alternatives.isEmpty() ? this : PacketValueDiscovery.select(alternatives, supplied, name);
}
}
private static final Map<Class<?>, Shape> SHAPES = new HashMap<>();
static {
define(Equipment.class, List.of(
Expand Down Expand Up @@ -45,6 +53,12 @@ public record Shape(List<FieldAccessor<Object, ?>> fields, Map<String, Object> d
(int)v.get("uses"), (int)v.get("max uses"), (int)v.get("xp"), (int)v.get("special price"),
(float)v.get("price multiplier"), (int)v.get("demand")));
}
private static final ClassValue<Optional<Shape>> DISCOVERED = new ClassValue<>() {
@Override protected Optional<Shape> computeValue(Class<?> type) {
return Optional.ofNullable(PacketValueDiscovery.discover(type));
}
};

private static void define(Class<?> type, List<FieldAccessor<Object, ?>> fields, Map<String, Object> defaults,
Function<Map<String, Object>, Object> factory) {
SHAPES.put(type, new Shape(List.copyOf(fields), Map.copyOf(defaults), factory));
Expand All @@ -63,22 +77,35 @@ public static Type elementType(Type type) {
public static boolean isList(Type type) {
Class<?> raw = PacketArguments.raw(type);
if (raw == Optional.class) return isList(PacketArguments.argument(type, 0));
return raw.isArray() || Collection.class.isAssignableFrom(raw);
return raw.isArray() || Collection.class.isAssignableFrom(raw) || Map.class.isAssignableFrom(raw);
}
public static Shape shape(Type type) {
if (PacketArguments.raw(type) == Optional.class) return shape(PacketArguments.argument(type, 0));
if (Map.class.isAssignableFrom(PacketArguments.raw(type)))
return new Shape(List.of(field("key", PacketArguments.argument(type, 0), false),
field("value", PacketArguments.argument(type, 1), false)), Map.of(), v -> v);
Class<?> raw = PacketArguments.raw(elementType(type));
Shape known = SHAPES.get(raw);
return known != null ? known : DISCOVERED.get(raw).orElse(null);
}
public static Shape shape(Type type) { return SHAPES.get(PacketArguments.raw(elementType(type))); }
public static Object construct(PacketRecord record, Type type, String name) {
Shape shape = SHAPES.get(PacketArguments.raw(type));
Shape shape = shape(type);
if (shape == null) throw new IllegalArgumentException("Packet field '" + name + "' does not support nested entries.");
return construct(record.fields(), shape.select(record.fields().keySet(), name), name);
}
static Object construct(Map<String, Object> supplied, Shape shape, String name) {
Map<String, Object> values = new HashMap<>(shape.defaults());
for (String key : record.fields().keySet()) {
for (String key : supplied.keySet()) {
if (shape.fields().stream().noneMatch(f -> f.name().equals(key)))
throw new IllegalArgumentException("Unknown nested packet field '" + name + "." + key + "'.");
}
values.putAll(record.fields());
values.putAll(supplied);
Map<String, Object> converted = new HashMap<>();
for (var field : shape.fields()) {
Object value = values.get(field.name());
if (value == null && !field.isOptional()) throw new IllegalArgumentException("Missing packet field '" + name + "." + field.name() + "'.");
if (value == null && PacketArguments.raw(field.nativeType()) == Optional.class)
converted.put(field.name(), Optional.empty());
if (value != null) converted.put(field.name(), PacketArguments.convert(value, field.nativeType(), name + "." + field.name()));
}
return shape.factory().apply(converted);
Expand Down
Loading
Loading