blob: 548a1a097e26516da78d69d7ef37aebf738a7d0b [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 android.util.Log;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.taobao.weex.WXEnvironment;
import com.taobao.weex.annotation.Component;
import com.taobao.weex.common.Constants;
import com.taobao.weex.dom.WXAttr;
import com.taobao.weex.dom.WXDomObject;
import com.taobao.weex.dom.WXEvent;
import com.taobao.weex.dom.binding.ELUtils;
import com.taobao.weex.dom.binding.WXStatement;
import com.taobao.weex.dom.flex.CSSLayoutContext;
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.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.WXRecyclerTemplateList;
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 {
/**
* recursive copy component, none parent connect
* */
public static WXComponent copyComponentTree(WXComponent component){
long start = System.currentTimeMillis();
WXComponent copy = copyComponentTree(component, component.getParent());
if(WXEnvironment.isApkDebugable()){
WXLogUtils.d(WXRecyclerTemplateList.TAG, Thread.currentThread() + component.getRef() + "copyComponentTree " + "used " + (System.currentTimeMillis() - start));
}
return copy;
}
/**
* recursive copy component,
* */
private static final WXComponent copyComponentTree(WXComponent source, WXVContainer parent){
WXDomObject node = (WXDomObject) source.getDomObject();
WXComponent component = WXComponentFactory.newInstance(source.getInstance(), node, parent);
if(source instanceof WXVContainer){
WXVContainer container = (WXVContainer) source;
WXVContainer childParent = (WXVContainer) component;
WXDomObject childParentNode = (WXDomObject) childParent.getDomObject();
int count = container.getChildCount();
for (int i = 0; i < count; ++i) {
WXComponent child = container.getChild(i);
if (child != null) {
WXComponent targetChild = copyComponentTree(child, childParent);
childParent.addChild(targetChild);
childParentNode.add((WXDomObject) targetChild.getDomObject(), -1);
}
}
}
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, ArrayStack 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, ArrayStack context,
List<WXComponent> updates){
WXVContainer parent = component.getParent();
WXDomObject domObject = (WXDomObject) component.getDomObject();
WXAttr attrs = domObject.getAttrs();
WXStatement statement = attrs.getStatement();
if(statement != null){
WXDomObject parentDomObject = (WXDomObject) parent.getDomObject();
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);
}
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;
}else{
key = item;
value = map.get(item);
}
if(indexKey != null){
loop.put(indexKey, key);
}
if(itemKey != null){
loop.put(itemKey, value);
}else{
context.push(value);
}
if(loop.size() > 0){
context.push(loop);
}
if(vif != null){
if(!Operators.isTrue(vif.execute(context))){
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);
WXDomObject renderNodeDomObject = (WXDomObject) renderNode.getDomObject();
renderNodeDomObject.getAttrs().setStatement(null); // clear node's statement
parentDomObject.add(renderNodeDomObject, renderIndex);
parent.addChild(renderNode, renderIndex);
updates.add(renderNode);
if(WXEnvironment.isApkDebugable()){
WXLogUtils.d(WXRecyclerTemplateList.TAG, Thread.currentThread().getName() + renderNode.getRef() + renderNode.getDomObject().getType() + "statements copy component tree used " + (System.currentTimeMillis() - start));
}
}
doBindingAttrsEventAndRenderChildNode(renderNode, domObject, context, updates);
renderIndex++;
if(loop.size() > 0){
context.push(loop);
}
if(itemKey == null) {
context.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))){
component.setWaste(true);
if(Thread.currentThread() == Looper.getMainLooper().getThread()) {
return 1;
}
}else{
component.setWaste(false);
}
}
}
doBindingAttrsEventAndRenderChildNode(component, domObject, context, updates);
return 1;
}
/**
* bind attrs and doRender component child
* */
private static void doBindingAttrsEventAndRenderChildNode(WXComponent component, WXDomObject domObject, ArrayStack context,
List<WXComponent> updates){
WXAttr attr = component.getDomObject().getAttrs();
/**
* sub component supported, sub component new 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
&& attr.get(ELUtils.COMPONENT_PROPS) instanceof JSONObject){
Map<String, Object> props = renderProps((JSONObject) attr.get(ELUtils.COMPONENT_PROPS), context);
context = new ArrayStack();
context.push(props);
}
}
doRenderBindingAttrsAndEvent(component, domObject, context);
if(component instanceof WXVContainer){
if(!domObject.isShow()){
if(!(component instanceof WXCell)){
if(Thread.currentThread() == Looper.getMainLooper().getThread()){
return;
}
}
}
WXVContainer container = (WXVContainer) component;
for(int k=0; k<container.getChildCount();){
WXComponent next = container.getChild(k);
k += doRenderComponent(next, context, updates);
}
}
}
/**
* 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, WXDomObject domObject, ArrayStack context){
component.setWaste(false);
WXAttr attr = domObject.getAttrs();
if(attr != null
&& attr.getBindingAttrs() != null
&& attr.getBindingAttrs().size() > 0){
ArrayMap<String, Object> bindAttrs = domObject.getAttrs().getBindingAttrs();
Map<String, Object> dynamic = renderBindingAttrs(bindAttrs, context);
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();
if(entry.getValue() == null){
if(attr.get(key) == null){
iterator.remove();
continue;
}
}
if(entry.getValue().equals(attr.get(key))){
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
domObject.getAttrs().put(Constants.Name.SRC, dynamic.get(Constants.Name.SRC));
}else {
domObject.updateAttr(dynamic); //dirty layout
}
if(Thread.currentThread() == Looper.getMainLooper().getThread()) {
component.updateProperties(dynamic);
}
dynamic.clear();
}
}
WXEvent event = domObject.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(context, 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 context){
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(context);
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(context);
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 context){
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(context);
renderProps.put(key, blockValue);
}else{
renderProps.put(key, value);
}
}
return renderProps;
}
public static List<Object> getBindingEventArgs(ArrayStack context, 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(context);
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(context);
params.add(blockValue);
}else{
params.add(bindings.toString());
}
}else{
params.add(bindings.toString());
}
return params;
}
}