Improve wrapping of lists.

With the current way that Java lists are wrapped into a Scriptable, all methods
that are not defined on the java.util.List interface are hidden. This pull
request makes NativeList extend NativeJavaObject in order to use reflection in
order to look up properties that would not be defined on the List interface.

Close #32
This commit is contained in:
Adrien Grand 2014-11-27 16:39:53 +01:00
parent 4549a3b4ee
commit 7417814154
5 changed files with 34 additions and 79 deletions

View File

@ -292,10 +292,10 @@ public class JavaScriptScriptEngineService extends AbstractComponent implements
public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType) {
if (javaObject instanceof Map) {
return new NativeMap(scope, (Map) javaObject);
return NativeMap.wrap(scope, (Map) javaObject);
}
if (javaObject instanceof List) {
return new NativeList(scope, (List) javaObject);
return NativeList.wrap(scope, (List) javaObject, staticType);
}
return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
}

View File

@ -19,29 +19,30 @@
package org.elasticsearch.script.javascript.support;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;
import java.util.Arrays;
import java.util.List;
/**
*
*/
public class NativeList implements Scriptable, Wrapper {
public class NativeList extends NativeJavaObject implements Scriptable, Wrapper {
private static final long serialVersionUID = 3664761893203964569L;
private static final String LENGTH_PROPERTY = "length";
private List<Object> list;
private Scriptable parentScope;
private Scriptable prototype;
private final List<Object> list;
public static NativeList wrap(Scriptable scope, List<Object> list) {
return new NativeList(scope, list);
public static NativeList wrap(Scriptable scope, List<Object> list, Class<?> staticType) {
return new NativeList(scope, list, staticType);
}
public NativeList(Scriptable scope, List<Object> list) {
this.parentScope = scope;
private NativeList(Scriptable scope, List<Object> list, Class<?> staticType) {
super(scope, list, staticType);
this.list = list;
}
@ -66,10 +67,10 @@ public class NativeList implements Scriptable, Wrapper {
*/
public Object get(String name, Scriptable start) {
if ("length".equals(name)) {
if (LENGTH_PROPERTY.equals(name)) {
return list.size();
} else {
return Undefined.instance;
return super.get(name, start);
}
}
@ -78,7 +79,7 @@ public class NativeList implements Scriptable, Wrapper {
*/
public Object get(int index, Scriptable start) {
if (index < 0 || index >= list.size()) {
if (has(index, start) == false) {
return Undefined.instance;
}
return list.get(index);
@ -89,10 +90,7 @@ public class NativeList implements Scriptable, Wrapper {
*/
public boolean has(String name, Scriptable start) {
if ("length".equals(name)) {
return true;
}
return false;
return super.has(name, start) || LENGTH_PROPERTY.equals(name);
}
/* (non-Javadoc)
@ -103,15 +101,6 @@ public class NativeList implements Scriptable, Wrapper {
return index >= 0 && index < list.size();
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public void put(String name, Scriptable start, Object value) {
// do nothing here...
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
@ -124,14 +113,6 @@ public class NativeList implements Scriptable, Wrapper {
}
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#delete(java.lang.String)
*/
public void delete(String name) {
// nothing here
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#delete(int)
*/
@ -140,59 +121,20 @@ public class NativeList implements Scriptable, Wrapper {
list.remove(index);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getPrototype()
*/
public Scriptable getPrototype() {
return this.prototype;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable)
*/
public void setPrototype(Scriptable prototype) {
this.prototype = prototype;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getParentScope()
*/
public Scriptable getParentScope() {
return this.parentScope;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable)
*/
public void setParentScope(Scriptable parent) {
this.parentScope = parent;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getIds()
*/
public Object[] getIds() {
int size = list.size();
Object[] ids = new Object[size];
final Object[] javaObjectIds = super.getIds();
final int size = list.size();
final Object[] ids = Arrays.copyOf(javaObjectIds, javaObjectIds.length + size);
for (int i = 0; i < size; ++i) {
ids[i] = i;
ids[javaObjectIds.length + i] = i;
}
return ids;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class)
*/
public Object getDefaultValue(Class hint) {
return null;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable)
*/

View File

@ -55,7 +55,7 @@ public class NativeMap implements Scriptable, Wrapper {
* @param scope
* @param map
*/
public NativeMap(Scriptable scope, Map<Object, Object> map) {
private NativeMap(Scriptable scope, Map<Object, Object> map) {
this.parentScope = scope;
this.map = map;
}

View File

@ -156,7 +156,7 @@ public final class ScriptValueConverter {
// convert array to a native JavaScript Array
value = Context.getCurrentContext().newArray(scope, array);
} else if (value instanceof Map) {
value = new NativeMap(scope, (Map) value);
value = NativeMap.wrap(scope, (Map) value);
}
// simple numbers, strings and booleans are wrapped automatically by Rhino

View File

@ -43,6 +43,7 @@ import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.
import static org.elasticsearch.search.aggregations.AggregationBuilders.terms;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
@ -285,4 +286,16 @@ public class JavaScriptScriptSearchTests extends ElasticsearchIntegrationTest {
assertThat(((Terms) response.getAggregations().asMap().get("score_agg")).getBuckets().get(0).getKeyAsNumber().floatValue(), is(1f));
assertThat(((Terms) response.getAggregations().asMap().get("score_agg")).getBuckets().get(0).getDocCount(), is(1l));
}
@Test
public void testUseListLengthInScripts() throws Exception {
createIndex("index");
index("index", "testtype", "1", jsonBuilder().startObject().field("f", 42).endObject());
ensureSearchable("index");
refresh();
SearchResponse response = client().prepareSearch().addScriptField("foobar", "js", "doc['f'].values.length", null).get();
assertSearchResponse(response);
assertHitCount(response, 1);
assertThat((Integer) response.getHits().getAt(0).getFields().get("foobar").value(), equalTo(1));
}
}