blob: b7b6706a7c88c34ff3c6b7d960fd706fbf8d7d4d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 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
* Mateusz Wenus <mateusz.wenus@gmail.com> - [override method] generate in declaration order [code generation] - https://bugs.eclipse.org/bugs/show_bug.cgi?id=140971
*******************************************************************************/
package org.eclipse.jdt.internal.corext.codemanipulation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.internal.ui.preferences.formatter.FormatterProfileManager;
/**
* Workspace runnable to add accessor methods to fields.
*
* @since 3.1
*/
public final class AddGetterSetterOperation implements IWorkspaceRunnable {
/** The empty strings constant */
private static final String[] EMPTY_STRINGS= new String[0];
/** The accessor fields */
private final IField[] fAccessorFields;
/** Should the resulting edit be applied? */
private boolean fApply= true;
/** The resulting text edit */
private TextEdit fEdit= null;
/** The getter fields */
private final IField[] fGetterFields;
/** The insertion point, or <code>null</code> */
private final IJavaElement fInsert;
/** Should the compilation unit content be saved? */
private final boolean fSave;
/** The setter fields */
private final IField[] fSetterFields;
/** The code generation settings to use */
private final CodeGenerationSettings fSettings;
/** Should all existing members be skipped? */
private boolean fSkipAllExisting= false;
/** The skip existing request query */
private final IRequestQuery fSkipExistingQuery;
/** Should the accessors be sorted? */
private boolean fSort= false;
/** The type declaration to add the constructors to */
private final IType fType;
/** The compilation unit ast node */
private final CompilationUnit fASTRoot;
/** The visibility flags of the new accessors */
private int fVisibility= Modifier.PUBLIC;
/**
* Creates a new add getter setter operation.
*
* @param type the type to add the accessors to
* @param getters the fields to create getters for
* @param setters the fields to create setters for
* @param accessors the fields to create both
* @param unit the compilation unit ast node
* @param skipExistingQuery the request query
* @param insert the insertion point, or <code>null</code>
* @param settings the code generation settings to use
* @param apply <code>true</code> if the resulting edit should be applied, <code>false</code> otherwise
* @param save <code>true</code> if the changed compilation unit should be saved, <code>false</code> otherwise
*/
public AddGetterSetterOperation(final IType type, final IField[] getters, final IField[] setters, final IField[] accessors, final CompilationUnit unit, final IRequestQuery skipExistingQuery, final IJavaElement insert, final CodeGenerationSettings settings, final boolean apply, final boolean save) {
Assert.isNotNull(type);
Assert.isNotNull(unit);
Assert.isNotNull(settings);
fType= type;
fGetterFields= getters;
fSetterFields= setters;
fAccessorFields= accessors;
fASTRoot= unit;
fSkipExistingQuery= skipExistingQuery;
fInsert= insert;
fSettings= settings;
fSave= save;
fApply= apply;
}
/**
* Adds a new accessor for the specified field.
*
* @param type the type
* @param field the field
* @param contents the contents of the accessor method
* @param rewrite the list rewrite to use
* @param insertion the insertion point
* @throws JavaModelException if an error occurs
*/
private void addNewAccessor(final IType type, final IField field, final String contents, final ListRewrite rewrite, final ASTNode insertion) throws JavaModelException {
final String delimiter= StubUtility.getLineDelimiterUsed(type);
final MethodDeclaration declaration= (MethodDeclaration) rewrite.getASTRewrite().createStringPlaceholder(
CodeFormatterUtil.format(CodeFormatter.K_CLASS_BODY_DECLARATIONS, contents, 0, delimiter, FormatterProfileManager.getProjectSettings(field.getJavaProject())),
ASTNode.METHOD_DECLARATION);
if (insertion != null)
rewrite.insertBefore(declaration, insertion, null);
else
rewrite.insertLast(declaration, null);
}
/**
* Generates a new getter method for the specified field
*
* @param field the field
* @param rewrite the list rewrite to use
* @throws CoreException if an error occurs
* @throws OperationCanceledException if the operation has been cancelled
*/
private void generateGetterMethod(final IField field, final ListRewrite rewrite) throws CoreException, OperationCanceledException {
final IType type= field.getDeclaringType();
final String name= GetterSetterUtil.getGetterName(field, null);
final IMethod existing= JavaModelUtil.findMethod(name, EMPTY_STRINGS, false, type);
if (existing == null || !querySkipExistingMethods(existing)) {
IJavaElement sibling= null;
if (existing != null) {
sibling= StubUtility.findNextSibling(existing);
removeExistingAccessor(existing, rewrite);
} else
sibling= fInsert;
ASTNode insertion= StubUtility2.getNodeToInsertBefore(rewrite, sibling);
addNewAccessor(type, field, GetterSetterUtil.getGetterStub(field, name, fSettings.createComments, fVisibility | (field.getFlags() & Flags.AccStatic)), rewrite, insertion);
}
}
/**
* Generates a new setter method for the specified field
*
* @param field the field
* @param astRewrite the AST rewrite to use
* @param rewrite the list rewrite to use
* @throws CoreException if an error occurs
* @throws OperationCanceledException if the operation has been cancelled
*/
private void generateSetterMethod(final IField field, ASTRewrite astRewrite, final ListRewrite rewrite) throws CoreException, OperationCanceledException {
final IType type= field.getDeclaringType();
final String name= GetterSetterUtil.getSetterName(field, null);
final IMethod existing= JavaModelUtil.findMethod(name, new String[] { field.getTypeSignature()}, false, type);
if (existing == null || !querySkipExistingMethods(existing)) {
IJavaElement sibling= null;
if (existing != null) {
sibling= StubUtility.findNextSibling(existing);
removeExistingAccessor(existing, rewrite);
} else
sibling= fInsert;
ASTNode insertion= StubUtility2.getNodeToInsertBefore(rewrite, sibling);
addNewAccessor(type, field, GetterSetterUtil.getSetterStub(field, name, fSettings.createComments, fVisibility | (field.getFlags() & Flags.AccStatic)), rewrite, insertion);
if (Flags.isFinal(field.getFlags())) {
FieldDeclaration fieldDecl= ASTNodes.getParent(NodeFinder.perform(fASTRoot, field.getNameRange()), FieldDeclaration.class);
if (fieldDecl != null) {
ModifierRewrite.create(astRewrite, fieldDecl).setModifiers(0, Modifier.FINAL, null);
}
}
}
}
/**
* Returns the resulting text edit.
*
* @return the resulting text edit
*/
public final TextEdit getResultingEdit() {
return fEdit;
}
/**
* Returns the scheduling rule for this operation.
*
* @return the scheduling rule
*/
public final ISchedulingRule getSchedulingRule() {
return ResourcesPlugin.getWorkspace().getRoot();
}
/**
* Returns the visibility modifier of the generated constructors.
*
* @return the visibility modifier
*/
public final int getVisibility() {
return fVisibility;
}
/**
* Should all existing members be skipped?
*
* @return <code>true</code> if they should be skipped, <code>false</code> otherwise
*/
public final boolean isSkipAllExisting() {
return fSkipAllExisting;
}
/**
* Queries the user whether to skip existing methods.
*
* @param method the method in question
* @return <code>true</code> to skip existing methods, <code>false</code> otherwise
* @throws OperationCanceledException if the operation has been cancelled
*/
private boolean querySkipExistingMethods(final IMethod method) throws OperationCanceledException {
if (!fSkipAllExisting) {
switch (fSkipExistingQuery.doQuery(method)) {
case IRequestQuery.CANCEL:
throw new OperationCanceledException();
case IRequestQuery.NO:
return false;
case IRequestQuery.YES_ALL:
fSkipAllExisting= true;
}
}
return true;
}
/**
* Removes an existing accessor method.
*
* @param accessor the accessor method to remove
* @param rewrite the list rewrite to use
* @throws JavaModelException if an error occurs
*/
private void removeExistingAccessor(final IMethod accessor, final ListRewrite rewrite) throws JavaModelException {
final MethodDeclaration declaration= ASTNodes.getParent(NodeFinder.perform(rewrite.getParent().getRoot(), accessor.getNameRange()), MethodDeclaration.class);
if (declaration != null)
rewrite.remove(declaration, null);
}
/*
* @see org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public final void run(IProgressMonitor monitor) throws CoreException {
if (monitor == null)
monitor= new NullProgressMonitor();
try {
monitor.setTaskName(CodeGenerationMessages.AddGetterSetterOperation_description);
monitor.beginTask("", fGetterFields.length + fSetterFields.length); //$NON-NLS-1$
final ICompilationUnit unit= fType.getCompilationUnit();
final ASTRewrite astRewrite= ASTRewrite.create(fASTRoot.getAST());
ListRewrite listRewriter= null;
if (fType.isAnonymous()) {
final ClassInstanceCreation creation= ASTNodes.getParent(NodeFinder.perform(fASTRoot, fType.getNameRange()), ClassInstanceCreation.class);
if (creation != null) {
final AnonymousClassDeclaration declaration= creation.getAnonymousClassDeclaration();
if (declaration != null)
listRewriter= astRewrite.getListRewrite(declaration, AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY);
}
} else {
final AbstractTypeDeclaration declaration= ASTNodes.getParent(NodeFinder.perform(fASTRoot, fType.getNameRange()), AbstractTypeDeclaration.class);
if (declaration != null)
listRewriter= astRewrite.getListRewrite(declaration, declaration.getBodyDeclarationsProperty());
}
if (listRewriter == null) {
throw new CoreException(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, IStatus.ERROR, CodeGenerationMessages.AddGetterSetterOperation_error_input_type_not_found, null));
}
fSkipAllExisting= (fSkipExistingQuery == null);
Set<IField> accessors = new HashSet<>(Arrays.asList(fAccessorFields));
Set<IField> getters = new HashSet<>(Arrays.asList(fGetterFields));
Set<IField> setters= new HashSet<>(Arrays.asList(fSetterFields));
IField[] fields= fType.getFields(); // generate methods in order of field declarations
if (!fSort) {
for (int i= 0; i < fields.length; i++) {
if (accessors.contains(fields[i])) {
generateGetterMethod(fields[i], listRewriter);
generateSetterMethod(fields[i], astRewrite, listRewriter);
monitor.worked(1);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}
for (int i= 0; i < fields.length; i++) {
if (getters.contains(fields[i])) {
generateGetterMethod(fields[i], listRewriter);
monitor.worked(1);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
for (int i= 0; i < fields.length; i++) {
if (setters.contains(fields[i])) {
generateSetterMethod(fields[i], astRewrite, listRewriter);
monitor.worked(1);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
fEdit= astRewrite.rewriteAST();
if (fApply) {
JavaModelUtil.applyEdit(unit, fEdit, fSave, new SubProgressMonitor(monitor, 1));
}
} finally {
monitor.done();
}
}
/**
* Determines whether existing members should be skipped.
*
* @param skip <code>true</code> to skip existing members, <code>false</code> otherwise
*/
public final void setSkipAllExisting(final boolean skip) {
fSkipAllExisting= skip;
}
public void setSort(boolean sort) {
fSort= sort;
}
/**
* Sets the visibility modifier of the generated constructors.
*
* @param visibility the visibility modifier
*/
public final void setVisibility(final int visibility) {
fVisibility= visibility;
}
}