TITLE:JRuby

Posted by at 2012-07-19

JRuby で Swing などのサンプル

概要

環境変数 JAVA_HOME, JRUBY_HOME を設定し、パスを通しておく。

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_04
> set JRUBY_HOME=C:\jruby-1.7.0
> set Path=%JAVA_HOME%\bin;%JRUBY_HOME%\bin;%Path%
> jruby -version
jruby 1.7.0.preview1 (ruby-1.9.3-p203) (2012-05-19 00c8c98) (Java HotSpot(TM) 64-Bit Server VM 1.7.0_05) [Windows 7-amd64-java]

Swing + JRuby サンプル

EDT で起動(JFrameを表示)

  • Event Dispatch Thread で起動するために、self が java.lang.Runnable の runメソッドを実装し、java.awt.EventQueue.invokeLater で実行している
  • require "java" より include Java が良い?
  • import より java_import javax.swing.JLabel が良い?
  • 適当なファイル名で保存して、 jruby Hoge.rb などで実行
include Java

java_import javax.swing.JLabel
java_import javax.swing.UIManager
java_import javax.swing.WindowConstants
def create_and_show_GUI
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  frame = javax.swing.JFrame.new "Title"
  frame.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  frame.content_pane.add JLabel.new "Test"
  frame.set_size(320, 240)
  frame.location_relative_to = nil
  frame.visible = true
end
def run
  create_and_show_GUI
end
java.awt.EventQueue.invokeLater self

クラスの継承(JPanelを継承)

  • class MainPanel < JPanel
include Java
java_import java.awt.Dimension
java_import javax.swing.JPanel
java_import javax.swing.JTextField

class MainPanel < JPanel
  def initialize
    super
    field = JTextField.new 32
    self.add field
    self.preferred_size = Dimension.new(320, 240)
  end
end

java_import javax.swing.UIManager
java_import javax.swing.WindowConstants
def create_and_show_GUI
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  frame = javax.swing.JFrame.new "Title"
  frame.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  frame.content_pane.add MainPanel.new
  frame.pack
  frame.location_relative_to = nil
  frame.visible = true
end
def run
  create_and_show_GUI
end
java.awt.EventQueue.invokeLater self

無名インナークラス(JButtonにActionListenerを追加)

include Java

java_import java.awt.Dimension
java_import javax.swing.JPanel
java_import javax.swing.JButton
java_import javax.swing.JTextField

class MainPanel < JPanel
  def initialize
    super
    field = JTextField.new 32
    button = JButton.new "add a"
    button.add_action_listener { |e|
      puts e
      field.text = field.text + "a"
    }
    self.add field
    self.add button
    self.preferred_size = Dimension.new(320, 240)
  end
end

java_import javax.swing.UIManager
java_import javax.swing.WindowConstants
def create_and_show_GUI
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  frame = javax.swing.JFrame.new "JRuby Swing JButton ActionListener"
  frame.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  frame.content_pane.add MainPanel.new
  frame.pack
  frame.location_relative_to = nil
  frame.visible = true
end
def run
  create_and_show_GUI
end
java.awt.EventQueue.invokeLater self

インタフェースの実装(MouseListenerを追加)

  • java_implements はもう使用できない?
    • 代わりに class MainPanel < JPanel; include MouseListener
    • カンマ区切りで複数指定しても可 class MainPanel < JPanel; include MouseListener, ActionListener
  • instanceof
    • self.java_kind_of?(MouseListener)
# -*- encoding: utf-8 -*-
include Java

java_import java.awt.event.MouseListener
java_import java.awt.Dimension
java_import javax.swing.JPanel
java_import javax.swing.JButton
java_import javax.swing.JTextField

class MainPanel < JPanel
                include MouseListener
  def initialize
    super
    field = JTextField.new 32
    button = JButton.new "add a"
    button.add_action_listener {
      field.text = field.text + "a"
    }

#     # https://blogs.oracle.com/nishigaya/entry/tips_for_accessing_java_objects2
#     button.add_mouse_listener do |ev|
#       p ev
#     end

