What causes a Java library to behave differently when called by JRuby?
Asked Answered
A

1

7

I am new to the Java world, but am familiar with Ruby. I am trying to write a program that interacts with some third-party jar files.

While the libraries seem to behave fine if called from Java, they behave incorrectly when I call them in JRuby. This is a problem because I would really like to use JRuby. For example, the two programs below try to do exactly the same thing but they produce different output:

This Java program behaves correctly.

I developed the Java program below in Netbeans and ran it by pressing F6 (Run Main Project). The Libraries folder for the project is set to "C:\Program Files (x86)\Microchip\MPLABX\mplab_ide\lib\nblibraries.properties". When I run it, it prints "pins: 17".

package pinbug1;    
import com.microchip.mplab.mdbcore.assemblies.Assembly;
import com.microchip.mplab.mdbcore.assemblies.AssemblyFactory;
import com.microchip.mplab.mdbcore.simulator.PinSet;
import com.microchip.mplab.mdbcore.simulator.Simulator;
import org.openide.util.Lookup;

public class PinBug1
{
    public static void main(String[] args)
    {
        AssemblyFactory assemblyFactory = Lookup.getDefault().lookup(AssemblyFactory.class);
        Assembly assembly = assemblyFactory.Create("PIC18F14K50");

        Simulator simulator = assembly.getLookup().lookup(Simulator.class);
        int num = simulator.getDataStore().getProcessor().getPinSet().getNumPins();
        System.out.println("pins: " + num);   // prints "pins: 17"
    }

}

This JRuby program behaves incorrectly.

I ran the JRuby program below by just typing jruby bug_reproduce.rb and it printed "pins: 0". I would expect it to print "pins: 17" like the Java program.

["mplab_ide/mdbcore/modules/*.jar",
 "mplab_ide/mplablibs/modules/*.jar",
 "mplab_ide/mplablibs/modules/ext/*.jar",
 "mplab_ide/platform/lib/org-openide-util*.jar",
 "mplab_ide/mdbcore/modules/ext/org-openide-filesystems.jar"
].each do |pattern|
  Dir.glob("C:/Program Files (x86)/Microchip/MPLABX/" + pattern).each do |x|
    require x
  end
end

assemblyFactory = org.openide.util.Lookup.getDefault.lookup(com.microchip.mplab.mdbcore.assemblies.AssemblyFactory.java_class)
assembly = assemblyFactory.create("PIC18F14K50")
simulator = assembly.getLookup.lookup(com.microchip.mplab.mdbcore.simulator.Simulator.java_class)
num = simulator.getDataStore.getProcessor.getPinSet.getNumPins
puts "pins: #{num}"    # => pins: 0

More details

There are about 80 third-party jar files. They are provided by Microchip as part of MPLAB X and implement a simulator for their microcontrollers. The jar files come with MPLAB X and I also downloaded the MPLAB X SDK to get help with using them. I am using lots of undocumented features of the libraries, but I don't see any alternative.

I am using Windows 7 64-bit SP1. I have the following Java-related things installed and listed under "Programs and Features":

  • Java 7 Update 17
  • Java 7 Update 17 (64-bit)
  • Java SE Development Kit 7 Update 17 (64-bit)
  • Java(TM) 6 Update 22 (64-bit)
  • Java(TM) 6 Update 29
  • Java(TM) SE Development Kit 6 Update 22 (64-bit)
  • JRuby 1.7.3
  • IntelliJ IDEA Community Edition 12.0.4
  • Netbeans IDE 7.3
  • MPLAB X IDE v1.70

I used System.getProperty("java.version") to verify that both of my programs are running under Java 1.6.0_22. That is good, because I followed the instructions in the MPLAB X SDK that say "For best results, use the exact same JDK that built the IDE/MDBCore your code will be talking to. For MPLAB X v1.70, this is JDK 6u22 from Oracle." I only installed JDK 7u17 after I encountered this problem, and it didn't make a difference.

I was able to find a workaround to the specific problem identified in the examples, but then I continued my development and ran into another problem where the libraries behaved differently. This makes me think that I am doing something fundamentally wrong in the way I use JRuby.

Thinking that a differing class path might cause this problem, I tried getting the java program to print out its class path and then edited my JRuby program to require precisely the files in that list, but it made no difference.

Questions

  • Do you know of anything that might cause code in JAR files to behave differently when called from JRuby instead of Java?
  • What version of the JDK does JRuby 1.7.3 use, or does that question even make sense?

Update: SOLVED

Thanks to D3mon-1stVFW for actually getting MPLAB X and solving my problem for me! For those who are interested in the nitty gritty details: The number of pins was 0 because the pins are lazy loaded when they are accessed with PinSet.getPin(String). Normally all pins would have been loaded because the peripherals load them, but under JRuby no peripherals were detected. This is because the periphal document could not be found. This is because PerDocumentLocator.findDocs() returned an empty list. PerDocumentLocator failed because com.microchip.mplab.open.util.pathretrieval.PathRetrieval.getPath(com.microchip.mplab.libs.MPLABDocumentLocator.MPLABDocumentLocator.class)) was returning the wrong thing.

