ClojureScript development environment

Here’s what you need to set up a pretty nice ClojureScript development environment.

Editor

I’m using Emacs. I actually have it set up through Clojure before I started playing with ClojureScript. There’s a tutorial on the main site:

  • https://github.com/clojure/clojurescript/wiki/Emacs-%26-inferior-lisp-mode

where you can also find tutorials for other editors:

  • https://github.com/clojure/clojurescript/wiki#tools

Lein + lein-cljsbuild

Install Leiningen, make a new project:

$ lein new com.icyrock.clojurescript
Generating a project called com.icyrock.clojurescript based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.

and add lein-cljsbuild plugin to your project.clj:

(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.4.0"]]
  :plugins [[lein-cljsbuild "0.3.0"]]
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

The above has some default configuration for the plugin:

  • The source folder for ClojureScript files is src-cljs
  • Turn some basic optimization and pretty-print the output
  • The output goes to resources/public/js/cljsbuild-main.js

Usually, you’d run cljsbuild in the background with:

$ lein cljsbuild auto

which will watch for the files in the source path for changes and then perform the necessary steps (compile, optimize, bundle) to get you the target .js file.

Ring

Ring is a Web server. It’s very nice for development, as it compiles the Clojure files on the fly. There’s also lein-ring which is a helper plugin to simplify usage when used with lein. Here’s the expanded project.clj:


(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "icyrock.com ClojureScript"
  :url "http://icyrock.com/clojure-script"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [ring/ring-core "1.1.8"]]
  :plugins [[lein-cljsbuild "0.3.0"]
            [lein-ring "0.8.3"]]
  :ring {:handler com.icyrock.clojure.ring-main/handler
         :port 3000}
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

As per Ring’s Getting Started, create a sample handler in src/com/icyrock/clojure/ring_main.clj:

(ns com.icyrock.clojure.ring-main)

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Welcome to com.icyrock.clojure.ring-main" })

You can run the server now with:

$ lein ring server
2013-04-15 23:16:21.815:INFO:oejs.Server:jetty-7.6.1.v20120215
2013-04-15 23:16:21.966:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000
Started server on port 3000

which will open your browser. You can use lein ring server-headless if you don’t want it to open your browser.

Compojure

The next step is to add some file serving to our Web server. Compojure allows for that. It’s a Clojure routing DSL that sits on top of Ring. Add this as a dependency and change the ring handler:

(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "icyrock.com ClojureScript"
  :url "http://icyrock.com/clojure-script"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [ring/ring-core "1.1.8"]
                 [compojure "1.1.5"]]
  :plugins [[lein-cljsbuild "0.3.0"]
            [lein-ring "0.8.3"]]
  :ring {:handler com.icyrock.clojure.compojure-main/app
         :port 3000}
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

then make a site router (see the routes.clj from the example project):

(ns com.icyrock.clojure.compojure-main
  (:require [compojure.core :refer :all]
            [hiccup.middleware :refer (wrap-base-url)]
            [compojure.route :as route]
            [compojure.handler :as handler]
            [compojure.response :as response]))

(defroutes main-routes
  (GET "/" [] "Welcome to com.icyrock.clojure.compojure-main")
  (route/resources "/")
  (route/not-found "Page not found"))

(def app
  (-> (handler/site main-routes)
      (wrap-base-url)))

The important part above is the (route/resources "/"). This allows for static resources to be served by ring. The default is to serve from the (first) public on the classpath. Lein accepts the parameter called :resources-path, which (as per this) defaults to resources. Thus, if we create a folder resources/public, the above would make ring serve all files from that folder under the root (i.e. /) of the site.

As an example:

$ mkdir -p resources/public
$ echo "I'm a resource" > resources/public/a-res.txt

Now do lein ring server and browse to http://localhost:3000/a-res.txt – you’ll get I'm a resource text, as expected.

Auto-recompiling ClojureScript

Let’s add:

  • jayq, which is a jQuery wrapper for ClojureScript
  • jQuery externs that allow Google Closure to recognize and not obfuscate jQuery calls (see this)
  • dommy, a small and fast ClojureScript templating engine

to our project.clj:

(defproject com.icyrock.clojurescript "0.1.0-SNAPSHOT"
  :description "icyrock.com ClojureScript"
  :url "http://icyrock.com/clojure-script"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [ring/ring-core "1.1.8"]
                 [compojure "1.1.5"]
                 [hiccup "1.0.3"]
                 [jayq "2.3.0"]
                 [prismatic/dommy "0.1.0"]]
  :plugins [[lein-cljsbuild "0.3.0"]
            [lein-ring "0.8.3"]]
  :ring {:handler com.icyrock.clojure.compojure-main/app
         :port 3000}
  :cljsbuild {:builds [{:source-paths ["src-cljs"]
                        :compiler {:output-to "resources/public/js/cljsbuild-main.js"
                                   :externs ["externs/jquery-1.9.1.js"]
                                   :optimizations :whitespace
                                   :pretty-print true}}]})

Now we can make a sample ClojureScript file. All these go to src-cljs. This file is within that folder under com/icyrock/clojurescript/prb/hello_world.cljs and the contents are simple:

(ns com.icyrock.clojurescript.prb.hello-world
    (:require [jayq.core :as $]
              [dommy.core :as dommy]
              [dommy.template :as tpl]))

(defn hello-world []
  (dommy/append! js/document.body [:h1 "Hello, world!"])
  (let [body ($/$ js/document.body)
        para (-> ($/$ "<p>")
                 ($/text "This is a sample paragraph"))
        link (tpl/node [:a {:href "http://icyrock.com/"} "icyrock.com"])]
    ($/append body para)
    ($/append body link)))

This is the main file that will be used to call child functions, just for separation:

(ns com.icyrock.clojurescript.main
  (:require [jayq.core :as $]
            [com.icyrock.clojurescript.prb.hello-world :refer [hello-world]]))

($/$ hello-world)

Let’s run lein-cljsbuild in another terminal in auto-mode:

$ lein cljsbuild auto
Compiling "resources/public/js/cljsbuild-main.js" from ["src-cljs"]...
Successfully compiled "resources/public/js/cljsbuild-main.js" in 12.54909577 seconds.

Add a simple HTML to resources/public/main.htm:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>com.icyrock.clojurescript.main</title>
    <script src="lib/jquery-1.9.1.min.js"></script>
    <script src="js/cljsbuild-main.js"></script>
  </head>
  <body>
  </body>
</html>

Download the required files as needed:

Browse to http://localhost:3000/main.htm and you’ll see something like this:

hello_world

Note that further updates to the .cljs files are automatically recompiled by Google Closure compiler, that is used by ClojureScript through lein-cljsbuild

Be Sociable, Share!

2 Responses to “ClojureScript development environment”

JimBeam on June 18th, 2013 02:06:

Great tutorial! Thx a lot

[WORDPRESS HASHCASH] The poster sent us ’0 which is not a hashcash value.


icyrock.com on June 22nd, 2013 09:24:

Thanks JimBeam, glad you liked it!


Leave a Reply


− two = 3

Maze display app for Always Turn Left

Last time, I presented a solution for Always Turn Left, a Google Code Jam problem. Given that their large dataset was quite big (up to 10k moves), I thought: “It would be interesting to see what mazes those moves produce”. So I set to write (in Clojure, of course) a maze-display app (using Seesaw, of course). Here’s what came out of that.

(ns com.icyrock.clojure.codejam.maze-display
  (:use clojure.java.io
        flatland.ordered.map
        seesaw.border
        seesaw.chooser
        seesaw.color
        seesaw.core
        seesaw.graphics
        seesaw.mig)
  (:require [seesaw.bind :as ssb]))

First, declare a lot of things I’m to use later. Most Seesaw and one thing from here, which is a Clojure implementation of ordered sets / maps which I wanted to try out.

(def state
  {:frame (atom nil)
   :file (atom nil)
   :cases (atom nil)
   :curr-case (atom nil)
   :maze (atom nil)})

Main state – contains:

  • Main frame
  • Currently selected case-file
  • Loaded cases themselves
  • Currently selected case
  • Maze bound to the currently selected case
(def room-width 16)
(def room-height 16)

Default room size when drawn, in pixels.

(def default-style
  (style
   :foreground "#000000"
   :stroke (stroke :width 3 :cap :round)))

Default style to use when drawing walls. It’s a black, 3-pixel wide line, with rounded edges.

(defn draw-wall [g w h wall]
  (case wall
    :n (draw g (line 0 0 w 0) default-style)
    :s (draw g (line 0 h w h) default-style)
    :w (draw g (line 0 0 0 h) default-style)
    :e (draw g (line w 0 w h) default-style)))

This draws a wall. Given that translation is used below, the north-west corner of the room is always at (0, 0), so the above is easy to understand given the case keys (:n for north, :s for south, :w for west and :e for east).

