text: package com.taobao.weex.analyzer.core.lint;
  
  import android.content.Context;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.text.TextUtils;
  import android.view.View;
  import android.view.ViewGroup;
  
  import com.taobao.weex.WXSDKInstance;
  import com.taobao.weex.analyzer.pojo.HealthReport;
  import com.taobao.weex.analyzer.utils.SDKUtils;
  import com.taobao.weex.analyzer.utils.ViewUtils;
  import com.taobao.weex.ui.component.WXComponent;
  import com.taobao.weex.ui.component.WXEmbed;
  import com.taobao.weex.ui.component.WXScroller;
  import com.taobao.weex.ui.component.WXVContainer;
  import com.taobao.weex.ui.component.list.WXCell;
  import com.taobao.weex.ui.component.list.WXListComponent;
  import com.taobao.weex.utils.WXLogUtils;
  import com.taobao.weex.utils.WXViewUtils;
  
  import java.util.ArrayDeque;
  import java.util.ArrayList;
  import java.util.Deque;
  import java.util.LinkedHashMap;
  import java.util.Locale;
  
  /**
   * Description:
   *
   * Created by rowandjj(chuyi)<br/>
   */
  
  class DomTracker {
      private WXSDKInstance mWxInstance;
      private Deque<LayeredNode<WXComponent>> mLayeredQueue;
      private ObjectPool<LayeredNode<WXComponent>> mVDomObjectPool;
      private ObjectPool<LayeredNode<View>> mRealDomObjectPool;
  
      private static final String TAG = "VDomTracker";
  
      private OnTrackNodeListener mOnTrackNodeListener;
  
      private static final int START_LAYER_OF_VDOM = 2;//instance为第一层，rootComponent为第二层
      private static final int START_LAYER_OF_REAL_DOM = 2;//renderContainer为第一层
  
  
  
      interface OnTrackNodeListener {
          void onTrackNode(@NonNull WXComponent component, int layer);
      }
  
      DomTracker(@NonNull WXSDKInstance instance) {
          this.mWxInstance = instance;
          mLayeredQueue = new ArrayDeque<>();
          mVDomObjectPool = new ObjectPool<LayeredNode<WXComponent>>(10) {
              @Override
              LayeredNode<WXComponent> newObject() {
                  return new LayeredNode<>();
              }
          };
          mRealDomObjectPool = new ObjectPool<LayeredNode<View>>(15) {
              @Override
              LayeredNode<View> newObject() {
                  return new LayeredNode<>();
              }
          };
      }
  
      void setOnTrackNodeListener(OnTrackNodeListener listener) {
          this.mOnTrackNodeListener = listener;
      }
  
      @Nullable
      HealthReport traverse() {
          long start = System.currentTimeMillis();
          if (SDKUtils.isInUiThread()) {
              WXLogUtils.e(TAG, "illegal thread...");
              return null;
          }
          int pageHeight = 0;
          WXComponent godComponent = mWxInstance.getRootComponent();
          if (godComponent == null) {
              WXLogUtils.e(TAG, "god component not found");
              return null;
          }
          HealthReport report = new HealthReport(mWxInstance.getBundleUrl());
  
          View hostView = godComponent.getHostView();
          if(hostView != null) {
              report.maxLayerOfRealDom = getRealDomMaxLayer(hostView);
              pageHeight = hostView.getMeasuredHeight();
          }
  
          LayeredNode<WXComponent> layeredNode = mVDomObjectPool.obtain();
          layeredNode.set(godComponent, ViewUtils.getComponentName(godComponent), START_LAYER_OF_VDOM);
  
          mLayeredQueue.add(layeredNode);
  
          while (!mLayeredQueue.isEmpty()) {
              LayeredNode<WXComponent> domNode = mLayeredQueue.removeFirst();
              WXComponent component = domNode.component;
              int layer = domNode.layer;
  
              report.maxLayer = Math.max(report.maxLayer,layer);
              report.estimateContentHeight = Math.max(report.estimateContentHeight,
                      ComponentHeightComputer.computeComponentContentHeight(component));
  
              //如果节点被染色，需要计算其该染色子树的最大深度
              if(!TextUtils.isEmpty(domNode.tint)) {
                  for(HealthReport.EmbedDesc desc : report.embedDescList) {
                      if(desc.src != null && desc.src.equals(domNode.tint)) {
                          desc.actualMaxLayer = Math.max(desc.actualMaxLayer,(layer-desc.beginLayer));
                      }
                  }
              }
  
              if (mOnTrackNodeListener != null) {
                  mOnTrackNodeListener.onTrackNode(component, layer);
              }
  
              if (component instanceof WXListComponent) {
                  report.hasList = true;
  
                  if(report.listDescMap == null) {
                      report.listDescMap = new LinkedHashMap<>();
                  }
  
                  HealthReport.ListDesc listDesc = report.listDescMap.get(component.getRef());
                  if(listDesc == null) {
                      listDesc = new HealthReport.ListDesc();
                  }
                  listDesc.ref = component.getRef();
                  listDesc.totalHeight = ComponentHeightComputer.computeComponentContentHeight(component);
                  report.listDescMap.put(listDesc.ref,listDesc);
  
              } else if (component instanceof WXScroller) {
                  if(ViewUtils.isVerticalScroller((WXScroller) component)) {
                      report.hasScroller = true;
                  }
  
              } else if (component instanceof WXCell) {
  
                  WXVContainer parentContainer = component.getParent();
                  if(parentContainer != null && parentContainer instanceof WXListComponent && report.listDescMap != null) {
                      HealthReport.ListDesc listDesc = report.listDescMap.get(parentContainer.getRef());
                      if(listDesc != null) {
                          listDesc.cellNum++;
                      }
                  }
  
                  int num = getComponentNumOfNode(component);
                  report.maxCellViewNum = Math.max(report.maxCellViewNum, num);
  
                  if(((WXCell) component).getHostView() != null) {
                      int height = ((WXCell) component).getHostView().getMeasuredHeight();
                      report.hasBigCell |= isBigCell(height);
                      report.componentNumOfBigCell = num;
                  }
  
              } else if(component instanceof WXEmbed) {
                  report.hasEmbed = true;
              }
  
              //restore to pool for later use
              domNode.clear();
              mVDomObjectPool.recycle(domNode);
  
              if(component instanceof WXEmbed) {
  
                  if(report.embedDescList == null) {
                      report.embedDescList = new ArrayList<>();
                  }
                  HealthReport.EmbedDesc desc = new HealthReport.EmbedDesc();
                  desc.src = ((WXEmbed)component).getSrc();
                  desc.beginLayer = layer;
  
                  report.embedDescList.add(desc);
  
                  //add nested component to layeredQueue
                  WXComponent nestedRootComponent = ViewUtils.getNestedRootComponent((WXEmbed) component);
                  if(nestedRootComponent != null) {
                      LayeredNode<WXComponent> childNode = mVDomObjectPool.obtain();
                      childNode.set(nestedRootComponent, ViewUtils.getComponentName(nestedRootComponent), layer+1);
                      mLayeredQueue.add(childNode);
  
                      //tint
                      childNode.tint = desc.src;
                  }
              } else if (component instanceof WXVContainer) {
                  WXVContainer container = (WXVContainer) component;
                  for (int i = 0, count = container.childCount(); i < count; i++) {
                      WXComponent child = container.getChild(i);
                      LayeredNode<WXComponent> childNode = mVDomObjectPool.obtain();
                      childNode.set(child, ViewUtils.getComponentName(child), layer + 1);
  
                      //if parent is tinted,then tint it's children
                      if(!TextUtils.isEmpty(domNode.tint)) {
                          childNode.tint = domNode.tint;
                      }
  
                      mLayeredQueue.add(childNode);
  
                  }
              }
          }
          Context context = mWxInstance.getContext();
          if(context != null) {
              pageHeight = (pageHeight == 0) ? ViewUtils.getScreenHeight(context) : pageHeight;
          }
  
          if(pageHeight != 0) {
              report.estimatePages = String.format(Locale.CHINA,"%.2f",report.estimateContentHeight/(double) pageHeight);
          } else {
              report.estimatePages = "0";
          }
  
          long end = System.currentTimeMillis();
          WXLogUtils.d(TAG,"[traverse] elapse time :"+(end-start)+"ms");
          return report;
  
      }
  
  
      private int getComponentNumOfNode(@NonNull WXComponent rootNode) {
          Deque<WXComponent> deque = new ArrayDeque<>();
          deque.add(rootNode);
          int viewNum = 0;
          while (!deque.isEmpty()) {
              WXComponent node = deque.removeFirst();
              viewNum++;
              if (node instanceof WXVContainer) {
                  WXVContainer container = (WXVContainer) node;
                  for (int i = 0, count = container.childCount(); i < count; i++) {
                      deque.add(container.getChild(i));
                  }
              }
          }
          return viewNum;
      }
  
      private int getRealDomMaxLayer(@NonNull View rootView) {
          int maxLayer = 0;
          Deque<LayeredNode<View>> deque = new ArrayDeque<>();
          LayeredNode<View> rootNode = mRealDomObjectPool.obtain();
          rootNode.set(rootView,null,START_LAYER_OF_REAL_DOM);
          deque.add(rootNode);
  
          while (!deque.isEmpty()) {
              LayeredNode<View> currentNode = deque.removeFirst();
              maxLayer = Math.max(maxLayer,currentNode.layer);
  
              View component = currentNode.component;
              int layer = currentNode.layer;
  
              currentNode.clear();
              mRealDomObjectPool.recycle(currentNode);
  
              if(component instanceof ViewGroup && ((ViewGroup) component).getChildCount() > 0) {
                  ViewGroup viewGroup = (ViewGroup) component;
                  for (int i = 0,count = viewGroup.getChildCount(); i < count; i++) {
                      View child = viewGroup.getChildAt(i);
                      LayeredNode<View> childNode = mRealDomObjectPool.obtain();
                      childNode.set(child,null,layer+1);
                      deque.add(childNode);
                  }
              }
          }
  
          return maxLayer;
      }
  
      private boolean isBigCell(float maxHeight) {
          if (maxHeight <= 0) {
              return false;
          }
          return maxHeight > WXViewUtils.getScreenHeight() * 2 / 3.0;
      }
  
  
      private static class LayeredNode<T> {
          T component;
          String simpleName;
          int layer;
  
          String tint;
  
          void set(T component, String simpleName, int layer) {
              this.component = component;
              this.layer = layer;
              this.simpleName = simpleName;
          }
  
          void clear() {
              component = null;
              layer = -1;
              simpleName = null;
          }
      }
  
      private static abstract class ObjectPool<T> {
          private final Deque<T> mPool;
  
          ObjectPool(int capacity) {
              capacity = Math.max(0, capacity);
              this.mPool = new ArrayDeque<>(capacity);
              for (int i = 0; i < capacity; i++) {
                  this.mPool.add(newObject());
              }
          }
  
          abstract T newObject();
  
          T obtain() {
              return mPool.isEmpty() ? newObject() : mPool.removeLast();
          }
  
          void recycle(@NonNull T obj) {
              mPool.addLast(obj);
          }
  
      }
  
  }
  