Jsource
What if I told you that the following is a bit of Java code?
require 'jsource'
jclass JsrcTest do
import %w|List ArrayList Map HashMap|.collect { |it| "java.util.#{it}" }
# have a public void say(String text) { ... }
say(:void, 'String text') {%|
println(text);
|}
# have a public static void main(String[] args) { ... }
# This uses an alternate form, instead of having the whole
# block be a string.
main {
c 'if (args.length < 1) {'
c ' println(main(3,14));'
c '} else {'
c ' new JsrcTest().say(args[0]);'
c '}'
}
# Manually make a method.
meth(:main,String,'int over, long loaded','static') {%|
// Just an overload.
return "Cor flipping blimey!";
|}
end
[download]
You’d probably say “you’re lying!”. And you’d be right too – that’s blatantly
not Java code, is it? But it could be, given the right jsource to require
up there in line one.
What am I on about now? Well, let’s cut to the chase:
# A Java class
class JavaClass
attr_accessor :name, :access, :superclass, :interfaces, :members, :package, :imports
def initialize(name, access = :public, superclass = nil, interfaces = nil, &block)
@name, @access, @superclass, @interfaces = name, access, superclass, interfaces
@members, @package, @imports = [], nil, []
instance_eval(&block) if block
end
# To Java source
def to_s
%Q[
#{"package #{package}" if package}
#{imports.collect { |it| "import #{it};" }.join("\n")}
#{access} class #{name}#{" extends #{superclass}" if superclass}#{" implements #{interfaces.join(', ')}" if interfaces} {
#{members.collect { |it| it.to_s }.join("\n")}
}\n]
end
# Add a memmber
def <<(member)
self.members << member
end
#--
# HELPERS FOR USE IN CLASS BLOCK
#++
# Set the package
def package(name = nil)
if name
@package = name
else
@package
end
end
# Add an import
def import(names)
@imports += names
end
# Create a method
def meth(name, ret_type = nil, args = [], access = :public,&block)
self << JavaMethod.new(name,ret_type,args, access,&block)
end
# create a standard static main method
def main(&block)
self << JavaMethod.new(:main,:void,'String[] args','public static',&block)
end
# allow methods to be defined like via method_missing too.
def method_missing(sym, *args, &block)
ret_type, arg_types, access = args
access ||= :public
meth(sym,ret_type,arg_types,access,&block)
end
#--
# COMMANDLINE
#++
def print(strm = 'STDOUT')
rstrm = eval(strm)
if rstrm.kind_of? IO
rstrm << self
else
rstrm && raise("'#{strm}' is a #{rstrm.class}, not a stream!") or raise("wtf is '#{strm}' supposed to mean?")
end
end
def dump(fn = ($0.gsub(/(.)\..*$/,'\1') + '.java'))
File.open(fn,'w+') do |f|
f << self.to_s
end
fn # allows use in compile
end
def compile(*opts)
exec "javac #{dump} #{opts.join(' ')}"
end
end
# A Java method
class JavaMethod
attr_accessor :name, :ret_type, :arg_types, :access
def initialize(name, ret_type = nil, arg_types = '', access = [:public], &block)
@name, @ret_type, @arg_types, @access, @code = name, ret_type, arg_types, access, []
c instance_eval(&block) if block
end
def each_line(&block)
self.code.each &block
end
# Get this method's Java code. Here is where we could do all
# the magic to replace Ruby idioms with Java idioms
def code
@code.collect { |it| it.gsub(/([^.])println/,'\1System.out.println') if it }
end
def code=(the_code)
@code = the_code
end
def to_s
%Q[#{access} #{ret_type || 'void'} #{name}(#{arg_types if arg_types}) {
#{self.code.join("\n")}
}]
end
def [](i)
self.code[i]
end
def []=(i,v)
self.code[i] = v
end
def <<(*s)
self.code += s
nil # so block using it doesn't also return it
end
alias :c :<<
end
# Whenever a missing const is requested, send back the requested
# const symbol as the const itself. *very* hacky but for this
# kind of toy it's probably okay...
class Module
def const_missing(sym)
sym
end
end
######
## Helper to create a class
def jclass(name, access = :public, superclass = nil, interfaces = nil, &block)
jc = JavaClass.new(name,access,superclass,interfaces,&block)
if (arg = ARGV[0])
jc.send(arg, *ARGV[1,ARGV.length])
else
jc.print
end
end
[download]
So it’s just a couple of classes and a helper (plus a bit of module hackery), but already it’s the beginnings of Java code that doesn’t take months to type. Obviously it’s more difficult to help with actual method code, but it could be done – I had the idea of supporting stuff like hash and array literals, auto-parens on method calls, and a whole lot of other stuff.
The thing that (I think) is interesting about this (apart from the beautiful
meta-programming stuff that Ruby supports) is the fact that it not only
describes the Java source, but writes it directly. With a bit of judicious
use of send and the command-line, I can easily set up to have the code
dumped out to stdout, a file, or even compiled directly. If you’ve gone
to the trouble of downloading both sources, save ‘em somewhere, go there,
and do:
./JsrcTest.rb
and you should get a screenful of really messy (but valid) Java code. Better still,
./JsrcTest.rb dump
will write a file (JsrcTest.java), and
./JsrcTest.rb compile
Will run javac automagically for you.
And that, as they say, is that. Something to play with for half an hour,
anyway… It could easily emulate attr_accessor et. al (and generate
bean getters and setters), and I imagine quite a few other standard
patters. I wonder exactly how much like Ruby we could make our Java…