Consider the following code, which is similar to what is happening inside PathRetrieval.getPath (except there it was written in Java):

com.microchip.mplab.libs.MPLABDocumentLocator.MPLABDocumentLocator.java_class.resource("MPLABDocumentLocator.class").getFile()

If I follow D3mon-1stVFW's tip and add JAR files to the $CLASSPATH, then that code returns:

file:C:/Program Files (x86)/Microchip/MPLABX/mplab_ide/mplablibs/modules/com-mi crochip-mplab-libs-MPLABDocumentLocator.jar!/com/microchip/mplab/libs/MPLABDocum entLocator/MPLABDocumentLocator.class

However, if I don't add things to the class path, then that code strangely returns:

file:C:%5CProgram%20Files%20(x86)%5CMicrochip%5CMPLABX%5Cmplab_ide%5Cmplablibs% 5Cmodules%5Ccom-microchip-mplab-libs-MPLABDocumentLocator.jar!/com/microchip/mpl ab/libs/MPLABDocumentLocator/MPLABDocumentLocator.class"

The %5C is actually the code for a backslash. The Microchip code in PathRetrieval.getPath does a lot of string manipulation and does not properly handle the case where slashes are represented by %5C. If anyone has any further insight about why the %5Cs are appearing, I would be interested to know, but my problem is solved.

Conclusion: Sometimes Java's getResource() returns a URL with %5C instead of slashes in it and this is affected by what is on the CLASSPATH. If you want to be safe, add the jar file to $CLASSPATH before requiring it, like this:

require 'java'
$CLASSPATH << jar_filename
require jar_filename
Antecede answered 3/4, 2013 at 17:35 Comment(7)
JRuby uses the JDK installed in the system. I think you might install it with a built-in JDK if you wish.Neely
I notice that you call assemblyFactory.Create(...), with a capitalized method name, in Java, but assemblyFactory.create(...) in Ruby. I know JRuby converts inner caps (camel case) into underscore + small letters, but I'm not sure how it works with initial caps.Hort
The original question as it stands seems like the poster boy for "too localised" to me, seeing as the answer was "a config file is missing from $CLASSPATH". (Now, "why is my resource not being located correctly?" / "why is MPLab mangling the classpath and how to make it stop?" is a valid, general question, but it's also a different question than the original one, and could use a test case that's not specific to this toolkit.)Brickey
I oppose closing the question. You should see my conclusion at the end, which will be helpful to future visitors.Antecede
The conclusion is that sometimes Java's Class.getResource() method returns a URL with %5C instead of slashes in it and this is affected by what is on the class path. It's not just that some config file was missing from the class path. It makes me think that in general, when using JRuby, you should always add jar files to the $CLASSPATH before requiring them.Antecede
Also, I tried to pose the question in a general way that is useful to everyone, which was "Do you know of anything that might cause code in JAR files to behave differently when called from JRuby instead of Java?"Antecede
The thing is it doesn't seem to me like code in JAR files does behave differently between the two environments. It's more that you misunderstood require to be equivalent to "loading a JAR", when require seems to be strictly a JRuby-specific mechanism that doesn't really affect how standard JVM functionality (i.e. loading resources from a classloader) works. So, I think my point remains, there is a valid question behind this one, but as stated this isn't it considering the answer was reached by what seems like trial and error.Brickey
B
4

I was able to get the expected results using this implementation. The main difference in this implantation is adding the jars to the classpath. If you comment this line ($CLASSPATH << jar_file) you will get 0 pins. (Explanation in the bottom of the question)

require 'java'

Dir.glob("C:/MyCustomLibraries/MATLAB/*.jar").each do |jar_file| #Has all MPLab jars except org.netbeans.*
  $CLASSPATH << jar_file
  require jar_file
end

module Mplab
  include_package "org.openide.util" #Lookup
  include_package "com.microchip.mplab.mdbcore.simulator" #PinSet, Simulator
  include_package "com.microchip.mplab.mdbcore.assemblies" #Assembly, AssemblyFactory
end

assembly_factory = Mplab::Lookup.getDefault.lookup(Mplab::AssemblyFactory.java_class)
assembly = assembly_factory.create("PIC18F14K50")
simulator = assembly.getLookup.lookup(Mplab::Simulator.java_class)
num = simulator.getDataStore.getProcessor.getPinSet.getNumPins
puts "pins: #{num}" 

outputs

content/mplab/mplab.deviceSupport

content/mplab/MPHeader.xml

content/mplab/PluginBoardSupport.xml

pins: 17

Bryonbryony answered 3/4, 2013 at 21:42 Comment(2)
Wow, I was only expecting to get some general tips, but you actually solved my whole problem. Thanks!Antecede
For a more correct explanation of what was happening, which I figured out using jdb, people should read the "Update: SOLVED" section in the question above.Antecede

© 2022 - 2024 — McMap. All rights reserved.