diff --git a/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml b/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml index 73e9359197..2317da834e 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Features/Decompiler/src/main/help/help/TOC_Source.xml @@ -68,14 +68,13 @@ text="Program Annotations Affecting the Decompiler" target="help/topics/DecompilePlugin/DecompilerAnnotations.html"> - - + - + - + diff --git a/Ghidra/Framework/Help/src/main/java/help/OverlayHelpTree.java b/Ghidra/Framework/Help/src/main/java/help/OverlayHelpTree.java index 2885308903..44dc98817f 100644 --- a/Ghidra/Framework/Help/src/main/java/help/OverlayHelpTree.java +++ b/Ghidra/Framework/Help/src/main/java/help/OverlayHelpTree.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +15,16 @@ */ package help; -import help.validator.LinkDatabase; -import help.validator.model.*; - import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import help.validator.LinkDatabase; +import help.validator.model.*; + /** - * A class that will take in a group of help directories and create a tree of + * A class that will take in a group of help directories and create a tree of * help Table of Contents (TOC) items. Ideally, this tree can be used to create a single * TOC document, or individual TOC documents, one for each help directory (this allows * for better modularity). @@ -33,7 +32,6 @@ import java.util.*; * We call this class an overlay tree to drive home the idea that each * help directory's TOC data is put into the tree, with any duplicate paths overlayed * on top of those from other help directories. - * */ public class OverlayHelpTree { @@ -44,10 +42,13 @@ public class OverlayHelpTree { public OverlayHelpTree(TOCItemProvider tocItemProvider, LinkDatabase linkDatabase) { this.linkDatabase = linkDatabase; - for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping().values()) { + for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping() + .values()) { addExternalTOCItem(external); } - for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping().values()) { + + for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping() + .values()) { addSourceTOCItem(definition); } } @@ -61,7 +62,7 @@ public class OverlayHelpTree { // // We will have equivalent items in the generated TOC files, as that is how we - // enable merging of TOC files in the JavaHelp system. So, multiple roots are + // enable merging of TOC files in the JavaHelp system. So, multiple roots are // OK. // @@ -86,7 +87,7 @@ public class OverlayHelpTree { if (parentID == null) { // must be the root, since the root has no parent if (rootItem != null) { - // when loading source items, it is only an error when there is more than one + // when loading source items, it is only an error when there is more than one // root item defined *in the same file* if (rootItem.getSourceFile().equals(item.getSourceFile())) { throw new IllegalArgumentException( @@ -125,7 +126,7 @@ public class OverlayHelpTree { PrintWriter writer = new PrintWriter(new BufferedWriter(osw)); printTreeForID(writer, sourceFileID); - // debug + // debug // writer = new PrintWriter(System.err); // printTreeForID(writer, sourceFileID); } @@ -167,12 +168,6 @@ public class OverlayHelpTree { return false; } -// TODO delete this debug -// Set>> entrySet = parentToChildrenMap.entrySet(); -// for (Entry> entry : entrySet) { -// System.out.println(entry.getKey() + " -> " + entry.getValue()); -// } - OverlayNode newRootNode = new OverlayNode(null, rootItem); buildChildren(newRootNode); @@ -255,6 +250,7 @@ public class OverlayHelpTree { } } + // TODO LOOKIE private static final Comparator CHILD_SORT_COMPARATOR = new Comparator() { @Override @@ -266,7 +262,7 @@ public class OverlayHelpTree { return o1.getSortPreference().compareTo(o2.getSortPreference()); } - // if sort preference is the same, then sort alphabetically by display name + // if sort preference is the same, then sort alphabetically by display name String text1 = o1.getTextAttribute(); String text2 = o2.getTextAttribute(); @@ -283,7 +279,16 @@ public class OverlayHelpTree { return -1; } - return text1.compareTo(text2); + int result = text1.compareTo(text2); + if (result != 0) { + return result; + } + + // At this point we have 2 nodes that have the same text attribute as children of + // a tag. This is OK, as we use text only for sorting, but not for the + // display text. Use the ID as a tie-breaker for sorting, which should provide + // sorting consistency. + return o1.getIDAttribute().compareTo(o2.getIDAttribute()); // ID should not be null } }; } diff --git a/Ghidra/Framework/Help/src/main/java/help/validator/model/TOCItem.java b/Ghidra/Framework/Help/src/main/java/help/validator/model/TOCItem.java index 64b024e7ee..13b0cc7ca7 100644 --- a/Ghidra/Framework/Help/src/main/java/help/validator/model/TOCItem.java +++ b/Ghidra/Framework/Help/src/main/java/help/validator/model/TOCItem.java @@ -15,27 +15,27 @@ */ package help.validator.model; -import help.validator.LinkDatabase; - import java.io.PrintWriter; import java.nio.file.Path; import java.util.*; +import help.validator.LinkDatabase; + /** * A Table of Contents entry, which is represented in the help output as an xml tag. */ public abstract class TOCItem { //@formatter:off - protected static final String[] INDENTS = { - "", - "\t", - "\t\t", - "\t\t\t", + protected static final String[] INDENTS = { + "", + "\t", + "\t\t", + "\t\t\t", "\t\t\t\t", - "\t\t\t\t\t", - "\t\t\t\t\t\t", - "\t\t\t\t\t\t\t", + "\t\t\t\t\t", + "\t\t\t\t\t\t", + "\t\t\t\t\t\t\t", "\t\t\t\t\t\t\t\t" }; //@formatter:on @@ -63,7 +63,8 @@ public abstract class TOCItem { String sortPreference, int lineNumber) { this.parentItem = parentItem; this.sourceFile = sourceFile; - this.IDAttribute = ID; + this.IDAttribute = Objects.requireNonNull(ID, + "TOC Tag missing 'id' attribute: " + sourceFile + ":" + lineNumber); this.textAttribute = text; this.targetAttribute = target; @@ -157,82 +158,108 @@ public abstract class TOCItem { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } TOCItem other = (TOCItem) obj; if (IDAttribute == null) { - if (other.IDAttribute != null) + if (other.IDAttribute != null) { return false; + } } - else if (!IDAttribute.equals(other.IDAttribute)) + else if (!IDAttribute.equals(other.IDAttribute)) { return false; + } if (sortPreference == null) { - if (other.sortPreference != null) + if (other.sortPreference != null) { return false; + } } - else if (!sortPreference.equals(other.sortPreference)) + else if (!sortPreference.equals(other.sortPreference)) { return false; + } if (sourceFile == null) { - if (other.sourceFile != null) + if (other.sourceFile != null) { return false; + } } - else if (!sourceFile.equals(other.sourceFile)) + else if (!sourceFile.equals(other.sourceFile)) { return false; + } if (targetAttribute == null) { - if (other.targetAttribute != null) + if (other.targetAttribute != null) { return false; + } } - else if (!targetAttribute.equals(other.targetAttribute)) + else if (!targetAttribute.equals(other.targetAttribute)) { return false; + } if (textAttribute == null) { - if (other.textAttribute != null) + if (other.textAttribute != null) { return false; + } } - else if (!textAttribute.equals(other.textAttribute)) + else if (!textAttribute.equals(other.textAttribute)) { return false; + } return true; } /** * True if the two items are the same, except that they come from a different source file. + * @param other the other item + * @return true if equivalent */ public boolean isEquivalent(TOCItem other) { - if (this == other) + if (this == other) { return true; - if (other == null) + } + if (other == null) { return false; - if (getClass() != other.getClass()) + } + if (getClass() != other.getClass()) { return false; + } if (IDAttribute == null) { - if (other.IDAttribute != null) + if (other.IDAttribute != null) { return false; + } } - else if (!IDAttribute.equals(other.IDAttribute)) + else if (!IDAttribute.equals(other.IDAttribute)) { return false; + } if (sortPreference == null) { - if (other.sortPreference != null) + if (other.sortPreference != null) { return false; + } } - else if (!sortPreference.equals(other.sortPreference)) + else if (!sortPreference.equals(other.sortPreference)) { return false; + } if (targetAttribute == null) { - if (other.targetAttribute != null) + if (other.targetAttribute != null) { return false; + } } - else if (!targetAttribute.equals(other.targetAttribute)) + else if (!targetAttribute.equals(other.targetAttribute)) { return false; + } if (textAttribute == null) { - if (other.textAttribute != null) + if (other.textAttribute != null) { return false; + } } - else if (!textAttribute.equals(other.textAttribute)) + else if (!textAttribute.equals(other.textAttribute)) { return false; + } return true; } @@ -253,14 +280,15 @@ public abstract class TOCItem { } } - public String generateTOCItemTag(LinkDatabase linkDatabase, boolean isInlineTag, int indentLevel) { + public String generateTOCItemTag(LinkDatabase linkDatabase, boolean isInlineTag, + int indentLevel) { StringBuilder buildy = new StringBuilder(); buildy.append(INDENTS[indentLevel]); buildy.append('<').append(TOC_TAG_NAME).append(' '); // text attribute // NOTE: we do not put our display text in this attribute. This is because JavaHelp uses - // this attribute for sorting. We want to separate sorting from display, so we + // this attribute for sorting. We want to separate sorting from display, so we // manipulate the JavaHelp software by setting this attribute the desired sort value. // We have overridden JavaHelp to use a custom renderer that will paint the display // text with the attribute we set below. diff --git a/Ghidra/Framework/Help/src/test/java/help/OverlayHelpTreeTest.java b/Ghidra/Framework/Help/src/test/java/help/OverlayHelpTreeTest.java index 20b41f9c57..1ba5143240 100644 --- a/Ghidra/Framework/Help/src/test/java/help/OverlayHelpTreeTest.java +++ b/Ghidra/Framework/Help/src/test/java/help/OverlayHelpTreeTest.java @@ -15,7 +15,7 @@ */ package help; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.io.PrintWriter; import java.io.StringWriter; @@ -36,31 +36,31 @@ import help.validator.model.*; public class OverlayHelpTreeTest { @Test - public void testSourceTOCFileThatDependsUponPreBuiltHelp() { + public void testSourceTOCFileThatDependsUponPreBuiltHelp() { // - // We want to make sure the overlay tree will properly resolve help TOC items being + // We want to make sure the overlay tree will properly resolve help TOC items being // built from TOC_Source.xml files when that file uses items that are defined // in a help that lives inside of a pre-built jar file. // /* - + Example makeup we will create: - + PreBuild_TOC.xml - + - - - TOC_Source.xml - + + + TOC_Source.xml + - + */ TOCItemExternal root = externalItem("root"); @@ -68,12 +68,12 @@ public class OverlayHelpTreeTest { Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml"); String root_ID = root.getIDAttribute(); - TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile); + TOCItemReference root_ref = tocref(root_ID, tocSourceFile); String child_1_ID = child_1.getIDAttribute(); - TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile); + TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, tocSourceFile); - TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile); + TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", tocSourceFile); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); tocProvider.addExternal(root); @@ -89,41 +89,92 @@ public class OverlayHelpTreeTest { } @Test - public void testSourceTOCFileThatDependsUponPreBuiltHelp_MultiplePreBuiltInputs() { + public void testSourceTOCFileThatDependsAnotherTOCSourceFile() { + + /* + + The first source file defines attributes that the second file references. + + Example makeup we will create: + + TOC_Source.xml + + + + + + + Another TOC_Source.xml + + + + + + + + */ + + Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml"); + TOCItemDefinition root = tocdef("root", toc_1); + TOCItemDefinition child_1 = tocdef(root, "child_1", toc_1); + + Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml"); + String root_ID = root.getIDAttribute(); + String child_1_ID = child_1.getIDAttribute(); + + TOCItemReference root_ref = tocref(root_ID, toc_2); + TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, toc_2); + TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", toc_2); + + TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); + tocProvider.addDefinition(root); + tocProvider.addDefinition(child_1); + tocProvider.addDefinition(child_2);// in the second TOC file + + TOCSpyWriter spy = printOverlayTree(tocProvider, toc_2); + + assertNodeCount(spy, 3); + assertOrder(spy, 1, root); + assertOrder(spy, 2, child_1); + assertOrder(spy, 3, child_2); + } + + @Test + public void testSourceTOCFileThatDependsUponPreBuiltHelp_MultiplePreBuiltInputs() { // - // We want to make sure the overlay tree will properly resolve help TOC items being + // We want to make sure the overlay tree will properly resolve help TOC items being // built from TOC_Source.xml files when that file uses items that are defined // in a help that lives inside of multiple pre-built jar files. // /* - + Example makeup we will create: - + PreBuild_TOC.xml - + - + Another PreBuild_TOC.xml - + - - - TOC_Source.xml - + + + TOC_Source.xml + - + */ TOCItemExternal root_a = externalItem("root"); @@ -131,18 +182,17 @@ public class OverlayHelpTreeTest { TOCItemExternal prebuilt_a_child = externalItem(child_1_a, "prebuilt_a_child"); // note: same ID values, since they represent the same nodes, but from different TOC files - TOCItemExternal root_b = externalItemAlt(null, "root"); - TOCItemExternal child_1_b = externalItemAlt(root_b, "child_1"); - TOCItemExternal prebuilt_b_child = externalItemAlt(child_1_b, "prebuilt_b_child"); + TOCItemExternal root_b = externalItem(null, "root"); + TOCItemExternal child_1_b = externalItem(root_b, "child_1"); + TOCItemExternal prebuilt_b_child = externalItem(child_1_b, "prebuilt_b_child"); Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml"); String root_ID = root_a.getIDAttribute(); - TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile); + TOCItemReference root_ref = tocref(root_ID, tocSourceFile); String child_1_ID = child_1_a.getIDAttribute(); - TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile); - - TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile); + TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, tocSourceFile); + TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", tocSourceFile); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); tocProvider.addExternal(root_a); @@ -160,60 +210,85 @@ public class OverlayHelpTreeTest { assertOrder(spy, 2, child_1_a);// could also be child_1_b, same ID assertOrder(spy, 3, child_2); - // note: prebuilt_a_child and prebuilt_b_child don't get output, since they do not have + // note: prebuilt_a_child and prebuilt_b_child don't get output, since they do not have // the same TOC file ID as the help file being processed (in other words, they don't // live in the TOC_Source.xml being processes, so they are not part of the output). } @Test - public void testSourceTOCFileThatDependsAnotherTOCSourceFile() { + public void testSourceTOCFileThatHasNodeWithSameTextAttributeAsOneOfItsExternalModluleDependencies() { /* - - The first source file defines attributes that the second file references. - + + The first source file defines attributes that the second file references. Both files + will have multiple nodes that coincidentally share 'text' attribute values. + + Note: the 'id' attributes have to be unique; the 'text' attributes do not have to be unique + Example makeup we will create: - TOC_Source.xml - - - - - - - Another TOC_Source.xml - + PreBuild_TOC.xml + + + + + + Another PreBuild_TOC.xml + + + + + + + + Another TOC_Source.xml + - - + + + */ - Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml"); - TOCItemDefinition root = definitionItem("root", toc_1); - TOCItemDefinition child_1 = definitionItem(root, "child_1", toc_1); + TOCItemExternal root_a = externalItem("root"); + TOCItemExternal child_1_1 = externalItem(root_a, "child_1_1", "Child 1"); - Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml"); - String root_ID = root.getIDAttribute(); - String child_1_ID = child_1.getIDAttribute(); + // note: same ID values, since they represent the same nodes, but from different TOC files + TOCItemExternal root_b = externalItem(null, "root"); + TOCItemExternal child_2_1 = externalItem(root_b, "child_2_1", "Child 1"); + TOCItemExternal child_2_2 = externalItem(root_b, "child_2_2", "Child 2"); - TOCItemReference root_ref = referenceItem(root_ID, toc_2); - TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, toc_2); - TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", toc_2); + Path toc = Paths.get("/fake/path_2/TOC_Source.xml"); + String root_ID = root_a.getIDAttribute(); + String child_1_ID = child_1_1.getIDAttribute(); + + TOCItemReference root_ref = tocref(root_ID, toc); + TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, toc); + TOCItemDefinition child_2_1a = tocdef(child_1_ref, "child_2_1a", "Child 1a", toc); + TOCItemDefinition child_3_2 = tocdef(root_ref, "child_3_2", "Child 2", toc); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); - tocProvider.addDefinition(root); - tocProvider.addDefinition(child_1); - tocProvider.addDefinition(child_2);// in the second TOC file + tocProvider.addExternal(root_a); + tocProvider.addExternal(child_1_1); + tocProvider.addExternal(root_b); + tocProvider.addExternal(child_2_1); + tocProvider.addExternal(child_2_2); + tocProvider.addDefinition(child_2_1a); // in the first external TOC file + tocProvider.addDefinition(child_3_2); - TOCSpyWriter spy = printOverlayTree(tocProvider, toc_2); + TOCSpyWriter spy = printOverlayTree(tocProvider, toc); - assertNodeCount(spy, 3); - assertOrder(spy, 1, root); - assertOrder(spy, 2, child_1); - assertOrder(spy, 3, child_2); + assertNodeCount(spy, 4); + assertOrder(spy, 1, root_a); + assertOrder(spy, 2, child_1_1); + assertOrder(spy, 3, child_2_1a); + assertOrder(spy, 4, child_3_2); + + // note: prebuilt_a_child and prebuilt_b_child don't get output, since they do not have + // the same TOC file ID as the help file being processed (in other words, they don't + // live in the TOC_Source.xml being processes, so they are not part of the output). } //================================================================================================== @@ -225,7 +300,7 @@ public class OverlayHelpTreeTest { // // Create a test version of the LinkDatabase for the overlay tree, with test versions of // it's required TOC input file and HelpModuleLocation - // + // GhidraTOCFileDummy toc = new GhidraTOCFileDummy(tocFile); OverlayHelpModuleLocationTestStub location = new OverlayHelpModuleLocationTestStub(toc); LinkDatabaseTestStub db = new LinkDatabaseTestStub(location); @@ -242,22 +317,26 @@ public class OverlayHelpTreeTest { return spy; } - private TOCItemDefinition definitionItem(String ID, Path tocSourceFile) { - return definitionItem(null, ID, tocSourceFile); + private TOCItemDefinition tocdef(String ID, Path tocSourceFile) { + return tocdef(null, ID, tocSourceFile); } - private TOCItemDefinition definitionItem(TOCItem parent, String ID, Path tocSourceFile) { + private TOCItemDefinition tocdef(TOCItem parent, String ID, Path tocSourceFile) { + return tocdef(parent, ID, ID, tocSourceFile); + } + + private TOCItemDefinition tocdef(TOCItem parent, String ID, String text, Path tocSourceFile) { String target = "fake"; String sort = ""; int line = 1; - return new TOCItemDefinition(parent, tocSourceFile, ID, ID, target, sort, line); + return new TOCItemDefinition(parent, tocSourceFile, ID, text, target, sort, line); } - private TOCItemReference referenceItem(String referenceID, Path tocSourceFile) { - return referenceItem(null, referenceID, tocSourceFile); + private TOCItemReference tocref(String referenceID, Path tocSourceFile) { + return tocref(null, referenceID, tocSourceFile); } - private TOCItemReference referenceItem(TOCItem parent, String referenceID, Path tocSourceFile) { + private TOCItemReference tocref(TOCItem parent, String referenceID, Path tocSourceFile) { return new TOCItemReference(parent, tocSourceFile, referenceID, 1); } @@ -266,24 +345,20 @@ public class OverlayHelpTreeTest { } private TOCItemExternal externalItem(TOCItem parent, String ID) { - Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml"); - String target = "fake"; - String sort = ""; - int line = 1; - return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line); + return externalItem(parent, ID, ID); } - private TOCItemExternal externalItemAlt(TOCItem parent, String ID) { + private TOCItemExternal externalItem(TOCItem parent, String ID, String text) { Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml"); String target = "fake"; String sort = ""; int line = 1; - return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line); + return new TOCItemExternal(parent, tocFile, ID, text, target, sort, line); } private void assertOrder(TOCSpyWriter spy, int ordinal, TOCItem item) { String ID = spy.getItem(ordinal - 1 /* make an index */); - assertEquals("Did not find TOC item at expected index: " + ordinal, item.getIDAttribute(), + assertEquals("Did not find TOC item at expected ordinal: " + ordinal, item.getIDAttribute(), ID); } @@ -324,7 +399,7 @@ public class OverlayHelpTreeTest { private void storeDisplayAttribute(String s) { // create a pattern to pull out the display string - Pattern p = Pattern.compile(".*display=\"(.*)\" toc_id.*"); + Pattern p = Pattern.compile(".*toc_id=\"(.*)\".*"); Matcher matcher = p.matcher(s.trim()); if (!matcher.matches()) { @@ -347,8 +422,8 @@ public class OverlayHelpTreeTest { Map definitions = new HashMap<>(); void addExternal(TOCItemExternal item) { - String displayText = item.getIDAttribute(); - externals.put(displayText, item); + String ID = item.getIDAttribute(); + externals.put(ID, item); } void addDefinition(TOCItemDefinition item) {