torsdag, augusti 17, 2006

The JRuby Tutorial #1.5: Using JIRB to check Java behaviour

I was planning on this issue to be on Camping, but I realized that it would be good to show how to use IRB with JRuby to make life as a Java developer easier. Since Java is a compiled language, it's exceedingly hard to just test things out. My hard drive is completely littered with 5-line programs testing one aspect of the Java libraries or whatnot. If I could have an easy way to interface with the libraries without having to go through a compile cycle, this would ease my life very much. And actually, the last months I have been using JIRB in this way. Of course there is BeanShell and Groovy, but I feel they get in the way. And I'm very fond of IRB, of course.

IRB stands for Interactive Ruby, and JIRB is the name of the program for starting IRB with JRuby. Starting it should be as easy as:
%JRUBY_HOME%\bin\jirb
and after two or three seconds you will get the Ruby prompt:
irb(main):001:0>
At this point, the first thing to do is to require the java libraries, so we can have something to work with:
irb(main):001:0> require 'java'
=> true
I would first like to show how I would go about finding the behaviour of the caret in a Java regexp. (I know I could find it in the API, but this is just to show how you could do it.)

First of all, I will include the relevant class and see what I can do:
irb(main):002:0> include_class 'java.util.regex.Pattern'
=> ["java.util.regex.Pattern"]
irb(main):003:0> Pattern.new
NameError: wrong # of arguments for constructor
from D:\Project\jruby-trunk\src\builtin\javasupport.rb:231:in `initialize'
from (irb):1:in `new'
from (irb):1:in `binding'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:150:in `eval_input'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:70:in `signal_status'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:189:in `eval_input'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:70:in `each_top_level_statement'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:190:in `loop'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:190:in `catch'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:190:in `eval_input'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:70:in `start'
from D:\project\jruby-trunk\bin\jirb:13:in `catch'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:71:in `start'
from D:\project\jruby-trunk\bin\jirb:13
irb(main):004:0> Pattern.methods
=> ["matches", "quote", "compile", "setup_constants", "setup", "singleton_class", "setup_attributes", "new_instance_for", "[]", "const_missing" ...]
irb(main):005:0> Pattern.methods.grep /compile/
=> ["compile"]
Ok, first I include the class, then I try it out. Whoopsie, there was no empty constructor for Pattern. I'm don't really remember what methods are in there, though, so I check out which methods Pattern have. There is a good many of them (and I haven't written them all out either), so I try again by grepping for a method name I believe is in there. Once I've found the method I'm looking for, I call it with the pattern I want to compile, and inspect the result in various ways. I vaguely remember a matcher method, and it seems to work as I remember:
irb(main):008:0> pt = Pattern.compile("^foo")
=> ^foo
irb(main):009:0> pt.inspect
=> "^foo"
irb(main):010:0> pt.java_class
=> java.util.regex.Pattern
irb(main):011:0> pt.class
=> Pattern
irb(main):012:0> m = pt.matcher("foo")
=> java.util.regex.Matcher[pattern=^foo region=0,3 lastmatch=]
Ok, now I have something to work with. Good, and the standard output from JRuby gives me some insight into the internals of the matcher too; interesting stuff. There is a find method, and a matches method on the Matcher:
irb(main):013:0> m.find
=> true
irb(main):014:0> m.find
=> false
irb(main):015:0> m.matches
=> true
irb(main):016:0> m.search
NoMethodError: undefined method `search' for java.util.regex.Matcher[pattern=^foo region=0,3 lastmatch=foo]:#
from (irb):1:in `method_missing'
from (irb):1:in `binding'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:150:in `eval_input'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:70:in `signal_status'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:189:in `eval_input'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:70:in `each_top_level_statement'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:190:in `loop'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:190:in `catch'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:190:in `eval_input'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:70:in `start'
from D:\project\jruby-trunk\bin\jirb:13:in `catch'
from D:/project/jruby-trunk/lib/ruby/1.8/irb.rb:71:in `start'
from D:\project\jruby-trunk\bin\jirb:13
I also belived there was a search method, and also a way to get back to the initial state of the Matcher. Hmm. Time to check out the methods of this object too:
irb(main):017:0> m.methods
=> ["hashCode", "start", "matches", "useAnchoringBounds", "region", "reset"...] irb(main):019:0> m.reset
=> java.util.regex.Matcher[pattern=^foo region=0,3 lastmatch=]
irb(main):020:0> m.find
=> true
irb(main):021:0> m.group 0
=> "foo"
irb(main):022:0>
I found the method I was looking for; reset, calls it and tries find again, and then gets the group with index 0, which is the complete match.

Some Swinging
JIRB works great for trying out Swing interactively. I'm just going to show a tidbit of this, and since this is one of the more common examples of JRuby, you will be able to find many different demonstrations of this on the net.

We first require java-support, and including the required classes for this example:
irb(main):001:0> require 'java'
=> true
irb(main):002:0> include_class(['javax.swing.JFrame','javax.swing.JLabel','javax.swing.JButton','javax.swing.JPanel'])
=> ["javax.swing.JFrame", "javax.swing.JLabel", "javax.swing.JButton", "javax.swing.JPanel"]

Then we create a frame and some panels:
irb(main):003:0> frame = JFrame.new("JRuby panel")
=> javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=JRuby panel,resizable,normal,defaultCloseOperation=HI
DE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,fl
ags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
irb(main):004:0> basePanel = JPanel.new
=> javax.swing.JPanel[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,pr
eferredSize=]
irb(main):005:0> labelPanel = JPanel.new
=> javax.swing.JPanel[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,pr
eferredSize=]
irb(main):006:0> buttonPanel = JPanel.new
=> javax.swing.JPanel[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,pr
eferredSize=]
After this, we add some labels and some buttons to the panes, and then add the panes to the base pane, and the base pane to the frame.
irb(main):007:0> labelPanel.add JLabel.new("Isn't JRuby cool?")
=> javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultI
con=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=Isn't JRuby cool?,verticalAlignm
ent=CENTER,verticalTextPosition=CENTER]
irb(main):008:0> labelPanel.add JLabel.new("JIrb is very useful")
=> javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultI
con=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=JIrb is very useful,verticalAlig
nment=CENTER,verticalTextPosition=CENTER]
irb(main):009:0> buttonPanel.add JButton.new("Pushbutton without action")
=> javax.swing.JButton[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource@1354
355,flags=296,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIReso
urce[top=2,left=14,bottom=2,right=14],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=
,selectedIcon=,text=Pushbutton without action,defaultCapable=true]
irb(main):010:0> basePanel.add labelPanel
=> javax.swing.JPanel[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,pr
eferredSize=]
irb(main):011:0> basePanel.add buttonPanel
=> javax.swing.JPanel[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,pr
eferredSize=]
irb(main):012:0> frame.add basePanel
=> javax.swing.JPanel[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,pr
eferredSize=]
Then it's just to pack the contents and show the frame:
irb(main):013:0> frame.pack
=> nil
irb(main):014:0> frame.show
=> nil
To continue playing the Swing, just poke away at the methods. You can always change it if something goes wrong. After you're finished you can dump most of the structure very easy. Just remember to call show again to see some of the updates. Certain changes call redraw directly, but mostly it's always a good choice to call show after updates on the structures.

