diff --git a/plugins/lang/javascript/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java b/plugins/lang/javascript/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java index 22eb7ca72e5..4627a7b1bb3 100644 --- a/plugins/lang/javascript/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java +++ b/plugins/lang/javascript/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java @@ -24,10 +24,12 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptEngineService; +import org.elasticsearch.script.javascript.support.NativeList; import org.elasticsearch.script.javascript.support.NativeMap; import org.elasticsearch.script.javascript.support.ScriptValueConverter; import org.mozilla.javascript.*; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; @@ -162,6 +164,9 @@ public class JavaScriptScriptEngineService extends AbstractComponent implements if (javaObject instanceof Map) { return new NativeMap(scope, (Map) javaObject); } + if (javaObject instanceof List) { + return new NativeList(scope, (List) javaObject); + } return super.wrapAsJavaObject(cx, scope, javaObject, staticType); } } diff --git a/plugins/lang/javascript/src/main/java/org/elasticsearch/script/javascript/support/NativeList.java b/plugins/lang/javascript/src/main/java/org/elasticsearch/script/javascript/support/NativeList.java new file mode 100644 index 00000000000..9a967d48a38 --- /dev/null +++ b/plugins/lang/javascript/src/main/java/org/elasticsearch/script/javascript/support/NativeList.java @@ -0,0 +1,207 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search licenses this + * file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.script.javascript.support; + +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.Undefined; +import org.mozilla.javascript.Wrapper; + +import java.util.List; + +/** + * @author kimchy (shay.banon) + */ +public class NativeList implements Scriptable, Wrapper { + private static final long serialVersionUID = 3664761893203964569L; + + private List list; + private Scriptable parentScope; + private Scriptable prototype; + + + public static NativeList wrap(Scriptable scope, List list) { + return new NativeList(scope, list); + } + + public NativeList(Scriptable scope, List list) { + this.parentScope = scope; + this.list = list; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Wrapper#unwrap() + */ + + public Object unwrap() { + return list; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + + public String getClassName() { + return "NativeList"; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) + */ + + public Object get(String name, Scriptable start) { + if ("length".equals(name)) { + return list.size(); + } else { + return Undefined.instance; + } + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) + */ + + public Object get(int index, Scriptable start) { + if (index < 0 || index >= list.size()) { + return Undefined.instance; + } + return list.get(index); + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) + */ + + public boolean has(String name, Scriptable start) { + if ("length".equals(name)) { + return true; + } + return false; + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) + */ + + public boolean has(int index, Scriptable start) { + 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) + */ + + public void put(int index, Scriptable start, Object value) { + if (index == list.size()) { + list.add(value); + } else { + list.set(index, value); + } + } + + /* (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) + */ + + public void delete(int index) { + 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]; + for (int i = 0; i < size; ++i) { + ids[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) + */ + + public boolean hasInstance(Scriptable value) { + if (!(value instanceof Wrapper)) + return false; + Object instance = ((Wrapper) value).unwrap(); + return List.class.isInstance(instance); + } + +} diff --git a/plugins/lang/javascript/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java b/plugins/lang/javascript/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java index b40d4b81380..d476da7c2ea 100644 --- a/plugins/lang/javascript/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java +++ b/plugins/lang/javascript/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.script.javascript; +import org.elasticsearch.common.collect.Lists; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.script.ExecutableScript; @@ -48,17 +49,20 @@ public class JavaScriptScriptEngineTests { assertThat(((Number) o).intValue(), equalTo(3)); } - @Test public void testMapPassedMapReturned() { + @Test public void testMapAccess() { Map vars = new HashMap(); Map obj2 = MapBuilder.newMapBuilder().put("prop2", "value2").map(); - Map obj1 = MapBuilder.newMapBuilder().put("prop1", "value1").put("obj2", obj2).map(); + Map obj1 = MapBuilder.newMapBuilder().put("prop1", "value1").put("obj2", obj2).put("l", Lists.newArrayList("2", "1")).map(); vars.put("obj1", obj1); Object o = se.execute(se.compile("obj1"), vars); assertThat(o, instanceOf(Map.class)); obj1 = (Map) o; assertThat((String) obj1.get("prop1"), equalTo("value1")); assertThat((String) ((Map) obj1.get("obj2")).get("prop2"), equalTo("value2")); + + o = se.execute(se.compile("obj1.l[0]"), vars); + assertThat(((String) o), equalTo("2")); } @Test public void testJavaScriptObjectToMap() { @@ -69,6 +73,27 @@ public class JavaScriptScriptEngineTests { assertThat((String) ((Map) obj1.get("obj2")).get("prop2"), equalTo("value2")); } + @Test public void testAccessListInScript() { + Map vars = new HashMap(); + Map obj2 = MapBuilder.newMapBuilder().put("prop2", "value2").map(); + Map obj1 = MapBuilder.newMapBuilder().put("prop1", "value1").put("obj2", obj2).map(); + vars.put("l", Lists.newArrayList("1", "2", "3", obj1)); + + Object o = se.execute(se.compile("l.length"), vars); + assertThat(((Number) o).intValue(), equalTo(4)); + + o = se.execute(se.compile("l[0]"), vars); + assertThat(((String) o), equalTo("1")); + + o = se.execute(se.compile("l[3]"), vars); + obj1 = (Map) o; + assertThat((String) obj1.get("prop1"), equalTo("value1")); + assertThat((String) ((Map) obj1.get("obj2")).get("prop2"), equalTo("value2")); + + o = se.execute(se.compile("l[3].prop1"), vars); + assertThat(((String) o), equalTo("value1")); + } + @Test public void testChangingVarsCrossExecution1() { Map vars = new HashMap(); Map ctx = new HashMap();