/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.scripting;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.mozilla.javascript.ClassCache;
import org.mozilla.javascript.JavaMembers;
import org.mozilla.javascript.MemberBox;
import org.mozilla.javascript.NativeJavaMethod;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSSetter;
import com.servoy.j2db.scripting.annotations.AnnotationManagerReflection;
import com.servoy.j2db.scripting.annotations.JSReadonlyProperty;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.Utils;
import com.servoy.j2db.util.keyword.Ident;
/**
* @author jcompagner
*/
public class InstanceJavaMembers extends JavaMembers
{
private List<String> gettersAndSettersToHide;
/**
* Constructor for InstanceJavaMembers.
*
* @param scope
* @param cl
*/
public InstanceJavaMembers(Scriptable scope, Class< ? > cl)
{
super(scope, cl);
}
/**
* @see JavaMembers#reflectField(Scriptable, Field)
*/
@Override
protected void reflectField(Scriptable scope, Field field)
{
if (IConstantsObject.class.isAssignableFrom(cl) && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) &&
Modifier.isPublic(field.getModifiers()))
{
super.reflectField(scope, field);
}
}
/*
* (non-Javadoc)
*
* @see org.mozilla.javascript.JavaMembers#discoverAccessibleMethods(java.lang.Class, boolean, boolean)
*/
@Override
protected final Method[] discoverAccessibleMethods(Class< ? > clazz, boolean includeProtected, boolean includePrivate)
{
Method[] discoverAccessibleMethods = super.discoverAccessibleMethods(clazz, includeProtected, includePrivate);
List<Method> lst = new ArrayList<Method>(discoverAccessibleMethods.length);
for (Method discoverAccessibleMethod : discoverAccessibleMethods)
{
if (isJsMethod(discoverAccessibleMethod, clazz))
{
lst.add(discoverAccessibleMethod);
}
}
return lst.toArray(new Method[lst.size()]);
}
protected boolean isJsMethod(Method method, Class< ? > originalClass)
{
return method.getName().startsWith("js_") || method.getName().startsWith("jsFunction_") || //$NON-NLS-1$ //$NON-NLS-2$
AnnotationManagerReflection.getInstance().isAnnotationPresent(method, originalClass, JSReadonlyProperty.class) ||
AnnotationManagerReflection.getInstance().isAnnotationPresent(method, originalClass, JSFunction.class) ||
AnnotationManagerReflection.getInstance().isAnnotationPresent(method, originalClass, JSGetter.class) ||
AnnotationManagerReflection.getInstance().isAnnotationPresent(method, originalClass, JSSetter.class);
}
/**
* @see org.mozilla.javascript.JavaMembers#makeBeanProperties(Scriptable, boolean)
*/
@SuppressWarnings("nls")
@Override
protected void makeBeanProperties(boolean isStatic)
{
Map<String, Object> ht = isStatic ? staticMembers : members;
Map<String, Object> copy = new HashMap<String, Object>(ht);
for (Entry<String, Object> entry : ht.entrySet())
{
String name = entry.getKey();
String newName = null;
if (name.startsWith("js_")) //$NON-NLS-1$
{
newName = name.substring(3);
}
else
{
Object member = entry.getValue();
if (member instanceof NativeJavaMethod)
{
if (((NativeJavaMethod)member).getMethods().length == 1)
{
MemberBox mb = ((NativeJavaMethod)member).getMethods()[0];
if (mb.isMethod())
{
if (AnnotationManagerReflection.getInstance().isAnnotationPresent(mb.method(), cl, JSReadonlyProperty.class))
{
newName = AnnotationManagerReflection.getInstance().getAnnotation(mb.method(), cl, JSReadonlyProperty.class).property();
if (newName == null || newName.length() == 0 && (entry.getKey().startsWith("get") || entry.getKey().startsWith("is")))
{
newName = entry.getKey().substring(entry.getKey().startsWith("get") ? 3 : 2);
// Make the bean property name.
char ch0 = newName.charAt(0);
if (Character.isUpperCase(ch0))
{
if (newName.length() == 1)
{
newName = newName.toLowerCase();
}
else
{
char ch1 = newName.charAt(1);
if (!Character.isUpperCase(ch1))
{
newName = Character.toLowerCase(ch0) + newName.substring(1);
}
}
}
}
}
else if (AnnotationManagerReflection.getInstance().isAnnotationPresent(mb.method(), cl, JSGetter.class))
{
newName = AnnotationManagerReflection.getInstance().getAnnotation(mb.method(), cl, JSGetter.class).value();
}
else if (AnnotationManagerReflection.getInstance().isAnnotationPresent(mb.method(), cl, JSSetter.class))
{
newName = AnnotationManagerReflection.getInstance().getAnnotation(mb.method(), cl, JSSetter.class).value();
}
}
}
for (MemberBox mb : ((NativeJavaMethod)member).getMethods())
{
if (mb.isMethod() && AnnotationManagerReflection.getInstance().isAnnotationPresent(mb.method(), cl, JSFunction.class))
{
String funcName = AnnotationManagerReflection.getInstance().getAnnotation(mb.method(), cl, JSFunction.class).value();
if (funcName == null || funcName.length() == 0)
{
funcName = entry.getKey();
}
newName = "jsFunction_".concat(funcName); //$NON-NLS-1$
break;
}
}
}
}
if (newName != null && newName.length() > 0 && !newName.equals(name) && !Ident.checkIfJavascriptKeyword(newName))
{
putNewValueMergeForDuplicates(copy, name, newName);
}
}
ht = copy;
if (isStatic)
{
staticMembers = ht;
}
else
{
members = ht;
}
super.makeBeanProperties(isStatic);
copy = new HashMap<String, Object>(ht);
for (Entry<String, Object> entry : ht.entrySet())
{
String name = entry.getKey();
if (name.startsWith("jsFunction_")) //$NON-NLS-1$
{
String newName = name.substring(11);
if (!Ident.checkIfKeyword(newName))
{
putNewValueMergeForDuplicates(copy, name, newName);
}
}
else
{
Object member = entry.getValue();
if (member instanceof NativeJavaMethod && ((NativeJavaMethod)member).getMethods().length == 1)
{
MemberBox mb = ((NativeJavaMethod)member).getMethods()[0];
if (mb.isMethod() &&
AnnotationManagerReflection.getInstance().isAnnotationPresent(mb.method(), mb.getDeclaringClass(), JSReadonlyProperty.class))
{
// make bean property
Object oldValue = copy.put(name, new BeanProperty(mb, null, null));
if (oldValue instanceof NativeJavaMethod)
{
// allow the method to be called directly as well
String functionName = ((NativeJavaMethod)oldValue).getFunctionName();
if (!functionName.equals(name))
{
copy.put(functionName, oldValue);
// but do not show it
addMethodToDelete(functionName);
}
}
}
}
}
}
if (isStatic)
{
staticMembers = copy;
}
else
{
members = copy;
}
}
/**
* @param copy
* @param name
* @param newName
*/
@SuppressWarnings("nls")
private void putNewValueMergeForDuplicates(Map<String, Object> copy, String name, String newName)
{
Object oldValue = copy.put(newName, copy.remove(name));
if (oldValue != null)
{
Object newValue = copy.get(newName);
if (oldValue instanceof NativeJavaMethod && newValue instanceof NativeJavaMethod)
{
MemberBox[] oldMethods = ((NativeJavaMethod)oldValue).getMethods();
MemberBox[] newMethods = ((NativeJavaMethod)newValue).getMethods();
copy.put(newName, new NativeJavaMethod(Utils.arrayJoin(oldMethods, newMethods), newName));
}
else
{
Debug.error("illegal state new_name '" + newName + "' from old_name '" + name + "' can't be merged: " + oldValue + ", " + newValue,
new RuntimeException());
}
}
}
/**
* @see JavaMembers#shouldDeleteGetAndSetMethods()
*/
@Override
protected boolean shouldDeleteGetAndSetMethods()
{
return true;
}
@SuppressWarnings("unchecked")
@Override
protected void deleteGetAndSetMethods(boolean isStatic, List toRemove)
{
if (gettersAndSettersToHide == null)
{
gettersAndSettersToHide = new ArrayList<String>(toRemove);
}
else
{
gettersAndSettersToHide.addAll(toRemove);
}
}
protected void addMethodToDelete(String name)
{
if (gettersAndSettersToHide == null)
{
gettersAndSettersToHide = new ArrayList<String>();
}
gettersAndSettersToHide.add(name);
}
public List<String> getGettersAndSettersToHide()
{
return Collections.<String> unmodifiableList(gettersAndSettersToHide);
}
static void registerClass(Scriptable scope, Class< ? > cls, InstanceJavaMembers ijm)
{
ClassCache cache = ClassCache.get(scope);
Map<Class< ? >, JavaMembers> ct = cache.getClassCacheMap();
ct.put(cls, ijm);
}
public static void deRegisterClass(Scriptable scope)
{
ClassCache cache = ClassCache.get(scope);
cache.getClassCacheMap().clear();
cache = null;
}
}