/*******************************************************************************
* Copyright (c) 2007, 2012 Institute for Software, HSR Hochschule fuer Technik
* Rapperswil, University of applied sciences 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:
* Institute for Software - initial API and implementation
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.osgi.util.NLS;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
import org.eclipse.cdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.PreferenceConstants;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTVisibilityLabel;
import org.eclipse.cdt.internal.ui.refactoring.utils.VisibilityEnum;
/**
* Adds a declaration to an existing class via the ModificationCollector. Automatically determines
* an appropriate insertion point for the desired visibility.
*
* @author Mirko Stocker
*/
public class ClassMemberInserter {
public static class InsertionInfo {
private final IASTNode parentNode;
/**
* The node before which the new node should be inserted. A null value indicates insertion
* to the end of parentNode
*/
private IASTNode insertBeforeNode; //
/** Visibility label to insert before the new node or null. */
private ICPPASTVisibilityLabel prologue;
/** Visibility label to insert after the new node or null. */
private ICPPASTVisibilityLabel epilogue;
public InsertionInfo(IASTNode parentNode, IASTNode insertBeforeNode) {
this.parentNode = parentNode;
this.insertBeforeNode = insertBeforeNode;
}
public InsertionInfo(IASTNode parentNode) {
this(parentNode, null);
}
public IASTNode getParentNode() {
return parentNode;
}
public IASTNode getInsertBeforeNode() {
return insertBeforeNode;
}
public ICPPASTVisibilityLabel getPrologue() {
return prologue;
}
public ICPPASTVisibilityLabel getEpilogue() {
return epilogue;
}
}
// Not instantiatable. All methods are static.
private ClassMemberInserter() {
}
/**
* Inserts a node inside a class declaration.
*
* @param classNode the type specifier of the class declaration
* @param visibility desired visibility of the inserted declaration
* @param nodeToAdd the node to add
* @param isField whether the node being inserted is a field declaration or not
* @param collector the modification collector recording the insertion
* @return the rewriter for making further modifications to the inserted node
*/
public static ASTRewrite createChange(ICPPASTCompositeTypeSpecifier classNode,
VisibilityEnum visibility, IASTNode nodeToAdd, boolean isField,
ModificationCollector collector) {
List<ASTRewrite> addedNodesRewrites =
createChange(classNode, visibility, Collections.singletonList(nodeToAdd), isField, collector);
return addedNodesRewrites.get(0);
}
/**
* Inserts one more adjacent nodes inside a class declaration.
*
* @param classNode the type specifier of the class declaration
* @param visibility desired visibility of the inserted declarations
* @param nodesToAdd the nodes to add
* @param isField whether the nodes being inserted are field declaration or not
* @param collector the modification collector recording the insertion
* @return the rewriters for making further modifications to the inserted nodes
*/
public static List<ASTRewrite> createChange(ICPPASTCompositeTypeSpecifier classNode,
VisibilityEnum visibility, List<IASTNode> nodesToAdd, boolean isField,
ModificationCollector collector) {
InsertionInfo info = findInsertionPoint(classNode, visibility, isField);
ASTRewrite rewrite = collector.rewriterForTranslationUnit(classNode.getTranslationUnit());
if (info.getPrologue() != null) {
rewrite.insertBefore(info.getParentNode(), info.getInsertBeforeNode(), info.getPrologue(),
createEditDescription(classNode));
}
List<ASTRewrite> addedNodeRewrites = new ArrayList<>(nodesToAdd.size());
for (IASTNode node : nodesToAdd) {
addedNodeRewrites.add(rewrite.insertBefore(info.getParentNode(), info.getInsertBeforeNode(), node,
createEditDescription(classNode)));
}
if (info.getEpilogue() != null) {
rewrite.insertBefore(info.getParentNode(), info.getInsertBeforeNode(), info.getEpilogue(),
createEditDescription(classNode));
}
return addedNodeRewrites;
}
public static InsertionInfo findInsertionPoint(ICPPASTCompositeTypeSpecifier classNode,
VisibilityEnum visibility, boolean isField) {
InsertionInfo info = new InsertionInfo(classNode);
VisibilityEnum defaultVisibility = classNode.getKey() == IASTCompositeTypeSpecifier.k_struct ?
VisibilityEnum.v_public : VisibilityEnum.v_private;
VisibilityEnum currentVisibility = defaultVisibility;
boolean ascendingVisibilityOrder = isAscendingVisibilityOrder(classNode);
int lastFunctionIndex = -1;
int lastFieldIndex = -1;
int lastMatchingVisibilityIndex = -1;
int lastPrecedingVisibilityIndex = -1;
IASTDeclaration[] members = classNode.getMembers();
// Find the insert location by iterating over the elements of the class
// and remembering the last element with the matching visibility and the last element
// with preceding visibility (according to the visibility order preference).
for (int i = 0; i < members.length; i++) {
IASTDeclaration declaration = members[i];
if (declaration instanceof ICPPASTVisibilityLabel) {
currentVisibility = VisibilityEnum.from((ICPPASTVisibilityLabel) declaration);
}
if (currentVisibility == visibility) {
lastMatchingVisibilityIndex = i;
if (declaration instanceof IASTSimpleDeclaration) {
IASTDeclarator[] declarators = ((IASTSimpleDeclaration) declaration).getDeclarators();
if (declarators.length > 0 && declarators[0] != null) {
if (declarators[0] instanceof IASTFunctionDeclarator) {
lastFunctionIndex = i;
} else {
lastFieldIndex = i;
}
}
} else if (declaration instanceof ICPPASTFunctionDefinition) {
lastFunctionIndex = i;
}
} else if (currentVisibility.compareTo(visibility) < 0 == ascendingVisibilityOrder) {
lastPrecedingVisibilityIndex = i;
}
}
int index = isField && lastFieldIndex >= 0 || !isField && lastFunctionIndex < 0 ?
lastFieldIndex : lastFunctionIndex;
if (index < 0)
index = lastMatchingVisibilityIndex;
if (index < 0)
index = lastPrecedingVisibilityIndex;
index++;
if (index < members.length)
info.insertBeforeNode = members[index];
if (lastMatchingVisibilityIndex < 0 &&
!(index == 0 && classNode.getKey() == IASTCompositeTypeSpecifier.k_struct &&
visibility == defaultVisibility)) {
info.prologue = new CPPASTVisibilityLabel(visibility.getVisibilityLabelValue());
if (index == 0 && info.insertBeforeNode != null &&
!(info.insertBeforeNode instanceof ICPPASTVisibilityLabel)) {
info.epilogue = new CPPASTVisibilityLabel(defaultVisibility.getVisibilityLabelValue());
}
}
return info;
}
private static TextEditGroup createEditDescription(ICPPASTCompositeTypeSpecifier classNode) {
return new TextEditGroup(NLS.bind(Messages.AddDeclarationNodeToClassChange_AddDeclaration,
classNode.getName()));
}
private static boolean isAscendingVisibilityOrder(ICPPASTCompositeTypeSpecifier classNode) {
IPreferencesService preferences = Platform.getPreferencesService();
IASTTranslationUnit ast = classNode.getTranslationUnit();
ITranslationUnit tu = ast.getOriginatingTranslationUnit();
IProject project = tu != null ? tu.getCProject().getProject() : null;
return preferences.getBoolean(CUIPlugin.PLUGIN_ID,
PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER, false,
PreferenceConstants.getPreferenceScopes(project));
}
}