JRuby #02 - Ruby rozmawia z Javą, Java rozmawia z Ruby’im

Posted by wiktor on May 9, 2008 in java, jruby, ruby

Widzę, że jesteś pierwszy raz na moim blogu. Witaj :). Może nawet będziesz miał ochotę subskrybować blog poprzez RSS. Trzymaj się ciepło!

Krok drugi: integracja

W poprzednim poście opisywałem proces instalacji JRuby’iego tak, żeby korzystanie z niego było proste. Czas wziąć na warsztat integrację Ruby’iego z Javą i odwrotnie.

Ruby rozumie Javę

Kiedy JRuby interpretuje skrypt Ruby’iego to mogą tam być używane wszystkie klasy Javowe. Chcemy, żeby skrypt korzystał z naszej wcześniej utworzonej biblioteki w Javie? Nie ma problemu, dołączamy ją do CLASSPATH i możemy z poziomu Ruby’iego z niej korzystać. Integracja została przedstawiona na poniższych przykładach. Dołożyłem wszelkich starań, aby komentarze były wyczerpujące.

Uruchommy interakcyjną konsolę JRuby’iego: jruby -S jirb i następnie pobawmy się kodem:

# Znane z Javy importowanie wygląda standardowo.
# Niestety importowanie z gwiazdką nie działa, więc import javax.swing.* poczęstuje nas błędem.
import javax.swing.JFrame
import javax.swing.JButton
 
# Składnia Ruby'iego została zachowana dla obiektów Javowych, widać to w tworzeniu nowego egzemplarza JFrame
# Warto zauważyć, że nawiasy są nieobowiązkowe. Poniższe wywołanie jest równoznaczne z JFrame.new("...")
frame = JFrame.new "Ruby swinguje z Java"
 
# Styl kodowania Ruby'iego zostaje zachowany także dla metod. W Javie lubimyTakPisać, a w Ruby'im trochę_inaczej.
# Poniższe wywołanie jest równoważne z frame.setSize(300, 300), które także byłoby poprawne.
frame.set_size 300, 300
 
# Gettery i settery Javowe są dostępne także w odmianie Ruby'iego.
# Poniższa linijka jest równoważna z frame.setAlwaysOnTop(true), które także byłoby poprawne.
frame.always_on_top = true
 
button = JButton.new "Nacisnij mnie"
 
# Blok kodu zostanie w locie przekształcony w klasę implementującą interfejs ActionListener. Niezłe!
# Jest to domyślne zachowanie JRuby'iego, jeśli parametrem jest interfejs z jedną metodą.
# Standardowo w Ruby'im używa się wcięć z 2 spacjami.
button.add_action_listener do |event|
  # Przykład getterów ala Ruby. Równoznaczne z event.getSource().setText("..."), które także jest OK.
  event.source.text = "Nie naciskaj mnie ponownie!"
  event.source.enabled = false
end
 
 
frame.add(button)
frame.show

Czas poczęstować Ciebie czytelniku jakimś smakołykiem. Oto on:

# Rozszerzenie klasy Javowej String o mechanizm missing_method, czyli
# ta metoda zostanie wywołana, jeśli na obiekcie zostanie wywołana metoda, która
# nie została zdeklarowana.
JavaUtilities.extend_proxy "java.lang.String" do
  def method_missing(symbol, *args)
    puts "Kogo wolasz?"
  end
end
 
txt = java.lang.String.new "Ala ma kota"
 
txt.hmmmm_jak_brzmiala_nazwa_tej_metody?()  # => "Kogo wolasz?"

Java rozumie Ruby’iego

Ruby może zostać wpleciony w kod Javy na 3 sposoby:

  • poprzez Scripting API (wprowadzone z Javą 6, JSR 223, unifikacja silników skryptowych używanych w Javie), szczegóły: https://scripting.dev.java.net,
  • poprzez Beans Scripting Framework (standard wprowadzany przez Apache Jakarta), szczegóły: http://jakarta.apache.org/bsf,
  • bezpośrednio odwołując się do interpretera JRuby’iego.

Zajmiemy się tylko pierwszą opcją. Będziemy do tego potrzebowali opakowania silnika JRuby’iego dla javax.script, które znajdziemy tutaj (najlepiej wersję 1.1.3).

jruby-scripting-api

