/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2014 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.server.ngclient.component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.sablo.BaseWebObject;
import org.sablo.specification.PropertyDescription;
import org.sablo.specification.property.ChangeAwareList;
import org.sablo.specification.property.ChangeAwareMap;
import org.sablo.specification.property.CustomJSONArrayType;
import org.sablo.specification.property.IPropertyType;
import com.servoy.j2db.server.ngclient.property.ComponentTypeSabloValue;
import com.servoy.j2db.server.ngclient.property.types.IRhinoNativeProxy;
import com.servoy.j2db.server.ngclient.property.types.NGConversions;
import com.servoy.j2db.server.ngclient.property.types.NGConversions.ISabloComponentToRhino;
import com.servoy.j2db.util.Utils;
/**
* @author jcompagner
*/
public final class RhinoMapOrArrayWrapper implements Scriptable
{
private final Object wrappedValue;
private final PropertyDescription propertyDescription;
private Scriptable prototype;
private Scriptable parent;
private final BaseWebObject baseWebObject;
public RhinoMapOrArrayWrapper(Object wrappedValue, BaseWebObject baseWebObject, PropertyDescription propertyDescription, Scriptable startScriptable)
{
this.baseWebObject = baseWebObject;
this.wrappedValue = wrappedValue;
this.propertyDescription = propertyDescription;
Object baseObject = null;
if (wrappedValue instanceof ChangeAwareList< ? , ? >) baseObject = ((ChangeAwareList)wrappedValue).getBaseList();
if (wrappedValue instanceof ChangeAwareMap< ? , ? >) baseObject = ((ChangeAwareMap)wrappedValue).getBaseMap();
if (baseObject instanceof IRhinoNativeProxy)
{
// allow it to use for example methods defined in Rhino although it's properties are kept in a Java map or array
setPrototype(((IRhinoNativeProxy)baseObject).getBaseRhinoScriptable());
}
else if (wrappedValue instanceof List)
{
// allow it to use native JS array methods
NativeArray proto = new NativeArray(0);
if (startScriptable != null) proto.setPrototype(ScriptableObject.getArrayPrototype(startScriptable));
setPrototype(proto); // new instance so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
}
else if (wrappedValue instanceof Map)
{
// allow it to use native JS array methods
NativeObject proto = new NativeObject();
if (startScriptable != null) proto.setPrototype(ScriptableObject.getObjectPrototype(startScriptable));
setPrototype(proto); // new instance so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
}
if (startScriptable != null) parent = ScriptableObject.getTopLevelScope(startScriptable);
}
public Object getWrappedValue()
{
return wrappedValue;
}
@Override
public String getClassName()
{
return wrappedValue instanceof Map ? "Object" : "Array";
}
protected Object getAsSabloValue(String name)
{
if (wrappedValue instanceof Map)
{
return ((Map<String, Object>)wrappedValue).get(name);
}
else if (wrappedValue instanceof List && name.equals("length"))
{
return Integer.valueOf(((List< ? >)wrappedValue).size());
}
return null;
}
@Override
public Object get(String name, Scriptable start)
{
Object value = getAsSabloValue(name);
if (wrappedValue instanceof List)
{
if (name.equals("length")) return value;
List list = (List)wrappedValue;
if (list.size() > 0)
{
for (Object element : list)
{
if (element instanceof ComponentTypeSabloValue && Utils.equalObjects(name, ((ComponentTypeSabloValue)element).getName()))
{
return NGConversions.INSTANCE.convertSabloComponentToRhinoValue(element,
((ComponentTypeSabloValue)element).getComponentPropertyDescription(), baseWebObject, start);
}
}
}
return Scriptable.NOT_FOUND; // then it will be searched for in prototype that is a native array prototype
}
PropertyDescription propDesc = propertyDescription.getProperty(name);
return propDesc != null ? NGConversions.INSTANCE.convertSabloComponentToRhinoValue(value, propDesc, baseWebObject, start) : Scriptable.NOT_FOUND;
}
protected Object getSabloValueForIndex(int index)
{
if (wrappedValue instanceof List< ? >)
{
if (((List< ? >)wrappedValue).size() > index)
{
return ((List< ? >)wrappedValue).get(index);
}
}
return Scriptable.NOT_FOUND;
}
@Override
public Object get(int index, Scriptable start)
{
Object value = getSabloValueForIndex(index);
return value == Scriptable.NOT_FOUND ? Scriptable.NOT_FOUND
: NGConversions.INSTANCE.convertSabloComponentToRhinoValue(value, getArrayElementDescription(), baseWebObject, start);
}
protected PropertyDescription getArrayElementDescription()
{
PropertyDescription elementType = propertyDescription;
if (propertyDescription.getType() instanceof CustomJSONArrayType< ? , ? >)
elementType = ((CustomJSONArrayType)propertyDescription.getType()).getCustomJSONTypeDefinition();
return elementType;
}
@Override
public boolean has(String name, Scriptable start)
{
if (wrappedValue instanceof Map)
{
if (((Map)wrappedValue).containsKey(name))
{
PropertyDescription pd = propertyDescription.getProperty(name);
IPropertyType< ? > type = pd.getType();
// it is available by default, so if it doesn't have conversion, or if it has conversion and is explicitly available
return !(type instanceof ISabloComponentToRhino< ? >) ||
((ISabloComponentToRhino)type).isValueAvailableInRhino(getAsSabloValue(name), pd, baseWebObject);
}
}
return false;
}
@Override
public boolean has(int index, Scriptable start)
{
if (wrappedValue instanceof List< ? >)
{
return ((List< ? >)wrappedValue).size() > index;
}
else if (wrappedValue instanceof RhinoMapOrArrayWrapper)
{
return ((RhinoMapOrArrayWrapper)wrappedValue).has(index, start);
}
return false;
}
@Override
public void put(String name, Scriptable start, Object value)
{
if (wrappedValue instanceof Map)
{
PropertyDescription pd = propertyDescription.getProperty(name);
if (pd != null)
{
Object convertedValue = NGConversions.INSTANCE.convertRhinoToSabloComponentValue(value, getAsSabloValue(name), pd, baseWebObject);
((Map)wrappedValue).put(name, convertedValue);
}
else
{
// so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
getPrototype().put(name, getPrototype(), value);
}
}
else if ("length".equals(name))
{
int length = ((Number)value).intValue();
if (wrappedValue instanceof List)
{
List lst = (List)wrappedValue;
if (length == 0) lst.clear();
else
{
while (lst.size() != length)
{
lst.remove(lst.size() - 1);
}
}
}
}
else
{
// so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
getPrototype().put(name, getPrototype(), value);
}
}
@Override
public void put(int index, Scriptable start, Object value)
{
if (wrappedValue instanceof List< ? >)
{
List<Object> lst = (List<Object>)wrappedValue;
Object prev = getSabloValueForIndex(index);
Object val = NGConversions.INSTANCE.convertRhinoToSabloComponentValue(value, prev == Scriptable.NOT_FOUND ? null : prev,
getArrayElementDescription(), baseWebObject);
while (lst.size() <= index)
{
lst.add(null);
}
lst.set(index, val);
}
else
{
// so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
getPrototype().put(index, getPrototype(), value);
}
}
@Override
public void delete(String name)
{
if (wrappedValue instanceof Map)
{
if (((Map)wrappedValue).containsKey(name))
{
((Map)wrappedValue).remove(name);
}
else
{
// so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
getPrototype().delete(name);
}
}
else
{
// so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
getPrototype().delete(name);
}
}
@Override
public void delete(int index)
{
if (wrappedValue instanceof List< ? >)
{
((List< ? >)wrappedValue).set(index, null);
}
else
{
// so that JS can use usual put/set even for non-defined things in PropertyDescription by forwarding to prototype
getPrototype().delete(index);
}
}
@Override
public Scriptable getPrototype()
{
return prototype;
}
@Override
public void setPrototype(Scriptable prototype)
{
this.prototype = prototype;
}
@Override
public Scriptable getParentScope()
{
return parent;
}
@Override
public void setParentScope(Scriptable parent)
{
this.parent = parent;
}
@Override
public Object[] getIds()
{
if (wrappedValue instanceof Map)
{
Set<Entry<String, Object>> tmp = ((Map<String, Object>)wrappedValue).entrySet();
List<Object> result = new ArrayList<Object>();
for (Entry<String, Object> entry : tmp)
{
PropertyDescription pd = propertyDescription.getProperty(entry.getKey());
IPropertyType< ? > type = pd.getType();
// it is available by default, so if it doesn't have conversion, or if it has conversion and is explicitly available
if (!(type instanceof ISabloComponentToRhino< ? >) ||
((ISabloComponentToRhino)type).isValueAvailableInRhino(entry.getValue(), pd, baseWebObject))
{
result.add(entry.getKey());
}
}
return result.toArray();
}
else if (wrappedValue instanceof List< ? >)
{
int length = ((List< ? >)wrappedValue).size();
Object[] result = new Object[length];
int i = result.length;
while (--i >= 0)
result[i] = Integer.valueOf(i);
return result;
}
return new Object[0];
}
@Override
public Object getDefaultValue(Class< ? > hint)
{
if (wrappedValue != null) return wrappedValue.toString();
return null;
}
@Override
public boolean hasInstance(Scriptable instance)
{
return false;
}
}