Поиск по блогу

Monday, August 23, 2010

Совместное использование Clojure и Java.

   В этом посте я на простом примере покажу как соединить в одном проекте Clojure и Java. Сразу к делу...



Создадим проект со следующим деревом каталогов по классическому шаблону для Maven проектов:


В папке src/main/clojure будут храниться исходники Clojure скриптов. В src/main/java - Java классы соответственно. В папку lib помещаем clojure-1.2.0.jar.

Мавеновский pom.xml выглядит следующим образом:


    4.0.0

    test.clojure
    TestClojure
    1.0-SNAPSHOT

    
                    
            
                com.theoryinpractise
                clojure-maven-plugin
                1.1
                
                    
                        src/main/clojure
                    
                    
                        !telchat.app
                    
                    
                
            
        
    

    
        
            org.clojure
            clojure
            1.2.0
        
    

    
        
          clojure-releases
          http://build.clojure.org/releases
        
        
          clojars
          http://clojars.org/repo/
        
        


Здесь определены зависимости Clojure версии 1.2.0, указаны репозитории и подключен специальный плагин для компиляции Clojure скриптов.

Теперь давайте немного по-программируем... Для начала определимся со средой для разработки. Лично я привык пользоваться IntellijIDEA. Для написания примера я использовал IntellijIDEA 9.0.3 Community Edition + La Clojure плагин. Данная среда довольно удобная. Clojure скрипты правильно подсвечиваются и даче работает autocomplition. Так же можно запускать REPL прямо из IDE. Хотя вы можете, так же, смело пользоваться Eclipse или, к примеру, Emacs.

Итак, создаем в папке clojure пакет example.clojure.hello и помещаем туда файл Hello.clj.


Далее наполняем Hello.clj следующим содержанием:

(ns example.clojure.hello.Hello
  (:gen-class
    :methods [[sayHello[] void]
               [sayHello[String] void]
               [callFunction[clojure.lang.IFn] String]]))

(defn -sayHello
  ([this] (println "Hello, stranger!"))
  ([this name] (println (str "Hello, " name "!"))))

(defn -callFunction [this javaFunction]
  (javaFunction (str "Vasya" "!")))

Примечание. К сожалению подсветка для Clojure кода в моем блоге не работает. Я думаю, эта проблема решится в ближайшем будущем.

Первой же строчкой в Hello.clj мы видим определение namespace. Это своего рода аналог пакетов в Java. Одна разница, что namespace в Clojure должен оканчиваться именем файла, в котором находятся скрипты. В нашем случае - это Hello.clj и поэтому namespace оканчивается словом Hello.
Далее идет инструкция (:gen-class). Эта инструкция сообщает, что для Hello.clj должен создаваться Hello.class. Для скомпилированного Hello.class автоматически будет создан статический метод main, который вы можете переопределять.
Так же в инструкцию :gen-class входит инструкция :methods. В ней определены Java методы для  Hello.class.
(defn -sayHello) и (defn -callFunction) переопределяют поведение будущих методов в Hello.class.

Функция sayHello может принимать от 0 до 1 аргументов. В эту функцию мы будем передавать строку(String) из Java.
Функция callFunction принимает на вход объект типа clojure.lang.IFn. Это интерфейс, который автоматически реализуют все функции в Clojure. Мы же реализуем его вручную в Java, чтобы можно было передавать Java код в Clojure.

Реализуем интерфейс clojure.lang.IFn.
В папке java создадим три класса: Function, MyJavaFunction и Greeting.


Класс Function - абстрактный. Он реализует интерфейс clojure.lang.IFn следующим образом:

import clojure.lang.IFn;
import clojure.lang.ISeq;

public abstract class Function implements IFn {

    @Override
    public Object invoke() throws Exception {
        return doInvoke();
    }

    @Override
    public Object invoke(Object o) throws Exception {
        return doInvoke(o);
    }

    @Override
    public Object invoke(Object o, Object o1) throws Exception {
        return doInvoke(o, o1);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2) throws Exception {
        return doInvoke(o, o1, o2);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3) throws Exception {
        return doInvoke(o, o1, o2, o3);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4) throws Exception {
        return doInvoke(o, o1, o2, o3, o4);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13, Object o14) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13, Object o14, Object o15) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13, Object o14, Object o15, Object o16) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13, Object o14, Object o15, Object o16, Object o17) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16, o17);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13, Object o14, Object o15, Object o16, Object o17, Object o18) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16, o17, o18);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13, Object o14, Object o15, Object o16, Object o17, Object o18, Object o19) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16, o17, o18, o19);
    }

    @Override
    public Object invoke(Object o, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7, Object o8, Object o9, Object o10, Object o11, Object o12, Object o13, Object o14, Object o15, Object o16, Object o17, Object o18, Object o19, Object... objects) throws Exception {
        return doInvoke(o, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, o16, o17, o18, o19, objects);
    }

    @Override
    public Object applyTo(ISeq iSeq) throws Exception {
        return null;
    }

    @Override
    public Object call() throws Exception {
        return doInvoke();
    }

    @Override
    public void run() {
        doInvoke();
    }

    public abstract Object doInvoke(Object... obj);
}


Т.о. мы создали что-то вроде адаптера. Теперь мы можем расширять класс Function и переопределять только один метод doInvoke(...); как показано в примере MyJavaFunction:

public class MyJavaFunction extends Function {

    @Override
    public Object doInvoke(Object... obj) {
        System.out.println("Function call from Java!");
        System.out.println(String.format("Hello, %s", obj[0]));
        return "JAVA FUNCTION WORK SUCCESS!";
    }
    
}


Отлично. Теперь собираем все воедино в классе Greeting:

import example.clojure.hello.Hello;

public class Greeting {

    public static void main(String[] args) {
        Hello hello = new Hello();
        if(args.length == 0) {
            hello.sayHello();
        } else {
            hello.sayHello(args[0]);
        }

        Function fun = new MyJavaFunction();
        String result = hello.callFunction(fun);
        System.out.println(result);
    }
}


Итак, перед сборкой и запуском несколько комментариев. В Greeting мы создаем объект типа Hello из Clojure namespace example.clojure.hello.Hello и вызываем функцию sayHello. Далее мы создаем объект типа Function и передаем его в Clojure функцию callFunction и выводим на экран вернувшийся результат.

Для того, чтобы собрать проект и упаковать его в jar нужно выполнить следующую команду:

mvn clean clojure:compile install

Чтобы запустить класс Greeting нужно выполнить команду:

java -cp target/TestClojure-1.0-SNAPSHOT.jar:lib/clojure-1.2.0.jar Greeting

или

java -cp target/TestClojure-1.0-SNAPSHOT.jar:lib/clojure-1.2.0.jar Greeting Vasya

(Я запускал под Linux. Символ разделения ':' в Windows другой - ';')

После выполнения вы должны увидеть что-то наподобие такого:


Hello, stranger!
Function call from Java!
Hello, Vasya!
JAVA FUNCTION WORK SUCCESS!

2 comments:

  1. just fyi - я добавил вас в русскую планету ФП (http://fprog.ru/planet/) по метке FP, и в русскую планету Clojure по метке Clojure

    ReplyDelete