Roman numeral conversion in Clojure, part II
Previously I translated Roman numerals to decimals with this code:
(def numerals {\I 1, \V 5, \X 10, \L 50, \C 100, \D 500, \M 1000})
(defn add-numeral [n t]
(if (> n (* 4 t)) (- n t) (+ t n)))
(defn roman [s]
(reduce add-numeral (map numerals (reverse s))))
Now I want to continue my Clojure practise by doing the reverse: translating from decimals to Roman numerals.
I started where I left off previously, so I already have the numerals map defined. I figure the inverse of this map will be handy for doing look-ups of decimals, and sure enough clojure has a handy function - map-invert:
=> (use '[clojure.set :only (map-invert)])
nil
=> (doc map-invert)
––––––––––––––––
clojure.set/map-invert
([m])
Returns the map with the vals mapped to the keys.
nil
Inverting my numerals map gives:
=> (map-invert numerals)
{10 \X, 5 \V, 1000 \M, 50 \L, 1 \I, 500 \D, 100 \C}
Great, but that highlights a lack of foresight in my original naming of the numerals map, so I define a new one named numerals-to-decimals
, and decimals to numerals as its inverse:
(def numerals-to-decimals {\I 1, \V 5, \X 10, \L 50, \C 100, \D 500, \M 1000})
(def decimals-to-numerals (map-invert numerals-to-decimals))
Now I can convert easily to numerals where there is an exact match for the decimal:
=> (decimals-to-numerals 10)
\X
But I get nil if there's no match:
=> (decimals-to-numerals 11)
nil
In Roman numerals there is no zero, but they did use the concept of nul (nulla), so I start composing a function which I will incrementally improve as I figure out more steps:
(defn decimal-to-roman [d]
(cond
(= 0 d) "nulla"
:else
(decimals-to-numerals d)))
Which yields the following results:
=> (decimal-to-roman 0)
"nulla"
=> (decimal-to-roman 5)
\V
=> (decimal-to-roman 11)
nil
So now I need to tackle the case where the decimal value can only be represented by some composite of the available Roman numerals. Clearly this is going to need to involve dividing the decimal number by the largest available numeral, then repeating for the remainder. Lets try for a single numeral:
(defn decompose-decimal-with-numeral [d v n]
[n (quot d v) (rem d v)])
Testing that gives:
=> (decompose-decimal-with-numeral 23 10 \X)
[\X 2 3]
=> (decompose-decimal-with-numeral 3 10 \X)
[\X 0 3]
Where the resulting vector contains the numeral, the number of times it divides into the decimal value, and the remainder. Lets apply that function to our map of decimals-to-numerals
:
(defn decompose-decimal-with-numerals [d]
(for [[v n] decimals-to-numerals]
(decompose-decimal-with-numeral d v n)))
Applying this function to the decimal 123 gives me the following:
=> (decompose-decimal-with-numerals 123)
([\X 12 3] [\V 24 3] [\M 0 123] [\L 2 23] [\I 123 0] [\D 0 123] [\C 1 23])
That's useful progress, but now I want to get the result of the biggest divisor only. To do that I need to filter when the divisors are larger than the input, and sort the results. Filtering is relatively straight-forward by supplying a :when
filter function to the comprehension.
(defn decompose-decimal-with-numerals [d]
(for [[v n] decimals-to-numerals :when (#(>= d v))]
(decompose-decimal-with-numeral d v n)))
I could sort at various points along the way. Most efficient is probably to work with a sorted list of numerals from the outset, so lets create one:
(def numerals-desc (sort-by first > decimals-to-numerals))
Printing the new list gives:
=> (println numerals-desc)
([1000 M] [500 D] [100 C] [50 L] [10 X] [5 V] [1 I])
Using that in the decompose method gives:
(defn decompose-decimal-with-numerals [d]
(for [[v n] numerals-desc :when (#(>= d v))]
(decompose-decimal-with-numeral d v n)))
=> (decompose-decimal-with-numerals 123)
([\C 1 23] [\L 2 23] [\X 12 3] [\V 24 3] [\I 123 0])
Sweet! We only actually need the result for the largest available divisor, so lets re-write this function to do that:
(defn largest-divisor [d]
(first (for [[v n] numerals-desc :when (#(>= d v))]
(decompose-decimal-with-numeral d v n))))
=> (largest-divisor 123)
[\C 1 23]
Actually what I really want is the string representation of the numeral * the number of occurrences, so I create a new function n-numerals which concatenates N of the numeral together into a string and modify the decompose-decimal-with-numeral
function:
(defn n-numerals [n num]
(apply str (for [n (range 0 n)] num)))
(defn decompose-decimal-with-numeral [d v n]
[(n-numerals (quot d v) n) (rem d v)])
Now I can apply decompose-decimal-with-numerals
and see the break-down for each numeral:
=> (decompose-decimal-with-numerals 23)
(["XX" 3] ["VVVV" 3] ["IIIIIIIIIIIIIIIIIIIIIII" 0])
Good to see that it is giving the correct values, but more importantly I can use largest-divisor
to see the Roman numeral form of the quotient:
=>(largest-divisor 323)
["CCC" 23]
What remains is to apply this recursively until the remainder is zero:
(defn decompose-decimal-recursively [d]
(let [[n r] (largest-divisor d)]
(if (= 0 r) n (apply str n (decompose-decimal-recursively r)))))
Here I'm using tail recursion and the recursion can never be very deep because the set of numerals is small, so there should be no danger of blowing the stack. Testing the function gives:
=> (decompose-decimal-recursively 424)
"CCCCXXIIII"
=> (decompose-decimal-recursively 1984)
"MDCCCCLXXXIIII"
These are correct answers, but there are two problems: First, the results are not optimal - we have runs of 4 numerals, for example IIII
which can be written more succinctly as IV
. Second, I think I've over-complicated things by using division instead of subtraction.
The decimal-to-roman translation program so far looks like:
(use '[clojure.set :only (map-invert)])
(def numerals-to-decimals {\I 1, \V 5, \X 10, \L 50, \C 100, \D 500, \M 1000})
(def decimals-to-numerals (map-invert numerals-to-decimals))
(def numerals-desc (sort-by first > decimals-to-numerals))
(defn n-numerals [n num]
(apply str (for [n (range 0 n)] num)))
(defn decompose-decimal-with-numeral [d v n]
[(n-numerals (quot d v) n) (rem d v)])
(defn largest-divisor [d]
(first (for [[v n] numerals-desc :when (#(>= d v))]
(decompose-decimal-with-numeral d v n))))
(defn decompose-decimal-recursively [d]
(let [[n r] (largest-divisor d)]
(if (= 0 r) n (apply str n (decompose-decimal-recursively r)))))
I'm going to try to simplify before fixing the optimisation problem. I can find the next largest numeral that can be subtracted from the total like this:
(defn largest-numeral-in [d]
(first (for [[v n] numerals-desc :when (#(>= d v))] [v n])))
Then I can recursively use this to eat away at the target decimal, like this:
(defn decimal-to-roman [d]
(let [[v n] (largest-numeral-in d) [r] [(- d v)]]
(if (= 0 r) (str n) (apply str n (decimal-to-roman r)))))
The program just got a lot simpler! From 4 functions in 10 lines to 3 functions in 6 lines. Here it is in entirety:
(use '[clojure.set :only (map-invert)])
(def numerals-to-decimals {\I 1, \V 5, \X 10, \L 50, \C 100, \D 500, \M 1000})
(def decimals-to-numerals (map-invert numerals-to-decimals))
(def numerals-desc (sort-by first > decimals-to-numerals))
(defn largest-numeral-in [d]
(first (for [[v n] numerals-desc :when (#(>= d v))] [v n])))
(defn decimal-to-roman [d]
(let [[v n] (largest-numeral-in d) [r] [(- d v)]]
(if (= 0 r) (str n) (apply str n (decimal-to-roman r)))))
Now to optimise the results. After noodling with this for a while I realise that there really aren't all that many optimisation cases. They are: 4, 9, 40, 45, 49, 90, 95, 99, 400, 450, 490, 495, 499, 900, 950, 990, 995, and 999. 18 cases. I could generate these and then just do a lookup
(def optimisations (apply hash-map (flatten
(for [[n1 d1] numerals-to-decimals]
(for [[n2 d2] numerals-to-decimals :when (#(< d2 (quot d1 2)))]
[(- d1 d2) (str n2 n1)])))))
I'm sure there's a much neater way of doing that, but it does the trick and produces this map:
=> (println optimisations)
{450 LD, 99 IC, 995 VM, 4 IV, 900 CM, 999 IM, 40 XL, 9 IX,
490 XD, 45 VL, 495 VD, 400 CD, 49 IL, 499 ID, 950 LM, 90 XC,
990 XM, 95 VC}
Now I can simply check this map in addition to the map of single numerals. Even better if there was just one map containing all of these numerals, so:
(def opt-decimals-to-numerals
(merge decimals-to-numerals optimisations))
Redefining numerals-desc
to include the optimisations should be all I need to do:
(def numerals-desc (sort-by first > opt-decimals-to-numerals))
So now when I invoke decimal-to-roman
I get:
=> (decimal-to-roman 1999)
"MIM"
=> (decimal-to-roman 1954)
"MLMIV"
=> (decimal-to-roman 1984)
"MLMXXXIV"
=> (decimal-to-roman 313)
"CCCXIII"
=> (decimal-to-roman 413)
"CDXIII"
=> (decimal-to-roman 419)
"CDXIX"
The program in entirety now looks like this:
(use '[clojure.set :only (map-invert)])
(def numerals-to-decimals {\I 1, \V 5, \X 10, \L 50, \C 100, \D 500, \M 1000})
(def decimals-to-numerals (map-invert numerals-to-decimals))
(def optimisations (apply hash-map (flatten
(for [[n1 d1] numerals-to-decimals]
(for [[n2 d2] numerals-to-decimals :when (#(< d2 (quot d1 2)))]
[(- d1 d2) (str n2 n1)])))))
(def opt-decimals-to-numerals
(merge decimals-to-numerals optimisations))
(def numerals-desc (sort-by first > opt-decimals-to-numerals))
(defn largest-numeral-in [d]
(first (for [[v n] numerals-desc :when (#(>= d v))] [v n])))
(defn decimal-to-roman [d]
(let [[v n] (largest-numeral-in d) [r] [(- d v)]]
(if (= 0 r) (str n) (apply str n (decimal-to-roman r)))))
There's quite a chunk of code there simply for converting the map of numerals-to-decimals to include the optimisations. The code would be shorter if I simply created that map to begin with:
(def numerals-desc
'([1000 "M"] [999 "IM"] [995 "VM"] [990 "XM"] [950 "LM"] [900 "CM"]
[500 "D"] [499 "ID"] [495 "VD"] [490 "XD"] [450 "LD"] [400 "CD"]
[100 "C"] [99 "IC"] [95 "VC"] [90 "XC"] [50 "L"] [49 "IL"] [45 "VL"]
[40 "XL"] [10 "X"] [9 "IX"] [5 "V"] [4 "IV"] [1 "I"]))
(defn largest-numeral-in [d]
(first (for [[v n] numerals-desc :when (#(>= d v))] [v n])))
(defn decimal-to-roman [d]
(let [[v n] (largest-numeral-in d) [r] [(- d v)]]
(if (= 0 r) (str n) (apply str n (decimal-to-roman r)))))
Nice! Now to put everything together so that I can convert both ways - decimal-to-roman and roman-to-decimal:
(use '[clojure.set :only (map-invert)])
(def numerals-desc
'([1000 "M"] [999 "IM"] [995 "VM"] [990 "XM"] [950 "LM"] [900 "CM"]
[500 "D"] [499 "ID"] [495 "VD"] [490 "XD"] [450 "LD"] [400 "CD"]
[100 "C"] [99 "IC"] [95 "VC"] [90 "XC"] [50 "L"] [49 "IL"] [45 "VL"]
[40 "XL"] [10 "X"] [9 "IX"] [5 "V"] [4 "IV"] [1 "I"]))
(def numerals-to-decimals
(map-invert (apply hash-map (flatten numerals-desc))))
(defn largest-numeral-in [d]
(first (for [[v n] numerals-desc :when (#(>= d v))] [v n])))
(defn decimal-to-roman [d]
(let [[v n] (largest-numeral-in d) [r] [(- d v)]]
(if (= 0 r) (str n) (apply str n (decimal-to-roman r)))))
(defn add-numeral [n t]
(if (> n (* 4 t)) (- n t) (+ t n)))
(defn roman-to-decimal [r]
(reduce add-numeral
(map numerals-to-decimals (map str (reverse r)))))
(defn optimise-roman [r]
(decimal-to-roman (roman-to-decimal r)))
For fun I added one extra function optimise-roman
which takes a roman-numeral string and returns the optimal representation, for example:
=> (optimise-roman "VIIII")
"IX"
=> (optimise-roman "XCVIIII")
"IC"
=> (optimise-roman "MDCCCCLXXXXVIIII")
"MIM"
I confess I am struggling with the mind-shift from imperative to functional programming. I find myself thinking of various convoluted ways to avoid recursion (why!?) and struggling to remember to use list comprehensions, even though (I think) I understand them well enough.
I'm not sure if it was because of this or in spite of it that it took me quite a while to realise that I was starting with a too-complex algorithm (division) when a much simpler one existed (subtraction).
Still, Clojure is fun :)
Comment on this postRoman numeral conversion in Clojure
(see also part II)
I've been reading Stu Halloway's Programming Clojure (which is excellent by the way). When I reached chapter 6 "Concurrency" I thought I'd better pause and practise some of the basics before venturing into deeper waters!
I once saw a programming challenge posted with a job ad, which asked applicants to write some code to convert numbers from the decimal numeral system to Roman. This seemed like a sufficiently challenging and self-contained thing to try for a first attempt at Clojure.
I started by creating a map of the individual numerals:
(def numerals {\I 1, \V 5, \X 10, \L 50, \C 100, \D 500, \M 1000})
Maps are functions, so invoking a map with a key gives the value, like this:
=> (numerals \X)
10
Next I created a function that maps roman numerals to decimal values (baby steps!):
(defn decimal-values [s]
(map numerals s))
Testing this at the REPL I get:
=> (decimal-values "MCXVII")
(1000 100 10 5 1 1)
Great, I have a list of the values which I need to compose together. Next I want to combine the values in this list by adding them together.
(defn roman [s]
(reduce + (decimal-values s)))
Testing again at the REPL I get:
=> (roman "MCXVII")
1117
Whoop! OK, we have our first conversion. Are we done? Unfortunately not - there's a complication.
The "simple" way of writing 4 in Roman numerals is IIII. That repetition is allowed, but not commonly used. The short-hand is to prefix a higher numeral with a lower one, e.g. IV, which means subtract the lower number from the higher. This works for all of the other numerals too, so 999 can be written IM (1000 - 1).
I pondered this for a while, then decided to write a combine-numerals
function to use in place of + in the reduce
call. I want my roman
function to look like this:
(defn roman [s]
(reduce combine-numerals (decimal-values s)))
Now, combine-numerals
needs to account for the more complicated logic described above. Here's what I came up with:
(defn combine-numerals [a b]
(if (> a b)
(+ a b)
(- b a)))
This simply checks that the value seen first is larger than the following value - if it is we add, if it isn't we subtract. Testing that at the REPL gives:
=> (combine-numerals 1 10)
9
=> (combine-numerals 10 1)
11
Perfect, if I see a 1 before a 10 it is subtracted to give 9, otherwise it is added to give 11. Lets try our roman
function again:
=> (roman "XVII")
17
=> (roman "MCMXVII")
2117
Hmm, that last one isn't right, it should give 1917. What's going on?
reduce
takes each value in the given list and applies the function to it and the reduced result so far. That means that when combine-numerals
is called it isn't called with two adjacent values except in the case where the numeral string only has two numerals. Bum.
What I really need is to have the sum so far, the previous numeral seen, and the current numeral being handled, so that I can decide whether to add/subtract the current numeral by comparing it with the previous numeral.
I think I could do this with a recursive function, but I'm struggling to remember the syntax, so I ponder a little more and decide to try running in reverse through the numerals, and add if the total so far is less than 4 times the current total.
This involves changing combine-numerals
to multiply the current numeral's value by 4 before comparing, and renaming it to add-numeral:
(defn add-numeral [n t]
(if (> n (* 4 t))
(- n t)
(+ t n)))
I also need to change roman
to reduce the list in reverse with the new add-numeral function:
(defn roman [s]
(reduce add-numeral (map numerals (reverse s))))
Testing at the REPL gives:
=> (roman "IX")
9
=> (roman "MCMXIX")
1919
=> (roman "MIM")
1999
=> (roman "MLMXXXIIII")
1984
=> (roman "MCMLXXXIV")
1984
So, lets see the whole program:
(def numerals {\I 1, \V 5, \X 10, \L 50, \C 100, \D 500, \M 1000})
(defn add-numeral [n t]
(if (> n (* 4 t))
(- n t)
(+ t n)))
(defn roman [s]
(reduce add-numeral (map numerals (reverse s))))
I confess myself truly amazed that -
- I managed to write some Clojure that actually works
- I think it is nearly idiomatic Clojure (is it? please comment!)
- It is incredibly concise and moderately readable (even to my unfamiliar eyes)
Here's a rough translation of the same algorithm to Java -
import java.util.*;
public class RomanNumerals {
private static Map<Character, Integer> numerals =
new HashMap<Character, Integer>();
static {
numerals.put('I', 1);
numerals.put('V', 5);
numerals.put('X', 10);
numerals.put('L', 50);
numerals.put('C', 100);
numerals.put('D', 500);
numerals.put('M', 1000);
}
public int convert(String aRoman) {
int total = 0;
for (int i=aRoman.length()-1; i>=0; i--) {
char c = aRoman.charAt(i);
total = combine(numerals.get(c), total);
}
return total;
}
private int combine(int aCurrent, int aRunningTotal) {
return (aRunningTotal > (aCurrent * 4)) ?
aRunningTotal - aCurrent : aRunningTotal + aCurrent;
}
public static void main(String[] args) {
System.out.println(new RomanNumerals().convert(args[0]));
}
}
25 lines of Java (not counting the main method) to 7 lines of Clojure, but I'm not judging (yet).
Java pads out with boiler-plate, and there are big losses in populating the HashMap
. I walked the String
in reverse rather than actually reversing it. I could have reversed it with new StringBuilder(aRoman).reverse().toString()
I suppose, but that's pretty ugly.
Fun stuff! I plan to come back to this exercise over time as I learn more Clojure - pretty sure there are several dozen alternative versions that are more idiomatic/elegant, and I haven't made any attempt at handling bad input, etc.
Comment on this postGWT i18n using browser locale
I was trying to get GWT to load a localised permutation based on the user's browser locale. For whatever reason I didn't have much luck coming up with a good combination of search keywords to get a good hit from Google.
After rummaging around in the bowels of GWT I found that it is possible to use the browser locale to determine the build permutation to use.
It doesn't seem all that well publicised or documented (does not figure in the i18n docs for example), although it is used in the showcase demo app.
There are clues as to why it isn't well publicised in the bug containing the original patch.
To use the browser's locale to determine the permutation, simply add the following to the module.gwt.xml:
<set-configuration-property name="locale.useragent" value="Y"/>
Setting the locale of chrome for testing is explained for windows, linux, and mac.
Comment on this postCross-domain inter-frame communication in javascript
Requirement:
Web-page A from domain A' loads web-page B from domain B' into an iframe. Web-page B wants to be able to render some content into the DOM of web-page A (outside of the view-port described by B's iframe). The content which B renders into A needs to be able to HTTP GET and POST data back to the domain B', handle the responses, and update the rendered content in web-page A.
Problems:
- Scripts loaded into pages from different domains cannot interact so, for example, page B's scripts cannot simply* render content into the parent frame (page A)
- Page A cannot simply* use XMLHTTPRequest to GET/POST/PUT/DELETE to host B
[*] I say "simply" because you can't just expect it to work like it would in a same-domain environment, but it is possible!
Escaping from an iframe
Lets start with rendering content into a parent frame. To get concrete, lets say that domain A is www.domain.com, while domain B is sub.domain.com.
Yes, they are sub-domains of a common root. No, B is not allowed to modify the DOM of A, or communicate with scripts in A, because web-browsers won't allow that unless the ports, protocols, and domains match exactly.
The only way for B to escape from the iframe is to have co-operation from A. That co-operation can come in one of two forms:
- Both pages must explicitly set the
document.domain
property to the same value (e.g. domain.com). Even if one of the pages is served directly from "domain.com", the act of explicitly setting the domain is required for this technique to work - it signals to the browser that the two pages want to collaborate. - Have host A serve an iframe-buster page (more below)
Iframe Buster
Host A can serve a page which loads scripts on behalf of B, sometimes known as an iframe-buster.
This is a common technique in the ad-delivery world to allow complex ads like page take-over's to escape from the iframe they are loaded into. Note that this is not an exploit as such, since it requires host A to be complicit.
To illustrate how it works, here's what a very simple iframe-buster might look like:
<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<body>
<script type="text/javascript" language="javascript">
var _url = "http://www.domain.com/bust.js?" + document.location.search;
var _script = document.createElement("script");
_script.setAttribute("type", "text/javascript");
_script.setAttribute("src", _url);
document.body.appendChild(_script);
</script>
</body>
</html>
The host-page A will initially load a page from host B. That page B will load the iframe-buster on host A with some parameters which can be used to direct what the bust-out actually does.
Rendering into the parent DOM
Now that we have assistance from the host-page domain, our iframe can communicate directly with the DOM and scripts in the parent frame, using the window.parent
handle.
var _ctx = window.parent.document;
var _div = _ctx.createElement("div");
_div.innerHTML = '<h1>Hooray!</h1>';
_ctx.body.appendChild(_div);
Great!
Making HTTP requests
Now we want to fetch some JSON data from host B by HTTP GET, and render it in the parent frame. For GET requests we might be OK - we just need the host API to support JSONP. If it doesn't we need one of the other techniques as for making POST requests …
What if we want to POST some data to host B? We can't use XMLHTTPRequest to POST from A to B, as the browser security policies won't allow it. So, what are our options?
- HTML Form POST
- CORS (Cross Origin Resource Sharing)
- Pipelined communication through another frame
HTML Form POST
We could use a form POST, which is allowed to POST to another domain (mostly because the HTML Form POST spec pre-dates the tightened security policies), and will receive the response.
You'll need to do a bit of scripting to wrap things up so that you can register callbacks and have things behave similarly to an XMLHTTPRequest.
This method has the advantage of broad browser compatibility, but the implementation is by necessity less clean, and you lose some of the advantages of XMLHTTPRequest (e.g. the ability to check the response status code).
If you're dealing with a pure RESTful API you'll struggle without the ability to check status codes.
If you have help from the server-side you can probably engineer your way around most of the problems, and even tunnel non-POST API calls by using hidden FORM params and a server-side intercept (e.g. Servlet Filter) to translate the request for you before it hits the API handlers.
That said, if you have control of (or co-operation from) the server-side you'll probably want to look at one of the other methods below.
Advantages:
- Good browser compatibility
- Easily understood
Disadvantages:
- Poor handling of pure RESTful APIs
CORS (Cross Origin Resource Sharing)
We could use CORS, which involves the web-server B checking and sending additional HTTP headers.
This requires a relatively modern browser and some server-side work to check and set additional HTTP headers. CORS is nice because it allows us to conveniently use XMLHTTPRequest for all of our requests (and no need for JSONP).
CORS might put a little extra demand on your servers, as browsers "pre-flight" requests as part of the CORS protocol.
Advantages:
- Just like working in a same-domain environment (good for RESTful API handling)
- CORS is an emerging standard, so you don't necessarily need to own/operate the host for this method to be a realistic possibility
Disadvantages:
- Requires modern browser
- Requires that the host supports CORS
- Some HTTP request overhead (pre-flight)
Pipeline communication through another iframe
A third option is to pipeline your HTTP calls through another iframe - loaded from the domain of the host you want to make calls to.
In newer browsers we can use window.postMessage
to send text between frames loaded from different domains.
Since this text can be JSON, and you can register event-handlers for the "message" event, you can set up a communication-frame per host that you need to talk to, and from inside that frame you can use straight-forward XMLHTTPRequest calls, same-domain style.
There are some neat libraries that use a variety of fallback methods (message-passing via window.name; flash) to make this work in older browsers. The most popular one seems to be EasyXDM.
Advantages: 1. Good browser compatibility (use libraries like EasyXDM) 2. Good for RESTful API handling
Disadvantages:
- More complex set-up
- You need control of the host
- There's some small overhead in piping everything as strings through nested iframe's
Summary
As with everything, there is no one-size-fits-all solution, and some flexibility and compromise is likely to be necessary. For the project I'm working on currently I'm using iframe busters, a little CORS, and a lot of pipelining through another frame, but YMMV.
Comment on this postSubversion 1.7 Eclipse integration in Ubuntu 12
Almost a year ago I posted about getting Subclipse/Subversion/Eclipse/Javahl to play nicely together in Ubuntu 11.10.
Things have changed a little with Quantal Quetzal (notably that canonical have updated their repo's to support SVN 1.7.7 and that the libsvn-java installation has moved), so here's an updated note for getting Subversion 1.7.x integration working with Eclipse (3.7.x) and Subclipse 1.8.x on Ubuntu 12.10.
I'm assuming you already have Eclipse and Subclipse installed (with all the optional extras).
To use the native svn integration you will of course need subversion installed, so install Subversion from canonical's repo's - sudo apt-get install subversion
.
You'll also need libsvn-java, to allow subclipse to talk to svn - sudo apt-get install libsvn-java
.
To enable Eclipse to see your libsvn-java installation, go to the eclipse install directory (I install in /home/steve/dev/tools/eclipse
) and edit the eclipse.ini file.
You need to add -Djava.library.path=/usr/lib/x86_64-linux-gnu/jni/
, which is where libsvn-java's native libraries get installed. Add it immediately following -vmargs
. My eclipse.ini file now looks like this:
-startup
plugins/org.eclipse.equinox.launcher_1.2.0.v20110502.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.1.100.v20110505
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
--launcher.defaultAction
openFile
-vmargs
-Djava.library.path=/usr/lib/x86_64-linux-gnu/jni/
-Xms40m
-Xmx600m
If you use Subclipse but never previously installed Javahl you probably see irritating warning dialogs the first time you do anything in Eclipse after a restart. Installing javahl correctly will prevent those :).
Comment on this post