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);
}
}
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
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.
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. Luckily not too much work.
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.
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.
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.