#     #:1 warning: singleton on non-persistent Java type #<Class:0x103bdaa8> (http://wiki.jruby.org/Persistence)
#     class << listener = java.awt.event.MouseListener.new
#       def mouseEntered(e)
#         puts "mouseEntered"
#       end
#       def mouseExited(e)
#         puts "mouseExited"
#       end
#       def mousePressed(e)
#         puts "mousePressed"
#       end
#       def mouseClicked(e)
#         puts "mouseClicked"
#       end
#       def mouseReleased(e)
#         puts "mouseReleased"
#       end
#     end
#     button.add_mouse_listener listener

    # puts self.java_kind_of?(MouseListener)
    button.add_mouse_listener self

    self.add field
    self.add button
    self.preferred_size = Dimension.new(320, 240)
  end

  # MouseListener
  def mouseEntered(e)
    puts "mouseEntered"
  end
  def mouseExited(e)
    puts "mouseExited"
  end
  def mousePressed(e)
    puts "mousePressed"
  end
  def mouseClicked(e)
    puts "mouseClicked"
  end
  def mouseReleased(e)
    puts "mouseReleased"
  end
end

java_import javax.swing.UIManager
java_import javax.swing.WindowConstants
def create_and_show_GUI
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  frame = javax.swing.JFrame.new "JRuby Swing JButton ActionListener"
  frame.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  frame.content_pane.add MainPanel.new
  frame.pack
  frame.location_relative_to = nil
  frame.visible = true
end
def run
  create_and_show_GUI
end
java.awt.EventQueue.invokeLater self

配列(JTableに行追加)

  • ["No.", "Name", "Path"].to_java でrubyの配列をjavaの配列に変換
# -*- encoding: utf-8 -*-
include Java
java_import javax.swing.UIManager
java_import javax.swing.WindowConstants
java_import javax.swing.table.DefaultTableModel
def make_ui
  m = DefaultTableModel.new(nil, ["No.", "Name", "Path"].to_java)
  m.add_row java.util.Vector.new(["テスト", "あああ", "いいいい"])
  m.add_row ["aaa", "bbb", "ccc"].to_java
  t = javax.swing.JTable.new m
  p = javax.swing.JPanel.new java.awt.BorderLayout.new
  p.add javax.swing.JScrollPane.new(t)
  return p
end
def run #Override
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  f = javax.swing.JFrame.new "jruby swing"
  f.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  f.content_pane.add make_ui
  f.set_size(320, 240)
  f.location_relative_to = nil
  f.visible = true
end
java.awt.EventQueue.invoke_later self

JRubyでリソース(URL)のエントリを取得してSwingで使用する

ソースコードの文字コード(エンコーディング)

# -*- encoding: utf-8 -*-
include Java
java_import javax.swing.UIManager
java_import javax.swing.WindowConstants
java_import javax.swing.table.DefaultTableModel
def make_ui
  p = javax.swing.JPanel.new java.awt.BorderLayout.new
  p.add javax.swing.JLabel.new("日本語")
  return p
end
def run #Override
  begin
    UIManager.look_and_feel = UIManager.system_look_and_feel_class_name
  rescue Exception => e
    proxied_e = JavaUtilities.wrap e.cause
    proxied_e.print_stack_trace
  end
  f = javax.swing.JFrame.new "タイトル"
  f.default_close_operation = WindowConstants::EXIT_ON_CLOSE
  f.content_pane.add make_ui
  f.set_size(320, 240)
  f.location_relative_to = nil
  f.visible = true
end
java.awt.EventQueue.invoke_later self

その他、JRuby + One-Liner などのサンプル

JRuby One-Liner で、UIManagerのDefaults.keySetを一覧表示する

> jruby -rjava -e "javax.swing.UIManager.lookAndFeelDefaults.keySet.each{|o| puts o}"

One-Liner での文字コード

> chcp
現在のコード ページ: 932
> jruby -J-Dfile.encoding=UTF-8 -rjava -e 'p java.net.URLEncoder.encode("日本語","UTF-8")'
> jruby -J-Dfile.encoding=UTF-8 -rjava -e "puts java.net.URLEncoder.encode('日本語','UTF-8')"
> jruby -J-Dfile.encoding=UTF-8 -rjava -e 'puts java.net.URLEncoder.encode(""日本語"",""UTF-8"")'
> jruby --1.9 -U -rjava -e 'puts java.net.URLEncoder.encode("日本語","UTF-8")'
> jruby -J-Dfile.encoding=UTF-8 -ruri -e 'puts URI.encode("日本語")'
%E6%97%A5%E6%9C%AC%E8%AA%9E
> jruby --1.9 -rjava -e 'puts java.net.URLEncoder.encode("日本語","MS932")'
> jruby --1.9 -ruri -e 'puts URI.encode_www_form_component("日本語")'
%93%FA%96%7B%8C%EA

JRuby 1.7.0.preview1 でArgumentErrorが発生する場合

