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

@ -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

@ -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;
} }