Sunday, September 25, 2011

Using the jruby eclipse plugin to generate an interface implemention

I started using asm to put together some ruby code generation scripts for the Eclipse plugin.  This one prints out a list of method definitions and their java signatures when you select an interface in the package explorer. If you select this interface
package com.restphone.jrubyeclipse;

import org.eclipse.jface.viewers.ISelection;

public interface IJrubyFilter {
  String do_filter(ISelection s);

  Integer someOtherMethod();
}
You'll get this output:
java_signature "java.lang.String do_filter (org.eclipse.jface.viewers.ISelection a)"
def do_filter *args
end

java_signature "java.lang.Integer someOtherMethod ()"
def someOtherMethod *args
end
Checked in to github as ruby_filter_interface.rb.

Friday, September 23, 2011

More JRuby + asm

Turns out you can get this jruby from yesterday's post even more concise:
require 'java'
require 'asm-3.3.1'
require 'pp'

# This lets me type org.objectweb.asm::... instead of Java::OrgObjectwebAsm::...
def org
  Java::Org
end

class SampleVisitor
  include org.objectweb.asm::MethodVisitor
  include org.objectweb.asm::ClassVisitor
  include org.objectweb.asm::FieldVisitor
  include org.objectweb.asm::AnnotationVisitor
  include org.objectweb.asm.signature::SignatureVisitor

  def method_missing name, *args
    name_string = name.to_s
    if name_string =~ /visit.*/
      puts "#{name}\t#{args}"
      self
    else
      super
    end
  end
end

# Loop through each .class file given on the command line
ARGV.each do |classfile|
  puts " ---------------- Reading file #{classfile}"
  f = java.io.File.new classfile
  fis = java.io.FileInputStream.new f
  
  class_reader = org.objectweb.asm::ClassReader.new fis
  
  v = SampleVisitor.new
  
  class_reader.accept v, 0
  
  puts