lines = src.rstrip.split(/\r?\n/).map {|line| line.chomp }
C:\pukiwiki2markdown>jruby p2m.rb wiki
ArgumentError: invalid byte sequence in Windows-31J
   split at org/jruby/RubyString.java:4643
 to_html at p2m.rb:45
    main at p2m.rb:219
    each at org/jruby/RubyArray.java:1611
    main at p2m.rb:214
  (root) at p2m.rb:234
C:\pukiwiki2markdown>jruby -J-Dfile.encoding=UTF-8 p2m.rb wiki
wiki/2010-04-12-swing-leaftreecelleditor.markdown
wiki/2007-06-18-swing-leftclippedcombobox.markdown
...
C:\pukiwiki2markdown>jruby -E UTF-8 p2m.rb wiki
wiki/2011-07-18-swing-listmouseselection.markdown
...

JRuby で PukiWiki のファイル名をデコード

if ARGV[0] == nil then
  while line = STDIN.gets
    STDOUT.puts "#{line.gsub!(/[A-F\d]{2}/) {|x| [x.hex].pack('C*')} }"
  end
else
  Dir::glob("*.#{ARGV[0]}").each {|f|
    puts "#{f.gsub!(/[A-F\d]{2}/) {|x| [x.hex].pack('C*')} }"
  }
end
> ls *.txt | jruby decode.rb
> ls *.txt | jruby -pe "$_.gsub!(/[A-F\d]{2}/) {|x| [x.hex].pack('C*')}"

PukiWiki形式のテキストをMarkdown形式のファイルに変換する

> jruby -J-Dfile.encoding=UTF-8 p2m.rb .\wiki
#require 'pukipa'
#require 'pukiwikiparser'
#require 'pukiwiki2markdown'
#require 'benchmark'
#require 'digest/md5'
require 'uri'

