Re: Preserving ordering in a Freemarker sequence when iterating over an OrderedBidiMap.

Jonathan Revusky <[email protected]>
Newsgroups gmane.comp.web.freemarker.user
Message-ID <[email protected]>
Stephen Smith wrote:
> Hi Jonathan,
> 
> Thanks for your explanation. Your answer makes a lot of sense, but as we 
> using Freemarker in conjunction with Spring we can't access a Freemarker 
> ObjectWrapper directly without exposing it in the Spring application 
> context, which seems like a bit of a smell.  So, our current workaround
> is to put our OrderedBidiMap inside a LinkedHashMap before sending it to 
> the Freemarker ViewResolver.
> 
> I haven't had a chance yet to poke around inside the Freemarker 
> codebase, but I'm a little hazy on why Freemarker knows about 
> java.util.Map implementations specifically within java.util.*, but not 
> other packages. 

Well, basically, it knows about java.util.Map vs. java.util.SortedMap. 
SortedMap is basically a marker interface that indicates that the map is 
meant to preserve order when you iterate over the key-value pairs, right?

The funny thing about the commons collection class OrderedBidiMap is 
that it implemnts an interface called 
org.apache.commons.collections.OrderedMap but that interface only 
extends java.util.Map, not java.util.SortedMap. (I just looked it up. I 
didn't know that until a minute ago, of course. :-))

So another solution would be to use a fairly trivial subclass of 
OrderedBidiMap that also implements java.util.SortedMap. Really, it 
seems like a mistake in the apache collections stuff that their 
OrderedMap extends (indirectly) java.util.Map but not 
java.util.SortedMap. (Of course what do you expect from a Jakarta Apache 
project... ;.))


> Is Freemarker explicitly checking map implementation 
> type for performance reasons?

No, it's certainly not for performance reasons, because making a copy of 
the maps before putting them in the data model is actually a bit costly 
and slows things down. The reason it works like that is that -- at least 
at a certain stage -- I was very concerned with things being thread-safe 
by default. I was thinking about scenarios where an application kept a 
hash around and modified it and meanwhile different template processing 
threads were accessing the data. I reasoned that copying the maps by 
default so that any template processing thread had its own thread-local 
copy would protect people from making this kind of thread-safety 
mistake. Of course, in many (maybe most) cases the application builds up 
the data structure to be used for the template from scratch each time, 
so copying the data for thread-safety is superfluous and inefficient. So 
basically I consciously went for a solution that was more foolproof 
insofar as typical naive usage would result in thread-safe code, at the 
cost the default being less efficient in general,

I'm actually starting to wonder whether it is worth doing this. It would 
be a lot simpler really, starting in 2.4, to just use the BeansWrapper 
for lists and maps as well. (Basically, right now the BeansWrapper is 
used for any POJO excluding  java.util Maps and Lists as well as 
Strings, and also specific things it knows about like DOM nodes and 
Jython and Rhino objects.)

It also seemed (though maybe only to me) that, in very basic usage of 
FreeMarker that only involved strings, lists, and maps, that the java 
API on java.lang.String and java.util.List etc. would not be exposed to 
the template layer, at least by default. IOW, you'd have to do it 
deliberately. Admittedly, there would be a stronger argument for this if 
we were going to have implementations of FreeMarker for other languages.

But anyway, for now, that's how it works, and I hope that answers your 
question. :-)

JR

> 
> Thanks
> 
> Steve.
> 
> ---
> Stephen Smith, MEng (Wales).
> http://www.stephen-smith.co.uk/
> 
> Jonathan Revusky wrote:
>> Stephen Smith wrote:
>>> Hi,
>>>
>>> We're using an OrderedBidiMap from Commons Collections 3.x to store a 
>>> mapping of
>>> keys<->values (the implementation we use is TreeBidiMap). Our 
>>> Freemarker syntax
>>> is as follows:
>>>
>>>     <#list subjectsMap?keys as subjectName>
>>>       ${subjectName}
>>>     </#if>
>>>
>>> The problem is that the sequence is returning an unordered list of 
>>> keys. The same
>>> Freemarker syntax seems to work with a LinkedHashMap, but I don't 
>>> understand what
>>> Freemarker is doing under the hood when getting the map's keys. 
>>> Surely it is just
>>> calling Map#keySet, in which case our ordering should be preserved?
>>
>> I think the problem is that the DefaultObjectWrapper is copying the 
>> map, and by default, is using HashMap. The code in there is aware, for 
>> example, of things in java.util.* such as LinkedHashMap or TreeMap and 
>> will preserve order, but the default behavior with maps otherwise is 
>> to copy them into a HashMap.
>>
>> Perhaps you need to explicitly use the BeansWrapper. Just use:
>>
>> BeansWrapper bw = new BeansWrapper();
>> dataModel.put("subjectsMap", bw.wrap(subjectsMap));
>>
>> Or, you could just set it as the default wrapper, and that would be 
>> something like:
>>
>> freeMarkerConfig.setObjectWrapper(new BeansWrapper());
>>
>>
>>>
>>> Is this a defect in Freemarker, 
>>
>> Arguably, it is, though the whole issue is a bit complex...
>>
>>> are we misusing the sequence syntax, or should we
>>> pass through the OrderedBidiMap's entry set and then call getKey and 
>>> getValue on
>>> each bean?
>>
>> For the moment, the solution would be to explicitly use 
>> freemarker.ext.beans.BeansWrapper.
>>
>> JR
>>
>>>
>>> Thanks in advance
>>>
>>> Stephen Smith
>>>
>>> ------------------------------------------------------------------------- 
>>>
>>> This SF.net email is sponsored by: Microsoft
>>> Defy all challenges. Microsoft(R) Visual Studio 2008.
>>> http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
>>> _______________________________________________
>>> FreeMarker-user mailing list
>>> [email protected]
>>> https://lists.sourceforge.net/lists/listinfo/freemarker-user
>>>
>>
>>
>>
> 


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.