Generate versioned Model metadata and consume it in shared binding discovery
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package de.neitzel.fxdesigner.protocol;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/** Build-produced Model signatures, independent of host toolkit and application runtime. */
|
||||
public record ModelMetadata(String model, List<Property> properties) {
|
||||
public static final String VERSION = "1.0";
|
||||
public static final int LIMIT = 256 * 1024;
|
||||
|
||||
public record Property(
|
||||
String name, String type, String elementType, String reader, String writer) {
|
||||
public Property {
|
||||
for (String value : List.of(name, type, reader, writer))
|
||||
if (!value.matches("[\\w.$\\[;]+"))
|
||||
throw new IllegalArgumentException("Invalid Model signature");
|
||||
if (!elementType.isEmpty() && !elementType.matches("[\\w.$\\[;]+"))
|
||||
throw new IllegalArgumentException("Invalid element type");
|
||||
}
|
||||
}
|
||||
|
||||
public ModelMetadata {
|
||||
if (!model.matches("[\\w$]+(\\.[\\w$]+)+"))
|
||||
throw new IllegalArgumentException("Invalid Model type");
|
||||
properties = List.copyOf(properties);
|
||||
if (properties.size() > 256
|
||||
|| properties.stream().map(Property::name).distinct().count() != properties.size())
|
||||
throw new IllegalArgumentException("Duplicate or excessive Model properties");
|
||||
}
|
||||
|
||||
public static String resource(String model) {
|
||||
return "META-INF/neitzel-fx-designer/models/"
|
||||
+ new ModelMetadata(model, List.of()).model()
|
||||
+ ".properties";
|
||||
}
|
||||
|
||||
public byte[] encode() {
|
||||
var p = new TreeMap<String, String>();
|
||||
p.put("schemaVersion", VERSION);
|
||||
p.put("model", model);
|
||||
p.put("count", "" + properties.size());
|
||||
for (int i = 0; i < properties.size(); i++) {
|
||||
var a = properties.get(i);
|
||||
String k = "property." + i + ".";
|
||||
p.put(k + "name", a.name());
|
||||
p.put(k + "type", a.type());
|
||||
p.put(k + "element", a.elementType());
|
||||
p.put(k + "reader", a.reader());
|
||||
p.put(k + "writer", a.writer());
|
||||
}
|
||||
var text = new StringBuilder();
|
||||
p.forEach((k, v) -> text.append(k).append('=').append(v).append('\n'));
|
||||
return text.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static ModelMetadata read(InputStream input) throws IOException {
|
||||
byte[] bytes = input.readNBytes(LIMIT + 1);
|
||||
if (bytes.length > LIMIT) throw new IOException("Model metadata exceeds limit");
|
||||
try {
|
||||
var p = new Properties();
|
||||
p.load(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8));
|
||||
if (!VERSION.equals(p.getProperty("schemaVersion")))
|
||||
throw new IllegalArgumentException("Unsupported Model metadata schema");
|
||||
int count = Integer.parseInt(p.getProperty("count"));
|
||||
if (count < 0 || count > 256 || p.size() != 3 + 5 * count)
|
||||
throw new IllegalArgumentException("Invalid Model metadata fields");
|
||||
var attributes = new ArrayList<Property>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String k = "property." + i + ".";
|
||||
attributes.add(
|
||||
new Property(
|
||||
p.getProperty(k + "name"),
|
||||
p.getProperty(k + "type"),
|
||||
p.getProperty(k + "element"),
|
||||
p.getProperty(k + "reader"),
|
||||
p.getProperty(k + "writer")));
|
||||
}
|
||||
return new ModelMetadata(p.getProperty("model"), attributes);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IOException("Invalid Model metadata: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package de.neitzel.fxdesigner.protocol;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ModelMetadataTest {
|
||||
@Test
|
||||
void deterministicRoundTripAndSchemaValidation() throws Exception {
|
||||
var m =
|
||||
new ModelMetadata(
|
||||
"app.Person",
|
||||
List.of(
|
||||
new ModelMetadata.Property("name", "java.lang.String", "", "getName", "setName")));
|
||||
assertEquals(m, ModelMetadata.read(new ByteArrayInputStream(m.encode())));
|
||||
assertThrows(
|
||||
IOException.class,
|
||||
() ->
|
||||
ModelMetadata.read(
|
||||
new ByteArrayInputStream(
|
||||
new String(m.encode(), java.nio.charset.StandardCharsets.UTF_8)
|
||||
.replace("schemaVersion=1.0", "schemaVersion=9.0")
|
||||
.getBytes(java.nio.charset.StandardCharsets.UTF_8))));
|
||||
assertThrows(
|
||||
IOException.class,
|
||||
() -> ModelMetadata.read(new ByteArrayInputStream(new byte[ModelMetadata.LIMIT + 1])));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new ModelMetadata(
|
||||
"app.Person", List.of(m.properties().getFirst(), m.properties().getFirst())));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user