/**
   * Replaces the View specified by oldTag with the View specified by newTag within oldTag's parent.
   */
  public void replaceExistingNonRootView(int oldTag, int newTag) {
    if (mShadowNodeRegistry.isRootNode(oldTag) || mShadowNodeRegistry.isRootNode(newTag)) {
      throw new IllegalViewOperationException("Trying to add or replace a root tag!");
    }

    ReactShadowNode oldNode = mShadowNodeRegistry.getNode(oldTag);
    if (oldNode == null) {
      throw new IllegalViewOperationException("Trying to replace unknown view tag: " + oldTag);
    }

    ReactShadowNode parent = oldNode.getParent();
    if (parent == null) {
      throw new IllegalViewOperationException("Node is not attached to a parent: " + oldTag);
    }

    int oldIndex = parent.indexOf(oldNode);
    if (oldIndex < 0) {
      throw new IllegalStateException("Didn't find child tag in parent");
    }

    WritableArray tagsToAdd = Arguments.createArray();
    tagsToAdd.pushInt(newTag);

    WritableArray addAtIndices = Arguments.createArray();
    addAtIndices.pushInt(oldIndex);

    WritableArray indicesToRemove = Arguments.createArray();
    indicesToRemove.pushInt(oldIndex);

    manageChildren(parent.getReactTag(), null, null, tagsToAdd, addAtIndices, indicesToRemove);
  }
  /**
   * Handles a manageChildren call. This may translate into multiple manageChildren calls for
   * multiple other views.
   *
   * <p>NB: the assumption for calling this method is that all corresponding ReactShadowNodes have
   * been updated **but tagsToDelete have NOT been deleted yet**. This is because we need to use the
   * metadata from those nodes to figure out the correct commands to dispatch. This is unlike all
   * other calls on this class where we assume all operations on the shadow hierarchy have already
   * completed by the time a corresponding method here is called.
   */
  public void handleManageChildren(
      ReactShadowNode nodeToManage,
      int[] indicesToRemove,
      int[] tagsToRemove,
      ViewAtIndex[] viewsToAdd,
      int[] tagsToDelete) {
    if (!ENABLED) {
      mUIViewOperationQueue.enqueueManageChildren(
          nodeToManage.getReactTag(), indicesToRemove, viewsToAdd, tagsToDelete);
      return;
    }

    // We operate on tagsToRemove instead of indicesToRemove because by the time this method is
    // called, these views have already been removed from the shadow hierarchy and the indices are
    // no longer useful to operate on
    for (int i = 0; i < tagsToRemove.length; i++) {
      int tagToRemove = tagsToRemove[i];
      boolean delete = false;
      for (int j = 0; j < tagsToDelete.length; j++) {
        if (tagsToDelete[j] == tagToRemove) {
          delete = true;
          break;
        }
      }
      ReactShadowNode nodeToRemove = mShadowNodeRegistry.getNode(tagToRemove);
      removeNodeFromParent(nodeToRemove, delete);
    }

    for (int i = 0; i < viewsToAdd.length; i++) {
      ViewAtIndex toAdd = viewsToAdd[i];
      ReactShadowNode nodeToAdd = mShadowNodeRegistry.getNode(toAdd.mTag);
      addNodeToNode(nodeToManage, nodeToAdd, toAdd.mIndex);
    }
  }
 private void removeShadowNode(ReactShadowNode nodeToRemove) {
   mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove);
   mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag());
   for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) {
     removeShadowNode(nodeToRemove.getChildAt(i));
   }
   nodeToRemove.removeAllChildren();
 }
 public void setJSResponder(int reactTag, boolean blockNativeResponder) {
   assertViewExists(reactTag, "setJSResponder");
   ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag);
   while (node.isVirtual() || node.isLayoutOnly()) {
     node = node.getParent();
   }
   mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder);
 }
 private void assertViewExists(int reactTag, String operationNameForExceptionMessage) {
   if (mShadowNodeRegistry.getNode(reactTag) == null) {
     throw new IllegalViewOperationException(
         "Unable to execute operation "
             + operationNameForExceptionMessage
             + " on view with "
             + "tag: "
             + reactTag
             + ", since the view does not exists");
   }
 }
  /** Invoked at the end of the transaction to commit any updates to the node hierarchy. */
  public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) {
    for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) {
      int tag = mShadowNodeRegistry.getRootTag(i);
      ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag);
      notifyOnBeforeLayoutRecursive(cssRoot);

      SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout")
          .arg("rootTag", tag)
          .flush();
      try {
        cssRoot.calculateLayout(mLayoutContext);
      } finally {
        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
      }
      applyUpdatesRecursive(cssRoot, 0f, 0f, eventDispatcher);
    }

    mNativeViewHierarchyOptimizer.onBatchComplete();
    mOperationsQueue.dispatchViewUpdates(batchId);
  }
  /** Invoked by React to create a new node with a given tag, class name and properties. */
  public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
    ViewManager viewManager = mViewManagers.get(className);
    ReactShadowNode cssNode = viewManager.createShadowNodeInstance();
    ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
    cssNode.setReactTag(tag);
    cssNode.setViewClassName(className);
    cssNode.setRootNode(rootNode);
    cssNode.setThemedContext(rootNode.getThemedContext());

    mShadowNodeRegistry.addNode(cssNode);

    CatalystStylesDiffMap styles = null;
    if (props != null) {
      styles = new CatalystStylesDiffMap(props);
      cssNode.updateProperties(styles);
    }

    if (!cssNode.isVirtual()) {
      mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootViewTag, styles);
    }
  }
  private void measureLayoutRelativeToParent(int tag, int[] outputBuffer) {
    ReactShadowNode node = mShadowNodeRegistry.getNode(tag);
    if (node == null) {
      throw new IllegalViewOperationException("No native view for tag " + tag + " exists!");
    }
    ReactShadowNode parent = node.getParent();
    if (parent == null) {
      throw new IllegalViewOperationException("View with tag " + tag + " doesn't have a parent!");
    }

    measureLayoutRelativeToVerifiedAncestor(node, parent, outputBuffer);
  }
  private void measureLayout(int tag, int ancestorTag, int[] outputBuffer) {
    ReactShadowNode node = mShadowNodeRegistry.getNode(tag);
    ReactShadowNode ancestor = mShadowNodeRegistry.getNode(ancestorTag);
    if (node == null || ancestor == null) {
      throw new IllegalViewOperationException(
          "Tag " + (node == null ? tag : ancestorTag) + " does not exist");
    }

    if (node != ancestor) {
      ReactShadowNode currentParent = node.getParent();
      while (currentParent != ancestor) {
        if (currentParent == null) {
          throw new IllegalViewOperationException(
              "Tag " + ancestorTag + " is not an ancestor of tag " + tag);
        }
        currentParent = currentParent.getParent();
      }
    }

    measureLayoutRelativeToVerifiedAncestor(node, ancestor, outputBuffer);
  }
  /** Invoked when native view that corresponds to a root node has its size changed. */
  public void updateRootNodeSize(
      int rootViewTag, int newWidth, int newHeight, EventDispatcher eventDispatcher) {
    ReactShadowNode rootCSSNode = mShadowNodeRegistry.getNode(rootViewTag);
    rootCSSNode.setStyleWidth(newWidth);
    rootCSSNode.setStyleHeight(newHeight);

    // If we're in the middle of a batch, the change will automatically be dispatched at the end of
    // the batch. As all batches are executed as a single runnable on the event queue this should
    // always be empty, but that calling architecture is an implementation detail.
    if (mOperationsQueue.isEmpty()) {
      dispatchViewUpdates(eventDispatcher, -1); // -1 = no associated batch id
    }
  }
  /**
   * Method which takes a container tag and then releases all subviews for that container upon
   * receipt. TODO: The method name is incorrect and will be renamed, #6033872
   *
   * @param containerTag the tag of the container for which the subviews must be removed
   */
  public void removeSubviewsFromContainerWithID(int containerTag) {
    ReactShadowNode containerNode = mShadowNodeRegistry.getNode(containerTag);
    if (containerNode == null) {
      throw new IllegalViewOperationException(
          "Trying to remove subviews of an unknown view tag: " + containerTag);
    }

    WritableArray indicesToRemove = Arguments.createArray();
    for (int childIndex = 0; childIndex < containerNode.getChildCount(); childIndex++) {
      indicesToRemove.pushInt(childIndex);
    }

    manageChildren(containerTag, null, null, null, null, indicesToRemove);
  }
  /**
   * Registers a root node with a given tag, size and ThemedReactContext and adds it to a node
   * registry.
   */
  public void registerRootView(
      SizeMonitoringFrameLayout rootView,
      int tag,
      int width,
      int height,
      ThemedReactContext context) {
    final ReactShadowNode rootCSSNode = new ReactShadowNode();
    rootCSSNode.setReactTag(tag);
    rootCSSNode.setThemedContext(context);
    rootCSSNode.setStyleWidth(width);
    rootCSSNode.setStyleHeight(height);
    rootCSSNode.setViewClassName("Root");
    mShadowNodeRegistry.addRootNode(rootCSSNode);

    // register it within NativeViewHierarchyManager
    mOperationsQueue.addRootView(tag, rootView, context);
  }
  /** Invoked by React to create a new node with a given tag has its properties changed. */
  public void updateView(int tag, String className, ReadableMap props) {
    ViewManager viewManager = mViewManagers.get(className);
    if (viewManager == null) {
      throw new IllegalViewOperationException("Got unknown view type: " + className);
    }
    ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag);
    if (cssNode == null) {
      throw new IllegalViewOperationException("Trying to update non-existent view with tag " + tag);
    }

    if (props != null) {
      CatalystStylesDiffMap styles = new CatalystStylesDiffMap(props);
      cssNode.updateProperties(styles);
      if (!cssNode.isVirtual()) {
        mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles);
      }
    }
  }
  private void applyUpdatesRecursive(
      ReactShadowNode cssNode, float absoluteX, float absoluteY, EventDispatcher eventDispatcher) {
    if (!cssNode.hasUpdates()) {
      return;
    }

    if (!cssNode.isVirtualAnchor()) {
      for (int i = 0; i < cssNode.getChildCount(); i++) {
        applyUpdatesRecursive(
            cssNode.getChildAt(i),
            absoluteX + cssNode.getLayoutX(),
            absoluteY + cssNode.getLayoutY(),
            eventDispatcher);
      }
    }

    int tag = cssNode.getReactTag();
    if (!mShadowNodeRegistry.isRootNode(tag)) {
      cssNode.dispatchUpdates(
          absoluteX, absoluteY, mOperationsQueue, mNativeViewHierarchyOptimizer, eventDispatcher);
    }
    cssNode.markUpdateSeen();
  }
 /** Unregisters a root node with a given tag. */
 public void removeRootView(int rootViewTag) {
   mShadowNodeRegistry.removeRootNode(rootViewTag);
   mOperationsQueue.enqueueRemoveRootView(rootViewTag);
 }
  /**
   * Invoked when there is a mutation in a node tree.
   *
   * @param tag react tag of the node we want to manage
   * @param indicesToRemove ordered (asc) list of indicies at which view should be removed
   * @param viewsToAdd ordered (asc based on mIndex property) list of tag-index pairs that represent
   *     a view which should be added at the specified index
   * @param tagsToDelete list of tags corresponding to views that should be removed
   */
  public void manageChildren(
      int viewTag,
      @Nullable ReadableArray moveFrom,
      @Nullable ReadableArray moveTo,
      @Nullable ReadableArray addChildTags,
      @Nullable ReadableArray addAtIndices,
      @Nullable ReadableArray removeFrom) {
    ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);

    int numToMove = moveFrom == null ? 0 : moveFrom.size();
    int numToAdd = addChildTags == null ? 0 : addChildTags.size();
    int numToRemove = removeFrom == null ? 0 : removeFrom.size();

    if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
      throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
    }

    if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
      throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
    }

    // We treat moves as an add and a delete
    ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
    int[] indicesToRemove = new int[numToMove + numToRemove];
    int[] tagsToRemove = new int[indicesToRemove.length];
    int[] tagsToDelete = new int[numToRemove];

    if (numToMove > 0) {
      Assertions.assertNotNull(moveFrom);
      Assertions.assertNotNull(moveTo);
      for (int i = 0; i < numToMove; i++) {
        int moveFromIndex = moveFrom.getInt(i);
        int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
        viewsToAdd[i] = new ViewAtIndex(tagToMove, moveTo.getInt(i));
        indicesToRemove[i] = moveFromIndex;
        tagsToRemove[i] = tagToMove;
      }
    }

    if (numToAdd > 0) {
      Assertions.assertNotNull(addChildTags);
      Assertions.assertNotNull(addAtIndices);
      for (int i = 0; i < numToAdd; i++) {
        int viewTagToAdd = addChildTags.getInt(i);
        int indexToAddAt = addAtIndices.getInt(i);
        viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
      }
    }

    if (numToRemove > 0) {
      Assertions.assertNotNull(removeFrom);
      for (int i = 0; i < numToRemove; i++) {
        int indexToRemove = removeFrom.getInt(i);
        int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
        indicesToRemove[numToMove + i] = indexToRemove;
        tagsToRemove[numToMove + i] = tagToRemove;
        tagsToDelete[i] = tagToRemove;
      }
    }

    // NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
    // moveTo and addAt are both relative to the final state of the View's children.
    //
    // 1) Sort the views to add and indices to remove by index
    // 2) Iterate the indices being removed from high to low and remove them. Going high to low
    //    makes sure we remove the correct index when there are multiple to remove.
    // 3) Iterate the views being added by index low to high and add them. Like the view removal,
    //    iteration direction is important to preserve the correct index.

    Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
    Arrays.sort(indicesToRemove);

    // Apply changes to CSSNode hierarchy
    int lastIndexRemoved = -1;
    for (int i = indicesToRemove.length - 1; i >= 0; i--) {
      int indexToRemove = indicesToRemove[i];
      if (indexToRemove == lastIndexRemoved) {
        throw new IllegalViewOperationException(
            "Repeated indices in Removal list for view tag: " + viewTag);
      }
      cssNodeToManage.removeChildAt(indicesToRemove[i]);
      lastIndexRemoved = indicesToRemove[i];
    }

    for (int i = 0; i < viewsToAdd.length; i++) {
      ViewAtIndex viewAtIndex = viewsToAdd[i];
      ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
      if (cssNodeToAdd == null) {
        throw new IllegalViewOperationException(
            "Trying to add unknown view tag: " + viewAtIndex.mTag);
      }
      cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
    }

    if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
      mNativeViewHierarchyOptimizer.handleManageChildren(
          cssNodeToManage, indicesToRemove, tagsToRemove, viewsToAdd, tagsToDelete);
    }

    for (int i = 0; i < tagsToDelete.length; i++) {
      removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
    }
  }