diff --git a/dumpAst/src/main/jastadd/DumpAst.relast b/dumpAst/src/main/jastadd/DumpAst.relast index c6157b0c6f83689266a27c1db37a4f9eea1aa50f..2fad59a121f72fb3629dda4bdbf7733a797ae1fa 100644 --- a/dumpAst/src/main/jastadd/DumpAst.relast +++ b/dumpAst/src/main/jastadd/DumpAst.relast @@ -1,7 +1,9 @@ DumpAst ::= DumpNode* <PackageName> BuildConfig PrintConfig ; rel DumpAst.RootNode -> DumpNode ; -BuildConfig ::= GlobalPatternCollection:PatternCollection StyleInformation ExcludeTypePattern:TypePatternCollectionMapping* IncludeTypePattern:TypePatternCollectionMapping* <TypeIgnorePattern> <IncludeEmptyString:boolean> <Debug:boolean>; +BuildConfig ::= StyleInformation GlobalPatternCollection:PatternCollection + ExcludeTypePattern:TypePatternCollectionMapping* IncludeTypePattern:TypePatternCollectionMapping* + <TypeIgnorePattern> <IncludeEmptyString:boolean> <ExcludeNullNodes:boolean> <Debug:boolean>; TypePatternCollectionMapping ::= <TypeRegex> PatternCollection ; PatternCollection ::= <TokenPattern> <ChildPattern> <RelationPattern> <AttributePattern> <NonterminalAttributePattern> ; StyleInformation ::= <NameMethod:StyleMethod> <BackgroundColorMethod:StyleMethod> <TextColorMethod:StyleMethod>; @@ -9,7 +11,9 @@ StyleInformation ::= <NameMethod:StyleMethod> <BackgroundColorMethod:StyleMethod PrintConfig ::= <Scale:double> <Version> <OrderChildren:boolean> Header* ; Header ::= <Value> ; -DumpNode ::= <Name> <Label> <BackgroundColor> <TextColor> <Object:Object> <Invisible:boolean> DumpChildNode* DumpToken* DumpRelation* /InvisiblePath/ ; +DumpNode ::= DumpChildNode* DumpToken* DumpRelation* + <Name> <Label> <BackgroundColor> <TextColor> <Object:Object> <Invisible:boolean> + /InvisiblePath/ ; InnerDumpNode ; rel InnerDumpNode.DumpNode <-> DumpNode.ContainerOfInner ; diff --git a/dumpAst/src/main/jastadd/GenerationBackend.jadd b/dumpAst/src/main/jastadd/GenerationBackend.jadd index 682f5285200adfa9bb281dc0c48e1632f7d52aab..beeb183de52a4adccbdb8413309b16c418b55511 100644 --- a/dumpAst/src/main/jastadd/GenerationBackend.jadd +++ b/dumpAst/src/main/jastadd/GenerationBackend.jadd @@ -26,11 +26,23 @@ aspect GenerationBackend { } protected DumpNode DumpAst.transform(TransformationTransferInformation tti, Object obj, Source source) throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException { - if (obj == null) { + return transform(tti, obj, source, false); + } + protected DumpNode DumpAst.transform( + TransformationTransferInformation tti, Object obj, Source source, boolean allowNullObj) + throws java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchMethodException { + if (obj == null && !allowNullObj) { return null; } - DumpNode node = tti.transformed.get(obj); - String objClassName = obj.getClass().getSimpleName(); + DumpNode node; + String objClassName; + if (obj == null) { + node = null; + objClassName = "null"; + } else { + node = tti.transformed.get(obj); + objClassName = obj.getClass().getSimpleName(); + } if (node != null) { if (source == Source.RELATION) { return node; @@ -47,9 +59,7 @@ aspect GenerationBackend { if (!node.isAstNode()) { return node; } - node.setLabel(getBuildConfig().getStyleInformation().getNameMethod().get(obj)); - node.setBackgroundColor(getBuildConfig().getStyleInformation().getBackgroundColorMethod().get(obj)); - node.setTextColor(getBuildConfig().getStyleInformation().getTextColorMethod().get(obj)); + applyStyle(node); // do not process node further if coming from a relation if (source == Source.RELATION) { tti.relationTargetsUnprocessed.put(node, true); @@ -58,13 +68,17 @@ aspect GenerationBackend { if (source == Source.INVISIBLE_PARENT || !isTypeEnabled(objClassName)) { node.setInvisible(true); } + if (obj == null) { + // for a null object, we do not need any further analysis + return node; + } final ClassAnalysisResult car = analyzeClass(obj.getClass()); // -- singleChild -- for (SingleChildMethod singleChildMethod : car.singleChildMethods()) { Object target = singleChildMethod.getMethod().invoke(obj); String childName = singleChildMethod.getName(); - DumpNode targetNode = transform(tti, target, nextSource(source, !isChildEnabled(objClassName, childName))); - if (target != null && targetNode != null) { + DumpNode targetNode = transform(tti, target, nextSource(source, !isChildEnabled(objClassName, childName)), !getBuildConfig().getExcludeNullNodes()); + if (targetNode != null) { DumpNormalChildNode normalChild = new DumpNormalChildNode(); normalChild.setName(childName); normalChild.setDumpNode(targetNode); @@ -143,6 +157,13 @@ aspect GenerationBackend { return node; } + private void DumpAst.applyStyle(DumpNode node) { + Object obj = node.getObject(); + node.setLabel(getBuildConfig().getStyleInformation().getNameMethod().get(obj)); + node.setBackgroundColor(getBuildConfig().getStyleInformation().getBackgroundColorMethod().get(obj)); + node.setTextColor(getBuildConfig().getStyleInformation().getTextColorMethod().get(obj)); + } + private Source DumpAst.nextSource(Source currentSource, boolean shouldBeInvisible) { return currentSource == Source.INVISIBLE_PARENT ? Source.INVISIBLE_PARENT : (shouldBeInvisible ? Source.INVISIBLE_PARENT : Source.PARENT); @@ -415,6 +436,10 @@ aspect GenerationBackend { // --- isAstNode --- syn boolean DumpNode.isAstNode() { + if (getObject() == null) { + // this is only possible for normal child nodes, and they are ast nodes + return true; + } Class<?> clazz = getObject().getClass(); for (java.lang.reflect.Method method : clazz.getMethods()) { if ("init$Children".equals(method.getName()) && method.getParameterCount() == 0) { @@ -462,7 +487,6 @@ aspect GenerationBackend { } } - // --- myChildren --- syn java.util.List<DumpNode> DumpNode.myChildren() { java.util.List<DumpNode> result = new java.util.ArrayList<>(); @@ -579,7 +603,7 @@ aspect GenerationBackend { static StyleInformation StyleInformation.createDefault() { StyleInformation result = new StyleInformation(); - result.setNameMethod(n -> n.getClass().getSimpleName() + "@" + Integer.toHexString(n.hashCode())); + result.setNameMethod(n -> n == null ? "null" : n.getClass().getSimpleName() + "@" + Integer.toHexString(n.hashCode())); result.setBackgroundColorMethod(n -> ""); result.setTextColorMethod(n -> ""); return result; diff --git a/dumpAst/src/main/jastadd/GenerationFrontend.jadd b/dumpAst/src/main/jastadd/GenerationFrontend.jadd index fb8e735eecde56b53f1d63cd3eb445f92a887bdc..97b622a1ba1d333ad23e44dabb0a8571721cf6ab 100644 --- a/dumpAst/src/main/jastadd/GenerationFrontend.jadd +++ b/dumpAst/src/main/jastadd/GenerationFrontend.jadd @@ -308,6 +308,16 @@ public class DumpBuilder { } } + public DumpBuilder excludeNullNodes() { + buildConfig.setExcludeNullNodes(true); + return this; + } + + public DumpBuilder includeNullNodes() { + buildConfig.setExcludeNullNodes(false); + return this; + } + public DumpBuilder customPreamble(String option) { printConfig.addHeader(new Header(option)); return this; diff --git a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestDumperMain.java b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestDumperMain.java index d759fbf707a38eeb398e936edf0d0e6ddedeec4a..d7b14047c0b144339dead510f89f8ea510e60464 100644 --- a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestDumperMain.java +++ b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestDumperMain.java @@ -35,7 +35,7 @@ public class TestDumperMain { builder.includeAttributes("simpleAttr") .orderChildren() .includeNonterminalAttributes("getCalculated") - .setNameMethod(n -> n.getClass().getSimpleName()); + .setNameMethod(n -> n == null ? "null" : n.getClass().getSimpleName()); System.out.println(">> PlantUml"); DumpAst dumpAst = builder.build(); diff --git a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java index c196d879305322e9d24fc329aa8d5147cf69e29b..1ccba6cc518d0ec487b937f0efd81068013ba2af 100644 --- a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java +++ b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestSimple.java @@ -31,9 +31,9 @@ public class TestSimple { @Test void testChildlessNonterminal() { Root root = createRoot(createA(A_NAME, a -> a.setD(new D())), null); List<DumpNode> nodes = TestUtils.dumpModel(root); - Optional<DumpNode> optionalCDumpNode = nodes.stream().filter(n -> n.getObject() instanceof D).findFirst(); - assertTrue(optionalCDumpNode.isPresent()); - assertTrue(optionalCDumpNode.get().isAstNode()); + Optional<DumpNode> actualD = nodes.stream().filter(n -> n.getObject() instanceof D).findFirst(); + assertTrue(actualD.isPresent()); + assertTrue(actualD.get().isAstNode()); } @Test @@ -49,6 +49,22 @@ public class TestSimple { assertThatMapOf(normalChildren(actualRoot)).containsExactlyInAnyOrder(tuple("A", A_NAME)); } + @Test + public void testOneNormalChildIncludeNullNodes() { + Root root = createRoot(createA(A_NAME), null); + + List<DumpNode> nodes = TestUtils.dumpModel(root, DumpBuilder::includeNullNodes); + assertThat(nodes).flatExtracting(NAME_EXTRACTOR).containsExactlyInAnyOrder( + ROOT_NAME, A_NAME, null, null, null, null); + DumpNode actualRoot = TestUtils.findByName(nodes, ROOT_NAME); + assertEquals(1, actualRoot.getNumDumpToken()); + assertEquals(2, actualRoot.getNumDumpChildNode()); + assertEquals(0, actualRoot.getNumDumpRelation()); + + DumpNode actualA = TestUtils.findByName(nodes, A_NAME); + assertEquals(3, actualA.getNumDumpChildNode()); + } + @Test public void testListChildren() { Root root = createRoot(null, null, createB(B1_NAME), createB(B2_NAME), createB(B3_NAME)); diff --git a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java index dafc29d8b58b71679d806ed3e70db467b10a9de5..10a0859b6dec99ff2b7797c0ea382c774722bdfd 100644 --- a/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java +++ b/testDumper/src/test/java/de/tudresden/inf/st/jastadd/testDumper/TestUtils.java @@ -156,6 +156,7 @@ public class TestUtils { public static List<DumpNode> dumpModel(Object target, Consumer<DumpBuilder> options) { ExposingDumpBuilder builder = new ExposingDumpBuilder(target); + builder.excludeNullNodes(); options.accept(builder); DumpAst dumpAst = builder.build(); // let mustache run, but ignore result