Tapestry loop through nested map in tml file

I have following map

Map<String, Map<String, Long>>

This map is accessible through following method in java file.

public List<Entry<String, List<Entry<String, Long>>>> getByEventTypeSorted() {
    List<Entry<String, List<Entry<String, Long>>>> ret = new ArrayList<Entry<String, List<Entry<String, Long>>>>();
    ret.addAll((Collection<? extends Entry<String, List<Entry<String, Long>>>>) byEventType.entrySet());
    return ret;
}

Thus I am converting Map<String, Map<String, Long>> into List<Entry<String, List<Entry<String, Long>>>> and returning it.

Now, in my tml file I am trying to loop through it like this.

<tr t:type="Loop" t:source="summarizer.byEventTypeSorted" t:value="entry">
    <td style="border: 1px solid #EEEEEE; padding: 3px">${entry.key}</td>
    <td style="border: 1px solid #EEEEEE; padding: 3px" t:type="Loop" t:source="${entry.value}" t:value="entry2"> 
        ${entry2.key}
    </td>
</tr>

Corresponding java file has following properties.

@Property
private Entry<String,Long> entry;
@Property
private Entry<String,Long> entry2;

When I run the above code. Following exception is generated.

Failure writing parameter 'value' of component ConceptSummaries:loop_1: Could not find a coercion from type java.lang.String to type java.util.Map$Entry.

If I change the type of property entry2 to String in java file like below.....

@Property
private Entry<String,Long> entry;
@Property
private String entry2;

And loop in tml file like below.

<tr t:type="Loop" t:source="summarizer.byEventTypeSorted" t:value="entry">
    <td style="border: 1px solid #EEEEEE; padding: 3px">${entry.key}</td>
    <td style="border: 1px solid #EEEEEE; padding: 3px" t:type="Loop" t:source="${entry.value}" t:value="entry2"> 
        ${entry2}
    </td>
</tr>

Then I get the whole list rendered as a single string.

This means that tapestry is converting the List<Entry<String, Long>> into a String. I do not want that to happen as I want to loop through the list and access individual entries.

How to loop through these nested maps? And is there any way to retain the type of inner list and prevent tapestry from converting it into a string?

Update: I was able to solve this problem using Lance Java's suggestions. Following are the details.

code in tml file.

<tr t:type="Loop" t:source="summarizer.byEventTypeSorted.entrySet()" t:value="entry">
    <td style="border: 1px solid #EEEEEE; padding: 3px">${entry.key}</td>
    <td style="border: 1px solid #EEEEEE; padding: 3px" t:type="Loop" t:source="KeySetForEntryValue" t:value="entry2"> 
        ${entry2.key}
    </td>
</tr>

I have a getter method for "KeySetForEntryValue" and Map<String, Map<String, Long>> in corresponding java class.

public Set<Entry<String, Long>> getKeySetForEntryValue(){
    return entry.getValue().entrySet();
}

public Map<String, Map<String, Long>> getByEventTypeSorted() {
    return byEventType;
}

And following are the properties in the same java class.

@Property
private Entry<String, Map<String, Long>> entry;
@Property
private Entry<String, Long> entry2;

Answers


Your problem is with t:source="${entry.value}"

Using ${...} in a template attribute causes the value to be coerced to a string

Try t:source="entry.value" instead.

Here's how I'd do it:

@Property
private Map<String, Map<String, Long>> byEventType;

@Property
private Entry<String, Map<String, Long>> entry;

@Property
private Entry<String, Long> entry2;

<tr t:type="Loop" t:source="byEventType.entrySet()" t:value="entry">
<td style="border: 1px solid #EEEEEE; padding: 3px">${entry.key}</td>
<td style="border: 1px solid #EEEEEE; padding: 3px" t:type="Loop" t:source="entry.value.entrySet()" t:value="entry2"> 
    ${entry2.key} = ${entry2.value}
</td>


Need Your Help

java: remove cdata tag from xml

java regex xslt xpath cdata

xpath is nice for parsing xml files, but its not working for data inside the cdata tag:

Is there any other better way to pass parameters to backgroundworker runasync?

c# backgroundworker

I am new to C# and currently working a project that requires the need for BackgroundWorkers. I found that the BackgroundWorker RunWorkerAsync simply takes one object as an argument. But there are