GP-155 - Help - fixed intermittent build failure due to different nodes

with shared 'text' attribute values
This commit is contained in:
dragonmacher 2020-11-02 19:25:10 -05:00
parent 798c3abf42
commit 68ef3a22c5
4 changed files with 252 additions and 145 deletions

View file

@ -68,14 +68,13 @@
text="Program Annotations Affecting the Decompiler" text="Program Annotations Affecting the Decompiler"
target="help/topics/DecompilePlugin/DecompilerAnnotations.html"> target="help/topics/DecompilePlugin/DecompilerAnnotations.html">
<tocdef id="AnnoteFunctionBody" sortgroup="a" text="Machine Instructions" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteFunctionBody"/> <tocdef id="AnnoteFunctionBody" sortgroup="a" text="Machine Instructions" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteFunctionBody"/>
<!-- Space added to text="Comments" attribute below to distinguish it from the CommentsPlugin section of the same name --> <tocdef id="AnnoteComments" sortgroup="b" text="Comments" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteComments"/>
<tocdef id="AnnoteComments" sortgroup="b" text="Comments " target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteComments"/>
<tocdef id="AnnoteVariables" sortgroup="c" text="Variable Annotations" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteVariables"/> <tocdef id="AnnoteVariables" sortgroup="c" text="Variable Annotations" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteVariables"/>
<tocdef id="AnnotePrototype" sortgroup="d" text="Function Prototypes" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnotePrototype"/> <tocdef id="AnnotePrototype" sortgroup="d" text="Function Prototypes" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnotePrototype"/>
<tocdef id="AnnoteMutability" sortgroup="e" text="Data Mutability" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteMutability"/> <tocdef id="AnnoteMutability" sortgroup="e" text="Data Mutability" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteMutability"/>
<tocdef id="AnnoteConstants" sortgroup="f" text="Constant Annotations" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteConstants"/> <tocdef id="AnnoteConstants" sortgroup="f" text="Constant Annotations" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteConstants"/>
<tocdef id="AnnoteRegister" sortgroup="g" text="Register Values" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteRegister"/> <tocdef id="AnnoteRegister" sortgroup="g" text="Register Values" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteRegister"/>
</tocdef> </tocdef>
<tocdef id="Decompiler Options" <tocdef id="Decompiler Options"
sortgroup="c" sortgroup="c"
text="Decompiler Options" text="Decompiler Options"
@ -98,5 +97,5 @@
</tocdef> </tocdef>
</tocdef> </tocdef>
</tocref> </tocref>
</tocroot> </tocroot>

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,16 +15,16 @@
*/ */
package help; package help;
import help.validator.LinkDatabase;
import help.validator.model.*;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; 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 * 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 * TOC document, or individual TOC documents, one for each help directory (this allows
* for better modularity). * for better modularity).
@ -33,7 +32,6 @@ import java.util.*;
* We call this class an <b>overlay</b> tree to drive home the idea that each * We call this class an <b>overlay</b> tree to drive home the idea that each
* help directory's TOC data is put into the tree, with any duplicate paths overlayed * help directory's TOC data is put into the tree, with any duplicate paths overlayed
* on top of those from other help directories. * on top of those from other help directories.
*
*/ */
public class OverlayHelpTree { public class OverlayHelpTree {
@ -44,10 +42,13 @@ 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().values()) { for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping()
.values()) {
addExternalTOCItem(external); addExternalTOCItem(external);
} }
for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping().values()) {
for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping()
.values()) {
addSourceTOCItem(definition); addSourceTOCItem(definition);
} }
} }
@ -61,7 +62,7 @@ public class OverlayHelpTree {
// //
// We will have equivalent items in the generated TOC files, as that is how we // 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. // OK.
// //
@ -86,7 +87,7 @@ public class OverlayHelpTree {
if (parentID == null) { if (parentID == null) {
// must be the root, since the root has no parent // must be the root, since the root has no parent
if (rootItem != null) { 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* // root item defined *in the same file*
if (rootItem.getSourceFile().equals(item.getSourceFile())) { if (rootItem.getSourceFile().equals(item.getSourceFile())) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -125,7 +126,7 @@ public class OverlayHelpTree {
PrintWriter writer = new PrintWriter(new BufferedWriter(osw)); PrintWriter writer = new PrintWriter(new BufferedWriter(osw));
printTreeForID(writer, sourceFileID); printTreeForID(writer, sourceFileID);
// debug // debug
// writer = new PrintWriter(System.err); // writer = new PrintWriter(System.err);
// printTreeForID(writer, sourceFileID); // printTreeForID(writer, sourceFileID);
} }
@ -167,12 +168,6 @@ public class OverlayHelpTree {
return false; return false;
} }
// TODO delete this debug
// Set<Entry<String, Set<TOCItem>>> entrySet = parentToChildrenMap.entrySet();
// for (Entry<String, Set<TOCItem>> entry : entrySet) {
// System.out.println(entry.getKey() + " -> " + entry.getValue());
// }
OverlayNode newRootNode = new OverlayNode(null, rootItem); OverlayNode newRootNode = new OverlayNode(null, rootItem);
buildChildren(newRootNode); buildChildren(newRootNode);
@ -255,6 +250,7 @@ public class OverlayHelpTree {
} }
} }
// TODO LOOKIE
private static final Comparator<OverlayNode> CHILD_SORT_COMPARATOR = private static final Comparator<OverlayNode> CHILD_SORT_COMPARATOR =
new Comparator<OverlayNode>() { new Comparator<OverlayNode>() {
@Override @Override
@ -266,7 +262,7 @@ public class OverlayHelpTree {
return o1.getSortPreference().compareTo(o2.getSortPreference()); 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 text1 = o1.getTextAttribute();
String text2 = o2.getTextAttribute(); String text2 = o2.getTextAttribute();
@ -283,7 +279,16 @@ public class OverlayHelpTree {
return -1; 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 <TOCDEF> 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
} }
}; };
} }

