| /******************************************************************************* |
| * Copyright (c) 2004, 2013 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Technical University Berlin - extended API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.dom.rewrite; |
| |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.Block; |
| import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; |
| import org.eclipse.jdt.core.dom.FieldDeclaration; |
| import org.eclipse.jdt.core.dom.RoleTypeDeclaration; |
| import org.eclipse.jdt.core.dom.Statement; |
| import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; |
| import org.eclipse.jdt.core.dom.TypeDeclaration; |
| import org.eclipse.jdt.internal.core.dom.rewrite.ListRewriteEvent; |
| import org.eclipse.jdt.internal.core.dom.rewrite.NodeInfoStore; |
| import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEvent; |
| import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEventStore; |
| import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEventStore.CopySourceInfo; |
| import org.eclipse.text.edits.TextEditGroup; |
| |
| /** |
| * For describing manipulations to a child list property of an AST node. |
| * <p> |
| * This class is not intended to be subclassed. |
| * </p> |
| * @see ASTRewrite#getListRewrite(ASTNode, ChildListPropertyDescriptor) |
| * @since 3.0 |
| */ |
| public final class ListRewrite { |
| |
| private ASTNode parent; |
| private ChildListPropertyDescriptor childListProperty; |
| private ASTRewrite rewriter; |
| |
| |
| /* package*/ ListRewrite(ASTRewrite rewriter, ASTNode parent, ChildListPropertyDescriptor childProperty) { |
| this.rewriter= rewriter; |
| this.parent= parent; |
| //{ObjectTeams: correct invalid combination TypeDeclaration/RoleTypeDeclaration: |
| // note: clients like ModifierCorrectionSubProcessor might need to handle |
| // RoleTypeDeclarations without explict knowledge. |
| if ( parent.getNodeType() == ASTNode.ROLE_TYPE_DECLARATION |
| && childProperty.getNodeClass() == TypeDeclaration.class) |
| { |
| if (childProperty == TypeDeclaration.MODIFIERS2_PROPERTY) |
| childProperty = RoleTypeDeclaration.MODIFIERS2_PROPERTY; |
| else if (childProperty == TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY) |
| childProperty = RoleTypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY; |
| else if (childProperty == TypeDeclaration.TYPE_PARAMETERS_PROPERTY) |
| childProperty = RoleTypeDeclaration.TYPE_PARAMETERS_PROPERTY; |
| else if (childProperty == TypeDeclaration.BODY_DECLARATIONS_PROPERTY) |
| childProperty = RoleTypeDeclaration.BODY_DECLARATIONS_PROPERTY; |
| } |
| // SH} |
| this.childListProperty= childProperty; |
| } |
| |
| private RewriteEventStore getRewriteStore() { |
| return this.rewriter.getRewriteEventStore(); |
| } |
| |
| private ListRewriteEvent getEvent() { |
| return getRewriteStore().getListEvent(this.parent, this.childListProperty, true); |
| } |
| |
| /** |
| * Returns the parent of the list for which this list rewriter was created. |
| |
| * @return the node that contains the list for which this list rewriter was created |
| * @see #getLocationInParent() |
| * @since 3.1 |
| */ |
| public ASTNode getParent() { |
| return this.parent; |
| } |
| |
| /** |
| * Returns the property of the parent node for which this list rewriter was created. |
| * |
| * @return the property of the parent node for which this list rewriter was created |
| * @see #getParent() |
| * @since 3.1 |
| */ |
| public StructuralPropertyDescriptor getLocationInParent() { |
| return this.childListProperty; |
| } |
| |
| /** |
| * Removes the given node from its parent's list property in the rewriter. |
| * The node must be contained in the list. |
| * The AST itself is not actually modified in any way; rather, the rewriter |
| * just records a note that this node has been removed from this list. |
| * |
| * @param node the node being removed. The node can either be an original node in this list |
| * or (since 3.4) a new node already inserted or used as replacement in this AST rewriter. |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits, or <code>null</code> if ungrouped |
| * @throws IllegalArgumentException if the node is null, or if the node is not |
| * part of this rewriter's AST, or if the described modification is invalid |
| * (not a member of this node's original list) |
| */ |
| public void remove(ASTNode node, TextEditGroup editGroup) { |
| if (node == null) { |
| throw new IllegalArgumentException(); |
| } |
| RewriteEvent event= getEvent().removeEntry(node); |
| if (editGroup != null) { |
| getRewriteStore().setEventEditGroup(event, editGroup); |
| } |
| } |
| |
| /** |
| * Returns the ASTRewrite instance from which this ListRewriter has been created from. |
| * @return the parent AST Rewriter instance. |
| * @since 3.1 |
| */ |
| public ASTRewrite getASTRewrite() { |
| return this.rewriter; |
| } |
| |
| |
| /** |
| * Replaces the given node from its parent's list property in the rewriter. |
| * The node must be contained in the list. |
| * The replacement node must either be brand new (not part of the original AST) |
| * or a placeholder node (for example, one created by |
| * {@link ASTRewrite#createCopyTarget(ASTNode)}, |
| * {@link ASTRewrite#createMoveTarget(ASTNode)}, |
| * or {@link ASTRewrite#createStringPlaceholder(String, int)}). The AST itself |
| * is not actually modified in any way; rather, the rewriter just records |
| * a note that this node has been replaced in this list. |
| * |
| * @param node the node being removed. The node can either be an original node in this list |
| * or (since 3.4) a new node already inserted or used as replacement in this AST rewriter. |
| * @param replacement the replacement node, or <code>null</code> if no |
| * replacement |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits, or <code>null</code> if ungrouped |
| * @throws IllegalArgumentException if the node is null, or if the node is not part |
| * of this rewriter's AST, or if the replacement node is not a new node (or |
| * placeholder), or if the described modification is otherwise invalid |
| * (not a member of this node's original list) |
| */ |
| public void replace(ASTNode node, ASTNode replacement, TextEditGroup editGroup) { |
| if (node == null) { |
| throw new IllegalArgumentException(); |
| } |
| validatePropertyType(node); |
| RewriteEvent event= getEvent().replaceEntry(node, replacement); |
| if (editGroup != null) { |
| getRewriteStore().setEventEditGroup(event, editGroup); |
| } |
| } |
| |
| /** |
| * Inserts the given node into the list after the given element. |
| * The existing node <code>previousElement</code> must be in the list, either as an original or as a new |
| * node that has been inserted. |
| * The inserted node must either be brand new (not part of the original AST) |
| * or a placeholder node (for example, one created by |
| * {@link ASTRewrite#createCopyTarget(ASTNode)}, |
| * {@link ASTRewrite#createMoveTarget(ASTNode)}, |
| * or {@link ASTRewrite#createStringPlaceholder(String, int)}). The AST itself |
| * is not actually modified in any way; rather, the rewriter just records |
| * a note that this node has been inserted into the list. |
| * |
| * @param node the node to insert |
| * @param previousElement the element after which the given node is to be inserted |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits, or <code>null</code> if ungrouped |
| * @throws IllegalArgumentException if the node or element is null, |
| * or if the node is not part of this rewriter's AST, or if the inserted node |
| * is not a new node (or placeholder), or if <code>previousElement</code> is not a member |
| * of the list (original or new), or if the described modification is |
| * otherwise invalid |
| */ |
| public void insertAfter(ASTNode node, ASTNode previousElement, TextEditGroup editGroup) { |
| if (node == null || previousElement == null) { |
| throw new IllegalArgumentException(); |
| } |
| int index= getEvent().getIndex(previousElement, ListRewriteEvent.BOTH); |
| if (index == -1) { |
| throw new IllegalArgumentException("Node does not exist"); //$NON-NLS-1$ |
| } |
| internalInsertAt(node, index + 1, true, editGroup); |
| } |
| |
| /** |
| * Inserts the given node into the list before the given element. |
| * The existing node <code>nextElement</code> must be in the list, either as an original or as a new |
| * node that has been inserted. |
| * The inserted node must either be brand new (not part of the original AST) |
| * or a placeholder node (for example, one created by |
| * {@link ASTRewrite#createCopyTarget(ASTNode)}, |
| * {@link ASTRewrite#createMoveTarget(ASTNode)}, |
| * or {@link ASTRewrite#createStringPlaceholder(String, int)}). The AST itself |
| * is not actually modified in any way; rather, the rewriter just records |
| * a note that this node has been inserted into the list. |
| * |
| * @param node the node to insert |
| * @param nextElement the element before which the given node is to be inserted |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits, or <code>null</code> if ungrouped |
| * @throws IllegalArgumentException if the node or next element is null, |
| * or if the node is not part of this rewriter's AST, or if the inserted node |
| * is not a new node (or placeholder), or if <code>nextElement</code> is not a member |
| * of the list (original or new), or if the described modification is |
| * otherwise invalid |
| */ |
| public void insertBefore(ASTNode node, ASTNode nextElement, TextEditGroup editGroup) { |
| if (node == null || nextElement == null) { |
| throw new IllegalArgumentException(); |
| } |
| int index= getEvent().getIndex(nextElement, ListRewriteEvent.BOTH); |
| if (index == -1) { |
| throw new IllegalArgumentException("Node does not exist"); //$NON-NLS-1$ |
| } |
| internalInsertAt(node, index, false, editGroup); |
| } |
| |
| /** |
| * Inserts the given node into the list at the start of the list. |
| * Equivalent to <code>insertAt(node, 0, editGroup)</code>. |
| * |
| * @param node the node to insert |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits, or <code>null</code> if ungrouped |
| * @throws IllegalArgumentException if the node is null, or if the node is not part |
| * of this rewriter's AST, or if the inserted node is not a new node (or |
| * placeholder), or if the described modification is otherwise invalid |
| * (not a member of this node's original list) |
| * @see #insertAt(ASTNode, int, TextEditGroup) |
| */ |
| public void insertFirst(ASTNode node, TextEditGroup editGroup) { |
| if (node == null) { |
| throw new IllegalArgumentException(); |
| } |
| internalInsertAt(node, 0, false, editGroup); |
| } |
| |
| /** |
| * Inserts the given node into the list at the end of the list. |
| * Equivalent to <code>insertAt(node, -1, editGroup)</code>. |
| * |
| * @param node the node to insert |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits, or <code>null</code> if ungrouped |
| * @throws IllegalArgumentException if the node is null, or if the node is not part |
| * of this rewriter's AST, or if the inserted node is not a new node (or |
| * placeholder), or if the described modification is otherwise invalid |
| * (not a member of this node's original list) |
| * @see #insertAt(ASTNode, int, TextEditGroup) |
| */ |
| public void insertLast(ASTNode node, TextEditGroup editGroup) { |
| if (node == null) { |
| throw new IllegalArgumentException(); |
| } |
| internalInsertAt(node, -1, true, editGroup); |
| } |
| |
| /** |
| * Inserts the given node into the list at the given index. |
| * The index corresponds to a combined list of original and new nodes; |
| * removed or replaced nodes are still in the combined list. |
| * The inserted node must either be brand new (not part of the original AST) |
| * or a placeholder node (for example, one created by |
| * {@link ASTRewrite#createCopyTarget(ASTNode)}, |
| * {@link ASTRewrite#createMoveTarget(ASTNode)}, |
| * or {@link ASTRewrite#createStringPlaceholder(String, int)}). The AST itself |
| * is not actually modified in any way; rather, the rewriter just records |
| * a note that this node has been inserted into the list. |
| * |
| * @param node the node to insert |
| * @param index insertion index in the combined list of original and |
| * inserted nodes; <code>-1</code> indicates insertion as the last element |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits, or <code>null</code> if ungrouped |
| * @throws IllegalArgumentException if the node is null, or if the node is not part |
| * of this rewriter's AST, or if the inserted node is not a new node (or |
| * placeholder), or if the described modification is otherwise invalid |
| * (not a member of this node's original list) |
| * @throws IndexOutOfBoundsException if the index is negative and not -1, |
| * or if it is larger than the size of the combined list |
| */ |
| public void insertAt(ASTNode node, int index, TextEditGroup editGroup) { |
| if (node == null) { |
| throw new IllegalArgumentException(); |
| } |
| internalInsertAt(node, index, isInsertBoundToPreviousByDefault(node), editGroup); |
| } |
| |
| private void internalInsertAt(ASTNode node, int index, boolean boundToPrevious, TextEditGroup editGroup) { |
| validatePropertyType(node); |
| RewriteEvent event= getEvent().insert(node, index); |
| if (boundToPrevious) { |
| getRewriteStore().setInsertBoundToPrevious(node); |
| } |
| if (editGroup != null) { |
| getRewriteStore().setEventEditGroup(event, editGroup); |
| } |
| } |
| |
| private void validatePropertyType(ASTNode node) { |
| if (!RewriteEventStore.DEBUG) { |
| return; |
| } |
| if (!this.childListProperty.getElementType().isAssignableFrom(node.getClass())) { |
| String message = node.getClass().getName() + " is not a valid type for " + this.childListProperty.getNodeClass().getName() //$NON-NLS-1$ |
| + " property '" + this.childListProperty.getId() + "'. Must be " + this.childListProperty.getElementType().getName(); //$NON-NLS-1$ //$NON-NLS-2$ |
| throw new IllegalArgumentException(message); |
| } |
| } |
| |
| |
| private ASTNode createTargetNode(ASTNode first, ASTNode last, boolean isMove, ASTNode replacingNode, TextEditGroup editGroup) { |
| if (first == null || last == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| NodeInfoStore nodeStore= this.rewriter.getNodeStore(); |
| ASTNode placeholder= nodeStore.newPlaceholderNode(first.getNodeType()); // revisit: could use list type |
| if (placeholder == null) { |
| throw new IllegalArgumentException("Creating a target node is not supported for nodes of type" + first.getClass().getName()); //$NON-NLS-1$ |
| } |
| |
| Block internalPlaceHolder= nodeStore.createCollapsePlaceholder(); |
| CopySourceInfo info= getRewriteStore().createRangeCopy(this.parent, this.childListProperty, first, last, isMove, internalPlaceHolder, replacingNode, editGroup); |
| nodeStore.markAsCopyTarget(placeholder, info); |
| |
| return placeholder; |
| } |
| |
| /** |
| * Creates and returns a placeholder node for a true copy of a range of nodes of the |
| * current list. |
| * The placeholder node can either be inserted as new or used to replace an |
| * existing node. When the document is rewritten, a copy of the source code |
| * for the given node range is inserted into the output document at the position |
| * corresponding to the placeholder (indentation is adjusted). |
| * |
| * @param first the node that starts the range |
| * @param last the node that ends the range |
| * @return the new placeholder node |
| * @throws IllegalArgumentException An exception is thrown if the first or last node |
| * are <code>null</code>, if a node is not a child of the current list or if the first node |
| * is not before the last node. An <code>IllegalArgumentException</code> is |
| * also thrown if the copied range is overlapping with an other moved or copied range. |
| */ |
| public final ASTNode createCopyTarget(ASTNode first, ASTNode last) { |
| if (first == last) { |
| return this.rewriter.createCopyTarget(first); |
| } else { |
| return createTargetNode(first, last, false, null, null); |
| } |
| } |
| |
| /** |
| * Creates and returns a placeholder node for a move of a range of nodes of the |
| * current list. |
| * The placeholder node can either be inserted as new or used to replace an |
| * existing node. When the document is rewritten, a copy of the source code |
| * for the given node range is inserted into the output document at the position |
| * corresponding to the placeholder (indentation is adjusted). |
| * |
| * @param first the node that starts the range |
| * @param last the node that ends the range |
| * @return the new placeholder node |
| * @throws IllegalArgumentException An exception is thrown if the first or last node |
| * are <code>null</code>, if a node is not a child of the current list or if the first node |
| * is not before the last node. An <code>IllegalArgumentException</code> is |
| * also thrown if the moved range is overlapping with an other moved or copied range. |
| * |
| * @since 3.1 |
| */ |
| public final ASTNode createMoveTarget(ASTNode first, ASTNode last) { |
| return createMoveTarget(first, last, null, null); |
| } |
| |
| /** |
| * Creates and returns a placeholder node for a move of a range of nodes of the |
| * current list. The moved nodes can optionally be replaced by a specified node. |
| * |
| * The placeholder node can either be inserted as new or used to replace an |
| * existing node. When the document is rewritten, a copy of the source code |
| * for the given node range is inserted into the output document at the position |
| * corresponding to the placeholder (indentation is adjusted). |
| * |
| * @param first the node that starts the range |
| * @param last the node that ends the range |
| * @param replacingNode a node that is set at the location of the moved nodes |
| * or <code>null</code> to remove all nodes |
| * @param editGroup the edit group in which to collect the corresponding |
| * text edits fro a replace, or <code>null</code> if ungrouped |
| * @return the new placeholder node |
| * @throws IllegalArgumentException An exception is thrown if the first or |
| * last node are <code>null</code>, if a node is not a child of the current list or |
| * if the first node is not before the last node. An <code>IllegalArgumentException |
| * </code> is also thrown if the moved range is overlapping with an other moved |
| * or copied range. |
| * |
| * @since 3.1 |
| */ |
| public final ASTNode createMoveTarget(ASTNode first, ASTNode last, ASTNode replacingNode, TextEditGroup editGroup) { |
| if (first == last) { |
| replace(first, replacingNode, editGroup); |
| return this.rewriter.createMoveTarget(first); |
| } else { |
| return createTargetNode(first, last, true, replacingNode, editGroup); |
| } |
| } |
| |
| /* |
| * Heuristic to decide if a inserted node is bound to previous or the next sibling. |
| */ |
| private boolean isInsertBoundToPreviousByDefault(ASTNode node) { |
| return (node instanceof Statement || node instanceof FieldDeclaration); |
| } |
| |
| /** |
| * Returns the original nodes in the list property managed by this |
| * rewriter. The returned list is unmodifiable. |
| * |
| * @return a list of all original nodes in the list |
| */ |
| public List getOriginalList() { |
| List list= (List) getEvent().getOriginalValue(); |
| return Collections.unmodifiableList(list); |
| } |
| |
| /** |
| * Returns the nodes in the revised list property managed by this |
| * rewriter. The returned list is unmodifiable. |
| * |
| * @return a list of all nodes in the list taking into account |
| * all the described changes |
| */ |
| public List getRewrittenList() { |
| List list= (List) getEvent().getNewValue(); |
| return Collections.unmodifiableList(list); |
| } |
| |
| } |