Обо мне

Моя фотография
Дикий Полярный Сов

четверг, 9 декабря 2010 г.

Clojure - пишем программу с базовым интерфейсом и собираем ее в .jar

Переписал пример из "Core Java. Fundamentals" на clojure, чтобы разобраться с тем, как на ней пишется GUI.
Код и комментарии - под катом.
Вот код:

(ns interfacetest.core
  (:import [javax.swing
            JFrame
            JLabel
            JFileChooser
            JMenuBar
            JMenuItem
            JMenu
            ImageIcon]
           [java.io
            File]
           [java.awt.event 
            ActionListener])
  (:gen-class))

(defn create-listener [lmbd]
  (proxy [ActionListener]
      []
    (actionPerformed [e] (lmbd e))))

(defn initframe [#^JFrame frame]
  (let [lbl (JLabel.)
        chooser (JFileChooser.)
        menubar (JMenuBar.)
        menu (JMenu. "File")
        openItem (JMenuItem. "Open")
        exitItem (JMenuItem. "Exit")]
    (doto openItem
      (.addActionListener
       (create-listener
        (fn [e] (let [result (.showOpenDialog chooser nil)
                      getname (fn [] (.. chooser getSelectedFile getPath))]
                  (if (= result JFileChooser/APPROVE_OPTION)
                      (.setIcon lbl (ImageIcon. (getname)))))))))
    (doto exitItem
      (.addActionListener
       (create-listener
        (fn [e] (System/exit 0)))))
    (.setCurrentDirectory chooser (File. "."))
    (doto menu
      (.add openItem)
      (.add exitItem))
    (.add menubar menu)
    (doto frame
      (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
      (.setSize 300 300)
      (.setVisible true)
      (.add lbl)
      (.setJMenuBar menubar))))


(defn -main []
  (let [fr (JFrame. "Hello world")]
    (initframe fr)))

Результатом работы программы будет небольшая формочка с двумя пунктами меню - "Open" и "Exit". Open предложит выбрать изображение, которое и будет помещено на формочку (изображение не ресайзится, от очень большой картинки будет показана только часть.
Разберем код по порядку.
Инструкция (ns) задает пространство имен под названием interfacetest.core. В ней же указано, что необходимо сгенерировать Java-класс для этого пространства имен (что и будет сделано при компиляции) - :gen-class. В директиве :import подключаются классы из стандартной библиотеки Java (написанные не на Clojure).
Функция create-listener превращает функцию clojure в объект, реализующий интерфейс ActionListener. Эти объекты понадобятся нам позже, для меню.
Далее следует функция initframe, принимающая объект класса JFrame (инструкция #^JFrame заставляет делать проверку типов при компиляции, если я ничего не путаю). В let-биндинге создаются объекты JMenuBar, JFileChooser, JMenuItem и так далее, а затем, при помощи инструкций doto производятся действия над этими объектами и собирается интерфейс.
(doto object ...)
позволяет производить несколько действий подряд над одним и тем же объектом. Обычно вызов методов объектов производится так:
(.method object args)
, в случае с doto можно делать так:
(doto object (.method args) ... )
, чем мы и пользуемся.
Затем определяется метод -main, который будет вызываться java'ой. В нем мы создаем фрейм и вызываем для него initframe.
Да, еще момент - я использовал в let-биндинге лямбда-функцию, чтобы отложить получение имени выбранного файла, потому что не был уверен, что на момент биндинга оно будет существовать. Такая вот ленивость.
Вроде все. Теперь про компиляцию.
Пространство имен называется interfacetest.core, значит, создаем на диске директорию interfacetest и кладем туда файл core.clj с указанным выше содержимым. Затем переходим в командной строке в директорию уровнем выше interfacetest, и запускаем REPL Clojure. В интерпретаторе пишем:
(compile 'interfacetest.core)
Файл откомпилирован, и на диске создана директория classes, в которой лежат interfacetest/*.class. Проверить, что все получилось, можно следующим образом - из командной строки переходим в директорию classes (желательно положить туда же clojure.jar) и пишем:
java -cp .;clojure.jar interfacetest.core
Обратите внимание - здесь указана версия для Windows - разделитель ";" в classpath. Для линукса нужно будет заменить ";" на ":".
Осталось только собрать наш jar. Распаковываем clojure.jar как обычный .zip-архив, копируем директорию clojure (ту, которая лежит в директории распаковки), кладем ее рядом с interfacetest и пишем:
jar cvfe jarfilename.jar interfacetest.core clojure\* interfacetest\*
Опять же, это версия для Windows - для линукса заменяем обратные слеши на прямые. Получаем исполняемый файл, который запускается без лишних библиотек.
Вроде все. За кадром остался вопрос, как определить только используемые классы из clojure.jar и не включать из в итоговый jar, тем самым уменьшая его размер (думаю, это можно сделать).

5 комментариев:

  1. Поменял дизайн - сейчас должно влезать все

    ОтветитьУдалить
  2. а можно добавить отдельную метку для всех постов, относящихся к ФП (хаскель, кложура и т.п.)? я бы тогда добавил вас в russian planet fp

    ОтветитьУдалить
  3. Alex Ott сунул под тег FP: http://stalker401.blogspot.com/search/label/FP

    ОтветитьУдалить
  4. добавил. через полчаса где-то появится

    ОтветитьУдалить