GP-155 - Help - fixed intermittent help build issue caused by map keys

using display text instead of the unique help item ID

Closes #2443
This commit is contained in:
dragonmacher 2020-11-16 19:17:08 -05:00
parent ddf6db0eec
commit 7940f96c8c
5 changed files with 85 additions and 65 deletions

View file

@ -42,13 +42,11 @@ public class OverlayHelpTree {
public OverlayHelpTree(TOCItemProvider tocItemProvider, LinkDatabase linkDatabase) { public OverlayHelpTree(TOCItemProvider tocItemProvider, LinkDatabase linkDatabase) {
this.linkDatabase = linkDatabase; this.linkDatabase = linkDatabase;
for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping() for (TOCItemExternal external : tocItemProvider.getExternalTocItemsById().values()) {
.values()) {
addExternalTOCItem(external); addExternalTOCItem(external);
} }
for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping() for (TOCItemDefinition definition : tocItemProvider.getTocDefinitionsByID().values()) {
.values()) {
addSourceTOCItem(definition); addSourceTOCItem(definition);
} }
} }
@ -171,8 +169,16 @@ public class OverlayHelpTree {
OverlayNode newRootNode = new OverlayNode(null, rootItem); OverlayNode newRootNode = new OverlayNode(null, rootItem);
buildChildren(newRootNode); buildChildren(newRootNode);
//
// The parent to children map is cleared as nodes are created. The map is populated by
// adding any references to the 'parent' key as they are loaded from the help files.
// As we build nodes, starting at the root, will will create child nodes for those that
// reference the 'parent' key. If the map is empty, then it means we never built a
// node for the 'parent' key, which means we never found a help file containing the
// definition for that key.
//
if (!parentToChildrenMap.isEmpty()) { if (!parentToChildrenMap.isEmpty()) {
throw new RuntimeException("Unresolved definitions in tree!"); throw new RuntimeException("Unresolved definitions in tree! - " + parentToChildrenMap);
} }
rootNode = newRootNode; rootNode = newRootNode;
return true; return true;

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,18 +15,25 @@
*/ */
package help; package help;
import java.util.Map;
import help.validator.model.TOCItemDefinition; import help.validator.model.TOCItemDefinition;
import help.validator.model.TOCItemExternal; import help.validator.model.TOCItemExternal;
import java.util.Map;
/** /**
* An interface that allows us to perform dependency injection in the testing * An interface that allows us to perform dependency injection in the testing environment
* environment.
*/ */
public interface TOCItemProvider { public interface TOCItemProvider {
public Map<String, TOCItemExternal> getTOCItemExternalsByDisplayMapping(); /**
* Returns all external TOC items referenced by this provider
* @return the items
*/
public Map<String, TOCItemExternal> getExternalTocItemsById();
public Map<String, TOCItemDefinition> getTOCItemDefinitionsByIDMapping(); /**
* Returns all TOC items defined by this provider
* @return the items
*/
public Map<String, TOCItemDefinition> getTocDefinitionsByID();
} }

View file

@ -108,7 +108,7 @@ public class LinkDatabase {
} }
private void collectTOCItemDefinitions(TOCItemProvider tocProvider) { private void collectTOCItemDefinitions(TOCItemProvider tocProvider) {
Map<String, TOCItemDefinition> map = tocProvider.getTOCItemDefinitionsByIDMapping(); Map<String, TOCItemDefinition> map = tocProvider.getTocDefinitionsByID();
Set<Entry<String, TOCItemDefinition>> entrySet = map.entrySet(); Set<Entry<String, TOCItemDefinition>> entrySet = map.entrySet();
for (Entry<String, TOCItemDefinition> entry : entrySet) { for (Entry<String, TOCItemDefinition> entry : entrySet) {
String key = entry.getKey(); String key = entry.getKey();
@ -124,7 +124,7 @@ public class LinkDatabase {
} }
private void collectTOCItemExternals(TOCItemProvider tocProvider) { private void collectTOCItemExternals(TOCItemProvider tocProvider) {
Map<String, TOCItemExternal> map = tocProvider.getTOCItemExternalsByDisplayMapping(); Map<String, TOCItemExternal> map = tocProvider.getExternalTocItemsById();
for (TOCItemExternal tocItem : map.values()) { for (TOCItemExternal tocItem : map.values()) {
mapOfIDsToTOCExternals.put(tocItem.getIDAttribute(), tocItem); mapOfIDsToTOCExternals.put(tocItem.getIDAttribute(), tocItem);
} }

View file

@ -32,22 +32,22 @@ import help.TOCItemProvider;
import help.validator.model.*; import help.validator.model.*;
/** /**
* A class that is meant to hold a single help <b>input</b> directory and 0 or more * A class that is meant to hold a single help <b>input</b> directory and 0 or more
* <b>external, pre-built</b> help sources (i.e., jar file or directory). * <b>external, pre-built</b> help sources (i.e., jar file or directory).
* <p> * <p>
* <pre> * <pre>
* Note * Note
* Note * Note
* Note * Note
* *
* This class is a bit conceptually muddled. Our build system is reflected in this class in that * This class is a bit conceptually muddled. Our build system is reflected in this class in that
* we currently build one help module at a time. Thus, any dependencies of that module being * we currently build one help module at a time. Thus, any dependencies of that module being
* built can be passed into this "collection" at build time. We used to build multiple help * built can be passed into this "collection" at build time. We used to build multiple help
* modules at once, resolving dependencies for all of the input modules after we built each * modules at once, resolving dependencies for all of the input modules after we built each
* module. This class will need to be tweaked in order to go back to a build system with * module. This class will need to be tweaked in order to go back to a build system with
* multiple input builds. * multiple input builds.
* *
* </pre> * </pre>
*/ */
public class HelpModuleCollection implements TOCItemProvider { public class HelpModuleCollection implements TOCItemProvider {
@ -62,6 +62,8 @@ public class HelpModuleCollection implements TOCItemProvider {
/** /**
* Creates a help module collection that contains only a singe help module from a help * Creates a help module collection that contains only a singe help module from a help
* directory, not a pre-built help jar. * directory, not a pre-built help jar.
* @param dir the directory containing help
* @return the help collection
*/ */
public static HelpModuleCollection fromHelpDirectory(File dir) { public static HelpModuleCollection fromHelpDirectory(File dir) {
return new HelpModuleCollection(toHelpLocations(Collections.singleton(dir))); return new HelpModuleCollection(toHelpLocations(Collections.singleton(dir)));
@ -70,6 +72,8 @@ public class HelpModuleCollection implements TOCItemProvider {
/** /**
* Creates a help module collection that assumes zero or more pre-built help jar files and * Creates a help module collection that assumes zero or more pre-built help jar files and
* one help directory that is an input into the help building process. * one help directory that is an input into the help building process.
* @param files the files from which to get help
* @return the help collection
*/ */
public static HelpModuleCollection fromFiles(Collection<File> files) { public static HelpModuleCollection fromFiles(Collection<File> files) {
return new HelpModuleCollection(toHelpLocations(files)); return new HelpModuleCollection(toHelpLocations(files));
@ -78,6 +82,8 @@ public class HelpModuleCollection implements TOCItemProvider {
/** /**
* Creates a help module collection that assumes zero or more pre-built help jar files and * Creates a help module collection that assumes zero or more pre-built help jar files and
* one help directory that is an input into the help building process. * one help directory that is an input into the help building process.
* @param locations the locations from which to get help
* @return the help collection
*/ */
public static HelpModuleCollection fromHelpLocations(Collection<HelpModuleLocation> locations) { public static HelpModuleCollection fromHelpLocations(Collection<HelpModuleLocation> locations) {
return new HelpModuleCollection(locations); return new HelpModuleCollection(locations);
@ -240,7 +246,7 @@ public class HelpModuleCollection implements TOCItemProvider {
} }
@Override @Override
public Map<String, TOCItemDefinition> getTOCItemDefinitionsByIDMapping() { public Map<String, TOCItemDefinition> getTocDefinitionsByID() {
Map<String, TOCItemDefinition> map = new HashMap<>(); Map<String, TOCItemDefinition> map = new HashMap<>();
GhidraTOCFile TOC = inputHelp.getSourceTOCFile(); GhidraTOCFile TOC = inputHelp.getSourceTOCFile();
map.putAll(TOC.getTOCDefinitionByIDMapping()); map.putAll(TOC.getTOCDefinitionByIDMapping());
@ -248,7 +254,7 @@ public class HelpModuleCollection implements TOCItemProvider {
} }
@Override @Override
public Map<String, TOCItemExternal> getTOCItemExternalsByDisplayMapping() { public Map<String, TOCItemExternal> getExternalTocItemsById() {
Map<String, TOCItemExternal> map = new HashMap<>(); Map<String, TOCItemExternal> map = new HashMap<>();
if (externalHelpSets.isEmpty()) { if (externalHelpSets.isEmpty()) {
@ -282,18 +288,17 @@ public class HelpModuleCollection implements TOCItemProvider {
if (parent != null) { if (parent != null) {
CustomTreeItemDecorator dec = (CustomTreeItemDecorator) parent.getUserObject(); CustomTreeItemDecorator dec = (CustomTreeItemDecorator) parent.getUserObject();
if (dec != null) { if (dec != null) {
parentItem = mapByDisplay.get(dec.getDisplayText()); parentItem = mapByDisplay.get(dec.getTocID());
} }
} }
ID targetID = item.getID(); ID targetID = item.getID();
String displayText = item.getDisplayText(); String displayText = item.getDisplayText();
String tocID = item.getTocID(); String tocId = item.getTocID();
String target = targetID == null ? null : targetID.getIDString(); String target = targetID == null ? null : targetID.getIDString();
TOCItemExternal external = new TOCItemExternal(parentItem, tocPath, tocID, displayText, TOCItemExternal external = new TOCItemExternal(parentItem, tocPath, tocId, displayText,
target, item.getName(), -1); target, item.getName(), -1);
mapByDisplay.put(displayText, external); mapByDisplay.put(tocId, external);
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -304,7 +309,10 @@ public class HelpModuleCollection implements TOCItemProvider {
} }
} }
/** Input TOC items are those that we are building for the input help module of this collection */ /**
* Input TOC items are those that we are building for the input help module of this collection
* @return the items
*/
public Collection<TOCItem> getInputTOCItems() { public Collection<TOCItem> getInputTOCItems() {
Collection<TOCItem> items = new ArrayList<>(); Collection<TOCItem> items = new ArrayList<>();
GhidraTOCFile TOC = inputHelp.getSourceTOCFile(); GhidraTOCFile TOC = inputHelp.getSourceTOCFile();

View file

@ -43,24 +43,24 @@ public class OverlayHelpTreeTest {
// in a help <TOCITEM> that lives inside of a pre-built jar file. // in a help <TOCITEM> that lives inside of a pre-built jar file.
// //
/* /*
Example makeup we will create: Example makeup we will create:
PreBuild_TOC.xml PreBuild_TOC.xml
<tocitem id="root" target="fake"> <tocitem id="root" target="fake">
<tocitem id="child_1" target="fake" /> <tocitem id="child_1" target="fake" />
</tocitem> </tocitem>
TOC_Source.xml TOC_Source.xml
<tocref id="root"> <tocref id="root">
<tocref="child_1"> <tocref="child_1">
<tocdef id="child_2" target="fake" /> <tocdef id="child_2" target="fake" />
</tocref> </tocref>
</tocref> </tocref>
*/ */
TOCItemExternal root = externalItem("root"); TOCItemExternal root = externalItem("root");
@ -92,26 +92,26 @@ public class OverlayHelpTreeTest {
public void testSourceTOCFileThatDependsAnotherTOCSourceFile() { public void testSourceTOCFileThatDependsAnotherTOCSourceFile() {
/* /*
The first source file defines attributes that the second file references. The first source file defines attributes that the second file references.
Example makeup we will create: Example makeup we will create:
TOC_Source.xml TOC_Source.xml
<tocdef id="root" target="fake"> <tocdef id="root" target="fake">
<tocdef id="child_1" target="fake" /> <tocdef id="child_1" target="fake" />
</tocdef> </tocdef>
Another TOC_Source.xml Another TOC_Source.xml
<tocref id="root"> <tocref id="root">
<tocref="child_1"> <tocref="child_1">
<tocdef id="child_2" target="fake" /> <tocdef id="child_2" target="fake" />
</tocref> </tocref>
</tocref> </tocref>
*/ */
Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml"); Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml");
@ -147,34 +147,34 @@ public class OverlayHelpTreeTest {
// in a help <TOCITEM> that lives inside of multiple pre-built jar files. // in a help <TOCITEM> that lives inside of multiple pre-built jar files.
// //
/* /*
Example makeup we will create: Example makeup we will create:
PreBuild_TOC.xml PreBuild_TOC.xml
<tocitem id="root" target="fake"> <tocitem id="root" target="fake">
<tocitem id="child_1" target="fake"> <tocitem id="child_1" target="fake">
<tocitem="prebuilt_a_child" target="fake" /> <tocitem="prebuilt_a_child" target="fake" />
</tocitem> </tocitem>
</tocitem> </tocitem>
Another PreBuild_TOC.xml Another PreBuild_TOC.xml
<tocitem id="root" target="fake"> <tocitem id="root" target="fake">
<tocitem id="child_1" target="fake"> <tocitem id="child_1" target="fake">
<tocitem="prebuilt_b_child" target="fake" /> <tocitem="prebuilt_b_child" target="fake" />
</tocitem> </tocitem>
</tocitem> </tocitem>
TOC_Source.xml TOC_Source.xml
<tocref id="root"> <tocref id="root">
<tocref="child_1"> <tocref="child_1">
<tocdef id="child_2" target="fake" /> <tocdef id="child_2" target="fake" />
</tocref> </tocref>
</tocref> </tocref>
*/ */
TOCItemExternal root_a = externalItem("root"); TOCItemExternal root_a = externalItem("root");
@ -219,37 +219,37 @@ public class OverlayHelpTreeTest {
public void testSourceTOCFileThatHasNodeWithSameTextAttributeAsOneOfItsExternalModluleDependencies() { public void testSourceTOCFileThatHasNodeWithSameTextAttributeAsOneOfItsExternalModluleDependencies() {
/* /*
The first source file defines attributes that the second file references. Both files The first source file defines attributes that the second file references. Both files
will have multiple nodes that coincidentally share 'text' attribute values. 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 Note: the 'id' attributes have to be unique; the 'text' attributes do not have to be unique
Example makeup we will create: Example makeup we will create:
PreBuild_TOC.xml PreBuild_TOC.xml
<tocitem id="root" target="fake"> <tocitem id="root" target="fake">
<tocitem id="child_1_1" text="Child 1" target="fake" /> <tocitem id="child_1_1" text="Child 1" target="fake" />
</tocitem> </tocitem>
Another PreBuild_TOC.xml Another PreBuild_TOC.xml
<tocitem id="root" target="fake"> <tocitem id="root" target="fake">
<tocitem id="child_2_1" text=Child 1" target="fake" /> <tocitem id="child_2_1" text=Child 1" target="fake" />
<tocitem id="child_2_2" text=Child 2" target="fake" /> <tocitem id="child_2_2" text=Child 2" target="fake" />
</tocitem> </tocitem>
Another TOC_Source.xml Another TOC_Source.xml
<tocref id="root"> <tocref id="root">
<tocref="child_1_1"> <tocref="child_1_1">
<tocdef id="child_2_1a" text="Child 1a" target="fake" /> <tocdef id="child_2_1a" text="Child 1a" target="fake" />
</tocref> </tocref>
<tocdef id="child_3_2" text="Child 2" target="fake" /> <tocdef id="child_3_2" text="Child 2" target="fake" />
</tocref> </tocref>
*/ */
TOCItemExternal root_a = externalItem("root"); TOCItemExternal root_a = externalItem("root");
@ -432,12 +432,12 @@ public class OverlayHelpTreeTest {
} }
@Override @Override
public Map<String, TOCItemExternal> getTOCItemExternalsByDisplayMapping() { public Map<String, TOCItemExternal> getExternalTocItemsById() {
return externals; return externals;
} }
@Override @Override
public Map<String, TOCItemDefinition> getTOCItemDefinitionsByIDMapping() { public Map<String, TOCItemDefinition> getTocDefinitionsByID() {
return definitions; return definitions;
} }