Skip to content

Commit

Permalink
Better handling of nested POJOs. Loop support for Collection/Iterator…
Browse files Browse the repository at this point in the history
…/Enumeration.
  • Loading branch information
Tom McClure committed Jul 16, 2015
1 parent 6279158 commit 03d1331
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 37 deletions.
71 changes: 41 additions & 30 deletions src/main/java/com/x5/template/Chunk.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* set up replacement rules for those tags like so:
*
* <PRE>
* TemplateSet templates = getTemplates(); // defined elsewhere
* Theme templates = getTemplates(); // defined elsewhere
* Chunk myChunk = templates.makeChunk("my_template");
* myChunk.set("my_tag","hello tag");
* System.out.print( myChunk.toString() );
Expand All @@ -48,9 +48,8 @@
* hash mark surrounded by curly brackets/braces.
*
* <P>
* TemplateSet is handy if you have a folder with lots of html templates.<BR>
* Here's an even simpler example, where the template string is supplied<BR>
* without using TemplateSet:
* without using a theme:
*
* <PRE>
* String templateBody = "Hello {$name}! Your balance is ${$balance}."
Expand Down Expand Up @@ -91,9 +90,9 @@
* <P>
* A: No*. To keep things simple and reduce potential for confusion, Chunk<BR>
* does not auto-magically fill any tags based on naming conventions or<BR>
* in-template directives. You must explicitly invoke the "include command:<BR>
* in-template directives. You must explicitly invoke the include command:<BR>
*
* bla bla bla {.include #myTemplate} foo foo foo
* bla bla bla {% include #myTemplate %} foo foo foo
*
* <P>* Actually, this documentation is outdated, and several extensions to the<BR>
* original template syntax are now available:
Expand All @@ -111,7 +110,7 @@
* <B>Q: My final output says "infinite recursion detected." What gives?</B>
*
* <P>
* A: You did some variation of this:
* A: You tripped the recursion depth limit (17) or you did some variation of this:
* <PRE>
* TEMPLATE:
* bla bla bla {$name}
Expand Down Expand Up @@ -167,10 +166,9 @@
* <B>Q: Are tag names and subtemplate names case sensitive?</B>
*
* <P>
* A: Yes. I prefer to use mixed case in {$tagNames} with first letter<BR>
* lowercase. In my experience this aids readability since tags are similar<BR>
* to java variables in concept and that is the java case convention for<BR>
* variables. Similarly, I prefer lowercase with underscores for all<BR>
* A: Yes. I prefer to use snake case in {$tag_names} but be aware<BR>
* that {$tag} and {$Tag} and {$TAG} are three different values.<BR>
* Similarly, I prefer lowercase with underscores for all<BR>
* {#sub_template_names}{#} since templates tend to be defined within html<BR>
* files which are typically named in all lowercase.
*
Expand Down Expand Up @@ -245,15 +243,15 @@
* Updates: <A href="http://www.x5software.com/chunk/">Chunk Documentation</A><BR>
*
* @author Tom McClure
* @version 3.0.0
* @version 3.0.1
*/

public class Chunk implements Map<String,Object>
{
public static final int HASH_THRESH = 8;
public static final int DEPTH_LIMIT = 17;

public static final String VERSION = "3.0.0";
public static final String VERSION = "3.0.1";

private static final String TRUE = "TRUE";

Expand Down Expand Up @@ -461,18 +459,7 @@ public void set(String tagName, Object tagValue, String ifNull)
if (tagName == null) return;
// ensure that tagValue is either a String or a Chunk (or some tabular data)
if (tagValue != null) {
tagValue = coercePrimitivesToString(tagValue);
if (tagValue instanceof Chunk || tagValue instanceof TableData) {
// don't treat chunk or tabledata as a Map
} else if (tagValue instanceof Map) {
// great, a map!
} else if (!(tagValue instanceof String
|| tagValue instanceof Snippet
|| tagValue instanceof List
|| tagValue instanceof Object[])) {
// force to map
tagValue = new ObjectDataMap(tagValue);
}
tagValue = coercePrimitivesToStringAndBoxAliens(tagValue);
}
if (tagValue == null) {
tagValue = (ifNull == null) ? "NULL" : ifNull;
Expand Down Expand Up @@ -1086,9 +1073,9 @@ protected Object _resolveTagValue(SnippetTag tag, int depth, boolean ignoreParen
tagValue = null;
}
}
// convert primitives to string
if (!(tagValue instanceof String)) {
tagValue = coercePrimitivesToString(tagValue);
// convert primitives to string, box illegal aliens
if (tagValue != null && !(tagValue instanceof String)) {
tagValue = coercePrimitivesToStringAndBoxAliens(tagValue);
}

String filters = tag.getFilters();
Expand Down Expand Up @@ -1126,16 +1113,40 @@ protected Object _resolveTagValue(SnippetTag tag, int depth, boolean ignoreParen
}
}

// unbox and stringify primitive wrapper objects
private Object coercePrimitivesToString(Object o)
// unbox and stringify primitive wrapper objects, box any objects if not chunk-friendly
private Object coercePrimitivesToStringAndBoxAliens(Object o)
{
if (o == null) return o;

if (o instanceof Boolean) {
return ((Boolean)o).booleanValue() ? "TRUE" : null;
} else if (o != null && ObjectDataMap.isWrapperType(o.getClass())) {
return o.toString();
} else {
return boxIfAlienObject(o);
}
}

private Object boxIfAlienObject(Object o)
{
if (o == null) return o;

if (o instanceof Chunk || o instanceof TableData) {
// Chunk and TableData can be handled natively
return o;
} else if (o instanceof Map) {
// Map can be handled natively
return o;
} else if (o instanceof String
|| o instanceof Snippet
|| o instanceof List
|| o instanceof Object[]) {
// can all be handled natively
return o;
}

// unrecognized object. wrap inside map.
return new ObjectDataMap(o);
}

protected Object resolveTagValue(String tagName, int depth)
Expand Down Expand Up @@ -1288,7 +1299,7 @@ public void setMultiple(Map<String,Object> rules)
if (rules == null || rules.size() <= 0) return;
Set<String> keys = rules.keySet();
for (String tagName : keys) {
setOrDelete(tagName,rules.get(tagName));
setOrDelete(tagName, rules.get(tagName));
}
}

Expand Down
19 changes: 16 additions & 3 deletions src/main/java/com/x5/template/LoopTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ private TableData fetchData(String dataVar, String origin)
data = new TableOfMaps(list);
} else {
// last-ditch effort to extract data, treat as POJOs
data = TableOfMaps.boxObjectList((List)dataStore);
data = TableOfMaps.boxCollection(list);
}
}
} else if (dataStore instanceof Object[]) {
Expand All @@ -252,8 +252,21 @@ private TableData fetchData(String dataVar, String origin)
data = TableOfMaps.boxObjectArray((Object[])dataStore);
}
} else if (dataStore instanceof Map) {
Map object = (Map)dataStore;
data = new ObjectTable(object);
if (dataStore instanceof com.x5.util.ObjectDataMap) {
Object unwrapped = ((com.x5.util.ObjectDataMap)dataStore).unwrap();
if (unwrapped instanceof java.util.Collection) {
data = TableOfMaps.boxCollection((java.util.Collection)unwrapped);
} else if (unwrapped instanceof java.util.Enumeration) {
data = TableOfMaps.boxEnumeration((java.util.Enumeration)unwrapped);
} else if (unwrapped instanceof java.util.Iterator) {
data = TableOfMaps.boxIterator((java.util.Iterator)unwrapped);
}
}
if (data == null) {
// Doesn't support traditional iteration. Loop over object's keys:values instead.
Map object = (Map)dataStore;
data = new ObjectTable(object);
}
}

