blob: 2e1cb92d47e208a7d1d52437240f9f553a3a1898 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.taobao.weex.ui.component.binding;
import android.os.Looper;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.taobao.weex.WXEnvironment;
import com.taobao.weex.bridge.EventResult;
import com.taobao.weex.bridge.WXBridgeManager;
import com.taobao.weex.common.Constants;
import com.taobao.weex.dom.WXAttr;
import com.taobao.weex.dom.WXEvent;
import com.taobao.weex.dom.WXStyle;
import com.taobao.weex.dom.binding.ELUtils;
import com.taobao.weex.dom.binding.JSONUtils;
import com.taobao.weex.dom.binding.WXStatement;
import com.taobao.weex.el.parse.ArrayStack;
import com.taobao.weex.el.parse.Operators;
import com.taobao.weex.el.parse.Token;
import com.taobao.weex.ui.action.BasicComponentData;
import com.taobao.weex.ui.action.GraphicPosition;
import com.taobao.weex.ui.action.GraphicSize;
import com.taobao.weex.ui.component.WXComponent;
import com.taobao.weex.ui.component.WXComponentFactory;
import com.taobao.weex.ui.component.WXImage;
import com.taobao.weex.ui.component.WXVContainer;
import com.taobao.weex.ui.component.list.WXCell;
import com.taobao.weex.ui.component.list.template.CellDataManager;
import com.taobao.weex.ui.component.list.template.CellRenderContext;
import com.taobao.weex.ui.component.list.template.VirtualComponentLifecycle;
import com.taobao.weex.ui.component.list.template.WXRecyclerTemplateList;
import com.taobao.weex.ui.component.list.template.jni.NativeRenderObjectUtils;
import com.taobao.weex.utils.WXLogUtils;
import com.taobao.weex.utils.WXUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Created by jianbai.gbj on 2017/8/17.
* simple statement execute, render component for template list
*/
public class Statements {
/**
* parse statements token from attrs and style and event
* */
public static void parseStatementsToken(WXComponent component){
if(component.getBasicComponentData().isRenderPtrEmpty()){
component.getBasicComponentData().setRenderObjectPr(component.getRenderObjectPtr());
}
if(component.getBasicComponentData() != null){
BasicComponentData basicComponentData = component.getBasicComponentData();
basicComponentData.getAttrs().parseStatements();
basicComponentData.getStyles().parseStatements();
basicComponentData.getEvents().parseStatements();
}
if(component instanceof WXVContainer){
WXVContainer container = (WXVContainer) component;
int count = container.getChildCount();
for (int i = 0; i < count; ++i) {
parseStatementsToken(container.getChild(i));
}
}
}
/**
* init component if component is lazy,
* if component is not lazy, do nothing
* */
public static void initLazyComponent(WXComponent component, WXVContainer mParent){
if(component.isLazy() || component.getHostView() == null){
component.lazy(false);
if(mParent != null){
int index = mParent.indexOf(component);
mParent.createChildViewAt(index);
}else {
component.createView();
}
component.applyLayoutAndEvent(component);
component.bindData(component);
}
}
/**
* recursive copy component, none parent connect
* */
public static WXComponent copyComponentTree(WXComponent component){
WXComponent copy = copyComponentTree(component, component.getParent());
return copy;
}
/**
* recursive copy component,
* */
private static final WXComponent copyComponentTree(WXComponent source, WXVContainer parent){
BasicComponentData basicComponentData = null;
try {
basicComponentData = source.getBasicComponentData().clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
WXComponent component = WXComponentFactory.newInstance(source.getInstance(), parent, basicComponentData);
GraphicPosition graphicPosition = source.getLayoutPosition();
GraphicSize graphicSize = source.getLayoutSize();
component.updateDemission(graphicPosition.getTop(), graphicPosition.getBottom(), graphicPosition.getLeft(), graphicPosition.getRight(),
graphicSize.getHeight(), graphicSize.getWidth());
component.updateExtra(source.getExtra());
if(source instanceof WXVContainer){
WXVContainer container = (WXVContainer) source;
WXVContainer childParent = (WXVContainer) component;
int count = container.getChildCount();
for (int i = 0; i < count; ++i) {
WXComponent sourceChild = container.getChild(i);
if (sourceChild != null) {
WXComponent targetChild = copyComponentTree(sourceChild, childParent);childParent.addChild(targetChild);
NativeRenderObjectUtils.nativeAddChildRenderObject(childParent.getRenderObjectPtr(),
targetChild.getRenderObjectPtr());
}
}
}
//copy info need be sync
if(source.isWaste()){
component.setWaste(true);
}
return component;
}
/**
* @param component component with v-for statement, v-if statement and bind attrs
* @param stack execute context
* render component in context, the function do the following work.
* execute component's v-for statement, v-if statement in context,
* and rebuild component's tree with the statement, v-for reuse component execute by pre render.
* if executed, component will be removed, don't remove, just mark it waste;
* may be next render it can be used.
* after statement has executed, render component's binding attrs in context and bind it to component.
* */
public static final List<WXComponent> doRender(WXComponent component, CellRenderContext stack){
List<WXComponent> updates = new ArrayList<>(4);
try{
doRenderComponent(component, stack, updates);
}catch (Exception e){
WXLogUtils.e("WeexStatementRender", e);
}
return updates;
}
public static final void doInitCompontent(List<WXComponent> updates) {
if(updates == null || updates.size() == 0){
return;
}
for(WXComponent renderNode : updates){
if(renderNode.getParent() == null){
throw new IllegalArgumentException("render node parent cann't find");
}
WXVContainer parent = renderNode.getParent();
int renderIndex = parent.indexOf(renderNode);
if(renderIndex < 0){
throw new IllegalArgumentException("render node cann't find");
}
parent.createChildViewAt(renderIndex);
renderNode.applyLayoutAndEvent(renderNode);
renderNode.bindData(renderNode);
}
}
/**
* @param component component with v-for statement, v-if statement and bind attrs
* @param context execute context
* render component in context, the function do the following work.
* execute component's v-for statement, v-if statement in context,
* and rebuild component's tree with the statement, v-for reuse component execute by pre render.
* if executed, component will be removed, don't remove, just mark it waste;
* may be next render it can be used.
* after statement has executed, render component's binding attrs in context and bind it to component.
* */
private static final int doRenderComponent(WXComponent component, CellRenderContext context,
List<WXComponent> updates){
WXVContainer parent = component.getParent();
WXAttr attrs = component.getAttrs();
WXStatement statement = attrs.getStatement();
if(statement != null){
Token vif = null;
JSONObject vfor = null;
if(statement.get(WXStatement.WX_IF) instanceof Token){
vif = (Token) statement.get(WXStatement.WX_IF);
}
if(statement.get(WXStatement.WX_FOR) instanceof JSONObject){
vfor = (JSONObject) statement.get(WXStatement.WX_FOR);
}
// execute v-for content
if(vfor != null){
int renderIndex = parent.indexOf(component);
if(vfor.get(WXStatement.WX_FOR_LIST) instanceof Token){
Token listBlock = (Token) vfor.get(WXStatement.WX_FOR_LIST);
String indexKey = vfor.getString(WXStatement.WX_FOR_INDEX);
String itemKey = vfor.getString(WXStatement.WX_FOR_ITEM);
Object data = null;
if(listBlock != null) {
data = listBlock.execute(context.stack);
}
if((data instanceof List || data instanceof Map)){
Collection collection = null;
Map map = null;
if(data instanceof List){
collection = (List)data;
}else{
map = (Map)data;
collection = map.keySet();
}
Map<String, Object> loop = new HashMap<>();
int index = 0;
for(Object item : collection){
Object key = null;
Object value = item;
if(map == null){
key = index;
value = item;
index ++;
}else{
key = item;
value = map.get(item);
}
if(indexKey != null){
loop.put(indexKey, key);
}
if(itemKey != null){
loop.put(itemKey, value);
}else{
context.stack.push(value);
}
if(loop.size() > 0){
context.stack.push(loop);
}
if(vif != null){
if(!Operators.isTrue(vif.execute(context.stack))){
continue;
}
}
//find resuable renderNode
WXComponent renderNode = null;
if(renderIndex < parent.getChildCount()){
renderNode = parent.getChild(renderIndex);
//check is same statment, if true, it is usabled.
if(!isCreateFromNodeStatement(renderNode, component)){
renderNode = null;
}
if(renderNode != null){
if(renderNode.isWaste()){
renderNode.setWaste(false);
}
}
}
//none resuable render node, create node, add to parent, but clear node's statement
if(renderNode == null){
long start = System.currentTimeMillis();
renderNode = copyComponentTree(component, parent);
renderNode.setWaste(false);
if(renderNode.getAttrs().getStatement() != null) {
renderNode.getAttrs().getStatement().remove(WXStatement.WX_FOR);
renderNode.getAttrs().getStatement().remove(WXStatement.WX_IF); //clear node's statement
}
parent.addChild(renderNode, renderIndex);
NativeRenderObjectUtils.nativeAddChildRenderObject(parent.getRenderObjectPtr(),
renderNode.getRenderObjectPtr());
updates.add(renderNode);
if(WXEnvironment.isApkDebugable()){
WXLogUtils.d(WXRecyclerTemplateList.TAG, Thread.currentThread().getName() + renderNode.getRef() + renderNode.getComponentType() + "statements copy component tree used " + (System.currentTimeMillis() - start));
}
}
doBindingAttrsEventAndRenderChildNode(renderNode, context, updates);
renderIndex++;
if(loop.size() > 0){
context.stack.push(loop);
}
if(itemKey == null) {
context.stack.pop();
}
}
}
}else{
WXLogUtils.e(WXRecyclerTemplateList.TAG, vfor.toJSONString() + " not call vfor block, for pre compile");
}
//after v-for execute, remove component created pre v-for.
for(;renderIndex<parent.getChildCount(); renderIndex++){
WXComponent wasteNode = parent.getChild(renderIndex);
if(!isCreateFromNodeStatement(wasteNode, component)){
break;
}
wasteNode.setWaste(true);
}
return renderIndex - parent.indexOf(component);
}
//execute v-if context
if(vif != null){
if(!Operators.isTrue(vif.execute(context.stack))){
component.setWaste(true);
return 1;
}else{
component.setWaste(false);
}
}
}
doBindingAttrsEventAndRenderChildNode(component, context, updates);
return 1;
}
/**
* bind attrs and doRender component child
* */
private static void doBindingAttrsEventAndRenderChildNode(WXComponent component, CellRenderContext context,
List<WXComponent> updates){
WXAttr attr = component.getAttrs();
/**
* sub component supported, sub component new stack
* */
ArrayStack stack = context.stack;
if(attr.get(ELUtils.IS_COMPONENT_ROOT) != null
&& WXUtils.getBoolean(attr.get(ELUtils.IS_COMPONENT_ROOT), false)){
if(attr.get(ELUtils.COMPONENT_PROPS) != null
&& JSONUtils.isJSON(attr.get(ELUtils.COMPONENT_PROPS))){
String compoentId = (String) attr.get(CellDataManager.SUB_COMPONENT_TEMPLATE_ID);
Object compoentData = null;
if(!TextUtils.isEmpty(compoentId)){
String virtualComponentId = context.getRenderState().getVirtualComponentIds().get(component.getViewTreeKey());
if(virtualComponentId == null){ //none virtualComponentId, create and do attach
virtualComponentId = CellDataManager.createVirtualComponentId(context.templateList.getRef(),
component.getViewTreeKey(), context.templateList.getItemId(context.position));
Map<String, Object> props = renderProps(JSONUtils.toJSON(attr.get(ELUtils.COMPONENT_PROPS)), context.stack);
EventResult result = WXBridgeManager.getInstance().syncCallJSEventWithResult(WXBridgeManager.METHD_COMPONENT_HOOK_SYNC, component.getInstanceId(), null, compoentId, VirtualComponentLifecycle.LIFECYCLE, VirtualComponentLifecycle.CREATE, new Object[]{
virtualComponentId,
props
}, null);
if(result != null
&& result.getResult() != null
&& result.getResult() instanceof Map){
props.putAll((Map<? extends String, ?>) result.getResult());
}
compoentData = props;
context.getRenderState().getVirtualComponentIds().put(component.getViewTreeKey(), virtualComponentId);
context.templateList.getCellDataManager().createVirtualComponentData(context.position, virtualComponentId, compoentData);
//create virtual componentId
WXBridgeManager.getInstance().asyncCallJSEventVoidResult(WXBridgeManager.METHD_COMPONENT_HOOK_SYNC, component.getInstanceId(), null, virtualComponentId, VirtualComponentLifecycle.LIFECYCLE, VirtualComponentLifecycle.ATTACH, null);
}else{ // get virtual component data check has dirty's update
compoentData = context.getRenderState().getVirtualComponentDatas().get(virtualComponentId);
if(context.getRenderState().isHasDataUpdate()){
Map<String, Object> props = renderProps((JSONObject) attr.get(ELUtils.COMPONENT_PROPS), context.stack);
EventResult result = WXBridgeManager.getInstance().syncCallJSEventWithResult(WXBridgeManager.METHD_COMPONENT_HOOK_SYNC, component.getInstanceId(), null, virtualComponentId, VirtualComponentLifecycle.LIFECYCLE, VirtualComponentLifecycle.SYNSTATE, new Object[]{
virtualComponentId,
props
}, null);
if(result != null
&& result.getResult() != null
&& result.getResult() instanceof Map){
props.putAll((Map<? extends String, ?>) result.getResult());
context.templateList.getCellDataManager().updateVirtualComponentData(virtualComponentId, props);
compoentData = props;
}
}
}
component.getAttrs().put(CellDataManager.VIRTUAL_COMPONENT_ID, virtualComponentId);
}else{ //stateless component
Map<String, Object> props = renderProps((JSONObject) attr.get(ELUtils.COMPONENT_PROPS), context.stack);
compoentData = props;
}
//virtual component is new context
context.stack = new ArrayStack();
if(compoentData != null){
context.stack.push(compoentData);
}
}
}
/**
* check node is render only once, if render once, and has rendered, just return
* */
Object vonce = null;
if(attr.getStatement() != null){
vonce = attr.getStatement().get(WXStatement.WX_ONCE);
}
if(vonce != null){
ArrayStack onceStack = context.getRenderState().getOnceComponentStates().get(component.getViewTreeKey());
if(onceStack == null){
onceStack = context.templateList.copyStack(context, stack);
context.getRenderState().getOnceComponentStates().put(component.getViewTreeKey(), onceStack);
}
context.stack = onceStack;
}
doRenderBindingAttrsAndEvent(component, context);
if(component instanceof WXVContainer){
if(component.isWaste()){
if(!(component instanceof WXCell)){
return;
}
}
WXVContainer container = (WXVContainer) component;
for(int k=0; k<container.getChildCount();){
WXComponent next = container.getChild(k);
k += doRenderComponent(next, context, updates);
}
}
if(stack != context.stack){
context.stack = stack;
}
}
/**
* check whether render node is create from component node statement.
* */
private static boolean isCreateFromNodeStatement(WXComponent renderNode, WXComponent component){
return (renderNode.getRef() != null && renderNode.getRef().equals(component.getRef()));
}
/**
* render dynamic binding attrs and bind them to component node.
* */
private static void doRenderBindingAttrsAndEvent(WXComponent component, CellRenderContext context){
ArrayStack stack = context.stack;
component.setWaste(false);
WXAttr attr = component.getAttrs();
if(attr != null
&& attr.getBindingAttrs() != null
&& attr.getBindingAttrs().size() > 0){
ArrayMap<String, Object> bindAttrs = component.getAttrs().getBindingAttrs();
Map<String, Object> dynamic = renderBindingAttrs(bindAttrs, stack);
Set<Map.Entry<String, Object>> entries = dynamic.entrySet();
/**
* diff attrs, see attrs has update, remove none update attrs
* */
Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<String, Object> entry = iterator.next();
String key = entry.getKey();
Object value = entry.getValue();
Object oldValue = attr.get(key);
if(value == null){
if(oldValue == null){
iterator.remove();
continue;
}
}else{
if(value.equals(oldValue)){
iterator.remove();
}
}
}
if(dynamic.size() > 0) {
if(dynamic.size() == 1
&& dynamic.get(Constants.Name.SRC) != null
&& component instanceof WXImage){
//for image avoid dirty layout, only update src attrs
component.getAttrs().put(Constants.Name.SRC, dynamic.get(Constants.Name.SRC));
}else {
component.nativeUpdateAttrs(dynamic);
}
if(isMainThread()) {
component.updateProperties(dynamic);
}
dynamic.clear();
}
}
WXStyle style = component.getStyles();
if(style != null && style.getBindingStyle() != null){
ArrayMap<String, Object> bindStyle = style.getBindingStyle();
Map<String, Object> dynamic = renderBindingAttrs(bindStyle, stack);
Set<Map.Entry<String, Object>> entries = dynamic.entrySet();
/**
* diff attrs, see attrs has update, remove none update attrs
* */
Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<String, Object> entry = iterator.next();
String key = entry.getKey();
Object value = entry.getValue();
Object oldValue = style.get(key);
if(value == null){
if(oldValue == null){
iterator.remove();
continue;
}
}else{
if(value.equals(oldValue)){
iterator.remove();
}
}
}
if(dynamic.size() > 0) {
component.updateNativeStyles(dynamic);
if(isMainThread()) {
component.updateProperties(dynamic);
}
}
}
WXEvent event = component.getEvents();
if(event == null || event.getEventBindingArgs() == null){
return;
}
Set<Map.Entry<String, Object>> eventBindArgsEntrySet = event.getEventBindingArgs().entrySet();
for(Map.Entry<String, Object> eventBindArgsEntry : eventBindArgsEntrySet){
List<Object> values = getBindingEventArgs(stack, eventBindArgsEntry.getValue());
if(values != null){
event.putEventBindingArgsValue(eventBindArgsEntry.getKey(), values);
}
}
}
/**
* @param bindAttrs none null,
* @param context context
* return binding attrs rended value in context
* */
private static final ThreadLocal<Map<String, Object>> dynamicLocal = new ThreadLocal<>();
public static Map<String, Object> renderBindingAttrs(ArrayMap bindAttrs, ArrayStack stack){
Set<Map.Entry<String, Object>> entrySet = bindAttrs.entrySet();
Map<String, Object> dynamic = dynamicLocal.get();
if(dynamic == null) {
dynamic = new HashMap<>();
dynamicLocal.set(dynamic);
}
if(dynamic.size() > 0){
dynamic.clear();
}
for(Map.Entry<String, Object> entry : entrySet){
Object value = entry.getValue();
String key = entry.getKey();
if(value instanceof JSONObject
&& (((JSONObject) value).get(ELUtils.BINDING) instanceof Token)){
JSONObject binding = (JSONObject) value;
Token block = (Token) (binding.get(ELUtils.BINDING));
Object blockValue = block.execute(stack);
dynamic.put(key, blockValue);
}else if(value instanceof JSONArray){
JSONArray array = (JSONArray) value;
StringBuilder builder = new StringBuilder();
for(int i=0; i<array.size(); i++){
Object element = array.get(i);
if(element instanceof CharSequence){
builder.append(element);
continue;
}
if(element instanceof JSONObject
&& (((JSONObject) element).get(ELUtils.BINDING) instanceof Token)){
JSONObject binding = (JSONObject) element;
Token block = (Token) (binding.get(ELUtils.BINDING));
Object blockValue = block.execute(stack);
if(blockValue == null){
blockValue = "";
}
builder.append(blockValue);
}
}
String builderString = builder.toString();
if(builderString.length() > 256){
if(WXEnvironment.isApkDebugable()){
WXLogUtils.w(WXRecyclerTemplateList.TAG, " warn too big string " + builderString);
}
}
dynamic.put(key, builderString);
}
}
return dynamic;
}
public static Map<String, Object> renderProps(JSONObject props, ArrayStack stack){
Set<Map.Entry<String, Object>> entrySet = props.entrySet();
Map<String, Object> renderProps = new ArrayMap<>(4);
for(Map.Entry<String, Object> entry : entrySet){
Object value = entry.getValue();
String key = entry.getKey();
if(value instanceof JSONObject
&& (((JSONObject) value).get(ELUtils.BINDING) instanceof Token)){
JSONObject binding = (JSONObject) value;
Token block = (Token) (binding.get(ELUtils.BINDING));
Object blockValue = block.execute(stack);
renderProps.put(key, blockValue);
}else{
renderProps.put(key, value);
}
}
return renderProps;
}
public static List<Object> getBindingEventArgs(ArrayStack stack, Object bindings){
List<Object> params = new ArrayList<>(4);
if(bindings instanceof JSONArray){
JSONArray array = (JSONArray) bindings;
for(int i=0; i<array.size(); i++){
Object value = array.get(i);
if(value instanceof JSONObject
&& (((JSONObject) value).get(ELUtils.BINDING) instanceof Token)){
Token block = (Token)(((JSONObject) value).get(ELUtils.BINDING));
Object blockValue = block.execute(stack);
params.add(blockValue);
}else{
params.add(value);
}
}
}else if(bindings instanceof JSONObject){
JSONObject binding = (JSONObject) bindings;
if(binding.get(ELUtils.BINDING) instanceof Token){
Token block = (Token) binding.get(ELUtils.BINDING);
Object blockValue = block.execute(stack);
params.add(blockValue);
}else{
params.add(bindings.toString());
}
}else{
params.add(bindings.toString());
}
return params;
}
private static boolean isMainThread(){
return Thread.currentThread() == Looper.getMainLooper().getThread();
}
public static String getComponentId(WXComponent component){
if(component instanceof WXCell || component == null){
return null;
}
WXAttr attr = component.getAttrs();
if(attr.get(ELUtils.IS_COMPONENT_ROOT) != null
&& WXUtils.getBoolean(attr.get(ELUtils.IS_COMPONENT_ROOT), false)){
if(attr.get(ELUtils.COMPONENT_PROPS) != null
&& attr.get(ELUtils.COMPONENT_PROPS) instanceof JSONObject){
Object componentId = attr.get(CellDataManager.VIRTUAL_COMPONENT_ID);
if(componentId == null){
return null;
}
return componentId.toString();
}
}
return getComponentId(component.getParent());
}
}