diff --git a/.gitignore b/.gitignore index a5b7d04336a4a3dd2c22ab2e8f7c2522d73f20c3..d84ffeed9a4cc08ae800a42ddd2d9d446b11e678 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -*.jarsrc/test/java/org/jastadd/relast/tests/Resolver2.java +*.jar .idea/ .gradle/ build @@ -8,6 +8,8 @@ out/ *.class src/test/jastadd/relations/Relations.ast src/test/jastadd/relations/Relations.jadd +src/test/jastadd/relations/Relations2.ast +src/test/jastadd/relations/Relations2.jadd src/test/jastadd/lowerbounds/LowerBounds.ast src/test/jastadd/lowerbounds/LowerBounds.jadd src/test/jastadd/multiple/Multiple.ast @@ -15,8 +17,14 @@ src/test/jastadd/multiple/Multiple.jadd src/test/jastadd/resolver/Resolver.ast src/test/jastadd/resolver/Resolver.jadd src/test/jastadd/resolver/ResolverRefResolver.jadd +src/test/jastadd/resolver/ResolverResolverStubs.jrag src/test/jastadd/resolver2/Resolver.ast src/test/jastadd/resolver2/Resolver.jadd src/test/jastadd/resolver2/ResolverRefResolver.jadd +src/test/jastadd/resolver2/ResolverResolverStubs.jrag src/test/jastadd/listnames/ListNames.ast -src/test/jastadd/listnames/ListNames.jadd \ No newline at end of file +src/test/jastadd/listnames/ListNames.jadd +src/test/jastadd/serializer/Serializer.ast +src/test/jastadd/serializer/Serializer.jadd +src/test/jastadd/serializer/SerializerSerializer.jadd +src/test/jastadd/serializer/SerializerRefResolver.jadd diff --git a/build.gradle b/build.gradle index 0383ec1426d52eb248d733b7fd55a1d748f8a07b..be8ffdec62de203a3abb621cbb96a04ce61f7b36 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,8 @@ buildscript { dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0' + compile 'com.fasterxml.jackson.core:jackson-core:2.9.8' + compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8' runtime 'org.jastadd:jastadd:2.3.2' compile group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0' @@ -49,6 +51,7 @@ jar { jastadd { configureModuleBuild() modules { + //noinspection GroovyAssignabilityCheck module("RelAst") { java { @@ -110,6 +113,7 @@ task preprocessRelationTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck args 'src/test/jastadd/relations/Relations.relast', '--file', '--grammarName=src/test/jastadd/relations/Relations' } @@ -121,6 +125,7 @@ task doublePreprocessRelationTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck args 'src/test/jastadd/relations/Relations.ast', '--file', '--grammarName=src/test/jastadd/relations/Relations2' } @@ -132,7 +137,11 @@ task compileRelationTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.JastAdd' - args '--o=src/test/java-gen/', '--package=relations.ast', 'src/test/jastadd/relations/Relations.ast', 'src/test/jastadd/relations/Relations.jadd', 'src/test/jastadd/Utils.jadd' + //noinspection GroovyAssignabilityCheck + args '--o=src/test/java-gen/', '--package=relations.ast', + 'src/test/jastadd/relations/Relations.ast', + 'src/test/jastadd/relations/Relations.jadd', + 'src/test/jastadd/Utils.jadd' } task preprocessLowerBoundsTest(type: JavaExec, group: 'verification') { @@ -143,6 +152,7 @@ task preprocessLowerBoundsTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck args 'src/test/jastadd/lowerbounds/LowerBounds.relast', '--file', '--grammarName=src/test/jastadd/lowerbounds/LowerBounds' } @@ -154,7 +164,11 @@ task compileLowerBoundsTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.JastAdd' - args '--o=src/test/java-gen/', '--package=lowerbounds.ast', 'src/test/jastadd/lowerbounds/LowerBounds.ast', 'src/test/jastadd/lowerbounds/LowerBounds.jadd', 'src/test/jastadd/Utils.jadd' + //noinspection GroovyAssignabilityCheck + args '--o=src/test/java-gen/', '--package=lowerbounds.ast', + 'src/test/jastadd/lowerbounds/LowerBounds.ast', + 'src/test/jastadd/lowerbounds/LowerBounds.jadd', + 'src/test/jastadd/Utils.jadd' } task preprocessMultipleTest(type: JavaExec, group: 'verification') { @@ -165,6 +179,7 @@ task preprocessMultipleTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck args 'src/test/jastadd/multiple/Part1.relast', 'src/test/jastadd/multiple/Part2.relast', 'src/test/jastadd/multiple/Part3.relast', '--file', '--grammarName=src/test/jastadd/multiple/Multiple' } @@ -176,17 +191,22 @@ task compileMultipleTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.JastAdd' - args '--o=src/test/java-gen/', '--package=multiple.ast', 'src/test/jastadd/multiple/Multiple.ast', 'src/test/jastadd/multiple/Multiple.jadd', 'src/test/jastadd/Utils.jadd' + //noinspection GroovyAssignabilityCheck + args '--o=src/test/java-gen/', '--package=multiple.ast', + 'src/test/jastadd/multiple/Multiple.ast', + 'src/test/jastadd/multiple/Multiple.jadd', + 'src/test/jastadd/Utils.jadd' } task preprocessResolverTest(type: JavaExec, group: 'verification') { doFirst { - delete 'src/test/jastadd/resolver/Resolver.ast', 'src/test/jastadd/resolver/Resolver.jadd', 'src/test/jastadd/resolver/ResolverRefResolver.jadd' + delete 'src/test/jastadd/resolver/Resolver.ast', 'src/test/jastadd/resolver/Resolver.jadd', 'src/test/jastadd/resolver/ResolverRefResolver.jadd', 'src/test/jastadd/resolver/ResolverResolverStubs.jrag' } classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck args 'src/test/jastadd/resolver/Resolver.relast', '--file', '--grammarName=src/test/jastadd/resolver/Resolver', '--resolverHelper' } @@ -198,7 +218,15 @@ task compileResolverTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.JastAdd' - args '--o=src/test/java-gen/', '--package=resolver.ast', 'src/test/jastadd/resolver/Resolver.ast', 'src/test/jastadd/resolver/Resolver.jadd', 'src/test/jastadd/resolver/ResolverUtils.jadd', 'src/test/jastadd/resolver/ResolverRefResolver.jadd', 'src/test/jastadd/resolver/MyRefResolver.jadd', 'src/test/jastadd/Utils.jadd' + //noinspection GroovyAssignabilityCheck + args '--o=src/test/java-gen/', '--package=resolver.ast', + 'src/test/jastadd/resolver/Resolver.ast', + 'src/test/jastadd/resolver/Resolver.jadd', + 'src/test/jastadd/resolver/ResolverUtils.jadd', + 'src/test/jastadd/resolver/ResolverRefResolver.jadd', + 'src/test/jastadd/resolver/ResolverResolverStubs.jrag', + 'src/test/jastadd/resolver/MyRefResolver.jadd', + 'src/test/jastadd/Utils.jadd' } @@ -210,6 +238,7 @@ task preprocessResolver2Test(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck args 'src/test/jastadd/resolver2/Resolver.relast', '--file', '--grammarName=src/test/jastadd/resolver2/Resolver', '--resolverHelper' } @@ -221,7 +250,15 @@ task compileResolver2Test(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.JastAdd' - args '--o=src/test/java-gen/', '--package=resolver2.ast', 'src/test/jastadd/resolver2/Resolver.ast', 'src/test/jastadd/resolver2/Resolver.jadd', 'src/test/jastadd/resolver2/ResolverUtils.jadd', 'src/test/jastadd/resolver2/ResolverRefResolver.jadd', 'src/test/jastadd/resolver2/MyRefResolver.jadd', 'src/test/jastadd/Utils.jadd' + //noinspection GroovyAssignabilityCheck + args '--o=src/test/java-gen/', '--package=resolver2.ast', + 'src/test/jastadd/resolver2/Resolver.ast', + 'src/test/jastadd/resolver2/Resolver.jadd', + 'src/test/jastadd/resolver2/ResolverUtils.jadd', + 'src/test/jastadd/resolver2/ResolverRefResolver.jadd', + 'src/test/jastadd/resolver2/ResolverResolverStubs.jrag', + 'src/test/jastadd/resolver2/MyRefResolver.jadd', + 'src/test/jastadd/Utils.jadd' } task preprocessListNamesTest(type: JavaExec, group: 'verification') { @@ -232,6 +269,7 @@ task preprocessListNamesTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck args 'src/test/jastadd/listnames/ListNames.relast', '--file', '--grammarName=src/test/jastadd/listnames/ListNames', '--jastAddList=ListyMcListface' } @@ -243,9 +281,44 @@ task compileListNamesTest(type: JavaExec, group: 'verification') { classpath = sourceSets.main.runtimeClasspath main = 'org.jastadd.JastAdd' - args '--o=src/test/java-gen/', '--package=listnames.ast', 'src/test/jastadd/listnames/ListNames.ast', 'src/test/jastadd/listnames/ListNames.jadd', 'src/test/jastadd/Utils.jadd', '--List=ListyMcListface' + //noinspection GroovyAssignabilityCheck + args '--o=src/test/java-gen/', '--package=listnames.ast', '--List=ListyMcListface', + 'src/test/jastadd/listnames/ListNames.ast', + 'src/test/jastadd/listnames/ListNames.jadd', + 'src/test/jastadd/Utils.jadd' } +task preprocessSerializerTest(type: JavaExec, group: 'verification') { + + doFirst { + delete 'src/test/jastadd/serializer/Serializer.ast', + 'src/test/jastadd/serializer/Serializer.jadd', + 'src/test/jastadd/serializer/SerializerRefResolver.jadd', + 'src/test/jastadd/serializer/SerializerSerializer.jadd' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.relast.compiler.Compiler' + //noinspection GroovyAssignabilityCheck + args 'src/test/jastadd/serializer/Serializer.relast', '--file', '--grammarName=src/test/jastadd/serializer/Serializer', '--serializer=jackson' +} + +task compileSerializerTest(type: JavaExec, group: 'verification') { + + doFirst { + delete 'src/test/java-gen/serializer' + } + + classpath = sourceSets.main.runtimeClasspath + main = 'org.jastadd.JastAdd' + //noinspection GroovyAssignabilityCheck + args '--o=src/test/java-gen/', '--package=serializer.ast', + 'src/test/jastadd/serializer/Serializer.ast', + 'src/test/jastadd/serializer/Serializer.jadd', + 'src/test/jastadd/serializer/SerializerRefResolver.jadd', + 'src/test/jastadd/serializer/SerializerSerializer.jadd', + 'src/test/jastadd/Utils.jadd' +} test { outputs.upToDateWhen { false } @@ -270,4 +343,7 @@ test.dependsOn compileResolver2Test compileResolver2Test.dependsOn preprocessResolver2Test test.dependsOn compileListNamesTest -compileListNamesTest.dependsOn preprocessListNamesTest \ No newline at end of file +compileListNamesTest.dependsOn preprocessListNamesTest + +test.dependsOn compileSerializerTest +compileSerializerTest.dependsOn preprocessSerializerTest \ No newline at end of file diff --git a/src/main/jastadd/Backend.jadd b/src/main/jastadd/Backend.jadd index b079db04c539f64711d790bfcebd2ecc46076215..c33893c3aec0ba7c50e507c6d544016880560779 100644 --- a/src/main/jastadd/Backend.jadd +++ b/src/main/jastadd/Backend.jadd @@ -3,7 +3,8 @@ aspect BackendAbstractGrammar { public static String ASTNode.listClass = "ArrayList"; public static String ASTNode.jastAddListType = "List"; -public static boolean ASTNode.resolverHelper = false; + public static boolean ASTNode.resolverHelper = false; + public static boolean ASTNode.serializer = false; public String Program.generateAbstractGrammar() { StringBuilder sb = new StringBuilder(); @@ -694,7 +695,13 @@ aspect NameResolutionHelper { return sb.toString(); } - public void Program.generateRewriteToSuperTypeStub(StringBuilder sb) { + public String Program.generateResolverStubs() { + StringBuilder sb = new StringBuilder(); + generateResolverStubs(sb); + return sb.toString(); + } + + public void Program.generateResolverStubs(StringBuilder sb) { sb.append("aspect RefResolverStubs {\n\n"); for (Relation r: getRelations()) { @@ -708,6 +715,9 @@ aspect NameResolutionHelper { } } sb.append("}\n\n"); + } + + public void Program.generateRewriteToSuperTypeStub(StringBuilder sb) { sb.append("aspect ReferenceCreation {\n\n"); @@ -871,6 +881,391 @@ aspect NameResolutionHelper { } } +aspect Serializer { + + protected static String ASTNode.jsonTypeKey = "type"; + protected static String ASTNode.jsonNodeType = "com.fasterxml.jackson.databind.JsonNode"; + protected static String ASTNode.jsonNodeTypeAccessor = ".get(\"" + jsonTypeKey + "\").asText()"; + public String Program.generateSerializer() { + StringBuilder sb = new StringBuilder(); + generateFromJson(sb); + generateToJson(sb); + writeUID(sb); + return sb.toString(); + } + + public void Program.generateFromJson(StringBuilder sb) { + sb.append("aspect JsonToModel {\n"); + + sb.append(ind(1) + "public class DeserializationException extends Exception {\n"); + sb.append(ind(2) + "public DeserializationException(String message) {\n"); + sb.append(ind(3) + "super(message);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(2) + "public DeserializationException(String message, Exception cause) {\n"); + sb.append(ind(3) + "super(message, cause);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(1) + "}\n"); + + sb.append(ind(1) + "public void ASTNode.serialize(com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {\n"); + sb.append(ind(2) + "serialize(g, null);\n"); + sb.append(ind(1) + "}\n"); + + sb.append(ind(1) + "public void ASTNode.serialize(com.fasterxml.jackson.core.JsonGenerator g, String field) throws SerializationException {\n"); + sb.append(ind(2) + "throw new SerializationException(\"unable to serialize class \" + this.getClass().getSimpleName());\n"); + sb.append(ind(1) + "}\n"); + + sb.append(ind(1) + "public void ASTNode.serialize(java.io.File file) throws SerializationException {\n"); + sb.append(ind(2) + "try {\n"); + sb.append(ind(3) + "com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n"); + sb.append(ind(3) + "com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(file, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n"); + sb.append(ind(3) + "generator.setPrettyPrinter(new com.fasterxml.jackson.core.util.DefaultPrettyPrinter());\n"); + sb.append(ind(3) + "serialize(generator);\n"); + sb.append(ind(3) + "generator.close();\n"); + sb.append(ind(2) + "} catch (java.io.IOException e) {\n"); + sb.append(ind(3) + "throw new SerializationException(\"Unable to serialize file \" + file.getAbsolutePath(), e);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(1) + "}\n"); + + for (TypeDecl decl : getTypeDeclList()) { + if (!decl.isUnresolved()) { + decl.deserialize(sb); + } + } + + sb.append("}\n"); + } + + public void Program.generateToJson(StringBuilder sb) { + sb.append("aspect ModelToJson {\n"); + + sb.append(ind(1) + "public class SerializationException extends Exception {\n"); + sb.append(ind(2) + "public SerializationException(String message) {\n"); + sb.append(ind(3) + "super(message);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(2) + "public SerializationException(String message, Exception cause) {\n"); + sb.append(ind(3) + "super(message, cause);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(1) + "}\n"); + + for (TypeDecl decl : getTypeDeclList()) { + if (!decl.isUnresolved()) { + decl.serialize(sb); + } + } + + sb.append("}\n"); + } + + public void TypeDecl.serialize(StringBuilder sb) { + sb.append(ind(1) + "public void " + getID() + ".serialize(com.fasterxml.jackson.core.JsonGenerator g, String fieldName) throws SerializationException {\n"); + sb.append(ind(2) + "try {\n"); + sb.append(ind(3) + "if (fieldName == null) {\n"); + sb.append(ind(4) + "g.writeStartObject();\n"); + sb.append(ind(3) + "} else {\n"); + sb.append(ind(4) + "g.writeObjectFieldStart(fieldName);\n"); + sb.append(ind(3) + "}\n"); + sb.append(ind(3) + "g.writeStringField(\"" + jsonTypeKey + "\", \"" + getID() + "\");\n"); + sb.append(ind(3) + "g.writeStringField(\"id\", __uid());\n"); + if (componentsTransitive().size() > 0) { + sb.append(ind(3) + "g.writeObjectFieldStart(\"children\");\n"); + for (Component child : componentsTransitive()) { + child.serialize(sb, 3); + } + sb.append(ind(3) + "g.writeEndObject(); // children\n"); + } + if (relationComponents().size() > 0) { + sb.append(ind(3) + "g.writeObjectFieldStart(\"relations\");\n"); + for (RelationComponent relation : relationComponents()) { + relation.serialize(sb, 3); + } + sb.append(ind(3) + "g.writeEndObject(); // relations\n"); + } + sb.append(ind(3) + "g.writeEndObject();\n"); + sb.append(ind(2) + "} catch (java.io.IOException e) {\n"); + sb.append(ind(3) + "throw new SerializationException(\"unable to serialize " + getID() + "\", e);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(1) + "}\n"); + + } + + public abstract void Component.serialize(StringBuilder sb, int indent); + + public void NormalComponent.serialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "get" + getID() + "().serialize(g, \"" + getID() + "\");\n"); + } + + public void OptComponent.serialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "if (has" + getID() + "()) {\n"); + sb.append(ind(indent + 1) + "get" + getID() + "().serialize(g, \"" + getID() + "\");\n"); + sb.append(ind(indent) + "}\n"); + } + + public void TokenComponent.serialize(StringBuilder sb, int indent) { + + String type = getTypeUse().getID(); + + switch (type) { + case "float": + case "Float": + case "double": + case "Double": + case "int": + case "Integer": + case "short": + case "Short": + case "long": + case "Long": + case "byte": + case "Byte": + sb.append(ind(indent) + "g.writeNumberField(\"" + getID() + "\", get" + getID() + "());\n"); + break; + case "boolean": + case "Boolean": + sb.append(ind(indent) + "g.writeBooleanField(\"" + getID() + "\", get" + getID() + "());\n"); + break; + case "char": + case "Character": + sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", Character.toString(get"+ getID() + "()));\n"); + break; + case "String": + sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "());\n"); + break; + case "java.time.Instant": + sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "()).toString());\n"); + break; + case "java.time.Period": + sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "()).toString());\n"); + break; + default: + // assume that the type is an enum. there is no way of checking this here + sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "()).name());\n"); + // sb.append("throw new DeserializationException(\"Unable to deserialize child node of type \"" + getTypeUse() + "\"\")") + } + } + + public void NTAComponent.serialize(StringBuilder sb, int indent) { + // do not serialize NTA + } + + public void ListComponent.serialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "if (getNum" + getID() + "() > 0) {\n"); + sb.append(ind(indent + 1) + "g.writeArrayFieldStart(\"" + getID() + "\");\n"); + sb.append(ind(indent + 1) + "for (" + getTypeUse().decl().getID() + " child : get" + getID() + "List()) {\n"); + sb.append(ind(indent + 2) + "child.serialize(g);\n"); + sb.append(ind(indent + 1) + "}\n"); + sb.append(ind(indent + 1) + "g.writeEndArray();\n"); + sb.append(ind(indent) + "}\n"); + } + + public void OneRelationComponent.serialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().__uid());\n"); + } + + public void OptionalRelationComponent.serialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "if (has" + getID() + "()) {\n"); + sb.append(ind(indent + 1) + "g.writeStringField(\"" + getID() + "\", get" + getID() + "().__uid());\n"); + sb.append(ind(indent) + "}\n"); + } + + public void ManyRelationComponent.serialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "g.writeArrayFieldStart(\"" + getID() + "\");\n"); + sb.append(ind(indent) + "for (" + ofTypeDecl().getID() + " child : get" + getID() + "List()) {\n"); + sb.append(ind(indent + 1) + "g.writeString(child.__uid());\n"); + sb.append(ind(indent) + "}\n"); + sb.append(ind(indent) + "g.writeEndArray();\n"); + } + + public void TypeDecl.deserialize(StringBuilder sb) { + + sb.append(ind(1) + "public static " + getID() + " " + getID() + ".deserialize(java.io.File file) throws DeserializationException {\n"); + sb.append(ind(2) + "try {\n"); + sb.append(ind(3) + "com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n"); + sb.append(ind(3) + "com.fasterxml.jackson.core.JsonFactory factory = mapper.getFactory();\n"); + sb.append(ind(3) + "com.fasterxml.jackson.core.JsonParser parser = factory.createParser(file);\n"); + sb.append(ind(3) + "return deserialize((com.fasterxml.jackson.databind.JsonNode)mapper.readTree(parser));\n"); + sb.append(ind(2) + "} catch (java.io.IOException e) {\n"); + sb.append(ind(3) + "throw new DeserializationException(\"unable to deserialize \" + file.getAbsolutePath(), e);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(1) + "}\n"); + + sb.append(ind(1) + "public static " + getID() + " " + getID() + ".deserialize(" + jsonNodeType + " node) throws DeserializationException {\n"); + sb.append(ind(2) + getID() + " element;\n"); + if (getAbstract()) { + // switch case between all implementations of the abstract class + sb.append(ind(2) + "switch (node" + jsonNodeTypeAccessor + ") {\n"); + for (TypeDecl subType : subTypeDecls()) { + sb.append(ind(3) + "case \"" + subType.getID() + "\":\n"); + sb.append(ind(4) + "element = " + subType.getID() + ".deserialize(node);\n"); + sb.append(ind(4) + "break;\n"); + } + sb.append(ind(3) + "default:\n"); + sb.append(ind(4) + "throw new DeserializationException(\"Unable to deserialize child of unexpected type \" + node" + jsonNodeTypeAccessor + " + \"(" + getID() + " expected)\");\n"); + sb.append(ind(2) + "}\n"); + } else { + sb.append(ind(2) + "element = new " + getID() + "();\n"); + } + + // deserialize id + sb.append(ind(2) + "if (node.has(\"id\")) {\n"); + sb.append(ind(3) + "element.__uid = node.get(\"id\").asText();\n"); + sb.append(ind(2) + "}\n"); + + // deserialize containment children + if (componentsTransitive().size() > 0) { + sb.append(ind(2) + "if (node.has(\"children\")) {\n"); + sb.append(ind(3) + jsonNodeType + " children = node.get(\"children\");\n"); + for (Component component : componentsTransitive()) { + sb.append(ind(3) + "if (children.has(\"" + component.getID() + "\")) {\n"); + component.deserialize(sb, 4); + sb.append(ind(3) + "}\n"); + } + sb.append(ind(2) + "}\n"); + } + // deserialize non-containment children + Set<RelationComponent> relationComponents = relationComponents(); + if (relationComponents.size() > 0) { + sb.append(ind(2) + "if (node.has(\"relations\")) {\n"); + sb.append(ind(3) + jsonNodeType + " relations = node.get(\"relations\");\n"); + for (RelationComponent component : relationComponents) { + sb.append(ind(3) + "if (relations.has(\"" + component.getID() + "\")) {\n"); + component.deserialize(sb, 4); + sb.append(ind(3) + "}\n"); + } + sb.append(ind(2) + "}\n"); + } + + sb.append(ind(2) + "return element;\n"); + sb.append(ind(1) + "}\n"); + + } + + public abstract void Component.deserialize(StringBuilder sb, int indent); + + public void NormalComponent.deserialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "element.set" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(children.get(\"" + getID() + "\")));\n"); + sb.append(ind(indent - 1) + "} else {\n"); + sb.append(ind(indent) + "throw new DeserializationException(\"deserializer of missing mandatory child " + getID() + "\");\n"); + } + + public void OptComponent.deserialize(StringBuilder sb, int indent) { + sb.append(ind(indent + 1) + "element.set" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(children.get(\"" + getID() + "\")));\n"); + } + + public void TokenComponent.deserialize(StringBuilder sb, int indent) { + + String type = getTypeUse().getID(); + + switch (type) { + case "float": + case "Float": + sb.append(ind(indent) + "element.set" + getID() + "(Float.valueOf(children.get(\"" + getID() + "\").asText()));\n"); + break; + case "double": + case "Double": + sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asDouble());\n"); + break; + case "int": + case "Integer": + sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asInt());\n"); + break; + case "short": + case "Short": + sb.append(ind(indent) + "element.set" + getID() + "(Short.valueOf(children.get(\"" + getID() + "\").asText()));\n"); + break; + case "long": + case "Long": + sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asLong());\n"); + break; + case "byte": + case "Byte": + sb.append(ind(indent) + "element.set" + getID() + "(Byte.valueOf(children.get(\"" + getID() + "\").asText()));\n"); + break; + case "boolean": + case "Boolean": + sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asBoolean());\n"); + break; + case "char": + case "Character": + sb.append(ind(indent) + "String chars = children.get(\"" + getID() + "\").asText();\n"); + sb.append(ind(indent) + "if (chars.length() == 1) {\n"); + sb.append(ind(indent + 2) + "element.set" + getID() + "(chars.charAt(0))\n"); + sb.append(ind(indent) + "} else {\n"); + sb.append(ind(indent + 2) + "throw new DeserializationException(\"unable to deserialize char '\" + chars + \"'\");\n"); + sb.append(ind(indent) + "}\n"); + break; + case "String": + sb.append(ind(indent) + "element.set" + getID() + "(children.get(\"" + getID() + "\").asText());\n"); + break; + case "java.time.Instant": + sb.append(ind(indent) + "element.set" + getID() + "(java.time.Instant.parse(children.get(\"" + getID() + "\").asText()));\n"); + break; + case "java.time.Period": + sb.append(ind(indent) + "element.set" + getID() + "(java.time.Period.parse(children.get(\"" + getID() + "\").asText()));\n"); + break; + default: + // assume that the type is an enum. there is no way of checking this here + sb.append(ind(indent) + "element.set" + getID() + "Enum.valueOf(" + type + ".getClass(), children.get(\"" + getID() + "\".asText())));\n"); + + // sb.append("throw new DeserializationException(\"Unable to deserialize child node of type \"" + getTypeUse() + "\"\")") + } + } + + public void Program.writeUID(StringBuilder sb) { + sb.append("aspect UID {\n"); + sb.append(ind(1) + "class UIDProvider {\n"); + sb.append(ind(2) + "private static long nextUID = 0;\n"); + sb.append(ind(2) + "public static String getUID() {\n"); + sb.append(ind(3) + "return String.valueOf(nextUID++);\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(1) + "}\n"); + sb.append("\n"); + sb.append(ind(1) + "protected String ASTNode.__uid = null;\n"); + sb.append("\n"); + sb.append(ind(1) + "protected String ASTNode.__uid() {\n"); + sb.append(ind(2) + "String customUID = serializationID();\n"); + sb.append(ind(2) + "if (customUID == null) {\n"); + sb.append(ind(3) + "if (__uid == null) {\n"); + sb.append(ind(4) + "__uid = UIDProvider.getUID();\n"); + sb.append(ind(3) + "}\n"); + sb.append(ind(3) + "return __uid;\n"); + sb.append(ind(2) + "} else {\n"); + sb.append(ind(3) + "return customUID;\n"); + sb.append(ind(2) + "}\n"); + sb.append(ind(1) + "}\n"); + sb.append("\n"); + sb.append(ind(1) + "protected String ASTNode.serializationID() {\n"); + sb.append(ind(2) + "return null;\n"); + sb.append(ind(1) + "}\n"); + sb.append("}\n"); + } + + public void NTAComponent.deserialize(StringBuilder sb, int indent) { + // do not serialize NTA + } + + public void ListComponent.deserialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "for (" + jsonNodeType + " child : children.get(\"" + getID() + "\")) {\n"); + sb.append(ind(indent + 1) + "element.add" + getID() + "(" + getTypeUse().decl().getID() + ".deserialize(child));\n"); + sb.append(ind(indent) + "}\n"); + } + + public void OneRelationComponent.deserialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "element.set" + getID() + "(" + ofTypeDecl().getID() + ".createRefDirection(relations.get(\"" + getID() + "\").asText()));\n"); + sb.append(ind(indent - 1) + "} else {\n"); + sb.append(ind(indent) + "throw new DeserializationException(\"deserializer of missing mandatory relation child " + getID() + "\");\n"); + } + + public void OptionalRelationComponent.deserialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "element.set" + getID() + "(" + ofTypeDecl().getID() + ".createRefDirection(relations.get(\"" + getID() + "\").asText()));\n"); + } + + public void ManyRelationComponent.deserialize(StringBuilder sb, int indent) { + sb.append(ind(indent) + "for (" + jsonNodeType + " child : relations.get(\"" + getID() + "\")) {\n"); + sb.append(ind(indent + 1) + "element.add" + getID() + "(" + ofTypeDecl().getID() + ".createRefDirection(child.asText()));\n"); + sb.append(ind(indent) + "}\n"); + } +} + aspect PrettyPrint { public String Relation.prettyPrint() { return "rel " diff --git a/src/main/java/org/jastadd/relast/compiler/Compiler.java b/src/main/java/org/jastadd/relast/compiler/Compiler.java index 135a34b6ce4e4d4bf0dfd734351f54922ea9ae9c..9c6c23bc753d3516e2a639d9e9beb2e51c2e2eb5 100644 --- a/src/main/java/org/jastadd/relast/compiler/Compiler.java +++ b/src/main/java/org/jastadd/relast/compiler/Compiler.java @@ -2,16 +2,14 @@ package org.jastadd.relast.compiler; import beaver.Parser; import org.jastadd.relast.ast.*; -import org.jastadd.relast.compiler.options.CommandLine; +import org.jastadd.relast.compiler.options.*; import org.jastadd.relast.compiler.options.CommandLine.CommandLineException; -import org.jastadd.relast.compiler.options.FlagOption; -import org.jastadd.relast.compiler.options.Option; -import org.jastadd.relast.compiler.options.StringOption; import org.jastadd.relast.parser.RelAstParser; import org.jastadd.relast.scanner.RelAstScanner; import java.io.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class Compiler { @@ -21,6 +19,7 @@ public class Compiler { private StringOption optionListClass; private StringOption optionJastAddList; private StringOption optionGrammarName; + private EnumOption optionSerializer; private FlagOption optionResolverHelper; private CommandLine commandLine; @@ -45,7 +44,7 @@ public class Compiler { } - if (optionResolverHelper.isSet()) { + if (optionResolverHelper.isSet() || optionSerializer.isSet()) { // get a list of all (abstract or not) non-terminals List<TypeDecl> nonTerminals = new ArrayList<>(); for (TypeDecl typeDecl : p.getTypeDecls()) { @@ -88,8 +87,21 @@ public class Compiler { if (optionWriteToFile.isSet()) { + if (optionSerializer.isSet()) { + ASTNode.serializer = true; + switch (optionSerializer.getValue()) { + case "jackson": + writeToFile(grammarName + "Serializer.jadd", p.generateSerializer()); + break; + } + } + if (optionResolverHelper.isSet()) { ASTNode.resolverHelper = true; + writeToFile(grammarName + "ResolverStubs.jrag", p.generateResolverStubs()); + } + + if (optionSerializer.isSet() || optionResolverHelper.isSet()) { writeToFile(grammarName + "RefResolver.jadd", p.generateRewriteToSuperTypeStub()); } @@ -131,6 +143,7 @@ public class Compiler { optionGrammarName = addOption(new StringOption("grammarName", "name of the generated grammar and aspect (without file extension)")); optionResolverHelper = addOption(new FlagOption("resolverHelper", "create a subtype for each type containing a string that can be used to resolve the type later")); optionJastAddList = addOption(new StringOption("jastAddList", "set the name of the List type in JastAdd (has to match the option '--List' or its default List)")); + optionSerializer = addOption(new EnumOption("serializer", "generate a (de-)serializer", Arrays.asList("jackson"), "jackson")); } diff --git a/src/test/jastadd/relations/Relations2.ast b/src/test/jastadd/relations/Relations2.ast deleted file mode 100644 index 4bd2e3a4b7a7d96773f46dad412c46b0157abbf5..0000000000000000000000000000000000000000 --- a/src/test/jastadd/relations/Relations2.ast +++ /dev/null @@ -1,3 +0,0 @@ -Root ::= A* B*; -A ::= <Name:String> <_impl_Di2:B> <_impl_Bi2:B> <_impl_Bi4:B> <_impl_Bi3:B> <_impl_Bi8:ArrayList<B>> <_impl_Bi9:ArrayList<B>> <_impl_Bi1:B> <_impl_Bi5:B> <_impl_Di3:ArrayList<B>> <_impl_Bi7:ArrayList<B>> <_impl_Bi6:B> <_impl_Di1:B>; -B ::= <Name:String> <_impl_Bi7:A> <_impl_Bi3:ArrayList<A>> <_impl_Bi5:A> <_impl_Bi1:A> <_impl_Bi6:ArrayList<A>> <_impl_Bi9:ArrayList<A>> <_impl_Bi2:A> <_impl_Bi4:A> <_impl_Bi8:A>; diff --git a/src/test/jastadd/relations/Relations2.jadd b/src/test/jastadd/relations/Relations2.jadd deleted file mode 100644 index 3fed2f9af474b969c9a97b1926f186c452316767..0000000000000000000000000000000000000000 --- a/src/test/jastadd/relations/Relations2.jadd +++ /dev/null @@ -1,43 +0,0 @@ -import java.util.ArrayList; -import java.util.Collections; -aspect RelAstAPI { - public boolean ASTNode.violatesLowerBounds() { - return !getLowerBoundsViolations().isEmpty(); - } - public java.util.List<Pair<ASTNode, String>> ASTNode.getLowerBoundsViolations() { - ArrayList<Pair<ASTNode, String>> list = new ArrayList<>(); - computeLowerBoundsViolations(list); - return list; - } - public void ASTNode.computeLowerBoundsViolations(java.util.List<Pair<ASTNode, String>> list) { - for (int i = 0; i < getNumChildNoTransform(); i++) { - getChildNoTransform(i).computeLowerBoundsViolations(list); - } - } - public class Pair<T1, T2> { - public final T1 _1; - public final T2 _2; - public Pair(T1 _1, T2 _2) { - ASTNode.assertNotNull(_1); - ASTNode.assertNotNull(_2); - this._1 = _1; - this._2 = _2; - } - public boolean equals(Object other) { - if (other instanceof Pair) { - Pair<?,?> p = (Pair<?,?>) other; - return _1.equals(p._1) && _2.equals(p._2); - } else { - return false; - } - } - public int hashCode() { - return 31*_1.hashCode() + _2.hashCode(); - } - } - public static void ASTNode.assertNotNull(Object obj) { - if (obj == null) { - throw new NullPointerException(); - } - } -} diff --git a/src/test/jastadd/serializer/Serializer.relast b/src/test/jastadd/serializer/Serializer.relast new file mode 100644 index 0000000000000000000000000000000000000000..41c9e621faf9cea7c6de73768b049b1927aab1b2 --- /dev/null +++ b/src/test/jastadd/serializer/Serializer.relast @@ -0,0 +1,21 @@ + +Root ::= A* B* C; +A:NamedElement; +B:NamedElement; +C:NamedElement ::= D1:D [D2:D] D3:D* <F1:float> <F2:Float>; +D:NamedElement; + +abstract NamedElement ::= <Name>; + +rel A.Di1 -> B; +rel A.Di2? -> B; +rel A.Di3* -> B; + +rel A.Bi1 <-> B.Bi1; +rel A.Bi2 <-> B.Bi2?; +rel A.Bi3 <-> B.Bi3*; + +rel A.Bi5? <-> B.Bi5?; +rel A.Bi6? <-> B.Bi6*; + +rel A.Bi9* <-> B.Bi9*; diff --git a/src/test/java/org/jastadd/relast/tests/Serializer.java b/src/test/java/org/jastadd/relast/tests/Serializer.java new file mode 100644 index 0000000000000000000000000000000000000000..5bfa84f5f51481f87dc5a81e060a7d51fdbc9545 --- /dev/null +++ b/src/test/java/org/jastadd/relast/tests/Serializer.java @@ -0,0 +1,98 @@ +package org.jastadd.relast.tests; + +import org.junit.jupiter.api.Test; +import serializer.ast.*; + +import java.io.File; + + +@SuppressWarnings("ArraysAsListWithZeroOrOneArgument") +class Serializer { + + + @Test + void testDi1() throws SerializationException, DeserializationException { + + Root r = new Root(); + A a1 = new A("a1"); + A a2 = new A("a2"); + A a3 = new A("a3"); + B b1 = new B("b1"); + B b2 = new B("b2"); + B b3 = new B("b3"); + C c = new C(); + c.setName("c"); + + // non-terminals + c.setD1(new D("d1")); + c.setD2(new D("d2")); + c.addD3(new D("D3-1")); + c.addD3(new D("D3-2")); + c.addD3(new D("D3-3")); + + // tokens + c.setF1(42f); + c.setF2(42.13f); + + r.setC(c); + + // non-containment relations + r.addA(a1); + r.addA(a2); + r.addA(a3); + r.addB(b1); + r.addB(b2); + r.addB(b3); + + // Di1 + a1.setDi1(b2); + a2.setDi1(b1); + a3.setDi1(b3); + + // Di2 + a1.setDi2(b2); + a3.setDi2(b1); + + // Di3 + a1.addDi3(b1); + a1.addDi3(b2); + a1.addDi3(b3); + a2.addDi3(b2); + + // Bi1 + a1.setBi1(b1); + a2.setBi1(b3); + a3.setBi1(b2); + + // Bi2 + a1.setBi2(b1); + a2.setBi2(b2); + a3.setBi2(b3); + + // Bi3 + a1.setBi3(b2); + a2.setBi3(b2); + a3.setBi3(b2); + + // Bi5 + a1.setBi5(b1); + a2.setBi5(b3); + + // Bi6 + a2.setBi6(b2); + a3.setBi6(b2); + + // Bi9 + a1.addBi9(b1); + a1.addBi9(b2); + a2.addBi9(b2); + + File f = new File("test.json"); + r.serialize(f); + + Root copy = Root.deserialize(f); + File f2 = new File("copy.json"); + copy.serialize(f2); + } + +}