/******************************************************************************* * Copyright (c) 2000, 2011 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.che.ide.ext.java.jdt.internal.corext.codemanipulation; import org.eclipse.che.ide.ext.java.jdt.core.Flags; import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTNode; import org.eclipse.che.ide.ext.java.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.BodyDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.ClassInstanceCreation; import org.eclipse.che.ide.ext.java.jdt.core.dom.CompilationUnit; import org.eclipse.che.ide.ext.java.jdt.core.dom.FieldDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.ITypeBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.IVariableBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.MethodDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.Modifier; import org.eclipse.che.ide.ext.java.jdt.core.dom.NodeFinder; import org.eclipse.che.ide.ext.java.jdt.core.dom.TypeDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ListRewrite; import org.eclipse.che.ide.ext.java.jdt.core.formatter.CodeFormatter; import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.ASTNodes; import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.ModifierRewrite; import org.eclipse.che.ide.ext.java.jdt.internal.corext.util.CodeFormatterUtil; import org.eclipse.che.ide.ext.java.worker.WorkerMessageHandler; import org.eclipse.che.ide.ext.java.jdt.text.Document; import org.eclipse.che.ide.ext.java.jdt.text.edits.MalformedTreeException; import org.eclipse.che.ide.ext.java.jdt.text.edits.TextEdit; import org.eclipse.che.ide.runtime.Assert; import org.eclipse.che.ide.runtime.CoreException; import org.eclipse.che.ide.runtime.IStatus; import org.eclipse.che.ide.runtime.OperationCanceledException; import org.eclipse.che.ide.runtime.Status; import org.eclipse.che.ide.api.text.BadLocationException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Workspace runnable to add accessor methods to fields. * * @since 3.1 */ public final class AddGetterSetterOperation { /** The empty strings constant */ private static final String[] EMPTY_STRINGS = new String[0]; /** The accessor fields */ private final IVariableBinding[] fAccessorFields; /** Should the resulting edit be applied? */ private boolean fApply = true; /** The resulting text edit */ private TextEdit fEdit = null; /** The getter fields */ private final IVariableBinding[] fGetterFields; /** Should the compilation unit content be saved? */ private final boolean fSave; /** The setter fields */ private final IVariableBinding[] fSetterFields; /** The code generation settings to use */ private final CodeGenerationSettings fSettings; /** Should all existing members be skipped? */ private boolean fSkipAllExisting = false; /** Should the accessors be sorted? */ private boolean fSort = false; /** The type declaration to add the constructors to */ private final ITypeBinding fType; /** The compilation unit ast node */ private final CompilationUnit fASTRoot; /** The visibility flags of the new accessors */ private int fVisibility = Modifier.PUBLIC; private final TypeDeclaration typeDeclaration; private final int insertPos; private final Document document; /** * 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 ITypeBinding type, final TypeDeclaration typeDeclaration, int insertPos, Document document, final IVariableBinding[] getters, final IVariableBinding[] setters, final IVariableBinding[] accessors, final CompilationUnit unit, final CodeGenerationSettings settings, final boolean apply, final boolean save) { this.typeDeclaration = typeDeclaration; this.insertPos = insertPos; this.document = document; Assert.isNotNull(type); Assert.isNotNull(unit); Assert.isNotNull(settings); fType = type; fGetterFields = getters; fSetterFields = setters; fAccessorFields = accessors; fASTRoot = unit; 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 ITypeBinding type, final IVariableBinding field, final String contents, final ListRewrite rewrite, final ASTNode insertion) { final String delimiter = StubUtility.getLineDelimiterUsed(); final MethodDeclaration declaration = (MethodDeclaration)rewrite.getASTRewrite().createStringPlaceholder( CodeFormatterUtil.format(CodeFormatter.K_CLASS_BODY_DECLARATIONS, contents, 0, delimiter), 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 IVariableBinding field, final ListRewrite rewrite) throws CoreException, OperationCanceledException { final ITypeBinding type = field.getDeclaringClass(); final String name = GetterSetterUtil.getGetterName(field, null); final MethodDeclaration existing = GetterSetterUtil.findMethod(name, EMPTY_STRINGS, false, typeDeclaration); if (existing == null || !querySkipExistingMethods(existing)) { BodyDeclaration sibling = null; int ins = 0; if (existing != null) { sibling = findNextSibling(existing); removeExistingAccessor(existing, rewrite); ins = sibling.getStartPosition(); } else ins = insertPos; ASTNode insertion = StubUtility2.getNodeToInsertBefore(rewrite, ins); addNewAccessor( type, field, GetterSetterUtil.getGetterStub(field, name, fSettings.createComments, fVisibility | (field.getModifiers() & Flags.AccStatic)), rewrite, insertion); } } /** * Returns the element after the give element. * * @param member * a Java element * @return the next sibling of the given element or <code>null</code> * @throws JavaModelException * thrown if the element could not be accessed */ public static BodyDeclaration findNextSibling(BodyDeclaration member) { ASTNode parent = member.getParent(); if (parent instanceof TypeDeclaration) { List<BodyDeclaration> elements = ((TypeDeclaration)parent).bodyDeclarations(); for (int i = elements.size() - 2; i >= 0; i--) { if (member.equals(elements.get(i))) { return elements.get(i + 1); } } } return null; } /** * 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 IVariableBinding field, ASTRewrite astRewrite, final ListRewrite rewrite) throws CoreException, OperationCanceledException { final ITypeBinding type = field.getDeclaringClass(); final String name = GetterSetterUtil.getSetterName(field, null); final MethodDeclaration existing = GetterSetterUtil.findMethod(name, new String[]{field.getType().getQualifiedName()}, false, typeDeclaration); if (existing == null || !querySkipExistingMethods(existing)) { BodyDeclaration sibling = null; int ins = 0; if (existing != null) { sibling = findNextSibling(existing); removeExistingAccessor(existing, rewrite); ins = sibling.getStartPosition(); } else ins = insertPos; ASTNode insertion = StubUtility2.getNodeToInsertBefore(rewrite, ins); addNewAccessor( type, field, GetterSetterUtil.getSetterStub(field, name, fSettings.createComments, fVisibility | (field.getModifiers() & Flags.AccStatic)), rewrite, insertion); if (Flags.isFinal(field.getModifiers())) { ASTNode fieldDecl = getField(field); // ASTNodes.getParent(NodeFinder.perform(fASTRoot, field.getNameRange()), FieldDeclaration.FIELD_DECLARATION); if (fieldDecl != null) { ModifierRewrite.create(astRewrite, fieldDecl).setModifiers(0, Modifier.FINAL, null); } } } } /** * @param field * @return */ private ASTNode getField(IVariableBinding field) { for (FieldDeclaration f : typeDeclaration.getFields()) { if (f.getType().resolveBinding().getBinaryName().equals(field.getType().getBinaryName())) { List<VariableDeclarationFragment> fragments = f.fragments(); for (VariableDeclarationFragment frag : fragments) { if (frag.getName().getFullyQualifiedName().equals(field.getName())) return f; } } } return null; } /** * Returns the resulting text edit. * * @return the resulting text edit */ public final TextEdit getResultingEdit() { return fEdit; } /** * 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 MethodDeclaration method) throws OperationCanceledException { if (!fSkipAllExisting) { //TODO // switch (fSkipExistingQuery.doQuery(method)) // { // case IRequestQuery.CANCEL : // throw new OperationCanceledException(); // case IRequestQuery.NO : // return false; // case IRequestQuery.YES_ALL : // fSkipAllExisting = true; // } return false; } return true; } /** * Removes an existing accessor method. * * @param accessor * the accessor method to remove * @param rewrite * the list rewrite to use */ private void removeExistingAccessor(final MethodDeclaration accessor, final ListRewrite rewrite) { final MethodDeclaration declaration = (MethodDeclaration)ASTNodes.getParent(NodeFinder.perform(rewrite.getParent().getRoot(), accessor.getName() .getStartPosition(), accessor.getName().getLength()), MethodDeclaration.METHOD_DECLARATION); if (declaration != null) rewrite.remove(declaration, null); } /* * @see org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse.core.runtime.IProgressMonitor) */ public final void run() throws CoreException { 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 = (ClassInstanceCreation)ASTNodes.getParent(NodeFinder.perform(fASTRoot, typeDeclaration.getName() .getStartPosition(), typeDeclaration.getName().getLength()), ClassInstanceCreation.CLASS_INSTANCE_CREATION); if (creation != null) { final AnonymousClassDeclaration declaration = creation.getAnonymousClassDeclaration(); if (declaration != null) listRewriter = astRewrite.getListRewrite(declaration, AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY); } } else { final AbstractTypeDeclaration declaration = (AbstractTypeDeclaration)ASTNodes.getParent(NodeFinder.perform(fASTRoot, typeDeclaration.getName() .getStartPosition(), typeDeclaration.getName().getLength()), AbstractTypeDeclaration.TYPE_DECLARATION); if (declaration != null) listRewriter = astRewrite.getListRewrite(declaration, declaration.getBodyDeclarationsProperty()); } if (listRewriter == null) { throw new CoreException(new Status(IStatus.ERROR, "", IStatus.ERROR, "Input type not found", null)); } fSkipAllExisting = false; Set<IVariableBinding> accessors = new HashSet<IVariableBinding>(Arrays.asList(fAccessorFields)); Set<IVariableBinding> getters = new HashSet<IVariableBinding>(Arrays.asList(fGetterFields)); Set<IVariableBinding> setters = new HashSet<IVariableBinding>(Arrays.asList(fSetterFields)); IVariableBinding[] fields = fType.getDeclaredFields(); // 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); } } } for (int i = 0; i < fields.length; i++) { if (getters.contains(fields[i])) { generateGetterMethod(fields[i], listRewriter); } } for (int i = 0; i < fields.length; i++) { if (setters.contains(fields[i])) { generateSetterMethod(fields[i], astRewrite, listRewriter); } } fEdit = astRewrite.rewriteAST(document, WorkerMessageHandler.get().getOptions()); if (fApply) { fEdit.apply(document); } } catch (MalformedTreeException ignore) { } catch (BadLocationException ignore) { } } /** * 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; } }