/*
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.Iterator;
import java.util.List;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeJavaArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;
import com.servoy.j2db.FlattenedSolution;
import com.servoy.j2db.Messages;
import com.servoy.j2db.dataprocessing.Record;
import com.servoy.j2db.persistence.ISupportScriptProviders;
import com.servoy.j2db.persistence.ITable;
import com.servoy.j2db.persistence.Relation;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.persistence.Table;
import com.servoy.j2db.util.Debug;
/**
* @author jcompagner
*/
public class TableScope extends LazyCompilationScope
{
private final static ThreadLocal<Object[]> values = new ThreadLocal<Object[]>();
private final static ThreadLocal<UsedDataProviderTracker> usedDataProviderTracker = new ThreadLocal<UsedDataProviderTracker>();
private final Table table;
private final FlattenedSolution solution;
public TableScope(Scriptable parent, IExecutingEnviroment engine, ITable table, FlattenedSolution solution, ISupportScriptProviders scriptLookup)
{
super(parent, engine, scriptLookup);
this.table = (Table)table;
this.solution = solution;
setFunctionParentScriptable(new RecordingScriptable(null, this));
}
public void setArguments(Object[] vargs)
{
Object[] array = getThreadLocalArray();
array[1] = vargs;
}
public UsedDataProviderTracker setUsedDataProviderTracker(UsedDataProviderTracker usedDataProviderTracker)
{
UsedDataProviderTracker current = TableScope.usedDataProviderTracker.get();
if (usedDataProviderTracker == null)
{
TableScope.usedDataProviderTracker.remove();
}
else
{
TableScope.usedDataProviderTracker.set(usedDataProviderTracker);
}
return current;
}
private Object[] getThreadLocalArray()
{
Object[] array = values.get();
if (array == null)
{
array = new Object[3];
values.set(array);
}
return array;
}
@Override
public Scriptable getPrototype()
{
Object[] array = getThreadLocalArray();
return (Scriptable)array[0];
}
@Override
public void setPrototype(Scriptable prototype)
{
getThreadLocalArray()[0] = prototype;
}
/**
* @see com.servoy.j2db.scripting.DefaultScope#get(java.lang.String, org.mozilla.javascript.Scriptable)
*/
@Override
public Object get(String name, Scriptable start)
{
if ("allrelations".equals(name)) //$NON-NLS-1$
{
List<String> al = new ArrayList<String>();
try
{
Iterator<Relation> it = solution.getRelations(table, true, true);
while (it.hasNext())
{
Relation r = it.next();
if (!r.isGlobal())
{
al.add(r.getName());
}
}
}
catch (RepositoryException ex)
{
Debug.error(ex);
}
return new NativeJavaArray(this, al.toArray(new String[al.size()]));
}
Object o = super.get(name, start);
if (o instanceof Function)
{
// get all calcs via the Record (parent scope), if the value changes it will be saved/cached in the record
return Scriptable.NOT_FOUND;
}
return o;
}
private Object getCalculationValue(Function calculation, String name)
{
Scriptable proto = getPrototype();
// if this happens record is not set as prototype.. (for example debugger)
// we can't return then anything.
if (proto == null)
{
return null;
}
Object[] array = getThreadLocalArray();
List<String> callStack = (List<String>)array[2];
if (callStack == null)
{
callStack = new ArrayList<String>();
array[2] = callStack;
}
Record record = null;
String callStackName = name;
if (proto instanceof Record)
{
record = (Record)proto;
callStackName = callStackName + '_' + record.getRawData().getPKHashKey();
}
UsedDataProviderTracker tracker = null;
try
{
boolean contains = callStack.contains(callStackName);
callStack.add(callStackName);//first add before doing anything else!
//now we decide if we try to return from row cache or calculate again
if (contains)
{
if (record != null)
{
return record.getRawData().getValue(name);
}
throw new RuntimeException(Messages.getString("servoy.error.cycleDetected", new Object[] { name, table.getName(), callStack })); //$NON-NLS-1$
}
tracker = usedDataProviderTracker.get();
if (tracker != null)
{
((RecordingScriptable)getFunctionParentScriptable()).pushRecordingTracker(tracker);
setUsedDataProviderTracker(null);
}
Object o = scriptEngine.executeFunction(calculation, this, calculation, (Object[])array[1], false, false);
if (o instanceof Undefined && record != null) o = record.getRawData().getValue(name);//record value trick
return o;
}
catch (Exception e)
{
throw new RuntimeException(Messages.getString("servoy.error.executingCalculation", new Object[] { name, table.getName(), e.toString() }), e); //$NON-NLS-1$
}
finally
{
if (tracker != null)
{
((RecordingScriptable)getFunctionParentScriptable()).popRecordingTracker();
}
callStack.remove(callStackName);
if (callStack.size() == 0)
{
// clear the thread locals.
values.remove();
}
}
}
public Object getCalculationValue(String calcName, Scriptable start)
{
Object o = super.get(calcName, start);
if (o instanceof Function)
{
return getCalculationValue((Function)o, calcName);
}
return o;
}
/**
* @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
@Override
public void put(String name, Scriptable start, Object value)
{
if (value instanceof Wrapper)
{
value = ((Wrapper)value).unwrap();
}
// Only functions can be added to a TableScope
if (value instanceof Function)
{
super.put(name, start, value);
}
else
{
Scriptable prototype = getPrototype();
if (prototype != null)
{
if (prototype.has(name, start))
{
prototype.put(name, start, value);
}
}
}
}
//used by scripting
public void putPrintVar(String name, Scriptable start, Object value)
{
if (value instanceof Wrapper)
{
value = ((Wrapper)value).unwrap();
}
super.put(name, start, value);
}
@Override
public String getScopeName()
{
return table.getServerName() + "." + table.getName();
}
}