3 kommentarer:

Anonym sa...

Ola,
there's also an EclipsePlugin
http://eclipse-shell.sourceforge.net/
that offers interactive access to JRuby.
It can script Eclipse plugins, but you can also launch a seperate JRuby instance and remote control it.
The benefits of EclipseShell are things like AutoComplete and that you can easily edit multi line code fragments, because the interface is actually an editor (a bit like similar tools in Smalltalk).

murphee

Disclaimer: I'm the maintainer of EclipseShell.

Ola Bini sa...

Thanks for the information, Murphee, I didn't know this. Of course, IRB has autocompletion too (by require 'irb/completion'). I am an avid Emacs user, so I tend to start jirb-shells inside of Emacs, thereby getting some of the editing benefits too.

Unknown sa...

Trying to get auto-completion to work I tried your require. I get the error below. auto completion works in my "normal" irb but I suppose there should be a jruby implementation of readline?

C:\marcus\devtools\jruby>bin/jirb
'bin' is not recognized as an internal or external command,
operable program or batch file.

C:\marcus\devtools\jruby>bin\jirb
irb(main):001:0> require 'irb/completion'
LoadError: No such file to load -- readline
from (irb):1:in `require'
from (irb):1
from (irb):1:in `require'
from (irb):1:in `binding'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:150:in `eval_input'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:70:in `signal_status'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:189:in `eval_input'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:70:in `each_top_level_statement'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:190:in `loop'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:190:in `catch'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:190:in `eval_input'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:70:in `start'
from C:\marcus\devtools\jruby\bin\jirb:13:in `catch'
from C:/marcus/devtools/jruby/lib/ruby/1.8/irb.rb:71:in `start'
from C:\marcus\devtools\jruby\bin\jirb:13