/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.sling.scripting.sightly.js.impl.rhino; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Wrapper; import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer; import org.apache.sling.scripting.sightly.js.impl.async.AsyncExtractor; /** * Converts JS objects to Java objects */ public class JsValueAdapter { private static final Map<String, Class<?>> knownConversions = new HashMap<String, Class<?>>(); static { knownConversions.put("String", String.class); knownConversions.put("Date", Date.class); } private final AsyncExtractor asyncExtractor; public JsValueAdapter(AsyncExtractor asyncExtractor) { this.asyncExtractor = asyncExtractor; } /** * Convert a given JS value to a Java object * @param jsValue the original JS value * @return the Java correspondent */ @SuppressWarnings("unchecked") public Object adapt(Object jsValue) { if (jsValue == null || jsValue == Context.getUndefinedValue() || jsValue == ScriptableObject.NOT_FOUND) { return null; } if (jsValue instanceof Wrapper) { return adapt(((Wrapper) jsValue).unwrap()); } if (asyncExtractor.isPromise(jsValue)) { return adapt(forceAsync(jsValue)); } if (jsValue instanceof ScriptableObject) { return extractScriptable((ScriptableObject) jsValue); } if (jsValue instanceof CharSequence) { //convert any string-like type to plain java strings return jsValue.toString(); } if (jsValue instanceof Map) { return convertMap((Map) jsValue); } if (jsValue instanceof Iterable) { return convertIterable((Iterable) jsValue); } if (jsValue instanceof Number) { return convertNumber((Number) jsValue); } if (jsValue instanceof Object[]) { return convertIterable(Arrays.asList((Object[]) jsValue)); } return jsValue; } private Object convertNumber(Number numValue) { if (numValue instanceof Double) { if (isLong((Double) numValue)) { return numValue.longValue(); } } if (numValue instanceof Float) { if (isLong((Float) numValue)) { return numValue.longValue(); } } return numValue; } private boolean isLong(double x) { return x == Math.floor(x); } private Object forceAsync(Object jsValue) { AsyncContainer asyncContainer = new AsyncContainer(); asyncExtractor.extract(jsValue, asyncContainer.createCompletionCallback()); return asyncContainer.getResult(); } private Object extractScriptable(ScriptableObject scriptableObject) { Object obj = tryKnownConversion(scriptableObject); if (obj != null) { return obj; } if (scriptableObject instanceof NativeArray) { return convertNativeArray((NativeArray) scriptableObject); } if (scriptableObject instanceof Function) { return callFunction((Function) scriptableObject); } return new HybridObject(scriptableObject, this); } private Object callFunction(Function function) { Object result = JsUtils.callFn(function, null, function, function, new Object[0]); return adapt(result); } private Object[] convertNativeArray(NativeArray nativeArray) { int length = (int) nativeArray.getLength(); Object[] objects = new Object[length]; for (int i = 0; i < length; i++) { Object jsItem = nativeArray.get(i, nativeArray); objects[i] = adapt(jsItem); } return objects; } private Map<Object, Object> convertMap(Map<Object, Object> original) { Map<Object, Object> map = new HashMap<Object, Object>(); for (Map.Entry<Object, Object> entry : original.entrySet()) { map.put(entry.getKey(), adapt(entry.getValue())); } return map; } private List<Object> convertIterable(Iterable<Object> iterable) { List<Object> objects = new ArrayList<Object>(); for (Object obj : iterable) { objects.add(adapt(obj)); } return objects; } private static Object tryKnownConversion(ScriptableObject object) { String className = object.getClassName(); Class<?> cls = knownConversions.get(className); return (cls == null) ? null : Context.jsToJava(object, cls); } }