All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidad.change.MoveChildComponentChange Maven / Gradle / Ivy

The newest version!
/*
 * 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 org.apache.myfaces.trinidad.change;

import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import org.apache.myfaces.trinidad.component.UIXComponent;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.ComponentUtils;

import org.w3c.dom.Node;


/**
 * Change specialization for moving a child from one container to another.
 * MoveChildComponent should be registered on a parent component that is
 * common to the child being moved and the container component at destination.
 * In other words, while calling addComponentChange() or addDocumentChange()
 * methods on the ChangeManager to add a MoveChildComponentChange, the common
 * parent component instance must be passed as an argument. The add() utility
 * method in this class can be alternatively used to conveniently register the
 * change against the common parent. While applying this change, if a child with
 * the same id as the movable child were to be already present in the destination
 * container, the move operation is aborted.
 * @see #add(FacesContext, ChangeManager)
 * @see ChangeManager#addComponentChange(FacesContext, UIComponent, ComponentChange)
 * @see ChangeManager#addDocumentChange(FacesContext, UIComponent, DocumentChange)
 */
public final class MoveChildComponentChange 
  extends ComponentChange
  implements DocumentChange
{
  /**
   * Constructs a MoveChildComponentChange. The child will be appended to the
   * list of children of the destinationContainer.
   * @param movableChild The child component to be moved.
   * @param destinationContainer The destination component into which the child
   * component is to be moved.
   * @throws IllegalArgumentException If movableChild or destinationContainer
   * is null
   */
  public MoveChildComponentChange(
    UIComponent movableChild,
    UIComponent destinationContainer)
  {
    this(movableChild, destinationContainer, null);
  }
  
  /**
   * Constructs a MoveChildComponentChange. The child will be inserted to the 
   * list of children of the destinationContainer, before the supplied 
   * insertBeforecomponent. If the supplied insertBeforeComponent is null, the 
   * child will be appended to the list of children of the destinationContainer.
   * If the insertBeforeComponent is non-null, and if it were not to be found
   * while applying this change, the movableChild will not be moved.
   * @param movableChild The child component to be moved.
   * @param destinationContainer The destination component into which the child 
   * component is to be moved. This should not be null if the insertBeforeComponent
   * is null.
   * @param insertBeforeComponent The component before which the moved child is
   * to be inserted. This can be null, in which case the movableChild is
   * appended.
   * @throws IllegalArgumentException If movableChild is null or destinationContainer
   * and insertBeforeComponent is null, or if a parent component common to 
   * movableChild and destinationContainer could not be found.
   */
  public MoveChildComponentChange(
    UIComponent movableChild,
    UIComponent destinationContainer, 
    UIComponent insertBeforeComponent)
  {
    if (movableChild == null)
    {
      throw new IllegalArgumentException(_LOG.getMessage("MOVABLE_CHILD_REQUIRED"));
    }
    
    /////////////////////////////
    
    FacesContext context = FacesContext.getCurrentInstance();

    // get the doc paths first and validate
    _moveCompDocPath = ComponentUtils.getDocumentLocationForComponent(context, movableChild);
    _destinationContainerDocPath = 
      ComponentUtils.getDocumentLocationForComponent(context, destinationContainer);

    if (_moveCompDocPath == null || _destinationContainerDocPath == null)
    {
      // if either components are not in a doc, component is not in the tree, error condition
      throw new IllegalArgumentException(
        _LOG.getMessage("NO_CONTAINING_DOC_FOUND", 
                        (_moveCompDocPath == null) ? movableChild : destinationContainer));
    }
    
    /////////////////////////////

    // validate destination container
    destinationContainer = _getValidatedDestinationContainer(destinationContainer, 
                                                             insertBeforeComponent);
    // find and validate the common parent
    UIComponent commonParent = _getValidatedCommonParent(context, movableChild, destinationContainer);
    _commonParentDocPath = ComponentUtils.getDocumentLocationForComponent(context, commonParent);
    
    /////////////////////////////
    
    UIComponent viewRoot = context.getViewRoot();

    // Get the scoped id's for move participants (scoped / relative to common parent)
    _moveCompScopedIdAtSource = ComponentUtils.getScopedIdForComponent(movableChild, commonParent);
    _moveCompParentScopedId = 
      ComponentUtils.getScopedIdForComponent(movableChild.getParent(), commonParent);
    _destinationContainerScopedId = ComponentUtils.getScopedIdForComponent(destinationContainer,
                                                                           commonParent);
    _commonParentScopedId = ComponentUtils.getScopedIdForComponent(commonParent, viewRoot);
    
    // cannot proceed if we could not get id for the move participants
    if (_moveCompScopedIdAtSource == null || 
        _moveCompParentScopedId == null || 
        _destinationContainerScopedId == null ||
        _commonParentScopedId == null)
    {
      throw new IllegalArgumentException(_LOG.getMessage("MOVE_PARTICIPANTS_WITHOUT_ID"));
    }
    
    /////////////////////////////

    // get the id path upto the naming container of the common parent so that we can compute the 
    //  absolute scoped ids from the scoped ids
    String commonParentPrefix = _getScopedIdPrefix(commonParent, _commonParentScopedId);
      
    // calculate the absolute scoped ids (scoped from ViewRoot) for the movable componen at its 
    //  source so that we can handle remapping scoped ids in the SessionChangeManager    
    _moveCompAbsoluteScopedIdAtSource = (commonParentPrefix != null) ?
                                          new StringBuilder(commonParentPrefix).
                                            append(NamingContainer.SEPARATOR_CHAR).
                                            append(_moveCompScopedIdAtSource).toString()
                                          : _moveCompScopedIdAtSource;
    
    // find the logical (id in context of the document where the component is defined) scoped id 
    _moveCompAbsoluteLogicalScopedIdAtSource = 
      ComponentUtils.getLogicalScopedIdForComponent(movableChild, viewRoot);
    
    /////////////////////////////
    
    // calculate the absolute scoped ids of the moveable component at destination after move
    String destinationContainerPrefix = _getScopedIdPrefix(destinationContainer, 
                                                           _destinationContainerScopedId);
    StringBuilder moveCompAtDestinationScopedIdBuilder = new StringBuilder();
    
    if (commonParentPrefix != null)
    {
      moveCompAtDestinationScopedIdBuilder.append(commonParentPrefix).
        append(NamingContainer.SEPARATOR_CHAR);
    }
    
    if (destinationContainerPrefix != null)
    {
      moveCompAtDestinationScopedIdBuilder.append(destinationContainerPrefix).
        append(NamingContainer.SEPARATOR_CHAR);
    }
    
    String movableChildId = movableChild.getId();
    _moveCompAbsoluteScopedIdAtDestination = 
      moveCompAtDestinationScopedIdBuilder.append(movableChildId).toString();
    String destinationLogicalPrefix = 
      _getScopedIdPrefix(destinationContainer,
                         ComponentUtils.getLogicalScopedIdForComponent(destinationContainer, 
                                                                       viewRoot));
    
    // find the logical id
    _moveCompAbsoluteLogicalScopedIdAtDestination = (destinationLogicalPrefix != null) ? 
                                                      new StringBuilder(destinationLogicalPrefix).
                                                        append(NamingContainer.SEPARATOR_CHAR).
                                                        append(movableChildId).toString()
                                                      : movableChildId;

    /////////////////////////////

    // For insertBeforeComponent, we do not care to obtain scoped id.
    _insertBeforeCompId = (insertBeforeComponent == null) ? null : insertBeforeComponent.getId();
  }
  
  /**
   * Convenience method to add this MoveChildComponentChange to the supplied
   * ChangeManager. The change will be registered against a parent component
   * that is common to the child being moved and the container component at
   * destination.
   * @param facesContext The FacesContext instance for the current request
   * @param changeManager The ChangeManager instance on which this
   * MoveChildComponentChange is to be added.
   * @return The common parent component against which this 
   * MoveChildComponentChange was registered.
   */
  public UIComponent add(
    FacesContext facesContext, 
    ChangeManager changeManager) 
  {
    UIComponent commonParent = facesContext.getViewRoot().findComponent(_commonParentScopedId);
    
    if (commonParent == null)
    {
      _LOG.warning("COMMON_PARENT_NOT_FOUND", _commonParentScopedId);
      return null;
    }
    
    // Register a move change against the common parent
    changeManager.addComponentChange(facesContext, commonParent, this);
    
    return commonParent;
  }
   
  /**
   * Apply this change to the specified component.
   * @param changeTargetComponent The component that is a common parent to the 
   * movable child and the destination container.
   * @throws IllegalArgumentException If the supplied changeTargetComponent
   * is null.
   */
  @Override
  public void changeComponent(UIComponent changeTargetComponent)
  {
    if (changeTargetComponent == null)
      throw new IllegalArgumentException(
        _LOG.getMessage("COMPONENT_REQUIRED"));
    
    // 1. Check for destination container component 
    UIComponent destinationContainer = 
      changeTargetComponent.findComponent(_destinationContainerScopedId);
    if(destinationContainer == null)
    {
      _LOG.warning("DESTINATION_CONTAINER_NOT_FOUND", _destinationContainerScopedId);
      return;
    }
    
    // 2. Find movableChild, gather the possible duplicates (theoritically only three) and keep a
    //  single copy among them.
    //  Duplicates are possible because 
    //  a) taghandlers re-create the component that was in the jspx file in
    //    their original location, no matter whether it was moved/removed due to 
    //    aplication of a different ComponentChange. Such components could now 
    //    be considered duplicates, because they are newly created from their vanilla definition
    //    in the jspx document, and would not have any further ComponentChanges applied on them. 
    //    There should be one such duplicate.
    //  b) Apart from adding the MoveComponentChange, we expect developers to apply the change in 
    //    order to reflect in the same request cycle (i.e. developers call 
    //    ComponentChange.changeComponent()). Consequently, the component tree contains a moved 
    //    child at the destination. Such components must be preserved, because they have 
    //    incorporated any subsequent ComponentChanges on them. There should be one such moved
    //    component.
    //  c) We would have moved/added components due to previous customization an earlier application 
    //    of ComponentChange, that could still be in the view tree. There should be one such zombie/ 
    //    duplicate.
    UIComponent sourceParent = 
      changeTargetComponent.findComponent(_moveCompParentScopedId);
    
    UIComponent foundChild = 
      changeTargetComponent.findComponent(_moveCompScopedIdAtSource);

    // To flag if a child was already found in a destination container (maybe due to previous move)    
    boolean isChildIdAtDestination = false;
    
    UIComponent movableChild = null;
    int movableChildIndex = 0;
    UIComponent movedChild = null;
    int movedChildIndex = 0;
    UIComponent duplicateChild = null;
    int duplicateChildIndex = 0;
    UIComponent duplicateChildParent = null;

    while (foundChild != null)
    {
      // 2.a. If the parent matches, this could be the component that JSF-Runtime re-created
      //  and added because it is in the physical document
      if (foundChild.getParent().equals(sourceParent))
      {
        movableChild = foundChild;
        movableChildIndex = sourceParent.getChildren().indexOf(movableChild);
      }
      // 2.b.a. We could possibly find the child at its destination, because apart from
      //  adding the change, the change was applied in previous request, and the move
      //  could have been within the same naming container umbrella. In this case
      //  we do not want to move anything and the movable child is considered as a 
      //  duplicate and candidate for removal.
      else if (foundChild.getParent().equals(destinationContainer))
      {
        isChildIdAtDestination = true;
        movedChild = foundChild;
        movedChildIndex = destinationContainer.getChildren().indexOf(movedChild);
      }
      // 2.c. Possible dup from subsequent MoveChildComponentChange in the sequence of multiple
      //  moves of the component in this same request. For example, if the move is from A->B->C,
      //  and if we are currently dealing with move from A->B, the component that was added at
      //  position C (in addition to adding the move change to changemanager) will now be dup.
      else
      {
        duplicateChild = foundChild;
        duplicateChildIndex = foundChild.getParent().getChildren().indexOf(foundChild);
        duplicateChildParent = foundChild.getParent();
      }

      // Invariably, remove the found component from the tree. We remove the
      //  movableChild also, otherwise, findComponent blind loops on this same 
      //  component if movableChild and duplicates are within same immediate
      //  NamingContainer.
      foundChild.getParent().getChildren().remove(foundChild);

      // Try and find the next potential copy of the component to move
      foundChild = changeTargetComponent.findComponent(_moveCompScopedIdAtSource);
    }
    
    //  We need to re-attach the dup for now, the dupes will be eliminated gradually while applying
    //  the successive move change involving the same component.
    if (duplicateChild != null)
    {
      duplicateChildParent.getChildren().add(duplicateChildIndex, duplicateChild);
    }

    // Can't do anything without a movable child.    
    if(movableChild == null)
    {
      _LOG.warning("MOVABLE_CHILD_NOT_FOUND", _moveCompScopedIdAtSource);

      // Reverse any damage that we might have caused, and exit
      if (movedChild != null)
      {
        destinationContainer.getChildren().add(movedChildIndex, movedChild);
      }
      return;
    }
    
    // 2.b.b. Similar to situation in step #2.b.a, but here the move is across different naming 
    //  containers, we could not catch this earlier.
    if (!isChildIdAtDestination)
    {
      String movableChildId = movableChild.getId();
      for (UIComponent childComponent:destinationContainer.getChildren())
      {
        if (movableChildId.equals(childComponent.getId()))
        {
          isChildIdAtDestination = true;
          movedChild = childComponent;
          // Temporarily remove this child, we might add it back in step #3 below.
          movedChild.getParent().getChildren().remove(movedChild);
          break;
        }
      }
    }

    // 3. Check whether the destination container has a child with same id.
    if (isChildIdAtDestination)
    {
      _LOG.warning("MOVABLE_CHILD_SAME_ID_FOUND", _moveCompScopedIdAtSource);

      // Component type matches, this means the child is already at destination. We have removed all
      //  duplicates, and have nothing more to do in this case
      if ( (movableChild.getFamily().equals(movedChild.getFamily())) &&
             (movableChild.getRendererType().equals(movedChild.getRendererType())) )
      {
        // Add back the moved child that we removed earlier.
        destinationContainer.getChildren().add(movedChildIndex, movedChild);
      }
      else
      {
        // Duplicate child by id, but not of the same component type - a condition we cannot handle.
        // Reverse any damage that we might have caused and exit
        sourceParent.getChildren().add(movableChildIndex, movableChild);
      }
      return;
    }

    // We are now dealing with case where there were no duplicates, and a proper point-to-point
    //  move should happen. Reattach the moveable child, so that move happens atomically at the end.
    sourceParent.getChildren().add(movableChildIndex, movableChild);
    
    // 4. See if we can find the insertBeforeComponent among the destinationContainer's children
    int insertIndex = -1;
    if (_insertBeforeCompId != null)
    {
      for (UIComponent childComponent:destinationContainer.getChildren())
      {
        if (_insertBeforeCompId.equals(childComponent.getId()))
        {
          insertIndex = 
            destinationContainer.getChildren().indexOf(childComponent);
          break;
        }
      }
  
      // insertBeforeId was specified, but we cannot find the insertBefore component. Exit.
      if (insertIndex == -1)
      {
        _LOG.warning("INSERT_BEFORE_NOT_FOUND", _insertBeforeCompId);
        return;
      }
    }
    
    // 5. Atomically move the child
    if (insertIndex == -1)
      destinationContainer.getChildren().add(movableChild);
    else
      destinationContainer.getChildren().add(insertIndex, movableChild);
  }
  
  /**
   * Given the DOM Node representing a Component, apply any necessary
   * DOM changes. The node passed will be the Node that is a common parent for
   * the movable child and the destination container.
   * There is a limitation with the document change, that the movable child 
   * Node, destination container Node, and the common parent Node have to belong
   * to the same document.
   * @param changeTargetNode DOM Node that is a common parent for the movable
   * child and the destination container.
   * @throws IllegalArgumentException If changeTargeNode were to be null.
   */
  public void changeDocument(Node changeTargetNode)
  {
    if (changeTargetNode == null)
      throw new IllegalArgumentException(_LOG.getMessage("NO_NODE_SPECIFIED"));

    if ( !_moveCompDocPath.equals(_destinationContainerDocPath) ||
         !_moveCompDocPath.equals(_commonParentDocPath) )
    {
      // If all three participants are not in same doc, we cannot proceed with appling doc change.
      // Throw an exception so that ChangeManagers can handle this failure and do alternate
      //  processing (eg. add a component change given that doc change failed)
      throw new IllegalStateException(
        _LOG.getMessage("MOVE_PARTICIPANTS_NOT_IN_SAME_DOC", 
                        new Object[] {_moveCompDocPath,
                                      _destinationContainerDocPath,
                                      _commonParentDocPath}));
    }

    // Move involves four steps.
    // 1. Finding the child node, the source of move
    Node movableChildNode = 
      ChangeUtils.__findNodeByScopedId(changeTargetNode, 
                                       _moveCompScopedIdAtSource, 
                                       Integer.MAX_VALUE);
    
    if(movableChildNode == null)
    {
      _LOG.warning("MOVABLE_CHILD_NOT_FOUND", _moveCompScopedIdAtSource);
      return;
    }
    
    // 2. Finding the destination container node
    Node destinationContainerNode = 
      ChangeUtils.__findNodeByScopedId(changeTargetNode, 
                                       _destinationContainerScopedId, 
                                       Integer.MAX_VALUE);

    
    if(destinationContainerNode == null)
    {
      _LOG.warning("DESTINATION_CONTAINER_NOT_FOUND", _destinationContainerScopedId);
      return;
    }
    
    //3. Finding the neighbor at the destination
    Node insertBeforeNode = (_insertBeforeCompId == null) ? 
      null:ChangeUtils.__findNodeByScopedId(destinationContainerNode, 
                                            _insertBeforeCompId, 
                                            1);
    // insertBeforeId was specified, but corresponding component is missing.
    //  Abort the move.
    if(_insertBeforeCompId != null && insertBeforeNode == null)
    {
      _LOG.warning("INSERT_BEFORE_NOT_FOUND", _insertBeforeCompId);
      return;
    }

    //4. Atomically move the child.
    destinationContainerNode.insertBefore(movableChildNode, insertBeforeNode);
  }

  /** 
   * Returns true if adding the DocumentChange should force the JSP Document
   * to reload
   * @return true Since moving of components should force the document to reload
   */
  public boolean getForcesDocumentReload()
  {
    return true;
  }
  
  /**
   * Returns the absolute scopedId (relative to the ViewRoot) of the movable component as it is 
   *  before the move
   */
  public String getSourceScopedId()
  {
    return _moveCompAbsoluteScopedIdAtSource;
  }

  /**
   * Returns the absolute scopedId (relative to the ViewRoot) of the movable component as it would 
   *  be after the move
   */
  public String getDestinationScopedId()
  {
    return _moveCompAbsoluteScopedIdAtDestination;
  }
  
  /**
   * Returns the absolute logical scopedId of the movable component as it is before the move. 
   * 
   * The id returned here will be in context of the document where the component is defined. For 
   *  example, consider a component that is defined in a base document and is relocated to a 
   *  different component subtree as in included template (included by an UIXInclude component) 
   *  via. its facet. In this case the logical id of the move component will be in context of base  
   *  document (as if it was never relocated) and not the document that defines the including 
   *  component.
   *  
   * @see #getSourceScopedId()
   */
  public String getSourceLogicalScopedId()
  {
    return _moveCompAbsoluteLogicalScopedIdAtSource;
  }
  
  /**
   * Returns the absolute logical scopedId of the movable component as it would be after the move.
   * 
   * The id returned here will be in context of the document where the component is defined. For 
   *  example, consider a component that is defined in a base document and is relocated to a 
   *  different component subtree as in included template (included by an UIXInclude component) 
   *  via. its facet. In this case the logical id of the move component will be in context of base  
   *  document (as if it was never relocated) and not the document that defines the including 
   *  component.
   *  
   * @see #getDestinationScopedId()
   */
  public String getDestinationLogicalScopedId()
  {
    return _moveCompAbsoluteLogicalScopedIdAtDestination;
  }
  
  @Override
  public boolean equals(Object o)
  {
    if (o == this)
      return true;
    
    if (!(o instanceof MoveChildComponentChange))
      return false;
    
    MoveChildComponentChange other = (MoveChildComponentChange)o;
    
    return  _equalsOrNull(_moveCompScopedIdAtSource, other._moveCompScopedIdAtSource) &&
            _equalsOrNull(_moveCompAbsoluteScopedIdAtSource, 
                          other._moveCompAbsoluteScopedIdAtSource) &&
            _equalsOrNull(_moveCompAbsoluteLogicalScopedIdAtSource, 
                          other._moveCompAbsoluteLogicalScopedIdAtSource) &&
            _equalsOrNull(_moveCompDocPath, other._moveCompDocPath) &&
            _equalsOrNull(_moveCompParentScopedId, other._moveCompParentScopedId) &&
            _equalsOrNull(_moveCompAbsoluteScopedIdAtDestination, 
                          other._moveCompAbsoluteScopedIdAtDestination) &&
            _equalsOrNull(_moveCompAbsoluteLogicalScopedIdAtDestination, 
                          other._moveCompAbsoluteLogicalScopedIdAtDestination) &&
            _equalsOrNull(_destinationContainerScopedId, other._destinationContainerScopedId) &&
            _equalsOrNull(_destinationContainerDocPath, other._destinationContainerDocPath) &&
            _equalsOrNull(_commonParentScopedId, other._commonParentScopedId) &&
            _equalsOrNull(_commonParentDocPath, other._commonParentDocPath) &&
            _equalsOrNull(_insertBeforeCompId, other._insertBeforeCompId);
  }
  
  @Override
  public int hashCode()
  {
    return ((_moveCompScopedIdAtSource == null) ? 0 : _moveCompScopedIdAtSource.hashCode()) + 
            37 * ((_moveCompAbsoluteScopedIdAtSource == null) ? 
                  0 : _moveCompAbsoluteScopedIdAtSource.hashCode()) +
            37 * ((_moveCompAbsoluteLogicalScopedIdAtSource == null) ? 
                  0 : _moveCompAbsoluteLogicalScopedIdAtSource.hashCode()) +
            37 * ((_moveCompDocPath == null) ? 0 : _moveCompDocPath.hashCode()) +
            37 * ((_moveCompParentScopedId == null) ? 
                  0 : _moveCompParentScopedId.hashCode()) +
            37 * ((_moveCompAbsoluteScopedIdAtDestination == null) ? 
                  0 : _moveCompAbsoluteScopedIdAtDestination.hashCode()) +
            37 * ((_moveCompAbsoluteLogicalScopedIdAtDestination == null) ? 
                  0 : _moveCompAbsoluteLogicalScopedIdAtDestination.hashCode()) +
            37 * ((_destinationContainerScopedId == null) ? 
                  0 : _destinationContainerScopedId.hashCode()) +
            37 * ((_destinationContainerDocPath == null) ? 
                  0 : _destinationContainerDocPath.hashCode()) +
            37 * ((_commonParentScopedId == null) ? 0 : _commonParentScopedId.hashCode()) +
            37 * ((_commonParentDocPath == null) ? 0 : _commonParentDocPath.hashCode()) +
            37 * ((_insertBeforeCompId == null) ? 0 : _insertBeforeCompId.hashCode());
  }
      
  @Override
  public String toString()
  {
    StringBuffer sb = new StringBuffer();
    
    sb.append(super.toString());
    sb.append("[moveCompAbsoluteLogicalScopedIdAtSource=").
       append(_moveCompAbsoluteScopedIdAtSource);
    sb.append(" moveCompAbsoluteLogicalScopedIdAtDestination=").
       append(_moveCompAbsoluteLogicalScopedIdAtDestination);
    sb.append(" moveCompAbsoluteScopedIdAtSource=").append(_moveCompAbsoluteScopedIdAtSource);
    sb.append(" moveCompAbsoluteScopedIdAtDestination=").
       append(_moveCompAbsoluteScopedIdAtDestination);
    sb.append(" insertBeforeCompId=").append(_insertBeforeCompId);
    sb.append(" commonParentScopedId=").append(_commonParentScopedId);
    sb.append(" moveCompDocPath=").append(_moveCompDocPath);
    sb.append(" destinationContainerDocPath=").append(_destinationContainerDocPath);
    
    return sb.append("]").toString();
  }

  /**
   * Returns best common parent of the two supplied components in a subtree to which this move 
   * change can be added.
   * - If the supplied components belong to same document, we try to get a common parent that is of
   *   type UIXComponent and belongs to the same document, this is to be able to support applying 
   *   document change.
   * - If they do not belong to same document, we just return the closest common UIComponent parent, 
   *   this should suffice to apply component change.
   * 
   * @throws IllegalArgumentException if we are not able to find the common parent for the supplied
   *          components
   */
  private UIComponent _getValidatedCommonParent(
    FacesContext context,
    UIComponent componentToMove,
    UIComponent parentAtDestination) 
  {
    // Calculate the depth of each node.
    int firstDepth = _computeDepth(componentToMove);
    int secondDepth = _computeDepth(parentAtDestination);
           
    // Move the deeper of the two components to its ancestor at the same depth
    // as the shallower.
    if (secondDepth > firstDepth)
    {
      parentAtDestination = _getAncestor(parentAtDestination, secondDepth - firstDepth);
    }
    else if(secondDepth < firstDepth)
    {
      componentToMove = _getAncestor(componentToMove, firstDepth - secondDepth);
    }

    // Crawl up until we find the shared ancestor.
    while (componentToMove != null && (componentToMove != parentAtDestination))
    {
      componentToMove = componentToMove.getParent();
      parentAtDestination = parentAtDestination.getParent();
    }
    
    UIComponent commonParent = componentToMove;
    
    // try to find a better ancestor complying these two conditions
    //  1. The common parent is a UIXComponent instance - only UIXComponents have tags
    //  2. The common parent belongs to same document that the other two participating components 
    //     belong to
    if (_moveCompDocPath.equals(_destinationContainerDocPath))
    {
      commonParent = _getUIXComponentAncestorInDoc(context, commonParent);
    }
    
    // we cannot proceed if we could not find the common parent
    if (commonParent == null)
    {
      throw new IllegalArgumentException(_LOG.getMessage("COMMON_PARENT_NOT_FOUND"));
    }
    
    return commonParent;
  }
  
  /**
   * For a supplied base component, returns the closest UIXComponent ancestor that is in same 
   * document as base component. If no such component is found, returns the supplied base component.
   */
  private UIComponent _getUIXComponentAncestorInDoc(
    FacesContext context, 
    UIComponent baseComp)
  {
    UIComponent ancestor = baseComp;
    
    while (ancestor != null)
    {
      if (ancestor instanceof UIXComponent &&
          _moveCompDocPath.equals(ComponentUtils.getDocumentLocationForComponent(context, 
                                                                                 ancestor)))
      {
        return ancestor;
      }

      ancestor = ancestor.getParent();
    }
    
    return baseComp;
  }
  
  /**
   * Trims the supplied scoped id of the supplied component until its immediate naming container
   *  and returns.
   */
  private String _getScopedIdPrefix(UIComponent component, String scopedId)
  {
    if (component instanceof NamingContainer)
      return scopedId;
    else
    {
      // remove the component's id from the end
      int separatorIndex = scopedId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
      
      if (separatorIndex >= 0)
        return scopedId.substring(0, separatorIndex);
      else
      {
        // component was at top level
        return null;
      }
    }
  }
  
  private boolean _equalsOrNull(Object obj1, Object obj2)
  {
    return (obj1 == null) ? (obj2 == null) : obj1.equals(obj2);
  }

  /**
   * Returns the destination container if passed non-null after doing needed validations. If null 
   * destinationContainer is passed, determines it from the supplied insertBeforeComponent. 
   */
  private static UIComponent _getValidatedDestinationContainer(
    UIComponent destinationContainer, 
    UIComponent insertBeforeComponent)
  {
    if (insertBeforeComponent != null)
    {
      UIComponent parent = insertBeforeComponent.getParent();
      
      if (destinationContainer == null)
      {
        destinationContainer = parent;
      }
      
      // if container was supplied, it better be parent of component to move next to
      else if (destinationContainer != parent)
      {
        throw new IllegalArgumentException(
          _LOG.getMessage("DESTINATION_CONTAINER_NOT_INSERTBEFORES_PARENT"));
      }
    }
    else if (destinationContainer == null)
    {
      throw new IllegalArgumentException(_LOG.getMessage("DESTINATION_CONTAINER_REQUIRED"));
    }
    
    return destinationContainer;
  }
  
  /**
   * Returns the depth of a UIComponent in the tree. 
   * @param comp the UIComponent whose depth has to be calculated
   * @return the depth of the passed in UIComponent
   */
  private static int _computeDepth(UIComponent comp) 
  {
    int i = 0;
    while((comp = comp.getParent()) != null) 
    {
      i++;
    }
    return i;
  }

  /**
   * Returns the nth ancestor of the passed in component.
   * @param component The UIComponent whose nth ancestor has to be found
   * @param level Indicates how many levels to go up from the component
   * @return The nth ancestor of the component
   */
  private static UIComponent _getAncestor(UIComponent component, int level) 
  {
    assert(level >= 0);
    
    while(level > 0)
    {
      component = component.getParent();
      level--;
    }
    return component;
  }
  
  // *ScopedId -> the id of the component scoped / relative to the common parent
  // *AbsoluteScopedId -> the id of the component scoped / relative to the ViewRoot
  // *AbsoluteLogicalScopedId -> id of the component scoped / relative to the ViewRoot in context
  //    of the document in which it is originally defined. For more details, see documentation in 
  //    getSourceLogicalScopedId() 
  
  // for component to be moved
  private final String _moveCompScopedIdAtSource;
  private final String _moveCompAbsoluteScopedIdAtSource;
  private final String _moveCompAbsoluteLogicalScopedIdAtSource;
  private final String _moveCompDocPath;
  private final String _moveCompParentScopedId;

  // for component at destination after the move
  private final String _moveCompAbsoluteScopedIdAtDestination;
  private final String _moveCompAbsoluteLogicalScopedIdAtDestination;
  
  // for new parent at destination
  private final String _destinationContainerScopedId;
  private final String _destinationContainerDocPath;

  // for parent common to the source and new parent at destination
  private final String _commonParentScopedId;
  private final String _commonParentDocPath;

  // for immediate sibling of moved component at destination
  private final String _insertBeforeCompId;

  private static final long serialVersionUID = 1L;

  private static final TrinidadLogger _LOG = 
    TrinidadLogger.createTrinidadLogger(MoveChildComponentChange.class);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy