/*
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.util.ArrayList;
import java.util.List;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.IdFunctionObject;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Wrapper;
import com.servoy.j2db.dataprocessing.IRecordInternal;
import com.servoy.j2db.util.IDelegate;
import com.servoy.j2db.util.ScopesUtils;
/**
* Scriptable for recording access to delegate scriptable. Used to determine dependencies for calculations.
*
* @author rgansevles
*
*/
public class RecordingScriptable implements Scriptable, IDelegate<Scriptable>, Wrapper
{
private final static ThreadLocal<List<UsedDataProviderTracker>> recordedThreadLocal = new ThreadLocal<List<UsedDataProviderTracker>>();
protected final Scriptable scriptable;
protected final String scriptableName;
public RecordingScriptable(String scriptableName, Scriptable scriptable)
{
this.scriptableName = scriptableName;
this.scriptable = scriptable;
}
public void pushRecordingTracker(UsedDataProviderTracker usedDataProviderTracker)
{
List<UsedDataProviderTracker> stack = recordedThreadLocal.get();
if (stack == null)
{
stack = new ArrayList<UsedDataProviderTracker>();
recordedThreadLocal.set(stack);
}
stack.add(usedDataProviderTracker);
}
public UsedDataProviderTracker popRecordingTracker()
{
List<UsedDataProviderTracker> stack = recordedThreadLocal.get();
if (stack == null || stack.size() <= 0)
{
// should never happen
throw new IllegalStateException("Cannot pop calculation recording tracker"); //$NON-NLS-1$
}
UsedDataProviderTracker obj = stack.remove(stack.size() - 1);
if (stack.size() == 0)
{
recordedThreadLocal.remove();
}
return obj;
}
public UsedDataProviderTracker peekRecordingTracker()
{
List<UsedDataProviderTracker> stack = recordedThreadLocal.get();
if (stack != null && stack.size() > 0)
{
return stack.get(stack.size() - 1);
}
return null;
}
public Scriptable getDelegate()
{
return scriptable;
}
public Object unwrap()
{
return scriptable instanceof Wrapper ? ((Wrapper)scriptable).unwrap() : scriptable;
}
public void delete(int index)
{
scriptable.delete(index);
}
public void delete(String name)
{
scriptable.delete(name);
}
public Object get(int index, Scriptable start)
{
return scriptable.get(index, getStart(start));
}
public Object get(String name, Scriptable start)
{
Object o = scriptable.get(name, getStart(start));
if (o != Scriptable.NOT_FOUND)
{
UsedDataProviderTracker tracker = peekRecordingTracker();
if (tracker != null)
{
if (scriptable instanceof GlobalScope)
{
tracker.usedGlobal(ScopesUtils.getScopeString(((GlobalScope)scriptable).getScopeName(), name));
}
else
{
tracker.usedName(scriptable, name);
}
return wrapIfNeeded(scriptableName, name, o);
}
}
return o;
}
static Object wrapIfNeeded(String scriptableName, String name, Object o)
{
if (o instanceof Scriptable && o != Scriptable.NOT_FOUND
// eval is a special case, cannot be called directly
&& !(o instanceof IdFunctionObject && "eval".equals(((IdFunctionObject)o).getFunctionName())) && !"Object".equals(name)) //$NON-NLS-1$ //$NON-NLS-2$
{
if (o instanceof Function)
{
if ((IExecutingEnviroment.TOPLEVEL_DATABASE_MANAGER.equals(scriptableName) || IExecutingEnviroment.TOPLEVEL_UTILS.equals(scriptableName)) &&
"hasRecords".equals(name)) //$NON-NLS-1$
{
// special case, databaseManager.hasRecords(record, relationName) checks existence of related foundsets
return new RecordingFunction(name, (Function)o)
{
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args)
{
if (args != null && args.length > 1 && args[1] instanceof String)
{
Object arg0 = args[0];
if (arg0 instanceof Wrapper)
{
arg0 = ((Wrapper)arg0).unwrap();
}
if (arg0 instanceof IRecordInternal)
{
UsedDataProviderTracker tracker = peekRecordingTracker();
if (tracker != null)
{
tracker.usedFromRecord((IRecordInternal)arg0, (String)args[1]);
}
}
}
return super.call(cx, scope, thisObj, args);
}
};
}
return new RecordingFunction(name, (Function)o);
}
return new RecordingScriptable(name, (Scriptable)o);
}
return o;
}
public static Object unwrapScriptable(Object obj)
{
if (obj instanceof IDelegate< ? >)
{
Object delegate = ((IDelegate< ? >)obj).getDelegate();
if (delegate instanceof Scriptable)
{
return delegate;
}
}
return obj;
}
public static Object[] unwrapScriptable(Object[] array)
{
if (array == null)
{
return null;
}
Object[] retval = new Object[array.length];
for (int i = 0; i < array.length; i++)
{
retval[i] = unwrapScriptable(array[i]);
}
return retval;
}
public String getClassName()
{
return scriptable.getClassName();
}
public Object getDefaultValue(Class< ? > hint)
{
return scriptable.getDefaultValue(hint);
}
public Object[] getIds()
{
return scriptable.getIds();
}
public Scriptable getParentScope()
{
Scriptable parentScope = scriptable.getParentScope();
// do not wrap toplevel scope. Note that scriptengine puts anything that needs to be tracked in solution scope.
return parentScope == null ? null : parentScope.getParentScope() == null ? parentScope : new RecordingScriptable(null, parentScope);
}
public Scriptable getPrototype()
{
Scriptable prototype = scriptable.getPrototype();
return prototype == null ? scriptable instanceof ImporterTopLevel ? scriptable : null : new RecordingScriptable(null, prototype);
}
/**
* When start is yourself use scriptable as start for put/get
*/
protected Scriptable getStart(Scriptable start)
{
if (start == this)
{
return scriptable;
}
return start;
}
public boolean has(int index, Scriptable start)
{
return scriptable.has(index, getStart(start));
}
public boolean has(String name, Scriptable start)
{
return scriptable.has(name, getStart(start));
}
public boolean hasInstance(Scriptable instance)
{
return scriptable.hasInstance(instance);
}
public void put(int index, Scriptable start, Object value)
{
scriptable.put(index, getStart(start), value);
}
public void put(String name, Scriptable start, Object value)
{
scriptable.put(name, getStart(start), value);
}
public void setParentScope(Scriptable parent)
{
scriptable.setParentScope(parent);
}
public void setPrototype(Scriptable prototype)
{
scriptable.setPrototype(prototype);
}
}