(defn draw-room [g w h walls-desc]
  (let [walls (case walls-desc
                \1 #{   :s :w :e}
                \2 #{:n    :w :e}
                \3 #{      :w :e}
                \4 #{:n :s    :e}
                \5 #{   :s    :e}
                \6 #{:n       :e}
                \7 #{         :e}
                \8 #{:n :s :w   }
                \9 #{   :s :w   }
                \a #{:n    :w   }
                \b #{      :w   }
                \c #{:n :s      }
                \d #{   :s      }
                \e #{:n         }
                \f #{           }
                )]
    (doseq [wall walls]
      (push g
            (draw-wall g w h wall)))))

The room is a set of cases to decipher the letter as set of walls for that room, as given in the problem description and then draw each of these walls.

(defn paint-maze 1
  (try
    (let [w room-width
          h room-height
          maze @(state :maze)]
      (when maze
        (anti-alias g)
        (translate g w h)
        (doseq [row maze]
          (push g
                (doseq [room row]
                  (draw-room g w h room)
                  (translate g w 0)))
          (translate g 0 h))))
    (catch Exception e
      (invoke-later (alert e))
      (println e))))

Main paint function:

  • Check if maze is valid (i.e. user has selected a case)
  • Turn on anit-aliasing
  • Go through the rows of the maze
  • Translate to the position of the current room
  • Draw it
(defn content-panel []
  (mig-panel
   :constraints ["fill" "[|grow]"]
   :items [[(button :id :load
                    :text "Load file...") ""]
           [(text :id :file-name) "growx, wrap"]
           [(scrollable (listbox :id :cases)
                        :border (line-border)) "grow"]
           [(let [s (scrollable (canvas :id :maze-pict
                                        :background "#ffffff"
                                        :paint paint-maze)
                                :border (line-border))]
              (-> s (.getHorizontalScrollBar) (.setUnitIncrement (* 3 room-width)))
              (-> s (.getVerticalScrollBar) (.setUnitIncrement (* 3 room-height)))
              s) "grow, push"]]))

Main window contents:

  • “Load” button
  • Current file name
  • List box for cases
  • Canvas for the maze

Uses MigLayout, of course.

