/**
 * Copyright (c) 2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.codegen.merge.java.facade.ast;


import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ChildPropertyDescriptor;
import org.eclipse.jdt.core.dom.SimplePropertyDescriptor;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.dom.rewrite.TargetSourceRangeComputer;

import org.eclipse.emf.codegen.merge.java.facade.AbstractJNode;
import org.eclipse.emf.codegen.merge.java.facade.FacadeHelper;
import org.eclipse.emf.codegen.merge.java.facade.JNode;


/**
 * Each subclass of <code>ASTJNode</code> wraps subclass of {@link ASTNode}.
 * <p>
 * <code>ASTJNode</code> may have reference to multiple <code>ASTNode</code> objects.
 * <p>
 * Wrapped <code>ASTNode</code> (returned by {@link #getWrappedObject()}) is always the node in the rewritten tree. Each object of <code>ASTJNode</code>
 * wraps a unique <code>ASTNode</code>. After nodes are removed, wrapped <code>ASTNode</code> has reference to the move target node that allows
 * the node to be inserted after, i.e. perform moving of the node.
 * <p>
 * Original ASTNode (returned by {@link #getASTNode()}) is used to make modifications to the node and its children. This node must
 * be used by get and set methods. Using this node allows to make modifications to the nodes after
 * they are moved.
 * <p>
 * Some subclasses (e.g. <code>ASTJAnnotation</code> and <code>ASTJField</code>) may need a different <code>ASTNode</code> to be used
 * by set methods. These subclasses use <code>getASTNode()</code> only for get methods.
 * <p>
 * Removed ASTNode (returned by {@link #getRemovedASTNode()}) is used to keep reference to the node
 * that was removed. This allows to notify the range computer when the removed node is inserted again.
 * <p>
 * 
 * @see #getWrappedObject()
 * @see #getASTNode()
 * @see #getRemovedASTNode()
 *
 * @param <T> wrapped AST node type
 * 
 * @since 2.2.0
 */
public abstract class ASTJNode<T extends ASTNode> extends AbstractJNode
{
  /**
   * Default value used as default value of fields of subclasses. 
   * Indicates that fields have not been initialized.
   * This value should never be returned by any get...() methods.
   */
  protected static final String UNITIALIZED_STRING = "UNITIALIZED_STRING";
  
  /**
   * Name of the node.
   * 
   * @see JNode#getName()
   * @see JNode#setName(String)
   */
  protected String name = UNITIALIZED_STRING;
    
  /**
   * <code>true</code> if the node is commented out, <code>false</code> otherwise
   * @see #commentOut()
   */
  protected boolean isCommentedOut = false;
  
  private ASTFacadeHelper facadeHelper;

  /**
   * AST node used to read contents and make modifications to the nodes.
   */
  private T astNode = null;
  
  private ASTNode wrappedObject;

  /**
   * Flag that determines if parent property has been set 
   * (if not, <code>ASTNode.getParent()</code> will be used to retrieve the parent).
   */
  private boolean isParentSet = false;

  /**
   * Current parent of ASTJNode.
   * <p>
   * This attribute should used instead of ASTNode parent attribute. In some situations
   * (i.e. moving nodes by removing and inserting) ASTNode parent attribute
   * might not be updated. In addition, for the cloned nodes ASTNode parent attribute might
   * not be set either.
   * <p> 
   * When ASTJNode is deleted, parent is set to <code>null</code>.<p>
   */
  private ASTJNode<?> parent = null;

  /**
   * Reference to the original AST node that was removed from the tree.
   * <p>
   * This reference is used to notify the source range computer when the
   * removed node is inserted again (i.e. node is moved). 
   */
  private ASTNode removedASTNode = null;

  /**
   * ASTRewrite object used to keep track of all modifications
   */
  protected ASTRewrite rewriter = null;

  /**
   * Map of nodes to their contents. This map may only contain wrapped node and/or its children.
   * This map is used to preserve the contents when node is removed and then inserted.
   * 
   * @see ASTJCompilationUnit#getAllTrackedContentsMap()
   */
  private Map<ASTNode, String> trackedContentsMap = new HashMap<ASTNode, String>(4);

  /**
   * @see AbstractJNode#AbstractJNode()
   * @param astNode to be used as wrapped object
   */
  protected ASTJNode(T astNode)
  {
    this.astNode = astNode;
    wrappedObject = astNode;
  }

