From 88ac1b7a85a5e390e4b0b352e0fec6a07cbac5ee Mon Sep 17 00:00:00 2001 From: Markus Schorn Date: Wed, 9 Jan 2008 14:37:20 +0000 Subject: [PATCH] Proposal for AST-rewriter API, bug 214334. --- .../org.eclipse.cdt.core/META-INF/MANIFEST.MF | 5 +- .../cdt/core/dom/rewrite/ASTRewrite.java | 201 ++++++++++++++++++ .../core/dom/rewrite/ASTLiteralNode.java | 77 +++++++ .../core/dom/rewrite/ASTModification.java | 63 ++++++ .../core/dom/rewrite/ASTModificationMap.java | 88 ++++++++ .../dom/rewrite/ASTModificationStore.java | 63 ++++++ .../core/dom/rewrite/ASTRewriteAnalyzer.java | 22 ++ 7 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/rewrite/ASTRewrite.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTLiteralNode.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModification.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationMap.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationStore.java create mode 100644 core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF index ef275abe466..facec27d6a4 100644 --- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF @@ -19,6 +19,7 @@ Export-Package: org.eclipse.cdt.core, org.eclipse.cdt.core.dom.parser, org.eclipse.cdt.core.dom.parser.c, org.eclipse.cdt.core.dom.parser.cpp, + org.eclipse.cdt.core.dom.rewrite, org.eclipse.cdt.core.envvar, org.eclipse.cdt.core.formatter, org.eclipse.cdt.core.index, @@ -45,6 +46,7 @@ Export-Package: org.eclipse.cdt.core, org.eclipse.cdt.internal.core.dom.parser;x-friends:="org.eclipse.cdt.refactoring", org.eclipse.cdt.internal.core.dom.parser.c;x-friends:="org.eclipse.cdt.refactoring", org.eclipse.cdt.internal.core.dom.parser.cpp;x-friends:="org.eclipse.cdt.ui,org.eclipse.cdt.refactoring", + org.eclipse.cdt.internal.core.dom.rewrite;x-friends:="org.eclipse.cdt.core.tests", org.eclipse.cdt.internal.core.envvar, org.eclipse.cdt.internal.core.index;x-friends:="org.eclipse.cdt.ui", org.eclipse.cdt.internal.core.index.provider, @@ -92,6 +94,7 @@ Require-Bundle: org.eclipse.core.resources;bundle-version="[3.2.0,4.0.0)", org.eclipse.text;bundle-version="[3.2.0,4.0.0)", org.eclipse.core.variables;bundle-version="[3.1.100,4.0.0)", org.eclipse.core.filebuffers;bundle-version="[3.2.0,4.0.0)", - org.eclipse.core.filesystem;bundle-version="[1.1.0,2.0.0)" + org.eclipse.core.filesystem;bundle-version="[1.1.0,2.0.0)", + org.eclipse.ltk.core.refactoring;bundle-version="3.4.0" Eclipse-LazyStart: true Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/rewrite/ASTRewrite.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/rewrite/ASTRewrite.java new file mode 100644 index 00000000000..047250ec6b5 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/rewrite/ASTRewrite.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. 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: + * Markus Schorn - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.core.dom.rewrite; + +import org.eclipse.cdt.core.dom.ast.IASTComment; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; +import org.eclipse.cdt.core.dom.ast.IASTProblem; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; +import org.eclipse.cdt.internal.core.dom.rewrite.ASTLiteralNode; +import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification; +import org.eclipse.cdt.internal.core.dom.rewrite.ASTModificationStore; +import org.eclipse.cdt.internal.core.dom.rewrite.ASTRewriteAnalyzer; +import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification.ModificationKind; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.text.edits.TextEditGroup; + +/** + * Infrastructure for modifying code by describing changes to AST nodes. + * The AST rewriter collects descriptions of modifications to nodes and + * translates these descriptions into text edits that can then be applied to + * the original source. This is all done without actually modifying the + * original AST. The rewrite infrastructure tries to generate minimal + * text changes, preserve existing comments and indentation, and follow code + * formatter settings. + *

+ * The initial implementation does not support nodes that implement + * {@link IASTPreprocessorStatement}, {@link IASTComment} or {@link IASTProblem}. + *

+ * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the CDT team. + *

+ * @since 5.0 + */ +public final class ASTRewrite { + + /** + * Creates a rewriter for a translation unit. + */ + public static ASTRewrite create(IASTTranslationUnit node) { + return new ASTRewrite(node, new ASTModificationStore(), null); + } + + private final IASTNode fRoot; + private final ASTModificationStore fModificationStore; + private final ASTModification fParentMod; + + public ASTRewrite(IASTNode root, ASTModificationStore modStore, ASTModification parentMod) { + fRoot= root; + fModificationStore= modStore; + fParentMod= parentMod; + } + + /** + * Creates and returns a node for a source string that is to be inserted into + * the output document. + * The string will be inserted without being reformatted beyond correcting + * the indentation level. + * + * @param code the string to be inserted; lines should not have extra indentation + * @return a synthetic node representing the literal code. + * @throws IllegalArgumentException if the code is null. + */ + public final IASTNode createLiteralNode(String code) { + return new ASTLiteralNode(code); + } + + /** + * Removes the given node in this rewriter. The ast is not modified, the rewriter + * just records the removal. + * + * @param node the node being removed + * @param editGroup the edit group in which to collect the corresponding + * text edits, or null + * @throws IllegalArgumentException if the node is null, the node is not + * part of this rewriter's AST. + */ + public final void remove(IASTNode node, TextEditGroup editGroup) { + checkBelongsToAST(node); + checkSupportedNode(node); + ASTModification mod= new ASTModification(ModificationKind.REPLACE, node, null, editGroup); + fModificationStore.storeModification(fParentMod, mod); + } + + /** + * Replaces the given node in this rewriter. The ast is not modified, the rewriter + * just records the replacement. + * The replacement node can be part of a translation-unit or it is a synthetic + * (newly created) node. + * + * @param node the node being replaced + * @param replacement the node replacing the given one + * @param editGroup the edit group in which to collect the corresponding + * text edits, or null + * @return a rewriter for further rewriting the replacement node. + * @throws IllegalArgumentException if the node or the replacement is null, or if the node is not + * part of this rewriter's AST + */ + public final ASTRewrite replace(IASTNode node, IASTNode replacement, TextEditGroup editGroup) { + if (replacement == null) { + throw new IllegalArgumentException(); + } + checkBelongsToAST(node); + checkSupportedNode(node); + checkSupportedNode(replacement); + ASTModification mod= new ASTModification(ModificationKind.REPLACE, node, replacement, editGroup); + fModificationStore.storeModification(fParentMod, mod); + return new ASTRewrite(replacement, fModificationStore, mod); + } + + /** + * Inserts the given node in this rewriter. The ast is not modified, the rewriter + * just records the insertion. + * The new node can be part of a translation-unit or it is a synthetic + * (newly created) node. + * @param parent the parent the new node is added to. + * @param insertionPoint the node before which the insertion shall be done, or null for inserting after the last child. + * @param newNode the node being inserted + * @param editGroup the edit group in which to collect the corresponding + * text edits, or null + * @return a rewriter for further rewriting the inserted node. + * @throws IllegalArgumentException if the parent or the newNode is null, or if the parent is not + * part of this rewriter's AST, or the insertionPoint is not a child of the parent. + */ + public final ASTRewrite insertBefore(IASTNode parent, IASTNode insertionPoint, IASTNode newNode, TextEditGroup editGroup) { + if (parent != fRoot) { + checkBelongsToAST(parent); + } + if (newNode == null) { + throw new IllegalArgumentException(); + } + checkSupportedNode(parent); + checkSupportedNode(insertionPoint); + checkSupportedNode(newNode); + + ASTModification mod; + if (insertionPoint == null) { + mod= new ASTModification(ModificationKind.APPEND_CHILD, parent, newNode, editGroup); + } + else { + if (insertionPoint.getParent() != parent) { + throw new IllegalArgumentException(); + } + mod= new ASTModification(ModificationKind.INSERT_BEFORE, insertionPoint, newNode, editGroup); + } + fModificationStore.storeModification(fParentMod, mod); + return new ASTRewrite(newNode, fModificationStore, mod); + } + + /** + * Converts all modifications recorded by this rewriter into the change object required by the + * refactoring framework. + *

+ * Calling this methods does not discard the modifications on record. Subsequence modifications + * are added to the ones already on record. If this method is called again later, + * the resulting text edit object will accurately reflect the net cumulative affect of all those changes. + *

+ * + * @return Change object describing the changes to the + * document corresponding to the changes recorded by this rewriter + * @since 5.0 + */ + public Change rewriteAST() { + if (!(fRoot instanceof IASTTranslationUnit)) { + throw new IllegalArgumentException("This API can only be used for the root rewrite object."); //$NON-NLS-1$ + } + return ASTRewriteAnalyzer.rewriteAST((IASTTranslationUnit) fRoot, fModificationStore); + } + + private void checkBelongsToAST(IASTNode node) { + while (node != null) { + node= node.getParent(); + if (node == fRoot) { + return; + } + } + throw new IllegalArgumentException(); + } + + private void checkSupportedNode(IASTNode node) { + if (node instanceof IASTComment) { + throw new IllegalArgumentException("Rewriting comments is not yet supported"); //$NON-NLS-1$ + } + if (node instanceof IASTPreprocessorStatement) { + throw new IllegalArgumentException("Rewriting preprocessor statements is not yet supported"); //$NON-NLS-1$ + } + if (node instanceof IASTProblem) { + throw new IllegalArgumentException("Rewriting problem nodes is supported"); //$NON-NLS-1$ + } + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTLiteralNode.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTLiteralNode.java new file mode 100644 index 00000000000..01227434487 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTLiteralNode.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. 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: + * Markus Schorn - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.core.dom.rewrite; + +import org.eclipse.cdt.core.dom.ast.ASTNodeProperty; +import org.eclipse.cdt.core.dom.ast.ASTVisitor; +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; + +/** + * Used for inserting literal code by means of the rewrite facility. The node + * will never appear in an AST tree. + * @since 5.0 + */ +public class ASTLiteralNode implements IASTNode { + private final String fCode; + + public ASTLiteralNode(String code) { + fCode= code; + } + + public String getRawSignature() { + return fCode; + } + + public boolean accept(ASTVisitor visitor) { + return false; + } + + public boolean contains(IASTNode node) { + return false; + } + + public String getContainingFilename() { + return null; + } + + public IASTFileLocation getFileLocation() { + return null; + } + + public IASTNodeLocation[] getNodeLocations() { + return null; + } + + public IASTNode getParent() { + return null; + } + + public ASTNodeProperty getPropertyInParent() { + return null; + } + + public IASTTranslationUnit getTranslationUnit() { + return null; + } + + public boolean isPartOfTranslationUnitFile() { + return false; + } + + public void setParent(IASTNode node) { + } + + public void setPropertyInParent(ASTNodeProperty property) { + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModification.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModification.java new file mode 100644 index 00000000000..aa886c3a2aa --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModification.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. 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: + * Markus Schorn - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.core.dom.rewrite; + +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.text.edits.TextEditGroup; + +public class ASTModification { + public enum ModificationKind { + REPLACE, + INSERT_BEFORE, + APPEND_CHILD + } + + private final ModificationKind fKind; + private final IASTNode fTargetNode; + private final IASTNode fNewNode; + private final TextEditGroup fTextEditGroup; + + public ASTModification(ModificationKind kind, IASTNode targetNode, IASTNode newNode, TextEditGroup group) { + fKind= kind; + fTargetNode= targetNode; + fNewNode= newNode; + fTextEditGroup= group; + } + + /** + * @return the kind of the modification + */ + public ModificationKind getKind() { + return fKind; + } + + /** + * Return the target node of this modification. + */ + public IASTNode getTargetNode() { + return fTargetNode; + } + + /** + * Return the new node of this modification, or null + */ + public IASTNode getNewNode() { + return fNewNode; + } + + /** + * Returns the edit group to collect the text edits of this modification. + * @return the edit group or null. + */ + public TextEditGroup getAssociatedEditGroup() { + return fTextEditGroup; + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationMap.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationMap.java new file mode 100644 index 00000000000..eaf332e224b --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationMap.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. 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: + * Markus Schorn - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.core.dom.rewrite; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.cdt.core.dom.ast.IASTNode; +import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification.ModificationKind; + +/** + * Represents a list of modifications to an ast-node. If there are nested modifications + * to nodes introduced by insertions or replacements, these modifications are collected + * in separate modification maps. I.e. a modification map represents one level of + * modifications. + * @see ASTModificationStore + * @since 5.0 + */ +public class ASTModificationMap { + + private HashMap> fModifications= new HashMap>(); + + /** + * Adds a modification to this modification map. + */ + public void addModification(ASTModification mod) { + final IASTNode targetNode = mod.getTargetNode(); + List mods= fModifications.get(targetNode); + if (mods == null || mods.isEmpty()) { + mods= new ArrayList(); + mods.add(mod); + fModifications.put(targetNode, mods); + } + else { + switch (mod.getKind()) { + case REPLACE: + if (mods.get(mods.size()-1).getKind() != ModificationKind.INSERT_BEFORE ) { + throw new IllegalArgumentException("Attempt to replace a node that has been modified"); //$NON-NLS-1$ + } + mods.add(mod); + break; + case APPEND_CHILD: + if (mods.get(mods.size()-1).getKind() == ModificationKind.REPLACE) { + throw new IllegalArgumentException("Attempt to modify a node that has been replaced"); //$NON-NLS-1$ + } + mods.add(mod); + break; + case INSERT_BEFORE: + int i; + for (i=mods.size()-1; i>=0; i--) { + if (mods.get(i).getKind() == ModificationKind.INSERT_BEFORE) { + break; + } + } + mods.add(i+1, mod); + break; + } + } + } + + /** + * Returns the list of modifications for a given node. The list can contain different modifications. + * It is guaranteed that INSERT_BEFORE modifications appear first. Furthermore, if there is a + * REPLACE modification the list will not contain any other REPLACE or APPEND_CHILD modifications. + * @return the modification list or null. + */ + public List getModificationsForNode(IASTNode node) { + return Collections.unmodifiableList(fModifications.get(node)); + } + + /** + * Returns the collection of nodes that are modified by this modification map. + */ + public Collection getModifiedNodes() { + return Collections.unmodifiableCollection(fModifications.keySet()); + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationStore.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationStore.java new file mode 100644 index 00000000000..661981e63a2 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTModificationStore.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. 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: + * Markus Schorn - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.core.dom.rewrite; + +import java.util.HashMap; + +/** + * Collects modifications to an AST in a hierarchical manner. The store gives access to + * the root modifications and for each modification you can obtain the nested modifications here. + * @since 5.0 + */ +public class ASTModificationStore { + private HashMap fNestedModMaps; + + public ASTModificationStore() { + fNestedModMaps= new HashMap(); + } + + /** + * Adds a potentially nested modification to the store. + * @param parentMod the parent for a nested modification, or null + * @param mod a modification + */ + public void storeModification(ASTModification parentMod, ASTModification mod) { + ASTModificationMap modMap = createNestedModificationMap(parentMod); + modMap.addModification(mod); + } + + private ASTModificationMap createNestedModificationMap(ASTModification parentMod) { + ASTModificationMap modMap= fNestedModMaps.get(parentMod); + if (modMap == null) { + modMap= new ASTModificationMap(); + fNestedModMaps.put(parentMod, modMap); + } + return modMap; + } + + /** + * Returns the modifications that are performed directly on the AST, or null if there + * are no modifications. + * @return the root modifications or null. + */ + public ASTModificationMap getRootModifications() { + return fNestedModMaps.get(null); + } + + /** + * Returns the modifications that are performed on the node that has been introduced by the + * given modification. If there are no nested modifications, null is returned. + * @return the nested modifications or null. + */ + public ASTModificationMap getNestedModifications(ASTModification mod) { + return fNestedModMaps.get(mod); + } +} diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java new file mode 100644 index 00000000000..311a1c25cee --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. 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: + * Markus Schorn - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.core.dom.rewrite; + +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; +import org.eclipse.ltk.core.refactoring.Change; + +public class ASTRewriteAnalyzer { + + public static Change rewriteAST(IASTTranslationUnit root, ASTModificationStore modificationStore) { + throw new UnsupportedOperationException("The rewriter is not yet implemented"); //$NON-NLS-1$ + } + +}