diff --git a/blobstore/pom.xml b/blobstore/pom.xml
index 46b3f23f8a..0a68b0b83f 100644
--- a/blobstore/pom.xml
+++ b/blobstore/pom.xml
@@ -149,6 +149,11 @@
+
+
+ !clojure.*
+
+
diff --git a/blobstore/src/main/clojure/org/jclouds/blobstore.clj b/blobstore/src/main/clojure/org/jclouds/blobstore.clj
index d766f45880..b99c18badf 100644
--- a/blobstore/src/main/clojure/org/jclouds/blobstore.clj
+++ b/blobstore/src/main/clojure/org/jclouds/blobstore.clj
@@ -1,76 +1,264 @@
-(ns
-#^{:doc
-"
-a lib for interacting with jclouds BlobStore.
+(ns org.jclouds.blobstore
+ "A clojure binding for the jclouds BlobStore.
Current supported services are:
[s3, azureblob, atmos, cloudfiles]
Here's a quick example of how to view blob resources in rackspace
-(ns example.jclouds
- (:use org.jclouds.blobstore)
- (:use clojure.contrib.pprint)
-)
+ (use 'org.jclouds.blobstore)
+ (use 'clojure.contrib.pprint)
- (def user \"rackspace_username\")
- (def password \"rackspace_password\")
- (def blobstore-name \"cloudfiles\")
+ (def user \"rackspace_username\")
+ (def password \"rackspace_password\")
+ (def blobstore-name \"cloudfiles\")
- (def blobstore (blobstore-context blobstore-name user password ))
+ (with-blobstore [blobstore-name user password]
+ (pprint (containers))
+ (pprint (blobs blobstore your_container_name)))
- (pprint (containers blobstore))
- (pprint (blobs blobstore "your_container_name" ))
+See http://code.google.com/p/jclouds for details."
+ (:use org.jclouds.core)
+ (:import java.io.File
+ [org.jclouds.blobstore
+ AsyncBlobStore BlobStore BlobStoreContext BlobStoreContextFactory
+ domain.BlobMetadata domain.StorageMetadata domain.Blob
+ options.ListContainerOptions]
+ [com.google.common.collect ImmutableSet]))
-"}
-org.jclouds.blobstore
- (:use clojure.contrib.duck-streams)
- (:import java.io.File)
- (:import org.jclouds.blobstore.BlobStore)
- (:import org.jclouds.blobstore.BlobStoreContext)
- (:import org.jclouds.blobstore.BlobStoreContextFactory)
- (:import org.jclouds.blobstore.domain.Blob)
- (:import org.jclouds.blobstore.options.ListContainerOptions))
+(defn blobstore
+ "Create a logged in context.
+Options for communication style
+ :sync and :async.
+Options can also be specified for extension modules
+ :log4j :enterprise :httpnio :apachehc :bouncycastle :joda :gae
+"
+ [#^String service #^String account #^String key & options]
+ (let [context
+ (.createContext
+ (BlobStoreContextFactory.) service account key
+ (apply modules (filter #(not #{:sync :async} %) options)))]
+ (if (some #(= :async %) options)
+ (.getAsyncBlobStore context)
+ (.getBlobStore context))))
(defn blobstore-context
- ([{service :service account :account key :key}]
- (blobstore-context service account key))
- ([s a k] (.createContext (new BlobStoreContextFactory) s a k )))
+ "Returns a blobstore context from a blobstore."
+ [blobstore]
+ (.getContext blobstore))
-(defn containers [blobstore] (.list (.getBlobStore blobstore) ))
+(defn blobstore?
+ [object]
+ (or (instance? BlobStore object)
+ (instance? AsyncBlobStore object)))
+
+(defn blobstore-context?
+ [object]
+ (instance? BlobStoreContext object))
+
+(defn as-blobstore
+ "Tries hard to produce a blobstore from its input arguments"
+ [& args]
+ (cond
+ (blobstore? (first args)) (first args)
+ (blobstore-context? (first args)) (.getBlobStore (first args))
+ :else (apply blobstore args)))
+
+(def *blobstore*)
+
+(defmacro with-blobstore [[& blobstore-or-args] & body]
+ `(binding [*blobstore* (as-blobstore ~@blobstore-or-args)]
+ ~@body))
+
+(defn- parse-args
+ "Takes a seq of 'ssh' arguments and returns a map of option keywords
+ to option values."
+ [args]
+ (loop [[arg :as args] args
+ opts {:cmd [] :out "UTF-8"}]
+ (if-not args
+ opts
+ (if (keyword? arg)
+ (recur (nnext args) (assoc opts arg (second args)))
+ (recur (next args) (update-in opts [:cmd] conj arg))))))
+
+(defn- parse-args
+ "Parses arguments, recognises keywords in the set single as boolean switches."
+ [args single default]
+ (loop [[arg :as args] args
+ opts default]
+ (if-not args
+ opts
+ (if (single arg)
+ (recur (next args) (assoc opts arg true))
+ (recur (nnext args) (assoc opts arg (second args)))))))
+
+(def list-options
+ (apply array-map
+ (concat (make-option-map option-fn-0arg [:recursive])
+ (make-option-map option-fn-1arg [:after-marker :in-directory :max-results]))))
+
+(defn- list-options-apply
+ [single target key value]
+ (if (single key)
+ ((list-options key) target)
+ ((list-options key) target value))
+ target)
+
+(defn containers
+ "List all containers in a blobstore."
+ ([] (containers *blobstore*))
+ ([blobstore] (.list blobstore)))
+
+(defn list-container
+ "List a container. Options are:
+ :after-marker string
+ :in-direcory path
+ :max-results n
+ :recursive"
+ [blobstore & args]
+ (if (blobstore? blobstore)
+ (let [single-keywords #{:recursive}
+ options (parse-args (next args) single-keywords {})
+ list-options (reduce
+ #(list-options-apply single-keywords %1 (first %2) (second %2))
+ (ListContainerOptions.)
+ options)]
+ (.list blobstore (first args) list-options))
+ (apply list-container *blobstore* blobstore args)))
+
+(defn create-container
+ "Create a container."
+ ([container-name]
+ (create-container *blobstore* "default" container-name))
+ ([blobstore container-name]
+ (if (blobstore? blobstore)
+ (create-container blobstore "default" container-name)
+ (create-container *blobstore* container-name blobstore)))
+ ([blobstore container-name location-name]
+ (.createContainerInLocation blobstore container-name location-name)))
+
+(defn clear-container
+ "Clear a container."
+ ([container-name]
+ (clear-container container-name))
+ ([blobstore container-name]
+ (.clearContainer blobstore container-name)))
+
+(defn delete-container
+ "Delete a container."
+ ([container-name]
+ (delete-container *blobstore* container-name))
+ ([blobstore container-name]
+ (.deleteContainer blobstore container-name)))
+
+(defn container-exists?
+ "Predicate to check presence of a container"
+ ([container-name]
+ (container-exists? *blobstore* container-name))
+ ([blobstore container-name]
+ (.containerExists blobstore container-name)))
+
+(defn directory-exists?
+ "Predicate to check presence of a directory"
+ ([container-name path]
+ (directory-exists? *blobstore* container-name path))
+ ([blobstore container-name path]
+ (.directoryExists blobstore container-name path)))
+
+(defn create-directory
+ "Create a directory path."
+ ([container-name path]
+ (create-directory *blobstore* container-name path))
+ ([blobstore container-name path]
+ (.createDirectory blobstore container-name path)))
+
+(defn delete-directory
+ "Delete a directory path."
+ ([container-name path]
+ (delete-directory *blobstore* container-name path))
+ ([blobstore container-name path]
+ (.deleteDirectory blobstore container-name path)))
+
+(defn blob-exists?
+ "Predicate to check presence of a blob"
+ ([container-name path]
+ (blob-exists? *blobstore* container-name path))
+ ([blobstore container-name path]
+ (.blobExists blobstore container-name path)))
+
+(defn put-blob
+ "Put a blob. Metadata in the blob determines location."
+ ([container-name blob]
+ (put-blob *blobstore* container-name blob))
+ ([blobstore container-name blob]
+ (.putBlob blobstore container-name blob)))
+
+(defn blob-metadata
+ "Get blob metadata from given path"
+ ([container-name path]
+ (blob-metadata *blobstore* container-name path))
+ ([blobstore container-name path]
+ (.blobMetadata blobstore container-name path)))
+
+(defn get-blob
+ "Get blob from given path"
+ ([container-name path]
+ (get-blob *blobstore* container-name path))
+ ([blobstore container-name path]
+ (.getBlob blobstore container-name path)))
+
+(defn remove-blob
+ "Remove blob from given path"
+ ([container-name path]
+ (remove-blob *blobstore* container-name path))
+ ([blobstore container-name path]
+ (.removeBlob blobstore container-name path)))
+
+(defn count-blobs
+ "Count blobs"
+ ([container-name]
+ (count-blobs *blobstore* container-name))
+ ([blobstore container-name]
+ (.countBlob blobstore container-name)))
(defn blobs
-
-"
-http://code.google.com/p/jclouds
-
-list the blobs in a container:
+ "List the blobs in a container:
blobstore container -> blobs
list the blobs in a container under a path:
blobstore container dir -> blobs
-example: (pprint
-(blobs
-(blobstore-context flightcaster-creds)
-\"somecontainer\" \"some-dir\"))
+example:
+ (pprint
+ (blobs
+ (blobstore-context flightcaster-creds)
+ \"somecontainer\" \"some-dir\"))
"
([blobstore container-name]
(.list (.getBlobStore blobstore) container-name ))
([blobstore container-name dir]
- (.list (.getBlobStore blobstore) container-name (.inDirectory (new ListContainerOptions) dir) ))
-)
+ (.list (.getBlobStore blobstore) container-name
+ (.inDirectory (new ListContainerOptions) dir))))
-(defn put-blob
-
-"
-http://code.google.com/p/jclouds
-
-Create an blob representing text data:
+(defn create-blob
+ "Create an blob representing text data:
container, name, string -> etag
"
+ ([container-name name data]
+ (create-blob *blobstore* container-name name data))
([blobstore container-name name data]
- (.putBlob (.getBlobStore blobstore) container-name (doto (.newBlob (.getBlobStore blobstore) name) (.setPayload data))))
-)
+ (put-blob blobstore container-name
+ (doto (.newBlob blobstore name)
+ (.setPayload data)))))
+
+(define-accessors StorageMetadata "blob" type id name location-id uri last-modfied)
+(define-accessors BlobMetadata "blob" content-type)
+
+(defn blob-etag [blob]
+ (.getETag blob))
+
+(defn blob-md5 [blob]
+ (.getContentMD5 blob))
diff --git a/blobstore/src/test/clojure/org/jclouds/blobstore_test.clj b/blobstore/src/test/clojure/org/jclouds/blobstore_test.clj
index 3e73bb82de..504470b41b 100644
--- a/blobstore/src/test/clojure/org/jclouds/blobstore_test.clj
+++ b/blobstore/src/test/clojure/org/jclouds/blobstore_test.clj
@@ -1,6 +1,81 @@
(ns org.jclouds.blobstore-test
(:use [org.jclouds.blobstore] :reload-all)
- (:use clojure.test))
+ (:use clojure.test)
+ (:import org.jclouds.blobstore.integration.StubBlobStoreContextBuilder))
-(deftest blobstore-test
- (is true))
+(def stub-context (.buildBlobStoreContext (StubBlobStoreContextBuilder.)))
+(def stub-blobstore (.getBlobStore stub-context))
+
+(defn clean-stub-fixture [f]
+ (doall
+ (map
+ #(.deleteContainer stub-blobstore (.getName %)) (.list stub-blobstore)))
+ (f))
+
+(use-fixtures :each clean-stub-fixture)
+
+(deftest blobstore?-test
+ (is (blobstore? stub-blobstore)))
+
+(deftest blobstore-context?-test
+ (is (blobstore-context? stub-context)))
+
+(deftest blobstore-context-test
+ (is (= stub-context (blobstore-context stub-blobstore))))
+
+(deftest as-blobstore-test
+ ;(is (blobstore? (blobstore "stub" "user" "password")))
+ (is (blobstore? (as-blobstore stub-blobstore)))
+ (is (blobstore? (as-blobstore stub-context))))
+
+(deftest with-blobstore-test
+ (with-blobstore [stub-blobstore]
+ (is (= stub-blobstore *blobstore*))))
+
+(deftest create-container-test
+ (is (not (container-exists? stub-blobstore "")))
+ (with-blobstore [stub-blobstore]
+ (is (not (container-exists? ""))))
+ (is (create-container stub-blobstore "fred"))
+ (is (container-exists? stub-blobstore "fred"))
+ (with-blobstore [stub-blobstore]
+ (is (container-exists? "fred"))))
+
+(deftest create-container-test
+ (is (create-container stub-blobstore "fred"))
+ (is (container-exists? stub-blobstore "fred"))
+ (with-blobstore [stub-blobstore]
+ (is (create-container "fred"))
+ (is (container-exists? "fred"))))
+
+(deftest containers-test
+ (is (empty? (containers stub-blobstore)))
+ (is (create-container stub-blobstore "fred"))
+ (is (= 1 (count (containers stub-blobstore)))))
+
+(deftest list-container-test
+ (is (create-container stub-blobstore "container"))
+ (is (empty? (list-container stub-blobstore "container")))
+ (is (create-blob stub-blobstore "container" "blob1" "blob1"))
+ (is (create-blob stub-blobstore "container" "blob2" "blob2"))
+ (is (= 2 (count (list-container stub-blobstore "container"))))
+ (is (= 1 (count (list-container stub-blobstore "container" :max-results 1))))
+ (create-directory stub-blobstore "container" "dir")
+ (is (create-blob stub-blobstore "container" "dir/blob2" "blob2"))
+ (is (= 3 (count (list-container stub-blobstore "container"))))
+ (is (= 4 (count (list-container stub-blobstore "container" :recursive))))
+ (is (= 1 (count (list-container stub-blobstore "container" :in-directory "dir")))))
+
+(deftest list-container-with-blobstore-test
+ (with-blobstore [stub-blobstore]
+ (is (create-container "container"))
+ (is (empty? (list-container "container")))
+ (is (create-blob "container" "blob1" "blob1"))
+ (is (create-blob "container" "blob2" "blob2"))
+ (is (= 2 (count (list-container "container"))))
+ (is (= 1 (count (list-container "container" :max-results 1))))
+ (create-directory "container" "dir")
+ (is (create-blob "container" "dir/blob2" "blob2"))
+ (is (= 3 (count (list-container "container"))))
+ (is (= 4 (count (list-container "container" :recursive))))
+ (is (= 1 (count (list-container "container" :in-directory "dir"))))))
diff --git a/compute/pom.xml b/compute/pom.xml
index f9d93ccc4b..09e87ce9f3 100644
--- a/compute/pom.xml
+++ b/compute/pom.xml
@@ -62,6 +62,11 @@
+
+
+ !clojure.*
+
+
diff --git a/compute/src/main/clojure/org/jclouds/compute.clj b/compute/src/main/clojure/org/jclouds/compute.clj
index 4d70662a3b..cc66b995c8 100644
--- a/compute/src/main/clojure/org/jclouds/compute.clj
+++ b/compute/src/main/clojure/org/jclouds/compute.clj
@@ -1,15 +1,13 @@
-(ns
- #^{:doc "
-a lib for interacting with jclouds ComputeService.
+(ns org.jclouds.compute
+ "A clojure binding to the jclouds ComputeService.
Current supported services are:
[ec2, rimuhosting, terremark, vcloud, hostingdotcom]
Here's an example of getting some compute configuration from rackspace:
- (ns example.jclouds
- (:use org.jclouds.compute
- clojure.contrib.pprint))
+ (use 'org.jclouds.compute)
+ (use 'clojure.contrib.pprint)
(def user \"username\")
(def password \"password\")
@@ -22,9 +20,9 @@ Here's an example of getting some compute configuration from rackspace:
(pprint (nodes compute))
(pprint (sizes compute))
-"}
- org.jclouds.compute
- (:use clojure.contrib.duck-streams
+See http://code.google.com/p/jclouds for details."
+ (:use org.jclouds.core
+ clojure.contrib.duck-streams
clojure.contrib.logging
[clojure.contrib.str-utils2 :only [capitalize lower-case map-str]]
[clojure.contrib.java-utils :only [wall-hack-field]])
@@ -39,40 +37,12 @@ Here's an example of getting some compute configuration from rackspace:
Architecture)
(com.google.common.collect ImmutableSet)))
-(def module-lookup
- {:log4j 'org.jclouds.logging.log4j.config.Log4JLoggingModule
- :ssh 'org.jclouds.ssh.jsch.config.JschSshClientModule
- :enterprise 'org.jclouds.enterprise.config.EnterpriseConfigurationModule})
-
-(defn- instantiate [sym]
- (let [loader (.getContextClassLoader (Thread/currentThread))]
- (try
- (.newInstance #^Class (.loadClass loader (name sym)))
- (catch java.lang.ClassNotFoundException e
- (warn (str "Could not find " (name sym) " module.
-Ensure the module is on the classpath. You are maybe missing a dependency on
- org.jclouds/jclouds-jsch
- org.jclouds/jclouds-log4j
- or org.jclouds/jclouds-enterprise."))))))
-
-(defn modules
- "Build a list of modules suitable for passing to compute-context"
- [& modules]
- (.build #^com.google.common.collect.ImmutableSet$Builder
- (reduce #(.add #^com.google.common.collect.ImmutableSet$Builder %1 %2)
- (com.google.common.collect.ImmutableSet/builder)
- (filter (complement nil?)
- (map (comp instantiate module-lookup) modules)))))
-
(defn compute-context
"Create a logged in context."
- ([s a k]
- (compute-context s a k (modules :log4j :ssh :enterprise)))
- ([#^String s #^String a #^String k #^ImmutableSet m]
- (.createContext (new ComputeServiceContextFactory) s a k m)))
-
-(defn- seq-from-immutable-set [#^ImmutableSet set]
- (map #(.getValue %) set))
+ ([service account key]
+ (compute-context service account key (modules :log4j :ssh :enterprise)))
+ ([#^String service #^String account #^String key #^ImmutableSet modules]
+ (.createContext (new ComputeServiceContextFactory) service account key modules)))
(defn locations
"Retrieve the available compute locations for the compute context."
@@ -203,49 +173,11 @@ Ensure the module is on the classpath. You are maybe missing a dependency on
[#^ComputeMetadata node]
(.getName node))
-(defn- dashed [a]
- (apply str (interpose "-" (map lower-case (re-seq #"[A-Z][^A-Z]*" a)))))
-
-(defn- camelize [a]
- (apply str (map-str capitalize (.split a "-"))))
-
-(defn camelize-mixed [a]
- (let [c (.split a "-")]
- (apply str (lower-case (first c)) (map capitalize (rest c)))))
-
-(defmacro #^{:private true} define-accessor
- [class property obj-name]
- (list 'defn (symbol (str obj-name "-" (name property)))
- (vector (with-meta (symbol obj-name) {:tag (.getName class)}))
- (list (symbol (str ".get" (camelize (name property)))) (symbol obj-name))))
-
-(defmacro #^{:private true} define-accessors
- "Defines read accessors, modelled on class-name-property-name. If the second
- argument is a string, it is used instead of the class-name prefix."
- [class & properties]
- (let [obj-name (if (string? (first properties))
- (first properties)
- (dashed (.getName class)))
- properties (if (string? (first properties))
- (rest properties)
- properties)]
- `(do
- ~@(for [property properties]
- `(define-accessor ~class ~property ~obj-name)))))
-
(define-accessors Template image size location options)
(define-accessors Image version os-family os-description architecture)
(define-accessors Size cores ram disk)
(define-accessors NodeMetadata "node" credentials extra state tag)
-(defmacro option-fn-0arg [key]
- `(fn [builder#]
- (~(symbol (str "." (camelize-mixed (name key)))) builder#)))
-
-(defmacro option-fn-1arg [key]
- `(fn [builder# value#]
- (~(symbol (str "." (camelize-mixed (name key)))) builder# value#)))
-
(defn builder-options [builder]
(or (wall-hack-field org.jclouds.compute.internal.TemplateBuilderImpl :options builder)
(TemplateOptions.)))
@@ -267,9 +199,6 @@ Ensure the module is on the classpath. You are maybe missing a dependency on
(~(symbol (str "." (camelize-mixed (name key)))) options# (seq-to-array value#))
(.options builder# options#))))
-(defmacro make-option-map [f keywords]
- `[ ~@(reduce (fn [v# k#] (conj (conj v# k#) `(~f ~k#))) [] keywords)])
-
(def option-1arg-map
(apply array-map
(concat
diff --git a/compute/src/test/clojure/org/jclouds/compute_test.clj b/compute/src/test/clojure/org/jclouds/compute_test.clj
index a60450012d..0cf04562e5 100644
--- a/compute/src/test/clojure/org/jclouds/compute_test.clj
+++ b/compute/src/test/clojure/org/jclouds/compute_test.clj
@@ -8,25 +8,6 @@ list, Alan Dipert and MeikelBrandmeyer."
`(let ~(reduce #(conj %1 %2 `@(ns-resolve '~ns '~%2)) [] fns)
~@tests))
-(with-private-vars [org.jclouds.compute [instantiate]]
- (deftest instantiate-test
- (is (instance? String (instantiate 'java.lang.String)))))
-
(deftest os-families-test
(is (some #{"centos"} (map str (os-families)))))
-(deftest modules-empty-test
- (is (.isEmpty (modules))))
-
-(deftest modules-instantiate-test
- (binding [org.jclouds.compute/module-lookup
- (assoc org.jclouds.compute/module-lookup
- :string 'java.lang.String)]
- (is (instance? String (first (modules :string))))
- (is (= 1 (count (modules :string))))))
-
-(deftest modules-instantiate-fail-test
- (binding [org.jclouds.compute/module-lookup
- (assoc org.jclouds.compute/module-lookup
- :non-existing 'this.doesnt.Exist)]
- (is (.isEmpty (modules :non-existing)))))
diff --git a/core/pom.xml b/core/pom.xml
index 54a62a1c2c..bb79a0ffe2 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -40,6 +40,38 @@
http://jclouds.googlecode.com/svn/trunk
+
+
+
+ src/main/clojure
+
+
+ src/main/resources
+
+
+
+
+ com.theoryinpractise
+ clojure-maven-plugin
+ 1.3.1
+
+
+ test-clojure
+ test
+
+ test
+
+
+
+
+
+ !clojure.*
+
+
+
+
+
+
gson
@@ -93,8 +125,26 @@
google-guava
1.0-r11
+
+ org.clojure
+ clojure
+ 1.1.0
+ test
+
+
+ org.clojure
+ clojure-contrib
+ 1.1.0
+ test
+
+
+ swank-clojure
+ swank-clojure
+ 1.1.0
+ test
+
-
+
distribution
diff --git a/core/src/main/clojure/org/jclouds/core.clj b/core/src/main/clojure/org/jclouds/core.clj
new file mode 100644
index 0000000000..8f0e01c655
--- /dev/null
+++ b/core/src/main/clojure/org/jclouds/core.clj
@@ -0,0 +1,81 @@
+(ns org.jclouds.core
+ "Core functionality used across blobstore and compute."
+ (:use clojure.contrib.logging
+ [clojure.contrib.str-utils2 :only [capitalize lower-case map-str]]
+ [clojure.contrib.java-utils :only [wall-hack-field]])
+ (:import java.io.File
+ (com.google.common.collect ImmutableSet)))
+
+(def module-lookup
+ {:log4j 'org.jclouds.logging.log4j.config.Log4JLoggingModule
+ :ssh 'org.jclouds.ssh.jsch.config.JschSshClientModule
+ :enterprise 'org.jclouds.enterprise.config.EnterpriseConfigurationModule
+ :httpnio 'org.jclouds.http.httpnio.config.NioTransformingHttpCommandExecutorServiceModule
+ :apachehc 'org.jclouds.http.apachehc.config.ApacheHCHttpCommandExecutorServiceModule
+ :bouncycastle 'org.jclouds.encryption.bouncycastle.config.BouncyCastleEncryptionServiceModule
+ :joda 'org.jclouds.date.joda.config.JodaDateServiceModule
+ :gae 'org.jclouds.gae.config.GoogleAppEngineConfigurationModule})
+
+(defn- instantiate [sym]
+ (let [loader (.getContextClassLoader (Thread/currentThread))]
+ (try
+ (.newInstance #^Class (.loadClass loader (name sym)))
+ (catch java.lang.ClassNotFoundException e
+ (warn (str "Could not find " (name sym) " module.
+Ensure the module is on the classpath. You are maybe missing a dependency on
+ org.jclouds/jclouds-jsch
+ org.jclouds/jclouds-log4j
+ or org.jclouds/jclouds-enterprise."))))))
+
+(defn modules
+ "Build a list of modules suitable for passing to compute or blobstore context"
+ [& modules]
+ (.build #^com.google.common.collect.ImmutableSet$Builder
+ (reduce #(.add #^com.google.common.collect.ImmutableSet$Builder %1 %2)
+ (com.google.common.collect.ImmutableSet/builder)
+ (filter (complement nil?)
+ (map (comp instantiate module-lookup) modules)))))
+
+(defn seq-from-immutable-set [#^ImmutableSet set]
+ (map #(.getValue %) set))
+
+(defn dashed [a]
+ (apply str (interpose "-" (map lower-case (re-seq #"[A-Z][^A-Z]*" a)))))
+
+(defn camelize [a]
+ (map-str capitalize (.split a "-")))
+
+(defn camelize-mixed [a]
+ (let [c (.split a "-")]
+ (apply str (lower-case (first c)) (map capitalize (rest c)))))
+
+(defmacro option-fn-0arg [key]
+ `(fn [builder#]
+ (~(symbol (str "." (camelize-mixed (name key)))) builder#)))
+
+(defmacro option-fn-1arg [key]
+ `(fn [builder# value#]
+ (~(symbol (str "." (camelize-mixed (name key)))) builder# value#)))
+
+(defmacro make-option-map [f keywords]
+ `[ ~@(reduce (fn [v# k#] (conj (conj v# k#) `(~f ~k#))) [] keywords)])
+
+(defmacro define-accessor
+ [class property obj-name]
+ (list 'defn (symbol (str obj-name "-" (name property)))
+ (vector (with-meta (symbol obj-name) {:tag (.getName class)}))
+ (list (symbol (str ".get" (camelize (name property)))) (symbol obj-name))))
+
+(defmacro define-accessors
+ "Defines read accessors, modelled on class-name-property-name. If the second
+ argument is a string, it is used instead of the class-name prefix."
+ [class & properties]
+ (let [obj-name (if (string? (first properties))
+ (first properties)
+ (dashed (.getName class)))
+ properties (if (string? (first properties))
+ (rest properties)
+ properties)]
+ `(do
+ ~@(for [property properties]
+ `(define-accessor ~class ~property ~obj-name)))))
diff --git a/core/src/test/clojure/org/jclouds/core_test.clj b/core/src/test/clojure/org/jclouds/core_test.clj
new file mode 100644
index 0000000000..19233fa617
--- /dev/null
+++ b/core/src/test/clojure/org/jclouds/core_test.clj
@@ -0,0 +1,29 @@
+(ns org.jclouds.core-test
+ (:use [org.jclouds.core] :reload-all)
+ (:use clojure.test))
+
+(defmacro with-private-vars [[ns fns] & tests]
+ "Refers private fns from ns and runs tests in context. From users mailing
+list, Alan Dipert and MeikelBrandmeyer."
+ `(let ~(reduce #(conj %1 %2 `@(ns-resolve '~ns '~%2)) [] fns)
+ ~@tests))
+
+(with-private-vars [org.jclouds.core [instantiate]]
+ (deftest instantiate-test
+ (is (instance? String (instantiate 'java.lang.String)))))
+
+(deftest modules-empty-test
+ (is (.isEmpty (modules))))
+
+(deftest modules-instantiate-test
+ (binding [module-lookup
+ (assoc module-lookup
+ :string 'java.lang.String)]
+ (is (instance? String (first (modules :string))))
+ (is (= 1 (count (modules :string))))))
+
+(deftest modules-instantiate-fail-test
+ (binding [module-lookup
+ (assoc module-lookup
+ :non-existing 'this.doesnt.Exist)]
+ (is (.isEmpty (modules :non-existing)))))