diff --git a/Ghidra/Framework/Docking/src/main/java/docking/help/GHelpHTMLEditorKit.java b/Ghidra/Framework/Docking/src/main/java/docking/help/GHelpHTMLEditorKit.java index 96c7df66a4..8f0484c207 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/help/GHelpHTMLEditorKit.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/help/GHelpHTMLEditorKit.java @@ -15,7 +15,6 @@ */ package docking.help; -import java.awt.Image; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.*; @@ -25,7 +24,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.swing.ImageIcon; import javax.swing.JEditorPane; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; @@ -380,20 +378,10 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit { */ private class GHelpImageView extends ImageView { - private Image myImage; - public GHelpImageView(Element elem) { super(elem); } - @Override - public Image getImage() { - if (myImage != null) { - return myImage; - } - return super.getImage(); - } - @Override public URL getImageURL() { @@ -419,10 +407,7 @@ public class GHelpHTMLEditorKit extends HTMLEditorKit { return null; } - ImageIcon icon = iconProvider.getIcon(); - myImage = icon.getImage(); - - URL url = iconProvider.getUrl(); + URL url = iconProvider.getOrCreateUrl(); return url; } diff --git a/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java b/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java index 4db7a5492a..8198341755 100644 --- a/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java +++ b/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java @@ -15,17 +15,30 @@ */ package resources; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; import java.net.URL; import javax.swing.ImageIcon; +import generic.Images; +import generic.util.image.ImageUtils; +import ghidra.util.Msg; + /** - * A class that knows how to provide an icon and the URL for that icon + * A class that knows how to provide an icon and the URL for that icon. If {@link #getUrl()} + * returns a non-null value, then that is the URL used to originally load the icon in this class. + * + *
If {@link #getUrl()} returns null, then {@link #getOrCreateUrl()} can be used to create a
+ * value URL by writing out the image for this class's icon.
*/
public class IconProvider {
private ImageIcon icon;
private URL url;
+ private URL tempUrl;
+ private boolean tempFileFailed;
public IconProvider(ImageIcon icon, URL url) {
this.icon = icon;
@@ -36,11 +49,76 @@ public class IconProvider {
return icon;
}
+ public boolean isInvalid() {
+ return icon == null; // as long as we have an icon, we are valid, url or not
+ }
+
public URL getUrl() {
return url;
}
- public boolean isInvalid() {
- return icon == null || url == null;
+ /**
+ * Returns the value of {@link #getUrl()} if it is non-null. Otherwise, this class will
+ * attempt to create a temporary file containing the image of this class in order to return
+ * a URL for that temp file. If a temporary file could not be created, then the URL
+ * returned from this class will point to the
+ * {@link ResourceManager#getDefaultIcon() default icon}.
+ *
+ * @return the URL
+ */
+ public URL getOrCreateUrl() {
+ if (url != null) {
+ return url;
+ }
+
+ createTempUrlAsNeeded();
+ return tempUrl;
+ }
+
+ private void createTempUrlAsNeeded() {
+ if (testUrl(tempUrl)) {
+ return;
+ }
+
+ tempUrl = createTempUrl();
+ if (tempUrl == null) {
+ tempUrl = getDefaultUrl();
+ }
+ }
+
+ private URL createTempUrl() {
+ if (tempFileFailed) {
+ return null; // don't repeatedly attempt to create a temp file
+ }
+
+ try {
+ File imageFile = File.createTempFile("temp.help.icon", null);
+ imageFile.deleteOnExit(); // don't let this linger
+ ImageUtils.writeFile(icon.getImage(), imageFile);
+ return imageFile.toURI().toURL();
+ }
+ catch (IOException e) {
+ tempFileFailed = true;
+ Msg.error(this, "Unable to write temp image to display in help for " +
+ ResourceManager.getIconName(icon));
+ }
+ return null;
+ }
+
+ private boolean testUrl(URL testUrl) {
+ if (testUrl == null) {
+ return false;
+ }
+
+ try {
+ return new File(testUrl.toURI()).exists();
+ }
+ catch (URISyntaxException e) {
+ return false;
+ }
+ }
+
+ private URL getDefaultUrl() {
+ return ResourceManager.getResource(Images.BOMB);
}
}
diff --git a/Ghidra/Framework/Generic/src/main/java/resources/Icons.java b/Ghidra/Framework/Generic/src/main/java/resources/Icons.java
index 4b45221f5f..4557609210 100644
--- a/Ghidra/Framework/Generic/src/main/java/resources/Icons.java
+++ b/Ghidra/Framework/Generic/src/main/java/resources/Icons.java
@@ -91,7 +91,7 @@ public class Icons {
ResourceManager.loadImage("images/dialog-cancel.png", 10, 10), 6, 6)));
public static final ImageIcon APPLY_BLOCKED_MATCH_ICON = ResourceManager.getImageIcon(
new MultiIcon(ResourceManager.loadImage("images/kgpg.png"), new TranslateIcon(
- ResourceManager.loadImage("images/checkmark_green.png", 12, 12), 4, 0)));
+ ResourceManager.loadImage("images/checkmark_green.gif", 12, 12), 4, 0)));
/**
* Returns true if the given string is a Java code snippet that references this class
@@ -126,24 +126,6 @@ public class Icons {
return new IconProvider(icon, url);
}
- /**
- * Returns a URL for the given code snippet if it is a field reference on this class
- *
- * @param snippet the snippet of Java code that references a field of this class
- * @return the URL; null if the snippet does not refer to a field of this class
- */
- public static URL getUrlForIconsReference(String snippet) {
-
- String fieldName = getIconName(snippet);
- if (fieldName == null) {
- return null;
- }
-
- ImageIcon icon = getIconByFieldName(fieldName);
- URL url = getUrlFromIcon(icon);
- return url;
- }
-
private static String getIconName(String snippet) {
if (!isIconsReference(snippet)) {
return null;
@@ -185,7 +167,7 @@ public class Icons {
return url;
}
catch (MalformedURLException e) {
- Msg.debug(Icons.class, "Unable to get URL for icon: " + description, e);
+ Msg.trace(Icons.class, "Unable to get URL for icon: " + description);
return null;
}
diff --git a/Ghidra/Framework/Generic/src/main/java/resources/MultiIcon.java b/Ghidra/Framework/Generic/src/main/java/resources/MultiIcon.java
index d9e89b47bd..706aaf7e0e 100644
--- a/Ghidra/Framework/Generic/src/main/java/resources/MultiIcon.java
+++ b/Ghidra/Framework/Generic/src/main/java/resources/MultiIcon.java
@@ -160,19 +160,18 @@ public class MultiIcon implements Icon {
@Override
public String toString() {
- // return getClass().getSimpleName() + "[" + getIconNames() + "]";
- return getDescription();
+ return getClass().getSimpleName() + "[" + getIconNames() + "]";
}
-// private String getIconNames() {
-// StringBuffer buffy = new StringBuffer();
-// for (Icon icon : iconList) {
-// if (buffy.length() > 0) {
-// buffy.append(", ");
-// }
-// buffy.append(ResourceManager.getIconName(icon));
-// }
-//
-// return buffy.toString();
-// }
+ private String getIconNames() {
+ StringBuffer buffy = new StringBuffer();
+ for (Icon icon : iconList) {
+ if (buffy.length() > 0) {
+ buffy.append(", ");
+ }
+ buffy.append(ResourceManager.getIconName(icon));
+ }
+
+ return buffy.toString();
+ }
}
diff --git a/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java b/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java
index 910b2e65dd..8e3ddd0579 100644
--- a/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java
+++ b/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java
@@ -400,14 +400,18 @@ public class ResourceManager {
}
/**
- * Get the name of this icon. If icon is an ImageIcon, its getDescription() is called to
- * get the name
+ * Get the name of this icon. The value is usually going to be the URL from which the icon
+ * was loaded
*
* @param icon the icon for which the name is desired
- * @return the name
+ * @return the name
*/
public static String getIconName(Icon icon) {
String iconName = icon.toString();
+
+ if (icon instanceof FileBasedIcon) {
+ return ((FileBasedIcon) icon).getFilename();
+ }
if (icon instanceof ImageIcon) {
iconName = ((ImageIcon) icon).getDescription();
}
diff --git a/Ghidra/Framework/Help/src/main/java/help/HelpBuildUtils.java b/Ghidra/Framework/Help/src/main/java/help/HelpBuildUtils.java
index 5370879310..a69d6c6128 100644
--- a/Ghidra/Framework/Help/src/main/java/help/HelpBuildUtils.java
+++ b/Ghidra/Framework/Help/src/main/java/help/HelpBuildUtils.java
@@ -25,6 +25,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import help.validator.location.*;
+import resources.IconProvider;
import resources.Icons;
public class HelpBuildUtils {
@@ -536,6 +537,7 @@ public class HelpBuildUtils {
* locate files based upon relative references, specialized help system references (i.e.,
* help/topics/...), and absolute URLs.
*
+ * @param sourceFile the source file path of the image reference
* @param ref the reference text
* @return an absolute path; null if the URI is remote
* @throws URISyntaxException
@@ -544,15 +546,21 @@ public class HelpBuildUtils {
throws URISyntaxException {
if (Icons.isIconsReference(ref)) {
+
// help system syntax:
- URL url = Icons.getUrlForIconsReference(ref);
- if (url == null) {
+ IconProvider iconProvider = Icons.getIconForIconsReference(ref);
+ if (iconProvider == null || iconProvider.isInvalid()) {
// bad icon name
return ImageLocation.createInvalidRuntimeLocation(sourceFile, ref);
}
- URI resolved = url.toURI();
- Path path = toPath(resolved);
+ URL url = iconProvider.getUrl();
+ URI resolved = null;
+ Path path = null;
+ if (url != null) { // we may have an icon with an invalid URL (e.g., a MultiIcon)
+ resolved = url.toURI();
+ path = toPath(resolved);
+ }
return ImageLocation.createRuntimeLocation(sourceFile, ref, resolved, path);
}
diff --git a/Ghidra/Framework/Help/src/main/java/help/ImageLocation.java b/Ghidra/Framework/Help/src/main/java/help/ImageLocation.java
index fd3ffd0145..53d1a48e49 100644
--- a/Ghidra/Framework/Help/src/main/java/help/ImageLocation.java
+++ b/Ghidra/Framework/Help/src/main/java/help/ImageLocation.java
@@ -21,6 +21,9 @@ import java.nio.file.Path;
/**
* A class that represents the original location of an IMG tag along with its location
* resolution within the help system.
+ *
+ *
Some images are represented by 'in memory' or 'runtime' values that do not have a valid
+ * url.
*/
public class ImageLocation {
@@ -30,8 +33,13 @@ public class ImageLocation {
private Path resolvedPath;
private URI resolvedUri;
private boolean isRemote;
+
+ /** An image that is taken from an image loaded by a Java class (e.g., Icons.XYZ_ICON) */
private boolean isRuntime;
+ /** A 'runtime' image that could not be located */
+ private boolean invalidRuntimeImage;
+
public static ImageLocation createLocalLocation(Path sourceFile, String imageSrc,
URI resolvedUri, Path resolvedPath) {
@@ -61,6 +69,7 @@ public class ImageLocation {
l.resolvedPath = null;
l.isRemote = false;
l.isRuntime = true;
+ l.invalidRuntimeImage = true;
return l;
}
@@ -84,48 +93,28 @@ public class ImageLocation {
return sourceFile;
}
- public void setSourceFile(Path sourceFile) {
- this.sourceFile = sourceFile;
- }
-
public String getImageSrc() {
return imageSrc;
}
- public void setImageSrc(String imageSrc) {
- this.imageSrc = imageSrc;
- }
-
public Path getResolvedPath() {
return resolvedPath;
}
- public void setResolvedPath(Path resolvedPath) {
- this.resolvedPath = resolvedPath;
- }
-
public URI getResolvedUri() {
return resolvedUri;
}
- public void setResolvedUri(URI resolvedUri) {
- this.resolvedUri = resolvedUri;
- }
-
public boolean isRemote() {
return isRemote;
}
- public void setRemote(boolean isRemote) {
- this.isRemote = isRemote;
- }
-
public boolean isRuntime() {
return isRuntime;
}
- public void setRuntime(boolean isRuntime) {
- this.isRuntime = isRuntime;
+ public boolean isInvalidRuntimeImage() {
+ return invalidRuntimeImage;
}
@Override
diff --git a/Ghidra/Framework/Help/src/main/java/help/validator/JavaHelpValidator.java b/Ghidra/Framework/Help/src/main/java/help/validator/JavaHelpValidator.java
index 09c5e392b5..fc1aa68b1f 100644
--- a/Ghidra/Framework/Help/src/main/java/help/validator/JavaHelpValidator.java
+++ b/Ghidra/Framework/Help/src/main/java/help/validator/JavaHelpValidator.java
@@ -120,14 +120,22 @@ public class JavaHelpValidator {
return; // don't even try to verify a remote URL
}
- Path imagePath = img.getImageFile();
- if (imagePath == null) {
- unresolvedLinks.add(new NonExistentIMGFileInvalidLink(img));
+ if (img.isRuntime()) {
+
+ //
+ // The tool will load this image at runtime--don't perform normal validation
+ // (runtime means an icon to be loaded from a Java file)
+ //
+ if (img.isInvalid()) {
+ unresolvedLinks.add(new InvalidRuntimeIMGFileInvalidLink(img));
+ return;
+ }
return;
}
- if (img.isRuntime()) {
- // the tool will load this image at runtime--don't perform normal validate
+ Path imagePath = img.getImageFile();
+ if (imagePath == null) {
+ unresolvedLinks.add(new NonExistentIMGFileInvalidLink(img));
return;
}
diff --git a/Ghidra/Framework/Help/src/main/java/help/validator/UnusedHelpImageFileFinder.java b/Ghidra/Framework/Help/src/main/java/help/validator/UnusedHelpImageFileFinder.java
index 59f7ffbb5e..483dfb6316 100644
--- a/Ghidra/Framework/Help/src/main/java/help/validator/UnusedHelpImageFileFinder.java
+++ b/Ghidra/Framework/Help/src/main/java/help/validator/UnusedHelpImageFileFinder.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,22 +15,25 @@
*/
package help.validator;
-import help.GHelpBuilder;
-import help.HelpBuildUtils;
-import help.validator.location.HelpModuleLocation;
-import help.validator.model.IMG;
-
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
+import org.apache.commons.lang3.StringUtils;
+
+import help.HelpBuildUtils;
+import help.validator.location.HelpModuleLocation;
+import help.validator.model.IMG;
+import util.CollectionUtils;
+
public class UnusedHelpImageFileFinder {
+ private static final String HELP_PATHS_OPTION = "-hp"; // taken from GHelpBuilder
private static final String DEBUG_SWITCH = "-debug";
- private static List referencedIMGs,
Collection
getReferencedIMGs(Collection
set = new HashSet
();
+ private static Collection
getReferencedIMGs(
+ Collection
set = new HashSet<>();
for (HelpModuleLocation help : helpCollections) {
Collection
IMGs = help.getAllIMGs();
set.addAll(IMGs);
@@ -122,7 +121,7 @@ public class UnusedHelpImageFileFinder {
private static Collection
{
return imageLocation.isRuntime();
}
+ public boolean isInvalid() {
+ return imageLocation.isInvalidRuntimeImage();
+ }
+
public Path getImageFile() {
return imgFile;
}
diff --git a/gradle/helpProject.gradle b/gradle/helpProject.gradle
index f77f7e7391..49cf074f63 100644
--- a/gradle/helpProject.gradle
+++ b/gradle/helpProject.gradle
@@ -141,10 +141,42 @@ task buildHelp(type: JavaExec, dependsOn: indexHelp) {
}
+
+// Task for finding unused images that are not referenced from Ghidra help files
+task findUnusedHelp(type: JavaExec) {
+ group rootProject.GHIDRA_GROUP
+ description " Finds unused help images for this module. [gradle/helpProject.gradle]\n"
+
+ File helpRootDir = file('src/main/help/help')
+ File outputDir = file('build/help/main/help')
+
+ dependsOn configurations.helpPath
+
+ inputs.dir helpRootDir
+
+ classpath = sourceSets.helpIndex.runtimeClasspath
+
+ main = 'help.validator.UnusedHelpImageFileFinder'
+
+ args '-debug' // print debug info
+
+ doFirst {
+ // this modules runtime classpath (contains jhall.jar)
+ classpath project(':Help').sourceSets.main.runtimeClasspath
+
+ // the current help dir to process
+ args "-hp"
+ args "${helpRootDir.absolutePath}"
+ }
+
+}
+
+
+
// include the help into the module's jar
jar {
from "build/help/main" // include the generated help index files
- from "src/main/help" // include the help source files
+ from "src/main/help" // include the help source files
}
// build the help whenever this module's jar file is built