(defn split-cases [acc line]
  (let [case (re-find #"^Case #\d+:$" line)]
    (if case
      ;; Found case start line
      (assoc acc
        :curr-case case
        :cases (assoc (acc :cases) case []))
      ;; Continuation of the current case (maze definition)
      (let [cases (acc :cases)
            curr-case (acc :curr-case)
            curr-maze (cases curr-case)
            new-maze (conj curr-maze line)
            new-cases (assoc cases curr-case new-maze)]
        (assoc acc
          :cases new-cases)))))

When loading, split the cases one by one, taking into account maze description has two kinds of lines:

  • Case start
  • Maze lines for the current case
(defn load-cases [file]
  (with-open [r (reader file)]
    (let [lines (reduce conj [] (line-seq r))
          {:keys [cases]} (reduce split-cases {:cases (ordered-map)} lines)]
      (reset! (state :cases) cases))))

Case loader function:

  • Use reader to read from the file
  • Get the lines
  • Reduce using previous split-cases function
(defn load [e]
  (let [frame (to-frame e)]
    (choose-file frame
                 :type :open
                 :success-fn (fn [fc file] (reset! (state :file) file)))))

Just shows the standard Java file chooser to pick the file.

(defn set-listeners [frame]
  (listen (select frame [:#load])
            :action load))

(defn set-bindings [frame]
  ;; File binding
  (ssb/bind
   (state :file)
   (ssb/tee
    (ssb/b-do* load-cases)
    (ssb/bind
     (ssb/transform #(.getPath %))
     (select frame [:#file-name]))))
  ;; Cases binding
  (ssb/bind
   (state :cases)
   (ssb/transform #(keys %))
   (ssb/tee
    (ssb/property (select frame [:#cases]) :model)
    (ssb/b-do* (fn [v] (selection! (select frame [:#cases]) (first v))))))
  ;; Case selection binding
  (ssb/bind
   (ssb/selection (select frame [:#cases]))
   (ssb/b-do* #(reset! (state :curr-case) %)))
  ;; Selected case binding
  (ssb/bind
   (state :curr-case)
   (ssb/transform #(@(state :cases) %))
   (ssb/b-do* #(reset! (state :maze) %)))
  ;; Maze binding
  (ssb/bind
   (state :maze)
   (ssb/b-do* (fn [maze] (let [canvas (select frame [:#maze-pict])
                               cw (* room-width (+ 2 (count (first maze))))
                               ch (* room-height (+ 2 (count maze)))]
                           (config! canvas :preferred-size [cw :by ch])
                           (.revalidate canvas)
                           (repaint! canvas))))))

These two set up the listeners (only one in this case – button click) and bindings which nicely describe the state machine for this simple app:

  • When file is selected, load the cases and display the file name
  • When cases were loaded, populate the list box with the case map description
  • When a case is selected, update the current case
  • When the current case changes, update the maze
  • When the maze is updated, draw it
(defn maze-display []
  (native!)
  (let [f (frame :title *ns*
                 :width 1200 :height 700
                 :on-close :dispose
                 :visible? true
                 :content (content-panel))]
    (.setLocation f (java.awt.Point. 100 100))
    (reset! (state :frame) f)
    (set-listeners f)
    (set-bindings f)))

Main function:

  • Make the frame
  • Set its location
  • Set the listeners and bindings

The final result looks like this:

maze-display-app

Be Sociable, Share!

Leave a Reply


six + = 14

Google Code Jam – Always Turn Left in Clojure

Last time I did the first practice problem (Alien Numbers). Now it’s time to do the second one – Always Turn Left.

Utilities

Utilities pretty much stayed the same – the only change is to the write-output function that now has three parameters – the last was added to allow for multi-line case output. This one is needed for this problem, while it was a series of one-liners in Alien Numbers. Read about these more in the Alien Numbers post, but here they are again for completeness:

(ns com.icyrock.clojure.codejam.utils
  (:use clojure.java.io))

(def base-res "com/icyrock/clojure/codejam/")

(defn read-case-lines [case-name]
  (let [res (resource (str base-res case-name))]
    (with-open [rdr (reader res)]
      (doall (line-seq rdr)))))
  
(defn read-cases [case-name]
  (let [lines (read-case-lines (str case-name ".in"))
        case-count (Integer/parseInt (first lines))
        cases (doall (rest lines))]
    {:case-count case-count
     :cases cases}))
  
(defn write-output [output case-name multi-line]
  (let [in-res (resource (str base-res case-name ".in"))
        in-file-name (.getFile in-res)
        out-file-name (clojure.string/replace in-file-name #".in$" ".out")]
    (with-open [wr (writer out-file-name)] 
      (doseq [[case-no case-out] (map vector (range 1 (inc (count output))) output)]
        (.write wr (str "Case #" case-no ":"
                        (if multi-line "\n" " ")
                        case-out "\n"))))))

Tests

Some tests to start with:

(ns com.icyrock.clojure.codejam.cj2008pp_test
  (:use 1)
  (:use [com.icyrock.clojure.codejam.cj2008pp]))

;; Always turn left
(deftest make-maze-test
  (is (= (make-maze
          {[0 0] #{   :s    :e}
           [0 1] #{      :w :e}
           [0 2] #{:n    :w   }
           [1 0] #{:n :s      }
           [1 1] #{         :e}
           [1 2] #{   :s :w   }
           [2 0] #{:n       :e}
           [2 1] #{      :w :e}
           [2 2] #{:n :s :w   }
           [3 0] #{   :s :w :e}
           [3 1] #{      :w   }
           [3 2] #{:n :s      }
           [4 0] #{:n       :e}
           [4 1] #{      :w :e}
           [4 2] #{:n    :w   }}
          0 0 4 2)
         '("ac5" "386" "9c7" "e43" "9c5"))))

(deftest always-turn-left-case-test
  (is (= (always-turn-left-case "WRWWLWWLWWLWLWRRWRWWWRWWRWLW" "WWRRWLWLWWLWWLWWRWWRWWLW")
         '("ac5" "386" "9c7" "e43" "9c5")))
  (is (= (always-turn-left-case "WW" "WW")
         '("3")))
  (is (= (always-turn-left-case "WWLW" "WLWRRWWW")
         '("3" "b" "1")))
  (is (= (always-turn-left-case "WWWRRWLW" "WLWW")
         '("3" "7" "1")))
  (is (= (always-turn-left-case "WWRWWLW" "WWRWWLW")
         '("ac7" "bc5")))
  (is (= (always-turn-left-case "WWRWRWW" "WWLWLWW")
         '("33" "95"))))

The first test is for the make-maze function. This one is used to generate the maze given the maze map, see below. Keys of the map are [row column] points and values are sets of directions (:n, :s, :w, :e, representing north, south, west and east, respectively).

As for the actual problem tests (always-turn-left-case-test) – the first two tests are from the problem page, the second of which covers an exit on the south. The next four of these cover east, west, south and north exits, in that order. I repeated the south exit as the one given on the problem page was rather trivial. Also, this example is also a “palindrome” maze – i.e. you walk in the same way (given the entrance-to-exit and exit-to-entrance descriptions) from entrance to exit and on the way back.

Solution overview

From my perspective, this problem yields itself quite nicely to the reduce function. That is – we start from the empty maze, we walk as given by the entrance-to-exit path description and improve our knowledge about the maze on the way. I chose to record the following information:

  • Current row index
  • Current column index
  • Minimal row index
  • Minimal column index
  • Maximal row index
  • Maximal column index
  • Current direction
  • The map of the maze, using points ([row column] vectors) as keys

After that, do the same in the opposite direction, updating the same set of information as above.

The first part above gives the maze map and the bounding box of the maze (min / max row / column index). The last thing left is to reconstruct the maze, which is rather trivial given that information.

Clojure solution

Here’s the actual code, explained step by step:

;; Always turn left
(defn opposite-dir [dir]
  ({:n :s, :e :w, :s :n, :w :e} dir))

This is a helper function to get the opposite direction of the one we are currently pointing toward.

(defn maze-move [[cr cc minr minc maxr maxc dir maze-map] cmd]
  (case cmd
    \W (let [nr (+ cr ({:n -1, :e 0, :s 1, :w 0} dir))
             nc (+ cc ({:n 0, :e 1, :s 0, :w -1} dir))
             croom (or (maze-map [cr cc]) #{})
             nroom (or (maze-map [nr nc]) #{})
             new-croom (conj croom dir)
             new-nroom (conj nroom (opposite-dir dir))
             new-maze-map (assoc maze-map [cr cc] new-croom [nr nc] new-nroom)]
         [nr nc
          (min minr nr) (min minc nc)
          (max maxr nr) (max maxc nc)
          dir new-maze-map])
    \L [cr cc minr minc maxr maxc
        ({:n :w, :e :n, :s :e, :w :s} dir)
        maze-map]
    \R [cr cc minr minc maxr maxc
        ({:n :e, :e :s, :s :w, :w :n} dir)
        maze-map]))

This one actually makes the move given the current parameters and yields the next state. This is used below to reduce over the set of moves. There are three possible moves:

  • L / R – these just change the direction, without changing the position or anything else. This is done by doing the map-lookup in both cases
  • W – perform the walk. Walking has three consequences:
    • Position is changed (so nr / nc = new row and column indices are updated, also using map-lookup method)
    • Current and new rooms’ (new-croom / new-nroom) walls are updated (i.e. removed on the appropriate sides, given walking is possible) and
    • The map is updated with the new rooms
(defn make-maze [maze-map minr minc maxr maxc]
  (for [r (range minr (inc maxr))] 
    (apply str
           (for 1
             (case (maze-map [r c])
               #{:n         } "1"
               #{   :s      } "2"
               #{:n :s      } "3"
               #{      :w   } "4"
               #{:n    :w   } "5"
               #{   :s :w   } "6"
               #{:n :s :w   } "7"
               #{         :e} "8"
               #{:n       :e} "9"
               #{   :s    :e} "a"
               #{:n :s    :e} "b"
               #{      :w :e} "c"
               #{:n    :w :e} "d"
               #{   :s :w :e} "e"
               #{:n :s :w :e} "f"
               "#")))))

This is the function that generates the maze layout in the format required by the problem given the parameters output by the reduce over the given moves.

(defn always-turn-left-case [ent-to-exit exit-to-ent]
  (let [[exr exc minr minc maxr maxc dir maze-map]
        (reduce maze-move [-1 0 0 0 0 0 :s {}] ent-to-exit)
        [_ _ _ minc maxr maxc _ maze-map]
        (reduce maze-move [exr exc minr minc maxr maxc (opposite-dir dir) maze-map] exit-to-ent)
        act-minc (if (= dir :w) (inc minc) minc)
        act-maxr (if (= dir :s) (dec maxr) maxr)
        act-maxc (if (= dir :e) (dec maxc) maxc)]
    (make-maze maze-map 0 act-minc act-maxr act-maxc)))

The core of the solver:

  • Reduce from entrance to exit
  • Reduce from exit to entrance
  • Calculate the bounding box (discarding the last step, as entrance and exit are always outside of the maze)
  • Make the maze map
(defn always-turn-left []
  (let [case-names ["sample" 
                    "B-small-practice" 
                    "B-large-practice"]
        case-names-full (for [case-name case-names] 
                          (str "08pp/always-turn-left/" case-name))] 
    (time
     (doseq [case-name case-names-full] 
       (let [{:keys [case-count cases]} (read-cases case-name)
             output (for [case cases]
                      (let [[ent-to-exit exit-to-ent] (split case #" ")]
                        (join "\n" (always-turn-left-case ent-to-exit exit-to-ent))))]
         (write-output output case-name true))))))

The wrapper around the solver to run the given problems – sample is the sample given in the description and B-small-practice and B-large-practice are the files that you can download when submitting the solution. Cases are read using the utility functions and written in the expected format.

Be Sociable, Share!

One Response to “Google Code Jam – Always Turn Left in Clojure”

Maze display app for Always Turn Left « Blog Archive « icyrock.com on April 3rd, 2013 21:50:

[...] Last time, I presented a solution for Always Turn Left, a Google Code Jam problem. Given that their large dataset was quite big (up to 10k moves), I thought: “It would be interesting to see what mazes those moves produce”. So I set to write (in Clojure, of course) a maze-display app (using Seesaw, of course). Here’s what came out of that. [...]


Leave a Reply


eight × 7 =

Google Code Jam – Alien Numbers in Clojure

Google Code Jam is a competition where participants compete in solving algorithmic puzzle. It’s been done usually a couple of times a year for different regions. The one for year 2013. starts in less than a couple of weeks from now!

I wanted to exercise my Clojure newbie skills to try to solve one of the practice problems from the first practice session available – Alien Numbers. Probably good to go read the problem statement to understand what I’m going to do below.

Here’s how I went about it…

Utilities

First, some generic utilities. Not sure if this applies to all, but looks like Code Jam problems are made so you read the .in file and write the .out file. The samples are given, for example for Alien Numbers the sample is:

  • Input
4
9 0123456789 oF8
Foo oF8 0123456789
13 0123456789abcdef 01
CODE O!CDE? A?JM!.

where the first line gives the number of cases (4 in this case) and then that many lines follow with the actual cases.

  • Output
Case #1: Foo
Case #2: 9
Case #3: 10011
Case #4: JAM!

where you have 4 cases with prefix “Case #N: ” and the corresponding output from the solver program.

So, my first thought is – I need a couple of functions to read and write these. Here’s how I did these:

(ns com.icyrock.clojure.codejam.utils
  (:use clojure.java.io))

(def base-res "com/icyrock/clojure/codejam/")

(defn read-case-lines [case-name]
  (let [res (resource (str base-res case-name))]
    (with-open [rdr (reader res)]
      (doall (line-seq rdr)))))
  
(defn read-cases [case-name]
  (let [lines (read-case-lines (str case-name ".in"))
        case-count (Integer/parseInt (first lines))
        cases (doall (rest lines))]
    {:case-count case-count
     :cases cases}))
  
(defn write-output [output case-name]
  (let [in-res (resource (str base-res case-name ".in"))
        in-file-name (.getFile in-res)
        out-file-name (clojure.string/replace in-file-name #".in$" ".out")]
    (with-open [wr (writer out-file-name)] 
      (doseq [[case-no case-out] (map vector (range 1 (inc (count output))) output)]
        (.write wr (str "Case #" case-no ": " case-out "\n"))))))

There are four things above:

  • base-res is the resource folder where I read the .in files from and then construct the name to write the .out file to
  • read-case-lines read the lines of the .in file
  • read-cases uses the above to read the number of cases and the cases themselves
  • write-output will get the program output and write the output in the required format (i.e. prefixing with “Case #N: “)

Actual solution methods

Solution is easy given two methods:

  • First part – alien to “computer” numbers converter
  • Second part – “computer” to alien numbers converter

I’ll make two methods here:

  • from-num-repr that does alien-to-computer conversion
  • to-num-repr that does computer-to-alien conversion

Tests

Let’s write some tests to define how these are to work:

(ns com.icyrock.clojure.codejam.cj2008pp_test
  (:use [com.icyrock.clojure.codejam.cj2008pp])
  (:use 1))

(deftest from-num-repr-test
  ;; bin / dec / hex 
  (is (= 9 (from-num-repr "1001" "01")))
  (is (= 72 (from-num-repr "72" "0123456789")))
  (is (= 0x53 (from-num-repr "53" "0123456789abcdef")))

  ;; same as above, just different symbols
  (is (= 9 (from-num-repr "tfft" "ft")))
  (is (= 72 (from-num-repr "hc" "abcdefghij")))
  (is (= 0x53 (from-num-repr "wz" "1qaz2wsx3edc4rfv"))))

(deftest to-num-repr-test
  ;; bin / dec / hex 
  (is (= "1001" (to-num-repr 9 "01")))
  (is (= "72" (to-num-repr 72 "0123456789")))
  (is (= "53" (to-num-repr 0x53 "0123456789abcdef")))

  ;; same as above, just different symbols
  (is (= "tfft" (to-num-repr 9 "ft")))
  (is (= "hc" (to-num-repr 72 "abcdefghij")))
  (is (= "wz" (to-num-repr 0x53 "1qaz2wsx3edc4rfv"))))

Here I used three simple numbers in three common bases – binary, decimal and hexadecimal. Also did two cases – with “normal” symbols and with “garbage” symbols.

First part – alien to “computer” numbers

(defn from-num-repr [num lan]
  (let [lan-dig (apply hash-map (interleave lan (range)))
        rnum (reverse num)
        val-rnum (map lan-dig rnum)
        dig-cnt (count num)
        lan-cnt (count lan) 
        exps (iterate #(* lan-cnt %) 1)
        vals (map * val-rnum exps)]
    (apply + vals)))

Here:

  • lan-dig is a map of alien language symbol to digit. E.g. if language symbols are “abcd”, then lan-dig would be: {\a: 0, \b: 1, \c: 2, \d: 3}
  • rnum is reverse number. We need it reverse as we apply exponents based on the positional value of the digit
  • val-rnum is the digit value for each of the symbols in rnum
  • exps is the exponents of the digit position in the target alien language. E.g. if alien language has 2 symbols (i.e. binary system), exps would be [1, 2, 4, 8, 16, ...]
  • vals is the positional value of the rnum
  • Finally, we just sum the values in vals to get the number value

Second part – “computer” to alien numbers

(defn to-num-repr [num lan]
  (let [dig-lan (apply hash-map (interleave (range) lan))
        lan-cnt (count lan)]
    (loop [left num tl-num '()]
      (if (= left 0)
        (apply str tl-num)
        (let [num-mod (mod left lan-cnt)
              num-left (quot left lan-cnt)] 
          (recur num-left (cons (dig-lan num-mod) tl-num)))))))

This is reverse from the previous:

  • dig-lan is reverse of lan-dig, i.e. keys and values reversed. E.g. if language symbols are “abcd”, then lan-dig would be: {0: \a, 1: \b, 2: \c, 3: \d}
  • We then loop until we reach zero, i.e. nothing more to convert
  • In each iteration, we get the modulus and reminder of the number and add the appropriate digit by mapping via dig-lan to the accumulated result (tl-num)
  • Finally, we just apply str function to tl-num list to get the actual string representation using the target alien language symbols

Third part – glue the above to get alien to another alien numbers converter

This is just a simple glue:

  • Get the number and the language symbols in the source alien language
  • Convert to “computer” representation
  • Convert that “computer” representation to the number in the target alien language, given the target language symbols
(defn alien-numbers-case [case]
  (let [[sl-num src-lan tgt-lan] (split case #" ")
        num (from-num-repr sl-num src-lan)
        tl-num (to-num-repr num tgt-lan)]
    tl-num))

Fourth part – do this for all available cases

There are usually three cases given for each Code Jam:

  • Sample – this is usually short and contains of a few input cases and their corresponding outputs
  • Small – a bigger set than Sample set. Looks like in real competition you get a different file each time you ask for the input (so outputs need to be re-created each time a file is downloaded)
  • Large set – larger set than Small set and looks like you get only this file always

I downloaded these in the resource folder of the app and ran for them all:

(defn alien-numbers []
  (let [case-names ["sample" 
                   "A-small-practice" 
                   "A-large-practice"]
        case-names-full (for [case-name case-names] 
                          (str "08pp/alien-numbers/" case-name))] 
    (time
      (doseq [case-name case-names-full] 
        (let [{:keys [case-count cases]} (read-cases case-name)
              output (for [case cases] (alien-numbers-case case))]
          (write-output output case-name))))))

End

The best thing – the output actually passes when submitted… Nice!

Be Sociable, Share!

2 Responses to “Google Code Jam – Alien Numbers in Clojure”

Google Code Jam – Always Turn Left in Clojure « Blog Archive « icyrock.com on March 21st, 2013 19:29:

[...] Last time I did the first practice problem (Alien Numbers). Now it’s time to do the second one – Always Turn Left. [...]


rebcabin on March 23rd, 2013 22:48:

There seems to be an unbound symbol, “resource”, in the definition of “read-case-lines.” I can’t get utils to run without it.


Leave a Reply


one + 6 =

Python – mix lists, preserving order

Say you have the following lists:

  • lower_case – A list of all lower-case letters
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
  • upper_case – A list of all upper-case letters
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
  • digits – A list of digits
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

and a list of the above, i.e.:

list = [lower_case, upper_case, digits]
 

Now, you want to make a single list with the following properties:

  • All elements of the given lists need to be present
  • The order of elements of each of the individual lists needs to be preserved
  • The final list needs to be randomized

In our case, the final list would be a list of all lower-case letters, upper-case letters and digits, where all individual categories are still ordered (e.g. ‘a’ needs to come before ‘b’ in the final list), but randomized. Here’s one way to do it:

  • Start from the starting list
[['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
  • Zip the lists to get the ids (which are conveniently list indices in the starting list) [
(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'], 0),
(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], 1),
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 2)]
  • For each list, create the list which has the same length, but whose every element is the index of that list. Make a master index list that contains these
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]]
  • Flatten the master index list
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
  • Shuffle the master index list
[2, 2, 1, 0, 1, 1, 1, 0, 2, 0, 1, 1, 1,
2, 1, 0, 0, 0, 1, 0, 1, 2, 1, 0, 1, 0,
0, 1, 1, 1, 2, 0, 0, 0, 1, 0, 2, 1, 1,
1, 2, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0,
0, 0, 2, 0, 1, 0, 0, 0, 2, 0]
  • Create iterators for each of the starting lists. Put them in the iterators list
  • Go through the master index list and pick the iterator from the iterators list based on the index which is given by the current element
[0, 1, 'A', 'a', 'B', 'C', 'D', 'b', 2, 'c',
'E', 'F', 'G', 3, 'H', 'd', 'e', 'f', 'I',
'g', 'J', 4, 'K', 'h', 'L', 'i', 'j', 'M',
'N', 'O', 5, 'k', 'l', 'm', 'P', 'n', 6,
'Q', 'R', 'S', 7, 'T', 'o', 'p', 'U', 'q',
'r', 'V', 'W', 'X', 'Y', 's', 't', 'u', 8,
'v', 'Z', 'w', 'x', 'y', 9, 'z']

In something readable :), i.e. Python:

#!/usr/bin/python
from __future__ import print_function
import random
import itertools
import string

def mix_lists(lists):
  id_pairs = zip(lists, range(len(lists))) 
  print(id_pairs)
  id_lists = [[id] * len(list) for (list, id) in id_pairs]
  print(id_lists)
  id_list = [id for sub_list in id_lists for id in sub_list]
  print(id_list)
  random.shuffle(id_list)
  print(id_list)
  iterators = [iter(list) for list in lists]
  mixed_lists = [iterators[id].next() for id in id_list]
  return mixed_lists

lower_case = 1
upper_case = 1
digits = range(10)
lists = [lower_case, upper_case, digits]

print(lists)

for i in range(1):
  print(mix_lists(lists))
Be Sociable, Share!

Leave a Reply


9 − five =

Luaj lcdui

Luaj is a library bringing Lua to Java 2 ME platform. It does not have the bindings to javax.microedition.lcdui package. I wanted to see how easy it is to add a few classes from this package in order for them to be available to Lua programs. You can grab the source from here.

Main MIDlet

Here’s the MIDlet code:

package com.icyrock.j2me.luaj.example;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jme.JmePlatform;

import com.icyrock.j2me.luaj.LcduiLib;
import com.icyrock.j2me.luaj.Midlet;

public class LuajMidlet extends MIDlet {
  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };
    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  private String readFile(String fileName) {
    String ret = "";

    FileConnection fc = null;
    try {
      fc = (FileConnection) Connector.open(fileName);
      InputStream is = fc.openInputStream();

      StringBuffer sb = new StringBuffer();

      int cap = 2000;
      byte[] bytes = new byte[cap];

      while (true) {
        int available = is.available();
        if (available == 0) {
          break;
        }

        int len = available > cap ? cap : available;
        is.read(bytes, 0, len);

        sb.append(new String(bytes, 0, len, "UTF-8"));
      }

      ret = sb.toString();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fc != null) {
        try {
          fc.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    return ret;
  }
}

It has a readFile method, which is nothing else but a method to read files. Java 2 ME is pretty limited, so there are no e.g. BufferedReader or classes such as that to help with this task, so I added this quick method to read the file in whole.

The main code is in startApp method, the breakdown of which follows:

  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

When used in an emulator, such as MicroEmulator, just print the string to know this is starting to be loaded. Also, define the Lua script to be run.

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

Define the _G, which is the global Luaj context. Add our LcduiLib, so it’s accessible to Lua scripts we write. Initialize a variable lcdui.midlet.current that will, when accessed from within Lua script, point to the current MIDlet instance.

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };

Define the loader function. This function will be called to load all parts of Lua script, which will then be concatenated and run. The script is loaded from:

"file:///SDCard/icyrock/lua/" + script + ".lua"

so in this case that would be:

file:///SDCard/icyrock/lua/phi.lua

containing a sample Lua script we’ll see later. If you use MicroEmulator under Linux, you should put this file in:

~/.microemulator/filesystem/SDCard/icyrock/lua/phi.lua 

The last step:

    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

is to load the Lua script and run it.

Sample Lua script

You can find this one in com/icyrock/j2me/luaj/example/lua/phi.lua:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

The breakdown is:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

Get the display. This is the standard J2ME lcdui stuff.

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

Create a from and put a command listener. The listener is just going to destroy the form when a command with label "Exit" is chosen.

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

Create two commands, both of which are used for exiting and add them to the form. In MicroEmulator, this allows for both shown commands to be used to exit the script.

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

Add a sample text message with label "Message" and the string "Hi from Luaj!" as the main text. Finally, set the display to be visible (again standard J2ME).

Note two things:

  • Lua function print is going to print to MicroEmulator's console
  • You can exit from MIDlet in the emulator, change the Lua script, run the MIDlet again and the changes will be applied
Be Sociable, Share!

Leave a Reply


3 + = ten

Developing Bluetooth-enabled apps with Java

A good starting explanation about network programming in general comes from this page. Look at the whole book – it’s called “An Introduction to Bluetooth Programming” and I find it a very good read.

Emulation

Before we start, I’d like to mention that, for development, it’s much easier to develop using emulation of bluetooth / mobile phone. For that, I’ve used BlueCove and MicroEmulator, respectively. The glue between them is the BlueCove JSR-82 Emulator module.

Scenario

The situation we’ll examine is simple. We have two devices – in this case a PC and a phone – communicating with each other. The example would be a simple searcher for Stack Exchange sites. The user on the mobile phone would type a word, that gets sent to the server on the PC, it searches using api.stackexchange.com and returns the results to the phone, which displays them.

Following are the steps needed for the above to happen.

Discovery

To communicate, a bluetooth app uses the SDP (Service Discovery Protocol) to find the other devices and services they provide. In this case, as PC acts as the server, the phone would initiate the discovery, i.e. be a client. The basic idea is presented in BlueCove’s javadoc. I’ve made a helper class called BluetoothUtils:

package com.icyrock.bluetooth;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class BluetoothUtils {
  private static final int btAttrServiceName = 0x0100;
  private final static Logger log = LoggerFactory.getLogger(BluetoothUtils.class);

  public List<RemoteDevice> discoverDevices() {
    final Object sync = new Object();
    final List<RemoteDevice> devs = new ArrayList<RemoteDevice>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
        try {
          log.info("Found device: address: {}, name: {}",
            dev.getBluetoothAddress(),
            dev.getFriendlyName(false));
          devs.add(dev);
        } catch (IOException e) {
          log.error(e.toString(), e);
        }
      }

      public void inquiryCompleted(int discType) {
        log.info("Device inquiry completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
      }
    };

    synchronized (sync) {
      boolean started;
      try {
        started = LocalDevice.getLocalDevice().getDiscoveryAgent()
          .startInquiry(DiscoveryAgent.GIAC, listener);
        if (started) {
          log.info("Device inquiry started");
          sync.wait();
          log.info("Devices count: {}", devs.size());
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    return devs;
  }

  public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) {
    final Object sync = new Object();
    final List<String> urls = new ArrayList<String>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
      }

      public void inquiryCompleted(int discType) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
        for (int i = 0; i < servRecord.length; i++) {
          String url = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
          if (url != null) {
            urls.add(url);
            DataElement name = servRecord[i].getAttributeValue(btAttrServiceName);
            log.info("Service found: url: {}, name: {}", url, name);
          }
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
        log.info("Service search completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

    };

    UUID[] uuidArr = new UUID[] { new UUID(uuidStr, false) };
    int[] attrIds = new int[] { btAttrServiceName };

    for (RemoteDevice device : devices) {
      synchronized (sync) {
        try {
          log.info("Searching for service: {} on device: {} / {}",
            new Object[] { uuidStr, device.getBluetoothAddress(), device.getFriendlyName(false) });
          LocalDevice.getLocalDevice().getDiscoveryAgent()
            .searchServices(attrIds, uuidArr, device, listener);
          sync.wait();
        } catch (Exception e) {
          log.error(e.toString(), e);
        }
      }
    }

    return urls;
  }
}

This one has two methods:

  • public List<RemoteDevice> discoverDevices() – Searches for the devices and returns a list of devices that have been found
  • public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) – Searches for the service given by uuidStr on the devices in the devices list. It returns the URLs that can be used to access these services

These methods are going to be used in client and server later.

Client

The client is fairly simple:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import javax.bluetooth.RemoteDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommClient {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommClient.class);
  @Autowired
  private BluetoothUtils bluetoothUtils;
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

  public void run() {
    try {
      startClient();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startClient() throws Exception {
    List<RemoteDevice> devices = bluetoothUtils.discoverDevices();
    List<String> urls = bluetoothUtils.searchForServices(devices, uuid);

    if (urls.size() > 0) {
      String keywords = "bluetooth";

      StreamConnection streamConn = (StreamConnection) Connector.open(urls.get(0));

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(Lists.newArrayList(keywords), null, os);
      os.close();

      InputStream is = streamConn.openInputStream();
      String result = IOUtils.toString(is);
      is.close();

      log.info("Result: {}", result);

      streamConn.close();

      log.info("Client done");
    }
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommClient sebc = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommClient.class);
    sebc.run();
  }
}

Aside from the boilerplate code needed, there are two important notes above. First, in main method, there’s this line:

    System.setProperty("bluecove.stack", "emulator");

This one is used when testing this with BlueCove Bluetooth emulation, to signal BlueCove to use the emulator instead of the real bluetooth stack. Read more about this here.

Second, note this line:

  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

This one defines the UUID of the service that we are going to look for. In Bluetooth, all services are represented by UUID. To generate these in Linux, you can do something like:

$ uuidgen|tr -d '-'
db56eca4204d4f478063dacab16e805f

The other things are all in startClient method. The method does this:

  • Searches for all devices
  • Searches for the given service (identified by the uuid) on all found devices
  • If the search result yields any URLs, pick the first one (the other option would be to present the user with the choice of which URL to use)
  • Open a StreamConnection – this is basically a RFCOMM channel
  • Write the search keywords to the output stream
  • Read the response from the server and log it

Note that I’m using a few helper libraries here:

Here’s a Gradle config for this project:

apply plugin: 'java'

repositories {
  mavenCentral()
}

dependencies {
  compile 'net.sf.bluecove:bluecove:2.1.0'
  runtime 'net.sf.bluecove:bluecove-gpl:2.1.0'
  runtime 'net.sf.bluecove:bluecove-emu:2.1.0'
  
  compile 'ch.qos.logback:logback-classic:1.0.7'
  compile 'commons-io:commons-io:2.4'
  compile 'org.apache.httpcomponents:httpclient:4.2.1'
  compile 'org.apache.httpcomponents:fluent-hc:4.2.1'
  compile 'com.fasterxml.jackson.core:jackson-databind:2.1.0'
 
  compile 'org.springframework:spring-context:3.1.2.RELEASE'
  runtime 'asm:asm:3.3.1'
  runtime 'cglib:cglib-nodep:2.2.2'
  runtime 'org.slf4j:jcl-over-slf4j:1.6.6'
  
  compile 'com.google.guava:guava:13.0.1'
}

StreamUtils

One problem that I encountered is that you cannot use BufferedReader to read from InputStream provided by the bluetooth connection. The issue is that readLine will block until the whole line is read, but the way it works is it reads chunks of data to be efficient and thus doesn’t know when the line ends. In order to circumvent this, I’m using a simple char-by-char reading, which is hacky, inefficient and might not work properly sometimes, but solves this problem and works well for this example.

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.google.common.base.Joiner;

@Component
public class StreamUtils {
  private final static Logger log = LoggerFactory.getLogger(StreamUtils.class);

  private String readLine(InputStream inputStream) {
    StringBuilder sb = new StringBuilder();
    int sleeps = 0;
    boolean haveNewLine = false;

    while (true) {
      int byteRead;
      try {
        if (inputStream.available() == 0) {
          if (sleeps < 3) {
            sleeps++;
            Thread.sleep(1000);
            continue;
          } else {
            break;
          }
        }

        byteRead = inputStream.read();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }

      if (byteRead == -1) {
        break;
      }

      char ch = (char) byteRead;
      if (ch == '\n') {
        haveNewLine = true;
        break;
      }
      sb.append(ch);
    }
    return haveNewLine ? sb.toString() : null;
  }

  public List<String> readLines(InputStream inputStream) {
    List<String> lines = new ArrayList<>();
    while (true) {
      String line = readLine(inputStream);
      if (line == null) {
        break;
      }
      lines.add(line);
    }
    return lines;
  }

  public String readLinesAsStr(InputStream inputStream) {
    return Joiner.on("\n").join(readLines(inputStream));
  }
}

Server

Server is pretty simple, too:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommServer {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommServer.class);
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";
  @Autowired
  private StreamUtils streamUtils;

  public void run() {
    try {
      startServer();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startServer() throws Exception {
    LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);

    StreamConnectionNotifier serverConnection = (StreamConnectionNotifier) Connector.open(
      "btspp://localhost:" + uuid + ";name=StackExchangeBluetoothServer");

    while (true) {
      log.info("Waiting for connection");
      StreamConnection streamConn = serverConnection.acceptAndOpen();

      log.info("Request received");

      InputStream is = streamConn.openInputStream();
      String content = streamUtils.readLinesAsStr(is);
      is.close();

      List<String> resp;
      if (content == null || content.equals("")) {
        log.info("Empty keywords received from the client.");
        resp = Lists.newArrayList("Please provide keywords to search for");
      } else {
        resp = queryStackExchange(content);
      }

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(resp, null, os);
      os.close();

      streamConn.close();
    }
  }

  @SuppressWarnings("unchecked")
  private List<String> queryStackExchange(String keywords) throws Exception {
    // Uncomment if you want to test without actually making the calls to StackExchange
//    if(true) {
//      return Lists.newArrayList("title1", "title2", "title3");
//    }
    
    URI uri = new URIBuilder()
      .setScheme("https")
      .setHost("api.stackexchange.com")
      .setPath("/2.1/search")
      .setParameter("order", "desc")
      .setParameter("intitle", keywords)
      .setParameter("site", "stackoverflow")
      .setParameter("pagesize", "5")
      .build();

    HttpResponse httpResp = Request.Get(uri)
      .connectTimeout(5000)
      .socketTimeout(5000)
      .execute().returnResponse();

    List<String> ret = new ArrayList<String>();
    if (httpResp.getStatusLine().getStatusCode() == 200) {
      HttpEntity httpEnt = httpResp.getEntity();

      InputStream is = httpEnt.getContent();
      Header contentEncoding = httpResp.getFirstHeader("Content-Encoding");
      if (contentEncoding != null && "gzip".equals(contentEncoding.getValue())) {
        is = new GZIPInputStream(is);
      }
      String response = IOUtils.toString(is);
      is.close();

      ObjectMapper om = new ObjectMapper();
      Map<String, Object> map = om.readValue(response, Map.class);
      List<Map<String, Object>> items = (List<Map<String, Object>>) map.get("items");

      for (Map<String, Object> item : items) {
        ret.add((String) item.get("title"));
      }
    }

    log.info("Result: {}", Joiner.on(", ").join(ret));
    return ret;
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommServer sebs = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommServer.class);
    sebs.run();
  }
}

In startServer method, it listens for the connection. When one is established, it reads the keywords from the client. If nothing was read, it will reply with a help message. If keywords were read, it will query the StackExchange. The query is separated into queryStackExchange method and uses Apache HttpClient fluid interface – seems pretty nice to me. If the response was received, it will un-GZIP it if needed and then use Jakson JSON parser to actually get the titles from the response.

Be Sociable, Share!

Leave a Reply


six − 4 =

Sample J2ME app using Eclipse / STS

I’m using Spring Tools Suite that’s based on Eclipse, which can be downloaded from here, but the process below should be the same in general if you use regular Eclipse. You’ll need STS not based on Eclipse Juno (see this for more info), so download STS 2.9.2, which is based on Eclipse 3.7.2.

For testing, I’ve been using MicroEmulator.

Configuring Eclipse

After getting the base Eclipse / STS distribution, install Mobile Tools for Java (MTJ) from here. There’s a good tutorial on eclipse.org itself on how to make the basic MIDlet and run it in the emulator.

Sample J2ME HTTP fetcher MIDlet

MIDlets are Java classes used as entry points to your application. You have three abstract methods to override:

  • destroyApp – called when the MIDlet is about to be destroyed
  • pauseApp – called when MIDlet is to be paused, so other MIDlet can run instead
  • startApp – called when MIDlet is to be activated (either for the first time or after being paused)

We’ll make a sample HTTP fetcher. I’ll go straight to the code. First, a helper class:

package com.icyrock.j2me.prb;

public interface IcyrockRunnable {
  public void run() throws Exception;
}

This one is the same as the regular java.lang.Runnable with the exception that it can throw Exceptions, just for convenience.

Main class

I’ll dissect the main class now – the whole class is pasted after this if you just want to copy & paste:

package com.icyrock.j2me.prb;

import java.io.InputStream;
import java.util.Hashtable;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class HttpFetchPrb extends MIDlet {
  private Hashtable commands = new Hashtable();
  private Form form;
  private TextField urlTxt;
  private TextField responseTxt;

After some imports, there are 4 fields:

  • commands field is going to store command-to-IcyrockRunnable mapping. Easy for mapping commands
  • form is our top-level form, holding the other GUI controls
  • urlTxt and responseTxt are two GUI controls put on the form that we’ll use to store the URL to fetch from and the response itself (including the eventual errors)
  public HttpFetchPrb() {
    form = new Form(this.getClass().getName());
    form.setCommandListener(new CommandListener() {
      public void commandAction(Command cmd, Displayable arg1) {
        IcyrockRunnable toRun = (IcyrockRunnable) commands.get(new Integer(cmd.getCommandType()));
        if (toRun != null) {
          try {
            toRun.run();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    });

This is the start of the constructor. We create the form and then initialize the command listener. Command listener is a top-level listener for all commands put on this form. They are differentiated by the command type, which we then map through our commands field and run the required IcyrockRunnable.

    urlTxt = new TextField("URL:", "http://192.168.122.1/", 256, TextField.ANY);
    form.append(urlTxt);

    final int maxLen = 1024;
    responseTxt = new TextField("Response", "", maxLen, TextField.ANY);
    form.append(responseTxt);

We now create the text field holding the actual URL and the response text field. The default URL is put there – in my case, I put 192.168.122.1, but to test you should put the real IP of the machine you are running the emulator on. MicroEmulator has the option to allow MIDlets to access network – this should be enabled by default, but if not, the option is in Options menu / MIDlet Network access.

    int priority = 1;
    addCommand(new Command("Fetch", Command.OK, priority++), new IcyrockRunnable() {
      public void run() throws Exception {

This is how a command is created. You give it the name (“Fetch”) and the type (Command.OK, an int). addCommand method is just a helper method that you’ll see at the end.

        HttpConnection con = (HttpConnection) Connector.open(urlTxt.getString());
        try {
          int respCode = con.getResponseCode();

          String responseStr = "";
          int responseLen = (int) con.getLength();

          int len = responseLen > maxLen ? maxLen : responseLen;
          byte[] respData = new byte[len];

          InputStream ins = con.openInputStream();
          ins.read(respData);
          ins.close();

          responseStr = new String(respData);

          responseTxt.setString("Response code: " + respCode + "\n" + responseStr);
        } catch (Exception e) {
          responseTxt.setString("Error: " + e.getMessage());
        } finally {
          con.close();
        }
      }
    });

This is the gist of the HTTP fetcher. In steps it:

  • Creates the HttpConnection to the given URL
  • Gets the response code
  • Creates the response buffer to hold the response itself (and limits to maxLen because that’s the length of our text field)
  • Opens and reads the input stream
  • Converts that to string
  • Puts the response code and the response itself into the responseTxt text field

There’s a guard for exceptions that prints the exception itself into the response text field. Not sure if that’s the general stance, but in MicroEmulator that I used for testing, fetching the URL that doesn’t exist (i.e. when HTTP response is 404) throws an exception instead of giving a 404 response code.

    addCommand(new Command("Exit", Command.EXIT, priority++), new IcyrockRunnable() {
      public void run() throws Exception {
        destroyApp(false);
        notifyDestroyed();
      }
    });
  }

This command is an exit command. When deliberately exiting, you should destroy the resources and then call notifyDestroyed to notify the container that it wants to get destroyed.

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  protected void startApp() throws MIDletStateChangeException {
    Display display = Display.getDisplay(this);
    display.setCurrent(form);
  }

Two required abstract method implementations – we don’t have anything to destroy, nor want to handle app pausing. startApp is important – here we take the display and slap our form on it, so it’s visible.

  private void addCommand(Command command, IcyrockRunnable icyrockRunnable) {
    commands.put(new Integer(command.getCommandType()), icyrockRunnable);
    form.addCommand(command);
  }
}

This is a helper method used above – it just records the command handler and puts it on the form.

Complete main class

Here’s the complete main class:

package com.icyrock.j2me.prb;

import java.io.InputStream;
import java.util.Hashtable;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class HttpFetchPrb extends MIDlet {
  private Hashtable commands = new Hashtable();
  private Form form;
  private TextField urlTxt;
  private TextField responseTxt;

  public HttpFetchPrb() {
    form = new Form(this.getClass().getName());
    form.setCommandListener(new CommandListener() {
      public void commandAction(Command cmd, Displayable arg1) {
        IcyrockRunnable toRun = (IcyrockRunnable) commands.get(new Integer(cmd.getCommandType()));
        if (toRun != null) {
          try {
            toRun.run();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    });

    urlTxt = new TextField("URL:", "http://192.168.122.1/", 256, TextField.ANY);
    form.append(urlTxt);

    final int maxLen = 1024;
    responseTxt = new TextField("Response", "", maxLen, TextField.ANY);
    form.append(responseTxt);

    int priority = 1;
    addCommand(new Command("Fetch", Command.OK, priority++), new IcyrockRunnable() {
      public void run() throws Exception {
        HttpConnection con = (HttpConnection) Connector.open(urlTxt.getString());
        try {
          int respCode = con.getResponseCode();

          String responseStr = "";
          int responseLen = (int) con.getLength();

          int len = responseLen > maxLen ? maxLen : responseLen;
          byte[] respData = new byte[len];

          InputStream ins = con.openInputStream();
          ins.read(respData);
          ins.close();

          responseStr = new String(respData);

          responseTxt.setString("Response code: " + respCode + "\n" + responseStr);
        } catch (Exception e) {
          responseTxt.setString("Error: " + e.getMessage());
        } finally {
          con.close();
        }
      }
    });

    addCommand(new Command("Exit", Command.EXIT, priority++), new IcyrockRunnable() {
      public void run() throws Exception {
        destroyApp(false);
        notifyDestroyed();
      }
    });
  }

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  protected void startApp() throws MIDletStateChangeException {
    Display display = Display.getDisplay(this);
    display.setCurrent(form);
  }

  private void addCommand(Command command, IcyrockRunnable icyrockRunnable) {
    commands.put(new Integer(command.getCommandType()), icyrockRunnable);
    form.addCommand(command);
  }
}

Result

Here’s how it looks in MicroEmulator:

Be Sociable, Share!

Leave a Reply


four + 2 =

Packaging for zero install

Here’s a quick tutorial on how to package things for Zero install. I am going to package the latest Groovy (2.0.4 at the time of this writing). There’s a tutorial on the site itself – check this.

Install 0publish-gui

I’m using Xubuntu 12.04, so I am going to install 0publish-gui – seems that for Windows you should use a different version, see the tutorial page for that. To install it, run this:

$ 0alias 0publish-gui http://0install.net/2007/interfaces/0publish-gui.xml

Creating a new feed

Start the program:

$ 0publish-gui

On the first dialog that opens:

click New feed.

Populate About tab

Populate the About tab like this:

All this is readily available from Groovy’s main site.

Versions tab

On the versions tab:

click Add Group, clcik OK to add an empty group, then add the Groovy binary archive by clicking Add archive button. You can find one from the Groovy’s download page, currently it’s http://dist.groovy.codehaus.org/distributions/groovy-binary-2.0.4.zip:

Click Download. After the zip has been downloaded:

click OK.

Version properties

Populate the version properties dialog like this:

Add dependencies

Groovy requires JDK, so let’s add that one as a dependency:

Click Add Requires:

From the Roscidus.com mirror search for “openjdk”. You should find this one. There’s a link under “Full name” section – the link is http://repo.roscidus.com/java/openjdk-jdk, so paste it here:

After clicking OK, you should have this:

To have the actual java command, openjdk-jre (http://repo.roscidus.com/java/openjdk-jre) is also needed, so add that, too:

Publishing tab

To publish the feed, enter the url and the key:

The above assumes you already have a GPG key to use for signing. If you do not have a GPG key generated, click on Add. This will open a GPG window, which you can populate like this (of course, change the personal information):

Saving the feed

Click Save now, you should get a confirmation of the files getting saved:

It will ask you for the GPG key password again:

After all that, you should have the following files:

total 27M
4.0K BC15B8C183209EDE.gpg
 27M groovy-binary-2.0.4.zip
4.0K groovy.xml
8.0K interface.xsl

Adding multiple commands

Exit 0publish-gui and edit the script to something like this:

<?xml version="1.0" ?>
<?xml-stylesheet type='text/xsl' href='interface.xsl'?>
<interface uri="http://icyrock.com/0install/ext/groovy.xml" xmlns="http://zero-install.sourceforge.net/2004/injector/interface">
  <name>groovy</name>
  <summary>agile and dynamic language for the Java Virtual Machine</summary>
  <description>
Groovy...

is an agile and dynamic language for the Java Virtual Machine

builds upon the strengths of Java but has additional power features inspired

by languages like Python, Ruby and Smalltalk

makes modern programming features available to Java developers with almost-

zero learning curve

provides the ability to statically type check and statically compile your

code

supports Domain-Specific Languages and other compact syntax so your code

becomes easy to read and maintain

makes writing shell and build scripts easy with its powerful processing

primitives, OO abilities and an Ant DSL

increases developer productivity by reducing scaffolding code when

developing web, GUI, database or console applications

simplifies testing by supporting unit testing and mocking out-of-the-box

seamlessly integrates with all existing Java classes and libraries

compiles straight to Java bytecode so you can use it anywhere you can use Java</description>
  <homepage>http://groovy.codehaus.org/</homepage>
  <category>Development</category>
  <group>
    <needs-terminal/>
    
    <command name="grape" path="bin/grape"/>
    <command name="groovy" path="bin/groovy"/>
    <command name="groovyc" path="bin/groovyc"/>
    <command name="groovyConsole" path="bin/groovyConsole"/>
    <command name="groovydoc" path="bin/groovydoc"/>
    <command name="groovysh" path="bin/groovysh"/>
    <command name="java2groovy" path="bin/java2groovy"/>
    <command name="startGroovy" path="bin/startGroovy"/>

    <environment name="GROOVY_HOME" insert="." mode="replace" />

    <implementation id="sha1new=d0f6ef2a42b0f5479993e0dd4e5d66b622d18a99" license="The Apache License 2.0" main="bin/groovy" released="2012-09-30" version="2.0.4">
      <archive extract="groovy-2.0.4" href="http://dist.groovy.codehaus.org/distributions/groovy-binary-2.0.4.zip" size="28273120" type="application/zip"/>
      <requires interface="http://repo.roscidus.com/java/openjdk-jre">
        <environment name="JAVA_HOME" insert="." mode="replace" />
      </requires>
      <requires interface="http://repo.roscidus.com/java/openjdk-jdk">
        <environment name="JAVA_HOME" insert="." mode="replace" />
      </requires>
    </implementation>
  </group>
</interface>

Save the file, exit the editor, open 0publish-gui, open the feed and do the signing again and you now have multiple commands. E.g. to run groovysh instead of groovy, you can do this:

$ 0alias --command groovysh groovysh http://icyrock.com/0install/ext/groovy.xml

Publishing on the Web server

The only thing needed at this point is to publish them on your Web server. You need only the three files generated by the program, not the archive itself – the archive will be downloaded from the URL you gave (so in this case Groovy’s main site) and it will be checked to confirm it did not change (thus all the SHA info in the groovy.xml file if you open it). This depends on the server you are using, but usually just putting it in the required folder should be sufficient. Obviously, the URL should match what was entered on the Publishing tab.

Testing the feed

When you install on the Web server (you can even test on your local server for that matter), you can test by using 0alias as usual:

$ 0alias groovy http://icyrock.com/0install/ext/groovy.xml
$ 0alias --command groovysh groovysh http://icyrock.com/0install/ext/groovy.xml

You should be prompted with an “unknown key” dialog:

Make sure the key fingerprint is the one you trust. After you trust a key, all programs signed with that key will be trusted for installation, so you better trust the owner of the key…

After accepting the key and installing, you should be able to use groovy or groovysh:

$ groovy --version
Groovy Version: 2.0.4 JVM: 1.7.0_04 Vendor: Oracle Corporation OS: Linux
$ groovysh -V
Groovy Shell 2.0.4

Adding to the feed search engine

As per this, you are able to add your programs to this feed list. This is a good way to contribute to 0install, so if you package someting, announce it there.

Be Sociable, Share!

2 Responses to “Packaging for zero install”

alberto on October 1st, 2012 03:17:

Thanks, very nice tutorial. However, I had a problem running your feed:

$ 0launch –command groovysh http://icyrock.com/0install/ext/groovy.xml

groovysh: JAVA_HOME is not defined correctly, can not execute: /home/acolombo/.cache/0install.net/implementations/sha1new=6b2afc4b5841dfa24b996b321d08b48109b0c5c3/./bin/java

(in fact, that folder contains javac, javadoc, and a dozen other executables, but not java).


icyrock.com on October 4th, 2012 20:03:

Hi Alberto – I looked inside the openjdk-jdk files, seems like actually openjdk-jre is needed to have java. I’ve updated the feed, please try again now.

Unfortunately, it seems that as long as Java is installed, it will not download the package to my machine. I guess this is a desired behavior from the duplication standpoint, but not sure how I can test it given that, as I already have Java installed through dpkg.

Do let me know if it still doesn’t work and I’ll try to find a way to test.


Leave a Reply


nine × 9 =

Linearized columns to HTML table in elisp

Let’s say you have linearized column data like this:

First name
Last name
Age
--
Barry
Yeengdull
33
--
Helga
Madbroom
44
--
Zelma
Landbeck
55
--
Peyton
Glonham
66

(names courtesy of Fictional Character Name Generator) and you want to make a HTML table out of it that will be displayed like this:

First nameLast nameAge
BarryYeengdull33
HelgaMadbroom44
ZelmaLandbeck55
PeytonGlonham66

The HTML for the above table would be:

<table>
<tr><th>First name</th><th>Last name</th><th>Age</th></tr>
<tr><td>Barry</td><td>Yeengdull</td><td>33</td></tr>
<tr><td>Helga</td><td>Madbroom</td><td>44</td></tr>
<tr><td>Zelma</td><td>Landbeck</td><td>55</td></tr>
<tr><td>Peyton</td><td>Glonham</td><td>66</td></tr>
</table>

Doing it in elisp

Here’s a general overview of what needs to be done:

  • Split the text by delimiters (in the above case, it’s --) to get a list of rows by column
  • Split column strings into cells
  • Transpose columns
  • Write the HTML out

Let’s do it one by one.

Split the text by delimiters (in the above case, it’s --) to get a list of rows by column

Elisp has a function called split-string that we can use for this purpose – see the docs in the elisp manual. For example, we can do this:

(defun icyrock-get-column-strs (str delim)
  (split-string str (concat "\n?" delim "\n?")))

;; Tests
(assert (equal
         (icyrock-get-column-strs "1\n2\n3\n--\n4\n5\n6\n--\n7\n8\n9" "--")
         '("1\n2\n3" "4\n5\n6" "7\n8\n9")))

which will:

  • Split the given string:
  1
  2
  3 
  --
  4
  5
  6
  --
  7
  8
  9

into lines using split-string. The result would be a list of strings, i.e. ("1\n\2\n3" "4\n5\n6" "7\n8\n9")

Split column strings into cells

Now that we have a list of strings, each representing a column, we can split each column into cells, again using split-string:

(defun icyrock-split-column-strs (strs)
  (mapcar (lambda (col-str) (split-string col-str "\n")) strs))

(defun icyrock-get-columns-from-string (str delim)
  (icyrock-split-column-strs (icyrock-get-column-strs str delim)))

;; Tests
(assert (equal
         (icyrock-get-columns-from-string "1\n2\n3\n--\n4\n5\n6\n--\n7\n8\n9" "--")
         '(("1" "2" "3") ("4" "5" "6") ("7" "8" "9"))))

Function icyrock-split-column-strs splits the list of column strings, so we get columns. icyrock-get-columns-from-string is a combination of this one and icyrock-get-column-strs, which, given our starting string, generates a list of cells ((1 2 3) (4 5 6) (7 8 9)).

Transpose columns

The list we got ((1 2 3) (4 5 6) (7 8 9)) seems ordered, however note that this is ordered by column, while we want it ordered by row. The list that we want is a transposition of this, i.e. ((1 4 7) (2 5 8) (3 6 9)).

(defun icyrock-transpose-table (table)
  (apply 'mapcar* 'list table))

(defun icyrock-get-rows-from-string (str delim)
  (icyrock-transpose-table (icyrock-get-columns-from-string str delim)))

;; Tests
(assert (equal
         (icyrock-get-rows-from-string "1\n2\n3\n--\n4\n5\n6\n--\n7\n8\n9" "--")
         '(("1" "4" "7") ("2" "5" "8") ("3" "6" "9"))))

Table transposition is done simply via mapcar* function, which does exactly what we need.

mapcar* takes the first element (a car in (e)lisp jargon) of all supplied arguments and applies the given function to it. To explain the above, try this:

(message (format "%s" (mapcar* 'list '(1 a x) '(2 b y) '(3 c z))))

What the above does is:

  • Get the list of first elements of supplied arguments, yielding (1 2 3)
  • Call the supplied function (“list” in this case) with them – effectively running (list 1 2 3)
  • Append that to the resulting list
  • Repeat, going the next list of elements, which in this case is (a b c)

The result in this case is the following list: ((1 2 3) (a b c) (x y z)).

Now, to transpose any list, what we need to do is exactly the above – take a look at the starting lists and the ending list. The only thing we need is to call mapcar* with the list of those arguments. This is exactly what apply does. Try this:

(message (format "%s" (apply '+ '(1 2 3))))

The above prints 6. In other words – (apply '+ '(1 2 3)) is the same as (+ 1 2 3). To generalize – (apply 'func '(a1 a2 ... aN)) is the same as (func a1 a2 ... aN), where func is any function and a1 through aN are its arguments.

Write the HTML out

Writing HTML output is fairly easy:

(defun icyrock-html-td-from-string (str)
  (format "<td>%s</td>" str))

(defun icyrock-list-to-string (list &optional sep)
  (mapconcat 'identity list sep))

(defun icyrock-html-tr-from-list (list)
  (format "<tr>%s</tr>"
          (icyrock-list-to-string (mapcar 'icyrock-html-td-from-string list))))

(defun icyrock-html-table-from-rows (table)
  (format "<table>\n%s\n</table>"
          (icyrock-list-to-string (mapcar 'icyrock-html-tr-from-list table) "\n")))

(defun icyrock-table-from-linearized-string (str delim)
  (icyrock-html-table-from-rows (icyrock-get-rows-from-string str delim)))

;; Tests
(assert (equal
         (icyrock-table-from-linearized-string "1\n2\n3\n--\n4\n5\n6\n--\n7\n8\n9" "--")
         (concat "<table>\n"
                 "<tr><td>1</td><td>4</td><td>7</td></tr>\n"
                 "<tr><td>2</td><td>5</td><td>8</td></tr>\n"
                 "<tr><td>3</td><td>6</td><td>9</td></tr>\n"
                 "</table>")))

The above produces the following:

<table>
<tr><td>1</td><td>4</td><td>7</td></tr>
<tr><td>2</td><td>5</td><td>8</td></tr>
<tr><td>3</td><td>6</td><td>9</td></tr>
</table>

Making it interactive

Now, obviously you’d want to use this while editing in Emacs. That is, write some linearized list, select the region and apply our function.

Here are the steps:

  • Get the currently selected region
  • Apply the above function to that, so you get the needed HTML
  • Overwrite the region with the HTML code. Alternatively, you can append after the region
(defun icyrock-make-html-table-from-current-region ()
  (interactive)
  (save-excursion
    (let ((current-region-string (buffer-substring (mark) (point))))
      (delete-region (mark) (point))
      (goto-char (mark))
      (insert (icyrock-table-from-linearized-string
               current-region-string "--")))))

(global-set-key (kbd "<f11>") 'icyrock-make-html-table-from-current-region)

Some comments for the above:

  • save-excursion is used to save the state of the mark / point, so the user will not “notice” any changes after we do our job. Just a courtesy to the user – it’s very nice when you feel “just right” after everything was converted to a HTML table, instead of having to move your cursor around to where it was
  • (interactive) is needed so the function can be bound to a key
  • (buffer-substring (mark) (point)) selects everything between the mark and point (essentially last two “important” cursor locations)
  • global-set-key function together with kbd function are used to bind this to a key

Now, if you are in a buffer which has the first test linearized table in this post, just select it and press F11 and you’ll get a nice formatted table.

Escaping

Obviously, if the table has some HTML-forbidden characters (such as >), the above will not work correctly. Some simple escaping can be done by changing the code like this:

(defun icyrock-html-escape-string (str)
  (let* ((s1 (replace-regexp-in-string "&" "&amp;" str))
         (s2 (replace-regexp-in-string "<" "&lt;" s1))
         (res (replace-regexp-in-string ">" "&gt;" s2)))
    res))

(defun icyrock-html-td-from-string (str)
  (format "<td>%s</td>" (icyrock-html-escape-string str)))

Similar things

If you have a “text-version” of the HTML table, see this article for an approach Emacs Lisp: How to Write a make-html-table Command.

Be Sociable, Share!

Leave a Reply


8 × three =