  @Override
  public void dispose()
  {
    rewriter = null;
    trackedContentsMap.clear();
    wrappedObject = null;
    removedASTNode = null;
    astNode = null;
    facadeHelper = null;
    name = null;
  }
  
  @Override
  public boolean isDisposed()
  {
    return rewriter == null;
  }

  @Override
  public ASTFacadeHelper getFacadeHelper()
  {
    return facadeHelper;
  }

  @Override
  public void setFacadeHelper(FacadeHelper facadeHelper)
  {
    this.facadeHelper = (ASTFacadeHelper)facadeHelper;
  }

  /**
   * Returns AST node used to read contents and make modifications to the nodes.
   * <p>
   * When the node is removed, the wrapped node returned by {@link #getWrappedObject()} will be
   * a place-holder node, while this method will return the original node that can be modified by
   * <code>set...</code> methods.
   * 
   * @return AST node
   */
  protected T getASTNode()
  {
    return astNode;
  }

  /**
   * @param astNode the astNode to set
   */
  protected void setASTNode(T astNode)
  {
    this.astNode = astNode;
  }

  /**
   * Returns wrapped AST node that is currently in the rewritten tree, or to be inserted into
   * the tree. This node can be a move target node or any other temporary node. 
   * Methods such as <code>set...</code> and <code>get...</code> should not use this node. 
   * 
   * @return wrapped AST node 
   * @see #getASTNode()
   * @see #getRemovedASTNode()
   */
  @Override
  protected ASTNode getWrappedObject()
  {
    return wrappedObject;
  }

  /**
   * Sets the wrapped object to the given <code>ASTNode</code>. Must be 
   * used with caution and only by subclasses.
   * @param node
   * @see #getWrappedObject()
   */
  protected void setWrappedObject(ASTNode node)
  {
    wrappedObject = node;
  }

  /**
   * @return the parent of this ASTJNode, or <code>null</code> if this node has been created or removed
   */
  public ASTJNode<?> getParent()
  {
    if (parent == null && !isParentSet)
    {
      setParent(facadeHelper.findParent(getWrappedObject()));
    }
    return parent;
  }

  /**
   * Sets the parent of this node.
   * 
   * @param parent the parent to set, <code>null</code> if the node does not have a parent
   */
  public void setParent(ASTJNode<?> parent)
  {
    isParentSet = true;
    this.parent = parent;
  }

  /**
   * @return the rewriter
   */
  public ASTRewrite getRewriter()
  {
    return rewriter;
  }

  /**
   * @param rewriter the rewriter to set
   */
  public void setRewriter(ASTRewrite rewriter)
  {
    this.rewriter = rewriter;
  }

  /**
   * Adds a child to this node.
   * <p>
   * Default implementation does nothing and returns <code>false</code>.
   * 
   * @param child to add
   * @return <code>true</code> if operation successful, <code>false</code> otherwise 
   */
  public boolean addChild(ASTJNode<?> child)
  {
    return false;
  }  
  
  /**
   * Adds <code>value</code> to the property of the node.
   * 
   * @param node
   * @param value
   * @param property
   */
  protected void addValueToListProperty(ASTNode node, ASTNode value, ChildListPropertyDescriptor property)
  {
    if (value == null)
    {
      return;
    }

    if (ASTFacadeHelper.DEBUG)
    {
      facadeHelper.logInfo("Adding value to list property <" + value + ">");
    }

    ListRewrite listRewrite = rewriter.getListRewrite(node, property);
    listRewrite.insertLast(value, null);
  }

  /**
   * Adds <code>value</code> as a new string place-holder to the <code>property</code> of the <code>node</code>.
   * <p>
   * If <code>value</code> is <code>null</code>, no changes are made.
   * 
   * @param node
   * @param value
   * @param property
   * @param nodeType of the place-holder
   */
  protected void addValueToListProperty(ASTNode node, String value, ChildListPropertyDescriptor property, int nodeType)
  {
    if (value == null)
    {
      return;
    }

    if (ASTFacadeHelper.DEBUG)
    {
      facadeHelper.logInfo("Adding value to list property <" + value + ">");
    }

    ListRewrite listRewrite = rewriter.getListRewrite(node, property);
    listRewrite.insertLast(rewriter.createStringPlaceholder(value, nodeType), null);
  }
  
  /**
   * Notifies the node that one of ancestors in the hierarchy or this node itself was inserted.
   * This means that this node was inserted as well.
   * <p>
   * This implementation enables tracked contents for this node and calls itself on all of the children.
   */
  protected void ancestorInserted()
  {
    enableTrackAndReplace();

    for (JNode node : getChildren())
    {
      ((ASTJNode<?>)node).ancestorInserted();
    }
  }

