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/