ikvmnet / ikvm

A Java Virtual Machine and Bytecode-to-IL Converter for .NET
Other
1.28k stars 121 forks source link

parse method from org.apache.commons.digester.Digester throws class not found exception #428

Closed ubythulla closed 1 year ago

ubythulla commented 1 year ago

Below are the sample java code

    package Main.XmlParser;
    import org.apache.commons.digester.Digester;
    public class XmlImportParser
    {
        private final URL ruleContextUrl;

        public XmlImportParser(URL ruleContextUrl)
        {
            this.ruleContextUrl = ruleContextUrl;
        }

        public List<PolicyXML> parse(InputStream input)
        {
            Digester digester = null;
            List<PolicyXML> policies = new ArrayList<>();

            digester = XmlUtil.getDigester(ruleContextUrl);
            digester.push(policies);
            policies = (List<PolicyXML>)digester.parse(input);
        }
    }

another class from another name space

    package Main.Policy
    public class PolicyXML
    {
        //sample code
    }

Digester ruleset is (fileName : ikvmDigesterRules.xml)

   <?xml version="1.0" encoding="UTF-8"?>
   <digester-rules>
    <pattern value="XMLParse/PolicyXML/Policy">
        <object-create-rule classname="Main.Policy.PolicyXML"/>
        <set-next-rule methodname="add" paramtype="Main.Policy.PolicyXML"/>
        <set-properties-rule>
            <alias attr-name="Name" prop-name="name"/>
        </set-properties-rule>
    </pattern>
    </digester-rules>

Policy file to be read (file Name : ikvmPolicyIssue.xml)

<?xml version="1.0" encoding="UTF-8"?>
<XMLParse>
<PolicyXML>
     <Policy Name="IkvmTest">          
     </Policy>
  </PolicyXML>
</XMLParse>

And, I reference this from a C# console application:

public static void main()
    {      
     var   parserField = new Main.XmlParser.XmlImportParser(new java.net.URL(@"C:\\Users\\Downloads\\ikvmDigesterRules.xml")));
        string path = @"C:\\Users\\Downloads\\ikvmPolicyIssue.xml";
        string xmlString = new StreamReader(path).ReadToEnd();
        var policyList = parserField.parse(new java.io.StringBufferInputStream(xmlString));
    }

below is the error message is thrown from the parse method

error message is Oct 13, 2023 2:56:00 PM org.apache.commons.digester.Digester startElement
SEVERE: Begin event threw exception
java.lang.ClassNotFoundException:  Main.Policy.PolicyXML
        at IKVM.Java.Externs.ikvm.runtime.AssemblyClassLoader.loadClass(Unknown Source)
        at ikvm.runtime.AssemblyClassLoader.loadClass(Native Method)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:371)
        at org.apache.commons.digester.ObjectCreateRule.begin(ObjectCreateRule.java:205)
        at org.apache.commons.digester.Rule.begin(Rule.java:175)
        at org.apache.commons.digester.Digester.startElement(Digester.java:1563)
        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:509)
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:743)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:380)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2787)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:118)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)
        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)
        at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:643)
        at org.apache.commons.digester.Digester.parse(Digester.java:1887)

This usecase was working in ikvm-0.46.0.1 version, due to updation of jar files forced to update the ikvm to latest ikvm nugget package 8.6.4 version started to throw the exception.

Your help will be much appreciated, we are stuck here and not able to proceed further. Thanks in advance.

wasabii commented 1 year ago

Well, it seems like a problem locating a specific java class. By what method did you generate the assemblies? Were there any errors printed about missing classes?

ubythulla commented 1 year ago

I added the jars directly to the dot net project and added ikvm reference in the csproj file as like below

  <ItemGroup>
    <IkvmReference Include="Main.jar">
      <AssemblyName>Main</AssemblyName>
      <AssemblyVersion>2.5</AssemblyVersion>
      <AssemblyFileVersion>2.5.0.0</AssemblyFileVersion>
     <References>commons-beanutils-1.8.2.jar;slf4j-api-2.0.7.jar;commons-digester-2.0.jar</References>
       //Note : Without these referenced jars I was getting error at runtime.
    <Debug>true</Debug>
    </IkvmReference>
  </ItemGroup>
  <ItemGroup>
    <IkvmReference Include="slf4j-api-2.0.7.jar">
      <AssemblyName>slf4j</AssemblyName>
      <AssemblyVersion>2.0.7</AssemblyVersion>
      <AssemblyFileVersion>2.0.7.0</AssemblyFileVersion>
    </IkvmReference>
  </ItemGroup>
<ItemGroup>
    <IkvmReference Include="commons-digester-2.0.jar">
      <AssemblyName>CommonDigester</AssemblyName>
      <AssemblyVersion>2.0</AssemblyVersion>
      <AssemblyFileVersion>2.0.0.0</AssemblyFileVersion>
     <References>commons-logging-1.1.1.jar;commons-beanutils-1.8.2.jar</References>
       //Note : without these referenced jars I was getting logging and beanutils missing errors at runtime. 
    <Debug>true</Debug>
    </IkvmReference>
  </ItemGroup>
<ItemGroup>
    <IkvmReference Include="commons-beanutils-1.8.2.jar">
      <AssemblyName>Beanutils</AssemblyName>
      <AssemblyVersion>1.8.2</AssemblyVersion>
      <AssemblyFileVersion>1.8.2.0</AssemblyFileVersion>
      <References>commons-logging-1.1.1.jar</References>
    </IkvmReference>
  </ItemGroup>
  <ItemGroup>
    <IkvmReference Include="commons-logging-1.1.1.jar">
      <AssemblyName>CommonLogging</AssemblyName>
      <AssemblyVersion>1.1.1</AssemblyVersion>
      <AssemblyFileVersion>1.1.1.0</AssemblyFileVersion>
    </IkvmReference>
  </ItemGroup>

The same use case works perfectly in the Java application, and only in Dot Net application running using IKVM has problems. I tried IKVM nugget versions from 8.4.4 to 8.6.4, and I encountered the same problem in every one of them. While generating the jars, there were no errors or warnings about missing classes.

Note : Earlier, we created the dll from the jars using ikvm-0.46.0.1. Now that jars are created using the java 8 version, I had to use the dot net nugget version because I could not locate an ikvm dll generating software that supported creating dlls for jars created with the java 8 version.

wasabii commented 1 year ago

So you haven't chosen to use a specific custom ClassLoader, so that's going to be your issue. By default, IkvmReference uses the default AssemblyClassLoader implementation, which delegates resource/class lookups based on .NET assembly references. If there ends up being no reference from one assembly to another, an attempt to lookup a Java class which is located in that assembly will fail.

In this case, it looks like commons-digester is attempting to load a class named Main.Policy.PolicyXML, which is in your code. commons-digester has no reference to your code (as it shouldn't), and so it can't find it.

What you'd likely want to do is either provide to the library the capability to lookup classes by name from a custom resolver of some kind (assuming that functionality exists, many libraries have this), or put all of the assemblies into a custom class loader capable of resolving outside simple .NET assembly references. For instance, ikvm.runtime.AppDomainAssemblyClassLoader. IkvmReference provides a property to alter the class loader implementation associated with an assembly (this is documented).

However, given that most of these references are located in Maven, I think it would make more sense for you to simply use Maven. MavenReference (https://github.com/ikvmnet/ikvm-maven). You'd have to put your own Java code either in Maven, or just build it using IKVM.NET.Sdk.

Maven is easier.