Powyższa grafika przedstawia zależności w Java Scripting API. Z naszego kodu będziemy odwoływać się wyłącznie do klas javax.script.*, żądać silnika JRuby’iego (jruby-engine.jar), który z kolei sam już będzie odpowiednio wywoływał interpreter JRuby’iego.

Czas podwinąć rękawy i trochę pokodować.

import org.jruby.RubyHash;
import javax.script.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import static java.lang.System.*;
 
public class RubyInJava {
 
    public static void main(String[] args) {
        // Od Java Scripting API żądamy silnik JRuby'iego
        ScriptEngine rubyEngine = new ScriptEngineManager().getEngineByName("jruby");
        ScriptContext context = rubyEngine.getContext();
 
        List<integer> list = new ArrayList<integer>() {{
            add(1); add(2); add(3); add(4);
        }};
 
        // Do środowiska JRuby'iego dodajemy globalną zmienną list
        rubyEngine.put("list", list);
 
        // Skrypt Ruby'iego, który będziemy wykonywać
        StringBuilder script = new StringBuilder();
        script.append("puts '--- Ruby ---'                                  ").append("\n")
              .append("puts $list.inspect                                   ").append("\n")
              // JRuby do kolekcji Javowych dodaje typowe metody Ruby'iego dla kolekcji
              .append(" $list.each { |item|                                 ")
              .append("   puts \"Element #{item}\"                          ")
              .append(" }                                                   ").append("\n")
              .append(" puts \"Suma: #{$list.inject { |sum, i| sum + i }}\" ").append("\n")
              .append("\n")
              .append(" $hash = { :ala => :ma, :co => :kota }               ").append("\n");
 
        // Wykonanie skryptu
        try {
            rubyEngine.eval(script.toString(), context);
        } catch (ScriptException e) {
            e.printStackTrace();
            return;
        }
 
        out.println("--- Java ---");
 
        // Pobranie zmiennej globalnej po wykonaniu skryptu
        Object hashAsObject = rubyEngine.get("hash");
        out.println("Klasa: " + hashAsObject.getClass());
 
        RubyHash hash = (RubyHash) hashAsObject;
        for (Object o : hash.entrySet()) {
            Map.Entry entry = (Map.Entry) o;
            System.out.println(entry.getKey() + " => " + entry.getValue());
        }
    }
}

Aby uruchomić powyższy kawałek kodu trzeba mieć w CLASSPATH: jruby.jar oraz jruby-engine.jar.

Powyższe przykłady ilustrują, że ludzie pracujący nad JRuby’im wykonali kawał dobrej roboty, żeby łączenie Javy z Ruby’im było bezstresowe dla developerów. Takie połączenie dwóch światów daje nam nowe możliwości: tworzenie mini-języków dla aplikacji Javowych, aplikacje Swingowe w Ruby’im, nie wspominając o Ruby on Rails, a dla Ruby’istów dostępny jest cały ekosystem Javy :).

Tags: , ,

3 Comments

mj
May 10, 2008 at 12:01 am

niezłe :] czekam na #3


 
Tomek
Jul 22, 2008 at 9:01 pm

Faktycznie ciekawe :D
a do czego tego się używa w świecie? jakie są tego silne strony?


 
katsu
Sep 11, 2008 at 7:03 pm

A ja mam problem, z którym walczę cały dzień.
No i niestety google nie chce mi zbyt pomóc.
Czy wiesz może czym może być to spowodowane?

Exception in thread "main" java.lang.NoSuchMethodError: org.jruby.Ruby.newInstance()Lorg/jruby/Ruby;
	at com.sun.script.jruby.JRubyScriptEngine.init(JRubyScriptEngine.java:474)
	at com.sun.script.jruby.JRubyScriptEngine.(JRubyScriptEngine.java:95)
	at com.sun.script.jruby.JRubyScriptEngineFactory.getScriptEngine(JRubyScriptEngineFactory.java:134)
	at javax.script.ScriptEngineManager.getEngineByName(ScriptEngineManager.java:225)
	at RubyInJava.main(RubyInJava.java:62)

Linia 62 to ScriptEngine jsEngine = mgr.getEngineByName(”jruby”);


 

Reply

Copyright © 2009 Mocna Kawa All rights reserved. Theme by Laptop Geek.