diff --git a/dumpAst2uml/src/main/jastadd/DumpAst.relast b/dumpAst2uml/src/main/jastadd/DumpAst.relast index 1d94dc6875215d74e9167d54a36154d4461bbbd3..91f4d16f74b37f33f4200aa9f91a79ac0f4a7947 100644 --- a/dumpAst2uml/src/main/jastadd/DumpAst.relast +++ b/dumpAst2uml/src/main/jastadd/DumpAst.relast @@ -1,5 +1,5 @@ -DumpAst ::= DumpNode* <PackageName> ; -DumpNode ::= <Name> <Object:Object> DumpChildNode* DumpToken* DumpRelation* ; +DumpAst ::= DumpNode* <PackageName> <Scale:double> ; +DumpNode ::= <Name> <Label> <Object:Object> DumpChildNode* DumpToken* DumpRelation* ; InnerDumpNode ; rel InnerDumpNode.DumpNode -> DumpNode ; diff --git a/dumpAst2uml/src/main/jastadd/Generation.jadd b/dumpAst2uml/src/main/jastadd/Generation.jadd index 7dd3c516032df66b344676cd2cde3afa572ed1c0..68de1bf3c40db41a370deb9dfad5c74b23c48aef 100644 --- a/dumpAst2uml/src/main/jastadd/Generation.jadd +++ b/dumpAst2uml/src/main/jastadd/Generation.jadd @@ -4,10 +4,12 @@ aspect Generation { return new DumpBuilder(obj); } } - class DumpBuilder { + public class DumpBuilder { private Object target; private String packageName; private DumpAst result; + private java.util.List<String> header = new java.util.ArrayList<>(); + protected DumpBuilder(Object target) { this.target = target; } @@ -29,11 +31,14 @@ aspect Generation { throw new RuntimeException("Could not transform :(", e); } catch (IllegalAccessException e) { throw new RuntimeException("Could not transform :(", e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Could not transform :(", e); } } return result; } public DumpBuilder dumpAsSource(java.nio.file.Path destination) throws java.io.IOException { + build().setScale(0.1); String content = build().toPlantUml(); try (java.io.Writer writer = java.nio.file.Files.newBufferedWriter(destination)) { writer.write(content); @@ -41,15 +46,25 @@ aspect Generation { return this; } public DumpBuilder dumpAsPNG(java.nio.file.Path destination) throws java.io.IOException { + build().setScale(0.1); String content = build().toPlantUml(); net.sourceforge.plantuml.SourceStringReader reader = new net.sourceforge.plantuml.SourceStringReader(content); reader.outputImage(java.nio.file.Files.newOutputStream(destination)); return this; } + public DumpBuilder dumpAsSVG(java.nio.file.Path destination) throws java.io.IOException { + build().setScale(1); + String content = build().toPlantUml(); + net.sourceforge.plantuml.SourceStringReader reader = new net.sourceforge.plantuml.SourceStringReader(content); + reader.outputImage(java.nio.file.Files.newOutputStream(destination), + new net.sourceforge.plantuml.FileFormatOption(net.sourceforge.plantuml.FileFormat.SVG)); + return this; + } } // --- transform --- (need to be a method, because it alters the AST while traversing the object structure) // maybe return type is unncessary - protected DumpNode DumpAst.transform(TransformationTransferInformation tti, Object obj) throws java.lang.reflect.InvocationTargetException, IllegalAccessException { + protected DumpNode DumpAst.transform(TransformationTransferInformation tti, Object obj) + throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException { if (obj == null) { return null; } @@ -58,11 +73,20 @@ aspect Generation { } DumpNode node = new DumpNode(); node.setObject(obj); + node.setLabel(obj.getClass().getSimpleName() + "@" + obj.hashCode()); + node.setName("node" + tti.transformed.size()); tti.transformed.put(obj, node); this.addDumpNode(node); if (node.isAstNode()) { // only caching node.analyseClass does not help, since we want to do this only once per class of a node - ClassAnalysisResult car = tti.classAnalysisResults.computeIfAbsent(node.getClass(), clz -> node.analyseClass()); + final ClassAnalysisResult car; + Class<?> clazz = obj.getClass(); + if (tti.classAnalysisResults.containsKey(clazz)) { + car = tti.classAnalysisResults.get(clazz); + } else { + car = node.analyzeClass(); + tti.classAnalysisResults.put(clazz, car); + } // -- singleChild -- for (java.lang.reflect.Method method : car.singleChildMethods) { Object target = method.invoke(obj); @@ -76,7 +100,7 @@ aspect Generation { // -- listChild -- for (java.lang.reflect.Method method : car.listChildMethods) { Iterable<?> targetList = (Iterable<?>) method.invoke(obj); - DumpListChildNode listChild = new DumpNormalChildNode(); + DumpListChildNode listChild = new DumpListChildNode(); listChild.setName(car.names.get(method)); for (Object target : targetList) { if (target != null) { @@ -89,23 +113,23 @@ aspect Generation { for (java.lang.reflect.Method method : car.singleRelationMethods) { Object target = method.invoke(obj); if (target != null) { - DumpNormalRelation normalChild = new DumpNormalRelation(); - normalChild.setName(car.names.get(method)); - normalChild.setDumpNode(transform(tti, target)); - node.addDumpRelation(normalChild); + DumpNormalRelation normalRelation = new DumpNormalRelation(); + normalRelation.setName(car.names.get(method)); + normalRelation.setDumpNode(transform(tti, target)); + node.addDumpRelation(normalRelation); } } - // -- listChild -- - for (java.lang.reflect.Method method : car.listChildMethods) { + // -- listRelation -- + for (java.lang.reflect.Method method : car.listRelationMethods) { Iterable<?> targetList = (Iterable<?>) method.invoke(obj); - DumpListRelation listChild = new DumpNormalRelation(); - listChild.setName(car.names.get(method)); + DumpListRelation listRelation = new DumpListRelation(); + listRelation.setName(car.names.get(method)); for (Object target : targetList) { if (target != null) { - listChild.addInnerDumpNode(new InnerDumpNode(transform(tti, target))); + listRelation.addInnerDumpNode(new InnerDumpNode(transform(tti, target))); } } - node.addDumpRelation(listChild); + node.addDumpRelation(listRelation); } // -- token -- for (java.lang.reflect.Method method : car.tokenMethods) { @@ -114,12 +138,12 @@ aspect Generation { DumpNode targetNode = transform(tti, target); DumpToken token; if (targetNode.isAstNode()) { - token = new DumpReferenceToken(); - token.setValue(targetNode); + token = new DumpReferenceToken().setValue(targetNode); } else { // maybe ignore empty string values here - token = new DumpValueToken(); - token.setValue(target); + DumpValueToken valueToken = new DumpValueToken(); + valueToken.setValue(target); + token = valueToken; } token.setName(car.names.get(method)); node.addDumpToken(token); @@ -129,28 +153,93 @@ aspect Generation { return node; } - syn ClassAnalysisResult DumpNode.analyseClass() { - // TODO - return null; + ClassAnalysisResult DumpNode.analyzeClass() + throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException { + ClassAnalysisResult result = new ClassAnalysisResult(); + Class<?> clazz = getObject().getClass(); + for (java.lang.reflect.Method method : clazz.getMethods()) { + for (java.lang.annotation.Annotation annotation : method.getAnnotations()) { + String canonicalName = annotation.annotationType().getCanonicalName(); + if (canonicalName.startsWith(astNodeAnnotationPrefix())) { + String simpleName = annotation.annotationType().getSimpleName(); + switch (simpleName) { + case "Child": + case "OptChild": + result.singleChildMethods.add(method); + break; + case "ListChild": + result.listChildMethods.add(method); + break; + case "Token": + // heuristic for relations + String tokenName = invokeName(annotation); + if (tokenName.startsWith("_impl_")) { + String relationName = tokenName.substring(6); + try { + java.lang.reflect.Method relationMethod = clazz.getMethod("get" + relationName); + // normal get + token-name -> singleRelation + result.singleRelationMethods.add(relationMethod); + result.names.put(relationMethod, relationName); + continue; + } catch (NoSuchMethodException e) { + // ignore, but we know this is probably not a single relation + } + // try list-relation next + try { + java.lang.reflect.Method relationMethod = clazz.getMethod("get" + relationName + "List"); + // normal get + token-name + "List" -> listRelation + result.listRelationMethods.add(relationMethod); + result.names.put(relationMethod, relationName); + continue; + } catch (NoSuchMethodException e) { + // ignore, but we know this is probably not a relation at all + } + } + result.tokenMethods.add(method); + break; + default: + continue; + } + result.names.put(method, invokeName(annotation)); + } + } + } + return result; + } + + private static String DumpNode.invokeName(java.lang.annotation.Annotation annotation) + throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException { + return (String) annotation.annotationType().getMethod("name").invoke(annotation); } syn boolean DumpNode.isAstNode() { - // TODO + Class<?> clazz = getObject().getClass(); + for (java.lang.reflect.Constructor<?> constructor : clazz.getConstructors()) { + for (java.lang.annotation.Annotation annotation : constructor.getAnnotations()) { + if (annotation.annotationType().getCanonicalName().startsWith(astNodeAnnotationPrefix()) && + annotation.annotationType().getSimpleName().equals("Constructor")) { + return true; + } + } + } return false; } + inh String DumpNode.astNodeAnnotationPrefix(); + eq DumpAst.getDumpNode().astNodeAnnotationPrefix() = getPackageName() + ".ASTNodeAnnotation"; + class TransformationTransferInformation { java.util.Map<Object, DumpNode> transformed = new java.util.HashMap<>(); java.util.Map<Class<?>, ClassAnalysisResult> classAnalysisResults = new java.util.HashMap<>(); } class ClassAnalysisResult { - java.lang.reflect.Method[] singleChildMethods; - java.lang.reflect.Method[] listChildMethods; - java.lang.reflect.Method[] singleRelationMethods; - java.lang.reflect.Method[] listRelationMethods; - java.lang.reflect.Method[] tokenMethods; - java.util.Map<java.lang.reflect.Method, String> names; + java.util.List<java.lang.reflect.Method> singleChildMethods = new java.util.ArrayList<>(); + java.util.List<java.lang.reflect.Method> listChildMethods = new java.util.ArrayList<>(); + java.util.List<java.lang.reflect.Method> singleRelationMethods = new java.util.ArrayList<>(); + java.util.List<java.lang.reflect.Method> listRelationMethods = new java.util.ArrayList<>(); + java.util.List<java.lang.reflect.Method> tokenMethods = new java.util.ArrayList<>(); + java.util.Map<java.lang.reflect.Method, String> names = new java.util.HashMap<>(); } syn String DumpAst.toPlantUml() { diff --git a/dumpAst2uml/src/main/jastadd/Printing.jrag b/dumpAst2uml/src/main/jastadd/Printing.jrag index 9a399ba6bfdfd9ec6f5c8e279d6cf6fb39e933c1..c41f02b1052560ba2bba70e84943d98a16f7c40b 100644 --- a/dumpAst2uml/src/main/jastadd/Printing.jrag +++ b/dumpAst2uml/src/main/jastadd/Printing.jrag @@ -19,5 +19,5 @@ aspect Printing { syn String DumpChildNode.label() = getName(); syn String DumpRelation.label() = getName(); syn String DumpToken.label() = getName(); - + syn String DumpNode.label() = getLabel(); } diff --git a/dumpAst2uml/src/main/java/org/jastadd/dumpAst2uml/compiler/SimpleMain.java b/dumpAst2uml/src/main/java/org/jastadd/dumpAst2uml/compiler/SimpleMain.java index f6e83dbf0441b9dc2a55a232c9e26ddfccfdd816..b0b6e02dffa8e7b4a9deb4e6f4ba6b1b25fa0eec 100644 --- a/dumpAst2uml/src/main/java/org/jastadd/dumpAst2uml/compiler/SimpleMain.java +++ b/dumpAst2uml/src/main/java/org/jastadd/dumpAst2uml/compiler/SimpleMain.java @@ -25,11 +25,13 @@ import static org.jastadd.dumpAst2uml.compiler.SimpleMain.Kind.*; * * @author rschoene - Initial contribution */ +@SuppressWarnings("unused") public class SimpleMain { - public static void main(String[] args) { -// printing(); - createManualAST(); + public static void main(String[] args) throws Exception { + printing(); +// createManualAST(); +// small(); } private static void createManualAST() { @@ -93,7 +95,7 @@ public class SimpleMain { System.out.println(content); } - private static void printing() { + private static void printing() throws IOException { System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); System.setProperty("mustache.debug", "true"); Grammar2Uml model = new Grammar2Uml(); @@ -109,7 +111,34 @@ public class SimpleMain { model.addFolder(folder1); model.treeResolveAll(); - traverseInitial(model.toMustache()); +// traverseInitial(model.toMustache()); + Dumper.read(model.toMustache()) + .dumpAsSource(Paths.get("temp.plantuml")) + .dumpAsSVG(Paths.get("temp.svg")); + } + + private static void small() throws IOException { + Program program = new Program(); + GrammarFile grammar = new GrammarFile(); + grammar.setFileName("foo.relast"); + program.addGrammarFile(grammar); + + TypeDecl typeDeclA = new TypeDecl(); + typeDeclA.setName("A"); + grammar.addDeclaration(typeDeclA); + + TypeDecl typeDeclB = new TypeDecl(); + typeDeclB.setName("B"); + grammar.addDeclaration(typeDeclB); + + LeftDirectedRelation relation = new LeftDirectedRelation(); + relation.setSource(new NormalRole(typeDeclA, "myB")); + relation.setTarget(new UnnamedRole(typeDeclB)); + grammar.addDeclaration(relation); + + Dumper.read(program) + .dumpAsSource(Paths.get("grammar.plantuml")) + .dumpAsSVG(Paths.get("grammar.svg")); } static final String MORE_INDENT = "| "; diff --git a/dumpAst2uml/src/main/resources/dumpAst.mustache b/dumpAst2uml/src/main/resources/dumpAst.mustache index ddbc1b11a0448d516319e8ddd9c0ce08a2b38bd0..fba11038ec059e56a2ee633d789595a1b216064f 100644 --- a/dumpAst2uml/src/main/resources/dumpAst.mustache +++ b/dumpAst2uml/src/main/resources/dumpAst.mustache @@ -1,19 +1,23 @@ @startuml +scale {{Scale}} {{#DumpNodes}} -object {{name}} { -{{! tokens }} +{{#isAstNode}} +object "{{label}}" as {{name}} { {{#DumpTokens}} {{#isDumpValueToken}} - {{label}} = {{Value}} + {{label}} = {{{Value}}} {{/isDumpValueToken}} {{/DumpTokens}} } +{{/isAstNode}} +{{/DumpNodes}} + +{{#DumpNodes}} {{#DumpTokens}} {{^isDumpValueToken}} {{outerNodeName}} ..> {{innerNodeName}} : {{label}} {{/isDumpValueToken}} {{/DumpTokens}} -{{! child nodes }} {{#DumpChildNodes}} {{#isList}} {{#InnerDumpNodes}} @@ -24,7 +28,6 @@ object {{name}} { {{outerNodeName}} *-- {{innerNodeName}} : {{label}} {{/isList}} {{/DumpChildNodes}} -{{! relations }} {{#DumpRelations}} {{#isList}} {{#InnerDumpNodes}}