blob: 449f65a314ef315306182e74e00184a9c9c02d65 [file] [log] [blame]
/*
* 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.cocoon.components.expression.jexl;
import java.util.Enumeration;
import java.util.Iterator;
import org.apache.commons.jexl.util.introspection.Info;
import org.apache.commons.jexl.util.introspection.UberspectImpl;
import org.apache.commons.jexl.util.introspection.VelMethod;
import org.apache.commons.jexl.util.introspection.VelPropertyGet;
import org.apache.commons.jexl.util.introspection.VelPropertySet;
import org.apache.commons.lang.StringUtils;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeJavaClass;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;
/**
* Jexl Introspector that supports Rhino JavaScript objects
* as well as Java Objects.
*
* @version $Id$
*/
public class JSIntrospector extends UberspectImpl {
static class JSMethod implements VelMethod {
Scriptable scope;
String name;
public JSMethod(Scriptable scope, String name) {
this.scope = scope;
this.name = name;
}
public Object invoke(Object thisArg, Object[] args) throws Exception {
Context cx = Context.enter();
try {
Object result;
Scriptable thisObj = !(thisArg instanceof Scriptable) ?
Context.toObject(thisArg, scope) : (Scriptable)thisArg;
result = ScriptableObject.getProperty(thisObj, name);
Object[] newArgs = null;
if (args != null) {
newArgs = new Object[args.length];
int len = args.length;
for (int i = 0; i < len; i++) {
newArgs[i] = args[i];
if (args[i] != null &&
!(args[i] instanceof Number) &&
!(args[i] instanceof Boolean) &&
!(args[i] instanceof String) &&
!(args[i] instanceof Scriptable)) {
newArgs[i] = Context.toObject(args[i], scope);
}
}
}
result = ScriptRuntime.call(cx, result, thisObj, newArgs, scope);
if (result == Undefined.instance || result == Scriptable.NOT_FOUND) {
result = null;
} else if (!(result instanceof NativeJavaClass)) {
while (result instanceof Wrapper) {
result = ((Wrapper)result).unwrap();
}
}
return result;
} catch (JavaScriptException e) {
throw new java.lang.reflect.InvocationTargetException(e);
} finally {
Context.exit();
}
}
public boolean isCacheable() {
return false;
}
public String getMethodName() {
return name;
}
public Class getReturnType() {
return Object.class;
}
}
static class JSPropertyGet implements VelPropertyGet {
Scriptable scope;
String name;
public JSPropertyGet(Scriptable scope, String name) {
this.scope = scope;
this.name = name;
}
public Object invoke(Object thisArg) throws Exception {
Context cx = Context.enter();
try {
Scriptable thisObj = !(thisArg instanceof Scriptable) ?
Context.toObject(thisArg, scope) : (Scriptable)thisArg;
Object result = ScriptableObject.getProperty(thisObj, name);
if (result == Scriptable.NOT_FOUND) {
result = ScriptableObject.getProperty(thisObj, "get" + StringUtils.capitalize(name));
if (result != Scriptable.NOT_FOUND && result instanceof Function) {
try {
result = ((Function)result).call(
cx, ScriptableObject.getTopLevelScope(thisObj), thisObj, new Object[] {});
} catch (JavaScriptException exc) {
exc.printStackTrace();
result = null;
}
}
}
if (result == Scriptable.NOT_FOUND || result == Undefined.instance) {
result = null;
} else if (result instanceof Wrapper && !(result instanceof NativeJavaClass)) {
result = ((Wrapper)result).unwrap();
}
return result;
} finally {
Context.exit();
}
}
public boolean isCacheable() {
return false;
}
public String getMethodName() {
return name;
}
}
static class JSPropertySet implements VelPropertySet {
Scriptable scope;
String name;
public JSPropertySet(Scriptable scope, String name) {
this.scope = scope;
this.name = name;
}
public Object invoke(Object thisArg, Object rhs) throws Exception {
Context.enter();
try {
Scriptable thisObj;
Object arg = rhs;
if (!(thisArg instanceof Scriptable)) {
thisObj = Context.toObject(thisArg, scope);
} else {
thisObj = (Scriptable)thisArg;
}
if (arg != null &&
!(arg instanceof Number) &&
!(arg instanceof Boolean) &&
!(arg instanceof String) &&
!(arg instanceof Scriptable)) {
arg = Context.toObject(arg, scope);
}
ScriptableObject.putProperty(thisObj, name, arg);
return rhs;
} finally {
Context.exit();
}
}
public boolean isCacheable() {
return false;
}
public String getMethodName() {
return name;
}
}
public static class NativeArrayIterator implements Iterator {
NativeArray arr;
int index;
public NativeArrayIterator(NativeArray arr) {
this.arr = arr;
this.index = 0;
}
public boolean hasNext() {
return index < (int)arr.jsGet_length();
}
public Object next() {
Context.enter();
try {
Object result = arr.get(index++, arr);
if (result == Undefined.instance ||
result == Scriptable.NOT_FOUND) {
result = null;
} else {
if (!(result instanceof NativeJavaClass)) {
while (result instanceof Wrapper) {
result = ((Wrapper)result).unwrap();
}
}
}
return result;
} finally {
Context.exit();
}
}
public void remove() {
arr.delete(index);
}
}
static class ScriptableIterator implements Iterator {
Scriptable scope;
Object[] ids;
int index;
public ScriptableIterator(Scriptable scope) {
this.scope = scope;
this.ids = scope.getIds();
this.index = 0;
}
public boolean hasNext() {
return index < ids.length;
}
public Object next() {
Context.enter();
try {
Object result = ScriptableObject.getProperty(scope, ids[index++].toString());
if (result == Undefined.instance || result == Scriptable.NOT_FOUND) {
result = null;
} else if (!(result instanceof NativeJavaClass)) {
while (result instanceof Wrapper) {
result = ((Wrapper)result).unwrap();
}
}
return result;
} finally {
Context.exit();
}
}
public void remove() {
Context.enter();
try {
scope.delete(ids[index].toString());
} finally {
Context.exit();
}
}
}
public Iterator getIterator(Object obj, Info i) throws Exception {
if (!(obj instanceof Scriptable)) {
// support Enumeration
/*
Booth Enumeration and Iterator are supported in
Uberspect. The only difference is that they emit a
rather long warning message to commons logging, telling
that Enumerations and Iterator not are resettable and
cannot be reused.
*/
if (obj instanceof Enumeration) {
final Enumeration e = (Enumeration)obj;
return new Iterator() {
public boolean hasNext() {
return e.hasMoreElements();
}
public Object next() {
return e.nextElement();
}
public void remove() {
// no action
}
};
}
if (obj instanceof Iterator) {
// support Iterator
return (Iterator)obj;
}
return super.getIterator(obj, i);
}
if (obj instanceof NativeArray) {
return new NativeArrayIterator((NativeArray)obj);
}
return new ScriptableIterator((Scriptable)obj);
}
public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception {
return !(obj instanceof Scriptable) ?
super.getMethod(obj, methodName, args, i) : new JSMethod((Scriptable)obj, methodName);
}
public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i) throws Exception {
return !(obj instanceof Scriptable) ?
super.getPropertyGet(obj, identifier, i) : new JSPropertyGet((Scriptable)obj, identifier);
}
public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i) throws Exception {
return !(obj instanceof Scriptable) ?
super.getPropertySet(obj, identifier, arg, i) : new JSPropertySet((Scriptable)obj, identifier);
}
}