/* 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.Date; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.mozilla.javascript.Context; import org.mozilla.javascript.JavaMembers; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import com.servoy.j2db.dataprocessing.DataException; import com.servoy.j2db.documentation.XMLScriptObjectAdapter; import com.servoy.j2db.util.Debug; /** * @author jcompagner * */ public class ScriptObjectRegistry { private static final Object NULL_SCOPE = new Object(); private static final ConcurrentHashMap<Object, Map<Class< ? >, JavaMembers>> javaMembersCache = new ConcurrentHashMap<Object, Map<Class< ? >, JavaMembers>>(); private static Map<Class< ? >, IScriptable> scriptObjectRegistry = new ConcurrentHashMap<Class< ? >, IScriptable>(); public static void registerScriptObjectForClass(Class< ? > clz, IScriptable scriptobject) { // If there is already an XMLScriptObjectAdapter registered for this class, then // don't allow it to be overwritten, but copy over the return types if needed if (scriptObjectRegistry.containsKey(clz)) { IScriptable existing = scriptObjectRegistry.get(clz); if (existing instanceof XMLScriptObjectAdapter) { if (scriptobject instanceof IReturnedTypesProvider) { mergeReturnTypes(existing, (IReturnedTypesProvider)scriptobject); } return; } } scriptObjectRegistry.put(clz, scriptobject); } public static void registerReturnedTypesProviderForClass(Class< ? > clz, IReturnedTypesProvider rtProvider) { // If there is already an XMLScriptObjectAdapter registered for this class, then // don't allow it to be overwritten, but copy over the return types if needed if (scriptObjectRegistry.containsKey(clz)) { IScriptable existing = scriptObjectRegistry.get(clz); if (existing instanceof XMLScriptObjectAdapter) { mergeReturnTypes(existing, rtProvider); } return; } scriptObjectRegistry.put(clz, new ReturnedTypesProviderToScriptObjectAdapter(rtProvider)); } /** * @param rtProvider * @param existing */ private static void mergeReturnTypes(IScriptable existing, IReturnedTypesProvider rtProvider) { Class< ? >[] allReturnedTypes = ((XMLScriptObjectAdapter)existing).getAllReturnedTypes(); if ((allReturnedTypes == null || allReturnedTypes.length == 0) && rtProvider.getAllReturnedTypes() != null) { ((XMLScriptObjectAdapter)existing).setReturnTypes(rtProvider.getAllReturnedTypes()); } } public static IScriptObject getScriptObjectForClass(Class< ? > clz) { IScriptable so = scriptObjectRegistry.get(clz); if (so == null) { try { Class.forName(clz.getName(), true, clz.getClassLoader()); } catch (ClassNotFoundException e) { // ignore } catch (Throwable t) { Debug.error("Error loading (plugin/bean) class: " + clz.getName() + " , please check your plugins/beans", t); //$NON-NLS-1$ //$NON-NLS-2$ return null; } so = scriptObjectRegistry.get(clz); if (so == null) { for (Class< ? > key : scriptObjectRegistry.keySet()) { if (key.isAssignableFrom(clz)) { so = scriptObjectRegistry.get(key); break; } } if (so == null) { if (IScriptable.class.isAssignableFrom(clz)) { try { // just try to make it. so = (IScriptable)clz.newInstance(); ScriptObjectRegistry.registerScriptObjectForClass(clz, so); } catch (Exception e) { // ignore } } } } } if (so instanceof IScriptObject) return (IScriptObject)so; return null; } /** * If there is an XMLScriptObject adapter in the registry, use that. * This means that the docs were loaded from an XML from the plugin jar. * The real scriptobject will be hidden behind the XMLScriptObject adapter. */ public static IScriptObject getAdapterIfAny(IScriptObject scriptObject) { if (scriptObject == null) return null; Class< ? > key = scriptObject.getClass(); if (scriptObjectRegistry.containsKey(key)) { IScriptable existing = scriptObjectRegistry.get(key); if (existing instanceof XMLScriptObjectAdapter) { return (XMLScriptObjectAdapter)existing; } } return scriptObject; } public static Set<Class< ? >> getRegisteredClasses() { return scriptObjectRegistry.keySet(); } public static void removeEntryFromCache(Scriptable scope) { Object key = ScriptableObject.getTopLevelScope(scope); javaMembersCache.remove(key); InstanceJavaMembers.deRegisterClass(scope); } public static JavaMembers getJavaMembers(final Class< ? > clss, Scriptable scope) { if (clss.isArray() || clss.isPrimitive() || clss == String.class || clss == Object.class || Number.class.isAssignableFrom(clss) || Date.class.isAssignableFrom(clss)) { return null; } Object key = scope; if (scope == null) { key = NULL_SCOPE; } else { key = ScriptableObject.getTopLevelScope(scope); } Map<Class< ? >, JavaMembers> map = javaMembersCache.get(key); if (map == null) { map = new ConcurrentHashMap<Class< ? >, JavaMembers>(); javaMembersCache.put(key, map); } JavaMembers jm = map.get(clss); if (jm == null) { try { Context.enter(); InstanceJavaMembers ijm; if (clss != DataException.class) { ijm = new InstanceJavaMembers(scope, clss); } else { // ugly workaround to hide the existence of an inherited deprecated method isServoyException (it was tested for existence in JS) and constants ijm = new InstanceJavaMembers(scope, clss) { @Override protected void reflectField(Scriptable scope, Field field) { if (field.getDeclaringClass() == cl && IConstantsObject.class.isAssignableFrom(cl) && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && Modifier.isPublic(field.getModifiers())) { super.reflectField(scope, field); } } /* * (non-Javadoc) * * @see com.servoy.j2db.scripting.InstanceJavaMembers#isJsMethod(java.lang.String) */ @Override protected boolean isJsMethod(Method method, Class< ? > originalClass) { return super.isJsMethod(method, originalClass) && !"js_isServoyException".equals(method.getName()); } }; } jm = ijm; if (ijm.getFieldIds(false).size() == 0 && ijm.getMethodIds(false).size() == 0 && ijm.getFieldIds(true).size() == 0 && !ReferenceOnlyInJS.class.isAssignableFrom(clss)) { jm = new JavaMembers(scope, clss); } else { if (scope != null) { InstanceJavaMembers.registerClass(scope, clss, ijm); } } map.put(clss, jm); } catch (Exception e) { Debug.error("Error creating java members returning null", e); //$NON-NLS-1$ } finally { Context.exit(); } } return jm; } private static class ReturnedTypesProviderToScriptObjectAdapter implements IScriptObject { private final IReturnedTypesProvider rtProvider; public ReturnedTypesProviderToScriptObjectAdapter(IReturnedTypesProvider rtProvider) { this.rtProvider = rtProvider; } public String[] getParameterNames(String methodName) { return null; } public String getSample(String methodName) { return null; } public String getToolTip(String methodName) { return null; } public boolean isDeprecated(String methodName) { return false; } public Class< ? >[] getAllReturnedTypes() { return rtProvider.getAllReturnedTypes(); } } }