  /**
   * Notifies the node that one of ancestors in the hierarchy or this node itself will be removed.
   * This means that this node will be removed as well.
   * <p>
   * This implementation disables tracked contents for this node and calls itself on all of the children.
   */
  protected void ancestorToBeRemoved()
  {
    disableTrackAndReplace();

    for (JNode node : getChildren())
    {
      ((ASTJNode<?>)node).ancestorToBeRemoved();
    }
  }

  /**
   * Notifies the node that the child will be changed.
   * <p>
   * Parents that are interested in changes to children must override this method.
   * Children that need to notify the parents about changes have to call this method.
   * <p>
   * Default implementation does nothing.
   * 
   * @param child that will be changed
   */
  protected void childToBeChanged(ASTJNode<?> child)
  {
    // default implementation does nothing
  }

  /**
   * Convert a given list of nodes to an array of strings.
   * <p>
   * All nodes in the given array must have valid source range and belong to the original tree.
   * 
   * @param nodes
   * @return string array
   * 
   * @see ASTFacadeHelper#toString(ASTNode)
   */
  protected String[] convertASTNodeListToStringArray(List< ? extends ASTNode> nodes)
  {
    if (nodes == null)
    {
      return EMPTY_STRING_ARRAY;
    }

    String[] strings = new String [nodes.size()];
    int i = 0;
    for (ASTNode node : nodes)
    {
      strings[i++] = facadeHelper.toString(node);
    }
    return strings;
  }

  /**
   * Temporarily disables tracking for all AST nodes that are tracked by this <code>ASTJNode</code>.
   * <p>
   * Used when modified <code>ASTJNode</code>s are removed from the tree, and then possibly inserted again.
   * 
   * @see #enableTrackAndReplace() 
   * @see #trackAndReplace(ASTNode, String)
   */
  protected void disableTrackAndReplace()
  {
    ASTJCompilationUnit compilationUnit = (ASTJCompilationUnit)facadeHelper.getCompilationUnit(this);

    if (compilationUnit != null)
    {
      for (ASTNode node : trackedContentsMap.keySet())
      {
        compilationUnit.getAllTrackedContentsMap().remove(node);
      }

      // disable commenting out
      if (isCommentedOut)
      {
        compilationUnit.getCommentedOutNodes().remove(getASTNode());
      }
    }
  }

  /**
   * Enables disabled tracking for all AST nodes that are tracked by this <code>ASTJNode</code>.
   * <p>
   * Used when modified <code>ASTJNode</code>s are removed from the tree, and then inserted again.
   * 
   * @see #disableTrackAndReplace()
   * @see #trackAndReplace(ASTNode, String)
   */
  protected void enableTrackAndReplace()
  {
    ASTJCompilationUnit compilationUnit = (ASTJCompilationUnit)facadeHelper.getCompilationUnit(this);

    if (compilationUnit != null)
    {
      compilationUnit.getAllTrackedContentsMap().putAll(trackedContentsMap);
      
      // enable commenting out
      if (isCommentedOut)
      {
        compilationUnit.getCommentedOutNodes().add(getASTNode());
      }
    }
  }
  
  /**
   * Wraps up inserting new node. Notifies the children and the range computer that the node is inserted.
   * 
   * @param newNode
   */
  private void finishInsert(ASTJNode<?> newNode)
  {
    // mark the node as moved if node has been removed      
    ASTNode removedASTNode = newNode.getRemovedASTNode();
    if (removedASTNode != null)
    {
      nodeToBeMoved(removedASTNode);
    }

    newNode.setParent(this);
    newNode.ancestorInserted();
  }
  
  
  /**
   * Get the original contents of the node using the source.
   * <p>
   * The contents of the node in AST implementation includes only the node itself without
   * the leading or trailing whitespace.
   * If the node has a Javadoc comment, it is included in the contents. No other leading or trailing
   * comments are included in the node contents.
   * <p>
   * Note that this method returns the contents before any modifications. This method will not
   * return the correct contents if the node has been cloned, removed or moved.
   * 
   * @see org.eclipse.emf.codegen.merge.java.facade.JNode#getContents()
   */
  public String getContents()
  {
    return facadeHelper.toString(getASTNode());
  }

  /**
   * @return the removedASTNode
   */
  protected ASTNode getRemovedASTNode()
  {
    return removedASTNode;
  }

