Unable to load Resource Icon

So, I have been working through creating a module that will add a project resource and I would like to use a custom resource icon.

The module compiles and runs, however, upon attempting to launch a designer I receive a FileNotFoundException. This is prior to opening any project.

The first thing I thought was odd is it is looking for the file in the Designer Launcher folder, but perhaps that is correct.

I take it that I have something messed up in my class path which means the ClassLoader can't resolve the filePath in getResourceAsStream() and that is the culprit, but I can't for the life of me figure out what's wrong.

I'm using IntelliJ 2202.2, and Gradle for building.

Here is the code in the DesignerHook class

public static final Icon RESOURCE_ICON;

static {
    String xmlParser = XMLResourceDescriptor.getXMLParserClassName();
    SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(xmlParser);
    String filePath = "code-block.svg";

    try (var inputStream = DesignerHook.class.getResourceAsStream(filePath)) {
        SVGDocument document = factory.createSVGDocument(filePath, inputStream);
        RESOURCE_ICON = new SvgIconUtil.SvgIcon(document, 16, 16);
    } catch (IOException e) {
        throw new RuntimeException("Unable to load resource icon", e);
    }
}

Any help is appreciated.

How are you launching the designer, and what's the full error message?

From the Designer Launcher, just the standard double click on the designer for the gateway. No special JVM arguments are applied.

com.inductiveautomation.ignition.common.modules.ModuleLoadException: Error loading hook class for module.
	at com.inductiveautomation.ignition.designer.IgnitionDesigner.loadModules(IgnitionDesigner.java:837)
	at com.inductiveautomation.ignition.designer.IgnitionDesigner.startup(IgnitionDesigner.java:449)
	at com.inductiveautomation.ignition.designer.DesignerStartupHook.lambda$startup$0(DesignerStartupHook.java:340)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.run(Unknown Source)
Caused by: java.lang.ExceptionInInitializerError
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Unknown Source)
	at com.inductiveautomation.ignition.designer.IgnitionDesigner.loadModules(IgnitionDesigner.java:830)
	... 15 more
Caused by: java.lang.RuntimeException: Unable to load resource icon
	at DesignerHook.<clinit>(DesignerHook.java:31)
	... 18 more
Caused by: java.io.FileNotFoundException: $USER_FOLDER$\Inductive Automation\Designer Launcher\code-block.svg (The system cannot find the file specified)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(Unknown Source)
	at java.base/java.io.FileInputStream.<init>(Unknown Source)
	at java.base/java.io.FileInputStream.<init>(Unknown Source)
	at java.base/sun.net.www.protocol.file.FileURLConnection.connect(Unknown Source)
	at java.base/sun.net.www.protocol.file.FileURLConnection.getInputStream(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.impl.XMLVersionDetector.determineDocVersion(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source)
	at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
	at org.apache.batik.dom.util.SAXDocumentFactory.createDocument(SAXDocumentFactory.java:453)
	at org.apache.batik.dom.util.SAXDocumentFactory.createDocument(SAXDocumentFactory.java:357)
	at org.apache.batik.anim.dom.SAXSVGDocumentFactory.createDocument(SAXSVGDocumentFactory.java:226)
	at org.apache.batik.anim.dom.SAXSVGDocumentFactory.createSVGDocument(SAXSVGDocumentFactory.java:135)
	at DesignerHook.<clinit>(DesignerHook.java:28)
	... 18 more

Some information has been redacted.

Huh, very weird. Obviously that path worked for me in the SDK example when I was authoring it, launching against a docker container running the gateway. I'll look at the internal code a bit tomorrow and see if I can make any guesses.

What's weird is that the Project-Resource example builds and runs just fine, and doesn't produce this error.

I'm lost.

Well, I'm still completely lost as to why it didn't work in that project. I finally bit the bullet and started over, and now, works as expected. :man_shrugging: Luckily not too much work.

1 Like

So, I'm curious if you know why this code:

public static Consumer<ProjectResourceBuilder> toResource(@NotNull ExpressionResource resource) {
        return builder -> builder
            .putAttribute("enabled", resource.enabled)
            .putData(
                RESOURCE_FILE,
                resource.getUserCode().getBytes(StandardCharsets.UTF_8)
            );
    }

Would not return the first line of the resource?

For example, when I first open the resource it has the value "pass", when I close the tab, I get a BadLocationException: No such line. If I first add a crlf to the resource, then the tab closes without error, however, when I reopen the resource file the first line is missing?

This is the exact way that you did this in the Project-Resource example project so I'm wondering what I am missing.

How are you setting "user code" on your expression resource?

It’s being set in the static fromResource function, which just calls the constructor, which is just a direct set.

public static ExpressionResource fromResource(ProjectResource resource) {
        String code = new String(
            Objects.requireNonNull(resource.getData(RESOURCE_FILE)),
            StandardCharsets.UTF_8
        );
        boolean isEnabled = resource.getAttribute("enabled")
            .map(JsonElement::getAsBoolean)
            .orElse(true);
        return new ExpressionResource(code, isEnabled);
    }

As you can see I haven’t really started to add my own things yet, really just trying insure I understand what’s going on here before I really try to break things.

And you haven't changed the editor yet? Can try decorating with loggers all over the place to see when the value is changing.

Okay, so I figured it out.

ExtensionFunctionPanel isn't the component I need. It appears that it expects for there to be a first line which is not actually part of the resource. That line is removed from the script. I had modified resource so that there was no ExtensionFunctionDescriptor, because I don't believe I need that for what I'm trying to accomplish. Therefore, the first line of the text in the area which was "real data" was being removed as it is expected to be something else.

Now, to figure out what panel I actually need. Thanks for the help.

Based on 'expression' in your resource name, look to the CodeEditorFactory class.

1 Like