Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I'm using J/Link to access data from an external Java program. The data are represented as a ConcurrentHashMap (see the official specification here).

link = JavaNew["org.ddpclient.DDPTestClient"];

When I evaluate data = link@mCollections in my Mathematica notebook to access mCollections field where the data of interest are stored, I get a HashMap object:

« JavaObject[java.util.HashMap]»

Is there a way to convert this HashMap into a native Mathematica list (or nested list, to be more precise) so that I could work with it?

I know that one can call a toString[] method on the HashMap object to convert it to a string. In my case, data@toString[] yields something like this:

{G9fuqeYuiQpcmL8MW={playerId=zxcgf24ta, lastSeen=null, status=0.0, \ active=true}, aiej2mQppBAefhad7={playerId=lklu453da1, lastSeen=null, \ status=0.0, active=true}, Bbd8YqAx8yFcdcMcz={playerId=poo10alll235, \ lastSeen=null, status=0.0, active=true}, \ Gnt3KKrrgypgFEorp={playerId=hjffxz535cd, lastSeen=null, status=0.0, \ active=true}, ioydvWm7GacPg5DyM={playerId=asdfa123sa, lastSeen=null, \ status=1.0, active=false}, 8vZ6i73t6mbwTB63D={playerId=66df24hl, \ lastSeen=null, status=0.0, active=true}}

It is probably possible to manually parse this, but I am afraid it would be really difficult to handle all the possible edge cases that could arise during the conversion. Is there a better way?

share|improve this question

2 Answers 2

There are two parts to accessing the contents of a Java Map object. The first is to traverse the iterator interface to extract the map elements. The second is to use accessor methods on those elements to extract their properties.

For the purpose of discussion, let's create a map from strings to Java date objects:

Needs["JLink`"]
InstallJava[];

$map = JavaNew["java.util.HashMap"];
$map@put[MakeJavaObject["start"], JavaNew["java.util.Date", 80, 0, 1]];
$map@put[MakeJavaObject["end"], JavaNew["java.util.Date", 90, 5, 10]];

We can extract the keys from the map using the keySet() method, and then traverse those keys using the iterator() method from the Iterable interface:

JavaBlock @ Module[{i = $map@keySet[]@iterator[]}
, Table[i@next[], {$map@size[]}]
]

(* {"start", "end"} *)

The use of Table is sort of cheating -- we are exploiting the fact that we know the size of the hash table. Not all iterables carry a size, and even those that do can change size as we traverse. So it would be more appropriate to use the hasNext() and next() methods from Iterator. We can capture the values using Sow and Reap, but since there is a lot of boilerplate when we use those functions we had better wrap them up in a helper function:

mapJavaIterable[f_, iterable_] :=
  JavaBlock @ Module[{tag, i = iterable@iterator[]}
  , Reap[
      While[i@hasNext[], Sow[f @ i@next[], tag]]
    , tag
    ][[2]] /. {l_} :> l
  ]

mapJavaIterable is like Map, except that it applies its function to each element of a java Iterable, collecting the results into a list. I have elected to embed JavaBlock in this function to ensure the release of any intermediate Java objects, but it can be removed if one prefers to have the caller manage such objects.

With this helper function, we can extract the keys of the map like this:

mapJavaIterable[Identity, $map@keySet[]]

(* {"start", "end"} *)

Or, we can extract the values (using the values() method):

mapJavaIterable[Identity, $map@values[]]

(* {<<JavaObject[java.util.Date]>>,<<JavaObject[java.util.Date]>>} *)

... which brings us to part two of the question. How can we extract elements from general Java objects? Well, we could use the toString() method, as noted in the question:

mapJavaIterable[#@toString[] &, $map@values[]]

(* {"Tue Jan 01 00:00:00 MST 1980", "Sun Jun 10 00:00:00 MDT 1990"} *)

But a more structured way is to use accessor methods on those objects. For example, let's extract the year and month fields from the date objects:

mapJavaIterable[{#@getYear[], #@getMonth[]}&, $map@values[]]

(* {{80,0},{90,5}} *)

Hmmm, gotta love that Java date representation. Let's clean it up a bit:

mapJavaIterable[{#@getYear[]+1900, #@getMonth[]+1}&, $map@values[]]

(* {{1980,1},{1990,6}} *)

As a final flourish, let's use the entrySet() method on Map to recover all key/value pairs:

mapJavaIterable[
   # -> {#2@getYear[] + 1900, #2@getMonth[] + 1} &[#@getKey[], #@getValue[]] &
 , $map@entrySet[]
 ]

(* {"start" -> {1980, 1}, "end" -> {1990, 6}} *)

entrySet() returns an iterable of Entry objects, each with key and value properties. First we use the getKey() and getValue() methods to extract those properties. Then we build up a list of rules, one for each entry. The key goes on the left-hand-side of each rule, then the year and month properties of the corresponding date object goes on the right-hand-side.

share|improve this answer

Turns out one can use Google's gson library to serialize any Java object into JSON, which can then be imported in Mathematica using ImportString[] function:

list = ImportString[JavaNew["com.google.gson.Gson"]@toJson[data@mCollections],"JSON"]

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.