// only loop if following pointer
Expand Down
39 changes: 37 additions & 2 deletions src/main/java/com/x5/template/TableOfMaps.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.x5.template;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -79,13 +81,46 @@ static TableData boxObjectList(List dataStore)
return null;
}

return boxIterator(dataStore.iterator());
}

@SuppressWarnings("rawtypes")
static TableData boxEnumeration(Enumeration dataStore)
{
if (dataStore == null || !dataStore.hasMoreElements()) {
return null;
}

// convert to list of POJOs
List<Map> boxedObjects = new ArrayList<Map>();
while (dataStore.hasMoreElements()) {
boxedObjects.add(new ObjectDataMap(dataStore.nextElement()));
}

return new TableOfMaps(boxedObjects);
}

@SuppressWarnings("rawtypes")
static TableData boxCollection(Collection collection)
{
if (collection == null || collection.size() < 1) {
return null;
}

return boxIterator(collection.iterator());
}

static TableData boxIterator(Iterator i)
{
if (i == null || !i.hasNext()) {
return null;
}

List<Map> boxedObjects = new ArrayList<Map>();
Iterator i = dataStore.iterator();
while (i.hasNext()) {
boxedObjects.add(new ObjectDataMap(i.next()));
}

return new TableOfMaps(boxedObjects);
}

}
5 changes: 5 additions & 0 deletions src/main/java/com/x5/template/filters/DefaultFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public String getFilterName()
return "default";
}

public String[] getFilterAliases()
{
return new String[]{"onnull"};
}

}
2 changes: 1 addition & 1 deletion src/main/java/com/x5/template/filters/OnEmptyFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public String transformText(Chunk chunk, String text, FilterArgs arg)
if (args != null && args.length > 0) swapFor = args[0];
if (swapFor == null) return null;

// null and empty string are both considered empty
// null and empty string and whitespace-only are all considered empty
return (text == null || text.trim().length() == 0) ? FilterArgs.magicBraces(chunk, swapFor) : text;
}

Expand Down
31 changes: 30 additions & 1 deletion src/test/java/com/x5/template/LoopTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.x5.template;

import java.util.Map;
import java.util.StringTokenizer;

import org.junit.Test;

import static org.junit.Assert.*;
Expand Down Expand Up @@ -363,11 +366,37 @@ public void testLoopOverSplit()
{
Theme theme = new Theme();
Chunk c = theme.makeChunk();
c.set("nums","1,2,3,4");
c.set("nums", "1,2,3,4");
c.append("{.loop in $nums|split(,) as $n}{$n}{.divider} {/divider}{/loop}");
assertEquals("1 2 3 4", c.toString());
}

@Test
public void testLoopOverNestedEnumeration()
{
Theme theme = new Theme();
Chunk c = theme.makeChunk();
StringTokenizer tokens = new StringTokenizer("a b c d");
Map<String,Object> map = new java.util.HashMap<String,Object>();
map.put("tokens", tokens);
c.set("token_holder", map);
c.append("{% loop in $token_holder.tokens as $token divider='-' %}{$token}{% endloop %}");

assertEquals("a-b-c-d", c.toString());
}

@Test
public void testLoopOverEnumeration()
{
Theme theme = new Theme();
Chunk c = theme.makeChunk();
StringTokenizer tokens = new StringTokenizer("a b c d");
c.set("tokens", tokens);
c.append("{% loop in $tokens as $token divider='-' %}{$token}{% endloop %}");

assertEquals("a-b-c-d", c.toString());
}

@Test
public void testBadCloseTag()
{
Expand Down

0 comments on commit 03d1331

Please sign in to comment.