  /**
   * Inserts AST node wrapped by new node beside target node in the list defined by given property.
   * <p>
   * No checks are performed if the new node can be inserted nor if the target node exists. 
   * 
   * @param newNode
   * @param property 
   * @param targetNode
   * @param before
   */
  protected void insert(ASTJNode<?> newNode, ChildListPropertyDescriptor property, ASTJNode<?> targetNode, boolean before)
  {
    ListRewrite listRewrite = rewriter.getListRewrite(getASTNode(), property);
    if (before)
    {
      listRewrite.insertBefore(newNode.getWrappedObject(), targetNode.getWrappedObject(), null);
    }
    else
    {
      listRewrite.insertAfter(newNode.getWrappedObject(), targetNode.getWrappedObject(), null);
    }

    finishInsert(newNode);
  }

  /**
   * Inserts AST node wrapped by new node at the beginning of the list of nodes defined by given property.
   * <p>
   * No checks are performed if the new node can be inserted nor if the target node exists. 
   * 
   * @param newNode
   * @param property
   */
  protected void insertFirst(ASTJNode<?> newNode, ChildListPropertyDescriptor property)
  {
    ListRewrite listRewrite = rewriter.getListRewrite(getASTNode(), property);
    listRewrite.insertFirst(newNode.getWrappedObject(), null);

    finishInsert(newNode);
  }

  /**
   * Inserts AST node wrapped by new node at the end of the list of nodes defined by given property.
   * <p>
   * No checks are performed if the new node can be inserted nor if the target node exists. 
   * 
   * @param newNode
   * @param property
   */
  protected void insertLast(ASTJNode<?> newNode, ChildListPropertyDescriptor property)
  {
    ListRewrite listRewrite = rewriter.getListRewrite(getASTNode(), property);
    listRewrite.insertLast(newNode.getWrappedObject(), null);

    finishInsert(newNode);
  }

  /**
   * Inserts <code>newSibling</code> as a child of this node before or after the given <code>node</code>.
   * <p>
   * Default implementation does nothing and returns <code>false</code>.
   * 
   * @param node
   * @param newSibling to insert
   * @param before <code>true</code> if <code>newSibling</code> must be before <code>node</code>, <code>false</code> if after
   * @return <code>true</code> if operation successful, <code>false</code> otherwise 
   */
  public boolean insertSibling(ASTJNode<?> node, ASTJNode<?> newSibling, boolean before)
  {
    return false;
  }

  /**
   * Notifies the range computer of {@link ASTRewrite} that the node will be moved.
   * 
   * @param node to be moved
   * 
   * @see CommentAwareSourceRangeComputer#computeSourceRange(ASTNode) 
   */
  protected void nodeToBeMoved(ASTNode node)
  {
    TargetSourceRangeComputer sourceRangeComputer = rewriter.getExtendedSourceRangeComputer();
    if (sourceRangeComputer instanceof CommentAwareSourceRangeComputer)
    {
      ((CommentAwareSourceRangeComputer)sourceRangeComputer).unmarkNodeForRemoval(node);
    }
  }

  /**
   * Notifies the range computer of {@link ASTRewrite} that the node will be removed.
   * 
   * @param node to be removed
   * @see CommentAwareSourceRangeComputer#computeSourceRange(ASTNode) 
   */
  protected void nodeToBeRemoved(ASTNode node)
  {
    TargetSourceRangeComputer sourceRangeComputer = rewriter.getExtendedSourceRangeComputer();
    if (sourceRangeComputer instanceof CommentAwareSourceRangeComputer)
    {
      ((CommentAwareSourceRangeComputer)sourceRangeComputer).markNodeForRemoval(node);
    }
  }

  /**
   * Removes a node.
   * <p>
   * Default implementation does nothing and returns <code>false</code>.
   * 
   * @param node must be a child of this node
   * @return <code>true</code> if operation successful, <code>false</code> otherwise 
   */
  public boolean remove(ASTJNode<?> node)
  {
    return false;
  }

