Skip to content

Commit

Permalink
Accessing map via primitive (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
ebussieres committed Apr 27, 2017
1 parent 9d49dbc commit fe4d8fe
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@
import com.mitchellbosecke.pebble.template.EvaluationContext;
import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;

import java.lang.reflect.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -75,27 +80,27 @@ public GetAttributeExpression(Expression<?> node, Expression<?> attributeNameExp

@Override
public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException {
Object object = node.evaluate(self, context);
Object attributeNameValue = attributeNameExpression.evaluate(self, context);
Object object = this.node.evaluate(self, context);
Object attributeNameValue = this.attributeNameExpression.evaluate(self, context);
String attributeName = String.valueOf(attributeNameValue);

Object result = null;

Object[] argumentValues = null;

Member member = object == null ? null : memberCache.get(new MemberCacheKey(object.getClass(), attributeName));
Member member = object == null ? null : this.memberCache.get(new MemberCacheKey(object.getClass(), attributeName));

if (object != null && member == null) {

/*
* If, and only if, no arguments were provided does it make sense to
* check maps/arrays/lists
*/
if (args == null) {
if (this.args == null) {

// first we check maps
if (object instanceof Map && ((Map<?, ?>) object).containsKey(attributeNameValue)) {
return ((Map<?, ?>) object).get(attributeNameValue);
if (object instanceof Map) {
return this.getObjectFromMap((Map<?, ?>) object, attributeNameValue);
}

try {
Expand All @@ -108,7 +113,7 @@ public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throw
if (context.isStrictVariables()) {
throw new AttributeNotFoundException(null,
"Index out of bounds while accessing array with strict variables on.",
attributeName, lineNumber, filename);
attributeName, this.lineNumber, this.filename);
} else {
return null;
}
Expand All @@ -129,7 +134,7 @@ public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throw
if (context.isStrictVariables()) {
throw new AttributeNotFoundException(null,
"Index out of bounds while accessing array with strict variables on.",
attributeName, lineNumber, filename);
attributeName, this.lineNumber, this.filename);
} else {
return null;
}
Expand All @@ -147,7 +152,7 @@ public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throw
* turn args into an array of types and an array of values in order
* to use them for our reflection calls
*/
argumentValues = getArgumentValues(self, context);
argumentValues = this.getArgumentValues(self, context);
Class<?>[] argumentTypes = new Class<?>[argumentValues.length];

for (int i = 0; i < argumentValues.length; i++) {
Expand All @@ -159,23 +164,23 @@ public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throw
}
}

member = reflect(object, attributeName, argumentTypes);
member = this.reflect(object, attributeName, argumentTypes);
if (member != null) {
memberCache.put(new MemberCacheKey(object.getClass(), attributeName), member);
this.memberCache.put(new MemberCacheKey(object.getClass(), attributeName), member);
}

}

if (object != null && member != null) {
if (argumentValues == null) {
argumentValues = getArgumentValues(self, context);
argumentValues = this.getArgumentValues(self, context);
}
result = invokeMember(object, member, argumentValues);
result = this.invokeMember(object, member, argumentValues);
} else if (context.isStrictVariables()) {
if (object == null) {

if (node instanceof ContextVariableExpression) {
final String rootPropertyName = ((ContextVariableExpression) node).getName();
if (this.node instanceof ContextVariableExpression) {
final String rootPropertyName = ((ContextVariableExpression) this.node).getName();
throw new RootAttributeNotFoundException(null, String.format(
"Root attribute [%s] does not exist or can not be accessed and strict variables is set to true.",
rootPropertyName), rootPropertyName, this.lineNumber, this.filename);
Expand All @@ -194,6 +199,37 @@ public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throw

}

private Object getObjectFromMap(Map<?, ?> object, Object attributeNameValue) throws PebbleException {
if (object.isEmpty()) {
return null;
}
if (Number.class.isAssignableFrom(attributeNameValue.getClass())) {
Number keyAsNumber = (Number) attributeNameValue;

Class<?> keyClass = object.keySet().iterator().next().getClass();
Object key = this.cast(keyAsNumber, keyClass);
return object.get(key);
}
return object.get(attributeNameValue);
}

private Object cast(Number number, Class<?> desiredType) throws PebbleException {
if (desiredType == Long.class) {
return number.longValue();
} else if (desiredType == Integer.class) {
return number.intValue();
} else if (desiredType == Double.class) {
return number.doubleValue();
} else if (desiredType == Float.class) {
return number.floatValue();
} else if (desiredType == Short.class) {
return number.shortValue();
} else if (desiredType == Byte.class) {
return number.byteValue();
}
throw new PebbleException(null, String.format("type %s not supported for key %s", desiredType, number), this.getLineNumber(), this.filename);
}

/**
* Invoke the "Member" that was found via reflection.
*
Expand Down Expand Up @@ -264,21 +300,21 @@ private Member reflect(Object object, String attributeName, Class<?>[] parameter
String attributeCapitalized = Character.toUpperCase(attributeName.charAt(0)) + attributeName.substring(1);

// check get method
result = findMethod(clazz, "get" + attributeCapitalized, parameterTypes);
result = this.findMethod(clazz, "get" + attributeCapitalized, parameterTypes);

// check is method
if (result == null) {
result = findMethod(clazz, "is" + attributeCapitalized, parameterTypes);
result = this.findMethod(clazz, "is" + attributeCapitalized, parameterTypes);
}

// check has method
if (result == null) {
result = findMethod(clazz, "has" + attributeCapitalized, parameterTypes);
result = this.findMethod(clazz, "has" + attributeCapitalized, parameterTypes);
}

// check if attribute is a public method
if (result == null) {
result = findMethod(clazz, attributeName, parameterTypes);
result = this.findMethod(clazz, attributeName, parameterTypes);
}

// public field
Expand Down Expand Up @@ -323,7 +359,7 @@ private Method findMethod(Class<?> clazz, String name, Class<?>[] requiredTypes)

boolean compatibleTypes = true;
for (int i = 0; i < types.length; i++) {
if (requiredTypes[i] != null && !widen(types[i]).isAssignableFrom(requiredTypes[i])) {
if (requiredTypes[i] != null && !this.widen(types[i]).isAssignableFrom(requiredTypes[i])) {
compatibleTypes = false;
break;
}
Expand Down Expand Up @@ -375,19 +411,19 @@ private MemberCacheKey(Class<?> clazz, String attributeName) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (o == null || this.getClass() != o.getClass()) return false;

MemberCacheKey that = (MemberCacheKey) o;

if (!clazz.equals(that.clazz)) return false;
return attributeName.equals(that.attributeName);
if (!this.clazz.equals(that.clazz)) return false;
return this.attributeName.equals(that.attributeName);

}

@Override
public int hashCode() {
int result = clazz.hashCode();
result = 31 * result + attributeName.hashCode();
int result = this.clazz.hashCode();
result = 31 * result + this.attributeName.hashCode();
return result;
}
}
Expand All @@ -398,15 +434,15 @@ public void accept(NodeVisitor visitor) {
}

public Expression<?> getNode() {
return node;
return this.node;
}

public Expression<?> getAttributeNameExpression() {
return attributeNameExpression;
return this.attributeNameExpression;
}

public ArgumentsNode getArgumentsNode() {
return args;
return this.args;
}

@Override
Expand Down
75 changes: 73 additions & 2 deletions src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
import com.mitchellbosecke.pebble.error.RootAttributeNotFoundException;
import com.mitchellbosecke.pebble.loader.StringLoader;
import com.mitchellbosecke.pebble.template.PebbleTemplate;

import org.junit.Test;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -505,8 +507,8 @@ public class ComplexObject {
public final Map<String, Object> map = new HashMap<>();

{
map.put("SimpleObject2", new SimpleObject2());
map.put("SimpleObject6", new SimpleObject6());
this.map.put("SimpleObject2", new SimpleObject2());
this.map.put("SimpleObject6", new SimpleObject6());
}
}

Expand Down Expand Up @@ -579,4 +581,73 @@ public String getStringFromBoolean(boolean bool) {
}
}

@Test
public void testAttributePrimitiveAccessWithEmptyMap() throws Exception {
PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();

PebbleTemplate template = pebble.getTemplate(String.format("hello {{ object[1].name }}"));
Map<String, Object> context = new HashMap<>();
context.put("object", new HashMap<>());

Writer writer = new StringWriter();
template.evaluate(writer, context);

assertEquals("hello ", writer.toString());
}

@Test
public void testAttributePrimitiveAccessWithInteger() throws Exception {
String result = this.testAttributePrimitiveAccess(1);

assertEquals("hello Steve", result);
}

private String testAttributePrimitiveAccess(Number value) throws PebbleException, IOException {
PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();

PebbleTemplate template = pebble.getTemplate("hello {{ object[key].name }}");
Map<String, Object> context = new HashMap<>();
context.put("key", value);
context.put("object", Collections.singletonMap(value, new SimpleObject()));

Writer writer = new StringWriter();
template.evaluate(writer, context);

return writer.toString();
}

@Test
public void testAttributePrimitiveAccessWithLong() throws Exception {
String result = this.testAttributePrimitiveAccess(1L);

assertEquals("hello Steve", result);
}

@Test
public void testAttributePrimitiveAccessWithDouble() throws Exception {
String result = this.testAttributePrimitiveAccess(1.05D);

assertEquals("hello Steve", result);
}

@Test
public void testAttributePrimitiveAccessWithFloat() throws Exception {
String result = this.testAttributePrimitiveAccess(1.05F);

assertEquals("hello Steve", result);
}

@Test
public void testAttributePrimitiveAccessWithShort() throws Exception {
String result = this.testAttributePrimitiveAccess((short) 1);

assertEquals("hello Steve", result);
}

@Test
public void testAttributePrimitiveAccessWithByte() throws Exception {
String result = this.testAttributePrimitiveAccess((byte) 1);

assertEquals("hello Steve", result);
}
}

0 comments on commit fe4d8fe

Please sign in to comment.