module HTMLUtils
  ESC = {
    '&' => '&amp;',
    '"' => '&quot;',
    '<' => '&lt;',
    '>' => '&gt;'
  }

  def escape(str)
    table = ESC   # optimize
    str.gsub(/[&"<>]/n) {|s| table[s] } #"
  end

  def urldecode(str)
    #str.gsub(/[^\w\.\-]/n) {|ch| sprintf('%%%02X', ch[0]) }
    str.gsub(/[A-F\d]{2}/) {|x| [x.hex].pack('C*')}
  end
end

class PukiWikiParser
  include HTMLUtils

  def initialize()
    @h_start_level = 2
  end
  def timestamp()
    @timestamp
  end
  def to_html(src, page_names, page, base_uri = 'http://terai.xrea.jp/', suffix= '/')
    @page_names = page_names
    @base_uri = base_uri
    @page = page.sub!(/\.txt$/, '')
    @pagelist_suffix = suffix
    @inline_re = nil   # invalidate cache

    @timestamp = ''

    buf = []
    lines = src.rstrip.split(/\r?\n/).map {|line| line.chomp }
    while lines.first
      case lines.first
      when ''
        buf.push lines.shift
      when /\ATITLE:/
        lines.shift
      when /\ARIGHT:/
        /at (\w{4}-\w{2}-\w{2})/ =~ lines.first
        @timestamp = $1
        buf.push parse_inline(lines.shift.sub(/\ARIGHT:/, ''))
      when /\A----/
        lines.shift
        buf.push '- - - -' #hr
      when /\A\*/
        buf.push parse_h(lines.shift)
      when /\A\#code.*\{\{/
        buf.concat parse_pre(take_multi_block(lines))
      when /\A\#.+/
        #lines.shift
        buf.push parse_block_plugin(lines.shift) #'' #("aaaaaaaaaaaaaaaaa")
      when /\A\s/
        buf.concat parse_pre(take_block(lines, /\A\s/))
      when /\A\/\//
        #buf.concat parse_comment(take_block(lines, /\A\/\//))
        take_block(lines, /\A\/\//)
      when /\A>/
        buf.concat parse_quote(take_block(lines, /\A>/))
      when /\A-/
        #buf.push parse_inline(lines.shift)
        buf.concat parse_list('ul', take_block(lines, /\A-/))
      when /\A\+/
        #buf.push "1. ".concat(parse_inline(lines.shift))
        buf.concat parse_list('ol', take_block(lines, /\A\+/))
      when /\A:/
        buf.concat parse_dl(take_block(lines, /\A:/))
      else
        buf.concat parse_p(take_block(lines, /\A(?![*\s>:\-\+\#]|----|\z)/))
      end
    end
    buf.join("\n")
  end

  private

  def take_block(lines, marker)
    buf = []
    until lines.empty?
      break unless marker =~ lines.first
      if /\A\/\// =~ lines.first then
        lines.shift
      else
        buf.push lines.shift.sub(marker, '')
      end
    end
    buf
  end

  def take_multi_block(lines)
    buf = []
    until lines.empty?
      l = lines.shift
      break if /^\}\}$/ =~ l
      next  if /^.code.*$/ =~ l
      buf.push l
    end
    buf
  end

  def parse_h(line)
    level = @h_start_level + (line.slice(/\A\*{1,4}/).length - 1)
    h = "#"*level
    # content = line.sub(/\A\*+/, '')
    content = line.gsub(/\A\*+(.+) \[#\w+\]$/) { $1 }
    #"<h#{level}>#{parse_inline(content)}</h#{level}>"
    "#{h} #{parse_inline(content)}"
  end

  def parse_list(type, lines)
    marker = ((type == 'ul') ? /\A-+/ : /\A\++/)
    parse_list0(type, lines, marker)
  end

  def parse_list0(type, lines, marker)
    buf = []
    until lines.empty?
      line = lines.shift.strip
      aaa = line.slice(marker)
      if aaa then
        level = aaa.length
        line = line.sub(marker,'').strip
      else
        level = 0
      end
      h = "    "*level
      s = ((type == 'ul') ? '-' : '1.')
      buf.push "#{h}#{s} #{parse_inline(line)}"
    end
    buf
  end

  def parse_dl(lines)
    buf = ["<dl>"]
    lines.each do |line|
      dt, dd = *line.split('|', 2)
      buf.push "<dt>#{parse_inline(dt)}</dt>"
      buf.push "<dd>#{parse_inline(dd)}</dd>" if dd
    end
    buf.push "</dl>"
    buf
  end

  def parse_quote(lines)
    [ "<blockquote><p>", lines.join("\n"), "</p></blockquote>"]
  end

  def parse_pre(lines)
    ["#{lines.map {|line| "    ".concat(escape(line)) }.join("\n")}"]
  end

  def parse_comment(lines)
    [ "<!-- #{lines.map {|line| escape(line) }.join("\n")}",
      ' -->']
  end

  def parse_p(lines)
    lines.map {|line| parse_inline(line) }
  end

  def parse_inline(str)
    @inline_re ||= %r<
        &(.+);                               # $1: plugin %%HTML escape characters%%
      | \[\[([^\>]+)\>?(.*)\]\]              # $2: label, $3: URI
      | \[(https?://\S+)\s*(.*)\]            # $4: URI,   $5: label
      | (#{autolink_re()})                   # $6: Page name autolink
      | (#{URI.regexp('http')})              # $7...: URI autolink
      >x
    #"
    str.gsub(@inline_re) {
      case
      when htmlchar = $1 then parse_inline_plugin(htmlchar.strip) #escape(htmlchar)
      when bracket  = $2 then a_href($3.strip, bracket, 'pagelink')
      when bracket  = $5 then a_href($4.strip, bracket, 'outlink')
      when pagename = $6 then a_href(page_uri(pagename), pagename, 'pagelink')
      when uri      = $7 then a_href(uri, uri, 'outlink')
      else
        raise 'must not happen'
      end
    }
  end
#"

#   def _parse_inline_plugin(line)
#     buf = []
#     case line
#     when /\Ajar$/
#       buf.push %Q|<a href="#{@base_uri}#{@page.downcase}/example.jar" onclick="_gaq.push(['_trackEvent', 'Jar', 'Download', '#{@page}']);">jar file(example.jar)</a>|
#     when /\Ajnlp$/
#       buf.push %Q|<img style="cursor:pointer" width="88" height="23" src="http://lh4.ggpht.com/_9Z4BYR88imo/TRD2KGq73BI/AAAAAAAAAwA/N8-6EXongNk/s800/webstart.png" onclick="_gaq.push(['_trackEvent', 'WebStart', 'Launch', '#{@page}']);location.href='#{@base_uri}#{@page.downcase}/example.jnlp'" onkeypress="location.href='#{@base_uri}#{@page.downcase}/example.jnlp'" title="Java Web Start" alt="Launch" />|
#     when /\Azip$/
#       buf.push %Q|<a href="#{@base_uri}#{@page.downcase}/src.zip" onclick="_gaq.push(['_trackEvent', 'Source', 'Download', '#{@page}']);">Source(src.zip)</a>|
#       buf.push %Q|-<a href="http://java-swing-tips.googlecode.com/svn/trunk/#{@page.sub(/Swing\//,'')}" onclick="_gaq.push(['_trackEvent', 'Subversion', 'View', '#{@page}']);">Repository(svn repository)</a>|
#     when /\Aauthor/
#       buf.push %Q|<a href="#{@base_uri}#{line.sub(/author\(/,'')}">aterai</a>|
#     else
#       buf.push 'EEE'
#     end
#     buf.join("\n")
#   end

  def parse_inline_plugin(line)
    @inline_plugin_re = %r<
    \A(\w+).?(.*)$
    >x
    args = []
    line.gsub(@inline_plugin_re) {
      args.push $1
      args.push $2.chop
    }
    #puts args.join(" - ")
    buf = []
    case args.first
    when 'jar'
      buf.push %Q|<a href="#{@base_uri}#{@page.downcase}/example.jar" onclick="_gaq.push(['_trackEvent', 'Jar', 'Download', '#{@page}']);">jar file(example.jar)</a>|
    when 'jnlp'
      buf.push %Q|<img style="cursor:pointer" width="88" height="23" src="http://lh4.ggpht.com/_9Z4BYR88imo/TRD2KGq73BI/AAAAAAAAAwA/N8-6EXongNk/s800/webstart.png" onclick="_gaq.push(['_trackEvent', 'WebStart', 'Launch', '#{@page}']);location.href='#{@base_uri}#{@page.downcase}/example.jnlp'" onkeypress="location.href='#{@base_uri}#{@page.downcase}/example.jnlp'" title="Java Web Start" alt="Launch" />|
    when 'zip'
      buf.push %Q|<a href="#{@base_uri}#{@page.downcase}/src.zip" onclick="_gaq.push(['_trackEvent', 'Source', 'Download', '#{@page}']);">Source(src.zip)</a>|
      buf.push %Q|- <a href="http://java-swing-tips.googlecode.com/svn/trunk/#{@page.sub(/Swing\//,'')}" onclick="_gaq.push(['_trackEvent', 'Subversion', 'View', '#{@page}']);">Repository(svn repository)</a>|
    when 'author'
      buf.push %Q|[#{args[1]}](#{@base_uri}#{args[1]}.html)|
    when 'new'
      buf.push %Q|#{args[1]}|
    else
      buf.push 'EEE'
    end
    buf.join("\n")
  end

  def parse_block_plugin(line)
    # content = line.sub(/\A\*+/, '')
    # plugin = line.sub(/\A\#([^\(]+)/) { $1 }
    @plugin_re = %r<
        \A\#([^\(]+)\(?([^\)]*)\)?
      >x
    args = []
    line.gsub(@plugin_re) {
      args.push $1
      args.push $2 #.slice(",")
    }
    buf = []
    case args.first
    when 'ref'
      buf.push %Q<![screenshot](#{args[1]})>
    else
      buf.push ''
    end
    buf
  end

  def a_href(uri, label, cssclass)
    if(cssclass.casecmp('pagelink')==0) then
      if(uri.size===0) then
        str = escape(label.strip)
        %Q<[#{str}](#{@base_uri}#{str}.html)>
      else
        %Q<[#{escape(label.strip)}](#{@base_uri}#{escape(uri.strip)}.html)>
      end
    else
      #%Q{<a class="#{cssclass}" href="#{escape(uri)}">#{escape(label)}</a>}
      %Q<[#{escape(label.strip)}](#{escape(uri.strip)})>
    end
  end

  def autolink_re
    Regexp.union(* @page_names.reject {|name| name.size <= 3 })
  end

  def page_uri(page_name)
    "#{@base_uri}#{urldecode(page_name)}#{@pagelist_suffix}"
  end
end

def main
  include HTMLUtils
  ppath = ARGV[0]
  #puts "#{ppath}/*.txt"
  Dir::glob("#{ppath}/*.txt").each {|f|
    fname = File.basename(f)
    tbody = File.read(f)
    page_names = [] #['文法', '自動リンク']
    parser = PukiWikiParser.new()
    xx = parser.to_html(tbody, page_names, HTMLUtils.urldecode(fname))
    md = HTMLUtils.urldecode(fname).downcase.gsub(/\//, '-').sub(/\.txt$/, '.markdown').sub(/\:/, '_')
    ts = parser.timestamp
    if ts.nil? || ts.size===0
      nname = "#{ppath}/#{md}";
    else
      nname = "#{ppath}/#{parser.timestamp}-#{md}";
    end
    #nname = "#{ppath}/#{parser.timestamp}#{md}";
    puts nname
    outf = open(nname, "w")
    outf.puts(xx)
    outf.close()
  }
end
main

リンク

コメント