  /**
   * Removes AST node wrapped by given <code>ASTJNode</code> from the given property.
   * <p>
   * This method notifies children and source range computer that the node is removed, then creates
   * a place-holder node that allows to insert the removed node later.
   * <p>
   * No checks are performed if the node is a child of this node. 
   * 
   * @param node
   * @param property
   */
  protected void remove(ASTJNode<?> node, ChildListPropertyDescriptor property)
  {
    node.ancestorToBeRemoved();

    ASTNode astNodeToBeRemoved = node.getWrappedObject();

    // if are dealing with original, not cloned node
    if (astNodeToBeRemoved.getParent() != null && astNodeToBeRemoved.getLocationInParent() != null)
    {
      // mark the node to be removed
      nodeToBeRemoved(astNodeToBeRemoved);

      // assume that the node is being moved (to allow insertion after)
      node.setRemovedASTNode(astNodeToBeRemoved);

      ASTNode moveTargetASTNode = rewriter.createMoveTarget(astNodeToBeRemoved);
      node.setWrappedObject(moveTargetASTNode);
      getFacadeHelper().updateObjectToNodeMap(node);
    }

    // remove the node
    removeNodeFromListProperty(getASTNode(), astNodeToBeRemoved, property);
    node.setParent(null);
  }

  /**
   * Removes value from list property.
   * 
   * @param parentNode
   * @param nodeToRemove
   * @param property
   */
  protected void removeNodeFromListProperty(ASTNode parentNode, ASTNode nodeToRemove, ChildListPropertyDescriptor property)
  {
    if (ASTFacadeHelper.DEBUG)
    {
      facadeHelper.logInfo("Removing node from list property <" + nodeToRemove + ">");
    }

    ListRewrite listRewrite = getRewriter().getListRewrite(parentNode, property);
    listRewrite.remove(nodeToRemove, null);
  }

  /**
   * Permanently disables tracking for the given AST node. 
   * <p>
   * The given <code>node</code> will be modified only by {@link ASTRewrite}.
   * 
   * @param node to disable tracking for 
   *
   * @see #disableTrackAndReplace()
   * @see #trackAndReplace(ASTNode, String)
   */
  protected void removeTrackAndReplace(ASTNode node)
  {
    trackedContentsMap.remove(node);

    ASTJCompilationUnit compilationUnit = (ASTJCompilationUnit)facadeHelper.getCompilationUnit(this);

    if (compilationUnit != null)
    {
      compilationUnit.getAllTrackedContentsMap().remove(node);
    }
    // TODO handle situation when ASTJNode is removed (compilationUnit is null)
  }

  /**
   * Default implementation does nothing.
   * 
   * @see org.eclipse.emf.codegen.merge.java.facade.JNode#setFlags(int)
   */
  public void setFlags(int flags)
  {
    // default implementation does nothing
  }

  /**
   * Sets the list property to the given array of values. For each string in the array,
   * a string place-holder node is created.
   * 
   * @param node
   * @param values
   * @param property
   * @param nodeType of the string place holders to create
   */
  protected void setListNodeProperty(ASTNode node, String[] values, ChildListPropertyDescriptor property, int nodeType)
  {
    ListRewrite listRewrite = rewriter.getListRewrite(node, property);
    
    @SuppressWarnings("unchecked")    
    List<ASTNode> oldValues = listRewrite.getRewrittenList();

    // clear old values
    for (ASTNode oldValue : oldValues)
    {
      listRewrite.remove(oldValue, null);
    }

    // insert new values
    for (String value : values)
    {
      listRewrite.insertLast(rewriter.createStringPlaceholder(value, nodeType), null);
    }
  }

  /**
   * Sets the property of the node to the given value.
   * <p>
   * Note that the type <code>value</code> parameter will only be checked by 
   * {@link ASTRewrite#set(ASTNode, StructuralPropertyDescriptor, Object, org.eclipse.text.edits.TextEditGroup)}
   * <p>
   * 
   * @param node
   * @param value
   * @param property
   */
  protected void setNodeProperty(ASTNode node, Object value, StructuralPropertyDescriptor property)
  {
    if (ASTFacadeHelper.DEBUG)
    {
      facadeHelper.logInfo("Setting node property <" + property + "> to <" + value + ">");
    }
    rewriter.set(node, property, value, null);
  }

  /**
   * Sets the property of the node to the given string value.
   * <p>
   * Note that the type <code>value</code> parameter will only be checked by 
   * {@link ASTRewrite#set(ASTNode, StructuralPropertyDescriptor, Object, org.eclipse.text.edits.TextEditGroup)}
   * <p>
   * 
   * @param node
   * @param stringValue
   * @param property
   * @param nodeType
   */
  protected void setNodeProperty(ASTNode node, String stringValue, StructuralPropertyDescriptor property, int nodeType)
  {
    if (ASTFacadeHelper.DEBUG)
    {
      facadeHelper.logInfo("Setting node property to <" + stringValue + ">");
    }
    Object value = null;
    if (stringValue != null)
    {
      value = rewriter.createStringPlaceholder(stringValue, nodeType);
    }
    rewriter.set(node, property, value, null);
  }