end
And the output is:
---------------- Reading file /Users/james/experimements/AsmSample/bin/com/restphone/classSignature/SampleOne.class
visit   [50, 33, "com/restphone/classSignature/SampleOne", nil, "java/lang/Object", #<#<Class:0x1144f3ba2>:0x2f6a23cf>]
visitSource     ["SampleOne.java", nil]
visitField      [9, "x", "Ljava/lang/Integer;", nil, nil]
visitEnd        []
visitMethod     [8, "<clinit>", "()V", nil, nil]
visitCode       []
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x2b071e12>]
visitLineNumber [4, #<Java::OrgObjectwebAsm::Label:0x2b071e12>]
visitFieldInsn  [178, "com/restphone/classSignature/SampleTwo", "y", "Ljava/lang/Integer;"]
visitFieldInsn  [179, "com/restphone/classSignature/SampleOne", "x", "Ljava/lang/Integer;"]
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x575c13ef>]
visitLineNumber [3, #<Java::OrgObjectwebAsm::Label:0x575c13ef>]
visitInsn       [177]
visitMaxs       [1, 0]
visitEnd        []
visitMethod     [1, "<init>", "()V", nil, nil]
visitCode       []
visitLabel      [#<Java::OrgObjectwebAsm::Label:0xc303a60>]
visitLineNumber [3, #<Java::OrgObjectwebAsm::Label:0xc303a60>]
visitVarInsn    [25, 0]
visitMethodInsn [183, "java/lang/Object", "<init>", "()V"]
visitInsn       [177]
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x66869470>]
visitLocalVariable      ["this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0xc303a60>, #<Java::OrgObjectwebAsm::Label:0x66869470>, 0]
visitMaxs       [1, 1]
visitEnd        []
visitMethod     [1, "doubleTheValue", "(Ljava/lang/Integer;)Ljava/lang/Integer;", nil, nil]
visitCode       []
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x10fa706d>]
visitLineNumber [7, #<Java::OrgObjectwebAsm::Label:0x10fa706d>]
visitVarInsn    [25, 1]
visitMethodInsn [182, "java/lang/Integer", "intValue", "()I"]
visitInsn       [5]
visitInsn       [104]
visitMethodInsn [184, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"]
visitInsn       [176]
visitLabel      [#<Java::OrgObjectwebAsm::Label:0x629a99eb>]
visitLocalVariable      ["this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x10fa706d>, #<Java::OrgObjectwebAsm::Label:0x629a99eb>, 0]
visitLocalVariable      ["x", "Ljava/lang/Integer;", nil, #<Java::OrgObjectwebAsm::Label:0x10fa706d>, #<Java::OrgObjectwebAsm::Label:0x629a99eb>, 1]
visitMaxs       [2, 2]
visitEnd        []
visitEnd        []
I'm including the jars on the command line:
jruby -I /Users/james/experimements/AsmSample/libs --1.9 asm_support.rb /Users/james/experimements/AsmSample/bin/com/restphone/classSignature/SampleOne.class  | less

Thursday, September 22, 2011

JRuby + Asm: easy processing of .class files

I started working with Asm this week.  After playing with it for a bit, it seemed obvious that JRuby would be a sweet spot for Asm code; very often, you need to implement only a small fraction of the interfaces that Asm uses.

Here’s my first JRuby effort with Asm.  It’s straightforward and doesn’t do much yet – just outputs the contents of the .class files given on the command line.  Here’s the JRuby:

require 'java'
require 'asm-3.3.1'
require 'pp'

# This lets me type org.objectweb.asm::... instead of Java::OrgObjectwebAsm::...
def org
  Java::Org
end

# This lets me use ClassReader without typing out org.objectweb.asm::ClassReader
java_import org.objectweb.asm::ClassReader

# Ruby supports mixins.  Both visitor classes (class and method) use this method_missing
# call.
#
# The generic visitor just prints information for any call to a visitor* method.
module GenericVisitor
  def method_missing *args
    if args.first.to_s =~ /visit.*/
      puts "In type: #{self.class} call to " + args.to_s
    else
      super
    end
  end
end

class SampleMethodVisitor
  include org.objectweb.asm::MethodVisitor
  include GenericVisitor
end

class SampleClassVisitor
  include org.objectweb.asm::ClassVisitor
  include GenericVisitor
  
  # we need to keep a copy of our method visitor
  def initialize args
    @method_visitor = args[:method_visitor]
  end
  
  def visit_method *args
    # The output has methods seperated with an easy-to-see string
    puts "visit method " + args.to_s + "================================"
      
    # Asm wants ClassVisitor#visitMethod to return a method visitor 
    @method_visitor
  end
end

# Loop through each .class file given on the command line
ARGV.each do |classfile|
  puts " ---------------- Reading file #{classfile}"
  f = java.io.File.new classfile
  fis = java.io.FileInputStream.new f
  
  class_reader = ClassReader.new fis
  
  v = SampleClassVisitor.new method_visitor: SampleMethodVisitor.new
  
  class_reader.accept v, 0
  
  puts
end

And here’s the output:

---------------- Reading file /Users/james/experimements/AsmSample/bin/com/restphone/classSignature/SampleOne.class
In type: SampleClassVisitor call to [:visit, 50, 33, "com/restphone/classSignature/SampleOne", nil, "java/lang/Object", #<#<Class:0x12690ed81>:0x28b53b32>]
In type: SampleClassVisitor call to [:visitSource, "SampleOne.java", nil]
In type: SampleClassVisitor call to [:visitField, 9, "x", "Ljava/lang/Integer;", nil, nil]
visit method [8, "<clinit>", "()V", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x236954e1>]
In type: SampleMethodVisitor call to [:visitLineNumber, 4, #<Java::OrgObjectwebAsm::Label:0x236954e1>]
In type: SampleMethodVisitor call to [:visitFieldInsn, 178, "com/restphone/classSignature/SampleTwo", "y", "Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitFieldInsn, 179, "com/restphone/classSignature/SampleOne", "x", "Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x643f58bb>]
In type: SampleMethodVisitor call to [:visitLineNumber, 3, #<Java::OrgObjectwebAsm::Label:0x643f58bb>]
In type: SampleMethodVisitor call to [:visitInsn, 177]
In type: SampleMethodVisitor call to [:visitMaxs, 1, 0]
In type: SampleMethodVisitor call to [:visitEnd]
visit method [1, "<init>", "()V", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>]
In type: SampleMethodVisitor call to [:visitLineNumber, 3, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>]
In type: SampleMethodVisitor call to [:visitVarInsn, 25, 0]
In type: SampleMethodVisitor call to [:visitMethodInsn, 183, "java/lang/Object", "<init>", "()V"]
In type: SampleMethodVisitor call to [:visitInsn, 177]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x13ad9b0f>]
In type: SampleMethodVisitor call to [:visitLocalVariable, "this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x1d7aaa0e>, #<Java::OrgObjectwebAsm::Label:0x13ad9b0f>, 0]
In type: SampleMethodVisitor call to [:visitMaxs, 1, 1]
In type: SampleMethodVisitor call to [:visitEnd]
visit method [1, "doubleTheValue", "(Ljava/lang/Integer;)Ljava/lang/Integer;", nil, nil]================================
In type: SampleMethodVisitor call to [:visitCode]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x9eae15f>]
In type: SampleMethodVisitor call to [:visitLineNumber, 7, #<Java::OrgObjectwebAsm::Label:0x9eae15f>]
In type: SampleMethodVisitor call to [:visitVarInsn, 25, 1]
In type: SampleMethodVisitor call to [:visitMethodInsn, 182, "java/lang/Integer", "intValue", "()I"]
In type: SampleMethodVisitor call to [:visitInsn, 5]
In type: SampleMethodVisitor call to [:visitInsn, 104]
In type: SampleMethodVisitor call to [:visitMethodInsn, 184, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"]
In type: SampleMethodVisitor call to [:visitInsn, 176]
In type: SampleMethodVisitor call to [:visitLabel, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>]
In type: SampleMethodVisitor call to [:visitLocalVariable, "this", "Lcom/restphone/classSignature/SampleOne;", nil, #<Java::OrgObjectwebAsm::Label:0x9eae15f>, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>, 0]
In type: SampleMethodVisitor call to [:visitLocalVariable, "x", "Ljava/lang/Integer;", nil, #<Java::OrgObjectwebAsm::Label:0x9eae15f>, #<Java::OrgObjectwebAsm::Label:0x2569a1c5>, 1]
In type: SampleMethodVisitor call to [:visitMaxs, 2, 2]
In type: SampleMethodVisitor call to [:visitEnd]
In type: SampleClassVisitor call to [:visitEnd]

Incidentally, I used my new Eclipse plugin to send the current selection through a filter to html-escape the text.  Check it out on github.