View file

@ -15,27 +15,27 @@
*/ */
package help.validator.model; package help.validator.model;
import help.validator.LinkDatabase;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import help.validator.LinkDatabase;
/** /**
* A Table of Contents entry, which is represented in the help output as an xml tag. * A Table of Contents entry, which is represented in the help output as an xml tag.
*/ */
public abstract class TOCItem { public abstract class TOCItem {
//@formatter:off //@formatter:off
protected static final String[] INDENTS = { 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\t\t",
"\t\t\t\t\t\t\t\t" "\t\t\t\t\t\t\t\t"
}; };
//@formatter:on //@formatter:on
@ -63,7 +63,8 @@ public abstract class TOCItem {
String sortPreference, int lineNumber) { String sortPreference, int lineNumber) {
this.parentItem = parentItem; this.parentItem = parentItem;
this.sourceFile = sourceFile; this.sourceFile = sourceFile;
this.IDAttribute = ID; this.IDAttribute = Objects.requireNonNull(ID,
"TOC Tag missing 'id' attribute: " + sourceFile + ":" + lineNumber);
this.textAttribute = text; this.textAttribute = text;
this.targetAttribute = target; this.targetAttribute = target;
@ -157,82 +158,108 @@ public abstract class TOCItem {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj) {
return true; return true;
if (obj == null) }
if (obj == null) {
return false; return false;
if (getClass() != obj.getClass()) }
if (getClass() != obj.getClass()) {
return false; return false;
}
TOCItem other = (TOCItem) obj; TOCItem other = (TOCItem) obj;
if (IDAttribute == null) { if (IDAttribute == null) {
if (other.IDAttribute != null) if (other.IDAttribute != null) {
return false; return false;
}
} }
else if (!IDAttribute.equals(other.IDAttribute)) else if (!IDAttribute.equals(other.IDAttribute)) {
return false; return false;
}
if (sortPreference == null) { if (sortPreference == null) {
if (other.sortPreference != null) if (other.sortPreference != null) {
return false; return false;
}
} }
else if (!sortPreference.equals(other.sortPreference)) else if (!sortPreference.equals(other.sortPreference)) {
return false; return false;
}
if (sourceFile == null) { if (sourceFile == null) {
if (other.sourceFile != null) if (other.sourceFile != null) {
return false; return false;
}
} }
else if (!sourceFile.equals(other.sourceFile)) else if (!sourceFile.equals(other.sourceFile)) {
return false; return false;
}
if (targetAttribute == null) { if (targetAttribute == null) {
if (other.targetAttribute != null) if (other.targetAttribute != null) {
return false; return false;
}
} }
else if (!targetAttribute.equals(other.targetAttribute)) else if (!targetAttribute.equals(other.targetAttribute)) {
return false; return false;
}
if (textAttribute == null) { if (textAttribute == null) {
if (other.textAttribute != null) if (other.textAttribute != null) {
return false; return false;
}
} }
else if (!textAttribute.equals(other.textAttribute)) else if (!textAttribute.equals(other.textAttribute)) {
return false; return false;
}
return true; return true;
} }
/** /**
* True if the two items are the same, except that they come from a different source file. * 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) { public boolean isEquivalent(TOCItem other) {
if (this == other) if (this == other) {
return true; return true;
if (other == null) }
if (other == null) {
return false; return false;
if (getClass() != other.getClass()) }
if (getClass() != other.getClass()) {
return false; return false;
}
if (IDAttribute == null) { if (IDAttribute == null) {
if (other.IDAttribute != null) if (other.IDAttribute != null) {
return false; return false;
}
} }
else if (!IDAttribute.equals(other.IDAttribute)) else if (!IDAttribute.equals(other.IDAttribute)) {
return false; return false;
}
if (sortPreference == null) { if (sortPreference == null) {
if (other.sortPreference != null) if (other.sortPreference != null) {
return false; return false;
}
} }
else if (!sortPreference.equals(other.sortPreference)) else if (!sortPreference.equals(other.sortPreference)) {
return false; return false;
}
if (targetAttribute == null) { if (targetAttribute == null) {
if (other.targetAttribute != null) if (other.targetAttribute != null) {
return false; return false;
}
} }
else if (!targetAttribute.equals(other.targetAttribute)) else if (!targetAttribute.equals(other.targetAttribute)) {
return false; return false;
}
if (textAttribute == null) { if (textAttribute == null) {
if (other.textAttribute != null) if (other.textAttribute != null) {
return false; return false;
}
} }
else if (!textAttribute.equals(other.textAttribute)) else if (!textAttribute.equals(other.textAttribute)) {
return false; return false;
}
return true; 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(); StringBuilder buildy = new StringBuilder();
buildy.append(INDENTS[indentLevel]); buildy.append(INDENTS[indentLevel]);
buildy.append('<').append(TOC_TAG_NAME).append(' '); buildy.append('<').append(TOC_TAG_NAME).append(' ');
// text attribute // text attribute
// NOTE: we do not put our display text in this attribute. This is because JavaHelp uses // 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. // 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 // We have overridden JavaHelp to use a custom renderer that will paint the display
// text with the attribute we set below. // text with the attribute we set below.

View file

@ -15,7 +15,7 @@
*/ */
package help; package help;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
@ -36,31 +36,31 @@ import help.validator.model.*;
public class OverlayHelpTreeTest { public class OverlayHelpTreeTest {
@Test @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 <TOCREF> items that are defined // built from TOC_Source.xml files when that file uses <TOCREF> items that are defined
// 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");
@ -68,12 +68,12 @@ public class OverlayHelpTreeTest {
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml"); Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute(); 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(); 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(); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root); tocProvider.addExternal(root);
@ -89,41 +89,92 @@ public class OverlayHelpTreeTest {
} }
@Test @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
<tocdef id="root" target="fake">
<tocdef id="child_1" target="fake" />
</tocdef>
Another TOC_Source.xml
<tocref id="root">
<tocref="child_1">
<tocdef id="child_2" target="fake" />
</tocref>
</tocref>
*/
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 <TOCREF> items that are defined // built from TOC_Source.xml files when that file uses <TOCREF> items that are defined
// 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");
@ -131,18 +182,17 @@ public class OverlayHelpTreeTest {
TOCItemExternal prebuilt_a_child = externalItem(child_1_a, "prebuilt_a_child"); 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 // note: same ID values, since they represent the same nodes, but from different TOC files
TOCItemExternal root_b = externalItemAlt(null, "root"); TOCItemExternal root_b = externalItem(null, "root");
TOCItemExternal child_1_b = externalItemAlt(root_b, "child_1"); TOCItemExternal child_1_b = externalItem(root_b, "child_1");
TOCItemExternal prebuilt_b_child = externalItemAlt(child_1_b, "prebuilt_b_child"); TOCItemExternal prebuilt_b_child = externalItem(child_1_b, "prebuilt_b_child");
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml"); Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root_a.getIDAttribute(); 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(); String child_1_ID = child_1_a.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 = tocdef(child_1_ref, "child_2", tocSourceFile);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root_a); 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, 2, child_1_a);// could also be child_1_b, same ID
assertOrder(spy, 3, child_2); 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 // 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). // live in the TOC_Source.xml being processes, so they are not part of the output).
} }
@Test @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: Example makeup we will create:
TOC_Source.xml PreBuild_TOC.xml
<tocdef id="root" target="fake"> <tocitem id="root" target="fake">
<tocdef id="child_1" target="fake" /> <tocitem id="child_1_1" text="Child 1" target="fake" />
</tocdef> </tocitem>
Another PreBuild_TOC.xml
Another TOC_Source.xml
<tocitem id="root" target="fake">
<tocitem id="child_2_1" text=Child 1" target="fake" />
<tocitem id="child_2_2" text=Child 2" target="fake" />
</tocitem>
Another TOC_Source.xml
<tocref id="root"> <tocref id="root">
<tocref="child_1"> <tocref="child_1_1">
<tocdef id="child_2" target="fake" /> <tocdef id="child_2_1a" text="Child 1a" target="fake" />
</tocref> </tocref>
<tocdef id="child_3_2" text="Child 2" target="fake" />
</tocref> </tocref>
*/ */
Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml"); TOCItemExternal root_a = externalItem("root");
TOCItemDefinition root = definitionItem("root", toc_1); TOCItemExternal child_1_1 = externalItem(root_a, "child_1_1", "Child 1");
TOCItemDefinition child_1 = definitionItem(root, "child_1", toc_1);
Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml"); // note: same ID values, since they represent the same nodes, but from different TOC files
String root_ID = root.getIDAttribute(); TOCItemExternal root_b = externalItem(null, "root");
String child_1_ID = child_1.getIDAttribute(); 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); Path toc = Paths.get("/fake/path_2/TOC_Source.xml");
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, toc_2); String root_ID = root_a.getIDAttribute();
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", toc_2); 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(); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addDefinition(root); tocProvider.addExternal(root_a);
tocProvider.addDefinition(child_1); tocProvider.addExternal(child_1_1);
tocProvider.addDefinition(child_2);// in the second TOC file 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); assertNodeCount(spy, 4);
assertOrder(spy, 1, root); assertOrder(spy, 1, root_a);
assertOrder(spy, 2, child_1); assertOrder(spy, 2, child_1_1);
assertOrder(spy, 3, child_2); 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 // Create a test version of the LinkDatabase for the overlay tree, with test versions of
// it's required TOC input file and HelpModuleLocation // it's required TOC input file and HelpModuleLocation
// //
GhidraTOCFileDummy toc = new GhidraTOCFileDummy(tocFile); GhidraTOCFileDummy toc = new GhidraTOCFileDummy(tocFile);
OverlayHelpModuleLocationTestStub location = new OverlayHelpModuleLocationTestStub(toc); OverlayHelpModuleLocationTestStub location = new OverlayHelpModuleLocationTestStub(toc);
LinkDatabaseTestStub db = new LinkDatabaseTestStub(location); LinkDatabaseTestStub db = new LinkDatabaseTestStub(location);
@ -242,22 +317,26 @@ public class OverlayHelpTreeTest {
return spy; return spy;
} }
private TOCItemDefinition definitionItem(String ID, Path tocSourceFile) { private TOCItemDefinition tocdef(String ID, Path tocSourceFile) {
return definitionItem(null, ID, 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 target = "fake";
String sort = ""; String sort = "";
int line = 1; 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) { private TOCItemReference tocref(String referenceID, Path tocSourceFile) {
return referenceItem(null, referenceID, 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); return new TOCItemReference(parent, tocSourceFile, referenceID, 1);
} }
@ -266,24 +345,20 @@ public class OverlayHelpTreeTest {
} }
private TOCItemExternal externalItem(TOCItem parent, String ID) { private TOCItemExternal externalItem(TOCItem parent, String ID) {
Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml"); return externalItem(parent, ID, ID);
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line);
} }
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"); Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml");
String target = "fake"; String target = "fake";
String sort = ""; String sort = "";
int line = 1; 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) { private void assertOrder(TOCSpyWriter spy, int ordinal, TOCItem item) {
String ID = spy.getItem(ordinal - 1 /* make an index */); 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); ID);
} }
@ -324,7 +399,7 @@ public class OverlayHelpTreeTest {
private void storeDisplayAttribute(String s) { private void storeDisplayAttribute(String s) {
// create a pattern to pull out the display string // 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()); Matcher matcher = p.matcher(s.trim());
if (!matcher.matches()) { if (!matcher.matches()) {
@ -347,8 +422,8 @@ public class OverlayHelpTreeTest {
Map<String, TOCItemDefinition> definitions = new HashMap<>(); Map<String, TOCItemDefinition> definitions = new HashMap<>();
void addExternal(TOCItemExternal item) { void addExternal(TOCItemExternal item) {
String displayText = item.getIDAttribute(); String ID = item.getIDAttribute();
externals.put(displayText, item); externals.put(ID, item);
} }
void addDefinition(TOCItemDefinition item) { void addDefinition(TOCItemDefinition item) {