  /**
   * Adds strings from strings list to existing array of strings, returns resulting
   * string array, and clears the list of strings.
   * 
   * @param strings can <b>not</b> be <code>null</code>
   * @param stringsList can be <code>null</code>
   */
  protected String[] combineArrayAndList(String[] strings, List<String> stringsList)
  {
    if (stringsList != null && stringsList.size() > 0)
    {
      if (strings.length > 0)
      {
        String[] newStringArray = new String[strings.length + stringsList.size()];
        for (int i = 0; i < strings.length; i++)
        {
          newStringArray[i] = strings[i];
        }
        for (int i = 0; i < stringsList.size(); i++)
        {
          newStringArray[strings.length + i] = stringsList.get(i);
        }
        strings = newStringArray;
      }
      else
      {
        strings = stringsList.toArray(EMPTY_STRING_ARRAY);
      }
      stringsList.clear();
    }
    return strings;
  }
  
  /**
   * @param removedASTNode the removedASTNode to set
   */
  protected void setRemovedASTNode(ASTNode removedASTNode)
  {
    this.removedASTNode = removedASTNode;
  }

  /**
   * Sets the property of the node. The position of the property value is tracked and value
   * is replaced by  <code>stringValue</code> during rewrite process.
   * 
   * @param node to set property of
   * @param stringValue
   * @param property must be {@link SimplePropertyDescriptor} or {@link ChildPropertyDescriptor}
   * @param nodeType to use if value is null, and new node has to be created
   */
  protected void setTrackedNodeProperty(ASTNode node, String stringValue, StructuralPropertyDescriptor property, int nodeType)
  {
    if (ASTFacadeHelper.DEBUG)
    {
      facadeHelper.logInfo("Setting tracked node property to string <" + stringValue + ">");
    }

    ASTNode nodeValue = (ASTNode)rewriter.get(node, property);

    if (stringValue == null || "".equals(stringValue))
    {
      removeTrackAndReplace(nodeValue);
      rewriter.set(node, property, null, null);
    }
    else if (nodeValue != null)
    {
      trackAndReplace(nodeValue, stringValue);
    }
    else
    // stringValue not null, nodeValue is null
    {
      nodeValue = rewriter.createStringPlaceholder(stringValue, nodeType);
      rewriter.set(node, property, nodeValue, null);
      trackAndReplace(nodeValue, stringValue);
    }
  }

  /**
   * Records that the given <code>node</code> must have the given <code>contents</code>.
   * <p> 
   * During rewrite process, the position of the <code>node</code> will be tracked, and the node
   * will replaced by the given <code>contents</code>. No changes will be made to <code>contents</code>.
   * <p>
   * This method should be used to undo correction of indentation done by {@link ASTRewrite#rewriteAST(org.eclipse.jface.text.IDocument, Map)}
   * on string place-holder nodes ({@link ASTRewrite#createStringPlaceholder(String, int)})
   * during rewrite process.
   * <p>
   * Note that all the tracked nodes must exist in the rewritten tree. If some tracked nodes are removed from rewritten tree, wrong
   * contents may be replaced. Use {@link #disableTrackAndReplace()} when <code>ASTJNode</code>s are removed.
   * 
   * @param node
   * @param contents
   */
  protected void trackAndReplace(ASTNode node, String contents)
  {
    if (ASTFacadeHelper.DEBUG)
    {
      facadeHelper.logInfo("Track and replace <" + node + "> by <" + contents + ">");
    }

    trackedContentsMap.put(node, contents);

    ASTJCompilationUnit compilationUnit = ((ASTJCompilationUnit)facadeHelper.getCompilationUnit(this));
    if (compilationUnit != null)
    {
      compilationUnit.getAllTrackedContentsMap().put(node, contents);
    }
  }

  /**
   * Comments out this node. Node is not removed from the tree, and is still returned by getChildren().
   */
  public void commentOut()
  {
    isCommentedOut = true;
    ASTJCompilationUnit compilationUnit = ((ASTJCompilationUnit)facadeHelper.getCompilationUnit(this));
    if (compilationUnit != null)
    {
      compilationUnit.getCommentedOutNodes().add(this.getASTNode());
    }
  }
}
