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