/*
* Licensed to Luca Cavanna (the "Author") 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.shell;
import java.util.*;
import org.mozilla.javascript.*;
/**
* Rhino implementation of the {@link Unwrapper}
*
* @author Luca Cavanna
*/
public class RhinoUnwrapper implements Unwrapper {
/**
* Converts an object from a script wrapper value to a serializable value valid outside
* of the Rhino script processor context.
*
* This includes converting JavaScript Array objects to Lists of valid objects.
*
* @param scriptObject Value to convert from script wrapper object to external object value
* @return unwrapped and converted value
*/
@Override
public Object unwrap(Object scriptObject, boolean prettify) {
if (scriptObject == null) {
return null;
} else if (scriptObject instanceof Wrapper) {
// unwrap a Java object from a JavaScript wrapper
// recursively call this method to convert the unwrapped value
return unwrap(((Wrapper) scriptObject).unwrap(), prettify);
} else if (scriptObject instanceof Function) {
return Context.toString(scriptObject);
} else if (scriptObject instanceof IdScriptableObject) {
// check for special case Native object wrappers
String className = ((IdScriptableObject) scriptObject).getClassName();
// check for special case of the String object
if (String.class.getSimpleName().equals(className)) {
return Context.jsToJava(scriptObject, String.class);
}
// check for special case of a Date object
else if (Date.class.getSimpleName().equals(className)) {
return Context.jsToJava(scriptObject, Date.class);
} else {
// a scriptable object will probably indicate a multi-value property set
// set using a JavaScript associative Array object
Scriptable values = (Scriptable) scriptObject;
Object[] propIds = values.getIds();
// is it a JavaScript associative Array object using Integer indexes?
if (values instanceof NativeArray && isArray(propIds)) {
// convert JavaScript array of values to a List of Serializable objects
List<Object> propValues = new ArrayList<Object>(propIds.length);
for (Object propIdObject : propIds) {
// work on each key in turn
// we are only interested in keys that indicate a list of values
if (propIdObject instanceof Integer) {
Integer propId = (Integer)propIdObject;
// getContext the value out for the specified key
Object val = values.get(propId, values);
// recursively call this method to convert the value
propValues.add(unwrap(val, prettify));
}
}
return propValues;
} else {
// any other JavaScript object that supports properties (json)
Context context = Context.getCurrentContext();
String space = prettify ? " " : "";
return NativeJSON.stringify(context, ScriptRuntime.getGlobal(context), values, null, space);
}
}
} else if (scriptObject instanceof Object[]) {
// convert back a list Object Java values
Object[] array = (Object[]) scriptObject;
ArrayList<Object> list = new ArrayList<Object>(array.length);
for (Object object : array) {
list.add(unwrap(object, prettify));
}
return list;
} else if (scriptObject instanceof Map) {
// ensure each value in the Map is unwrapped (which may have been an unwrapped NativeMap!)
@SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>) scriptObject;
Map<Object, Object> copyMap = new HashMap<Object, Object>(map.size());
for (Object key : map.keySet()) {
copyMap.put(key, unwrap(map.get(key), prettify));
}
return copyMap;
}
return scriptObject;
}
/**
* Look at the id's of a native array and try to determine whether it's actually an Array or a Hashmap
*
* @param ids id's of the native array
* @return boolean true if it's an array, false otherwise (ie it's a map)
*/
private boolean isArray(final Object[] ids) {
for (Object id : ids) {
if (!(id instanceof Integer)) {
return false;
}
}
return true;
}
}