1 /******************************************************************************* 2 * Copyright (c) 2010, 2019 Mateusz Matela and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Mateusz Matela <mateusz.matela@gmail.com> - [code manipulation] [dcr] toString() builder wizard - https://bugs.eclipse.org/bugs/show_bug.cgi?id=26070 13 * Mateusz Matela <mateusz.matela@gmail.com> - [toString] finish toString() builder wizard - https://bugs.eclipse.org/bugs/show_bug.cgi?id=267710 14 * Red Hat Inc. - moved to jdt.core.manipulation 15 *******************************************************************************/ 16 package org.eclipse.jdt.internal.corext.codemanipulation.tostringgeneration; 17 18 import java.util.Iterator; 19 import java.util.List; 20 21 import org.eclipse.core.runtime.CoreException; 22 import org.eclipse.core.runtime.IProgressMonitor; 23 import org.eclipse.core.runtime.NullProgressMonitor; 24 import org.eclipse.core.runtime.jobs.ISchedulingRule; 25 26 import org.eclipse.core.resources.IWorkspaceRunnable; 27 import org.eclipse.core.resources.ResourcesPlugin; 28 29 import org.eclipse.text.edits.TextEdit; 30 31 import org.eclipse.ltk.core.refactoring.RefactoringStatus; 32 33 import org.eclipse.jdt.core.ICompilationUnit; 34 import org.eclipse.jdt.core.IJavaElement; 35 import org.eclipse.jdt.core.JavaModelException; 36 import org.eclipse.jdt.core.dom.ASTMatcher; 37 import org.eclipse.jdt.core.dom.ASTNode; 38 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; 39 import org.eclipse.jdt.core.dom.BodyDeclaration; 40 import org.eclipse.jdt.core.dom.CompilationUnit; 41 import org.eclipse.jdt.core.dom.ITypeBinding; 42 import org.eclipse.jdt.core.dom.MethodDeclaration; 43 import org.eclipse.jdt.core.dom.SingleVariableDeclaration; 44 import org.eclipse.jdt.core.dom.rewrite.ListRewrite; 45 46 import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationMessages; 47 import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2Core; 48 import org.eclipse.jdt.internal.corext.dom.ASTNodes; 49 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; 50 import org.eclipse.jdt.internal.corext.util.JavaModelUtil; 51 52 53 /** 54 * <p> 55 * A workspace runnable to add implementation for <code>{@link java.lang.Object#toString()}</code> 56 * </p> 57 * 58 * @since 3.5 59 */ 60 public class GenerateToStringOperation implements IWorkspaceRunnable { 61 62 /** The insertion point, or <code>null</code> */ 63 private IJavaElement fInsert; 64 65 private CompilationUnitRewrite fRewrite; 66 67 private ToStringGenerationContext fContext; 68 69 private AbstractToStringGenerator fGenerator; 70 71 private CompilationUnit fUnit; 72 73 private boolean fApply= false; 74 75 private boolean fSave= false; 76 77 private TextEdit fEdit= null; 78 GenerateToStringOperation(IJavaElement insert, ToStringGenerationContext context, AbstractToStringGenerator generator, CompilationUnit unit, CompilationUnitRewrite rewrite, boolean apply, boolean save)79 private GenerateToStringOperation(IJavaElement insert, ToStringGenerationContext context, AbstractToStringGenerator generator, CompilationUnit unit, CompilationUnitRewrite rewrite, boolean apply, boolean save) { 80 fInsert= insert; 81 fContext= context; 82 fRewrite= rewrite; 83 fUnit= unit; 84 fGenerator= generator; 85 fApply= apply; 86 fSave= save; 87 } 88 89 @Override run(IProgressMonitor monitor)90 public void run(IProgressMonitor monitor) throws CoreException { 91 if (monitor == null) 92 monitor= new NullProgressMonitor(); 93 try { 94 monitor.beginTask("", 1); //$NON-NLS-1$ 95 monitor.setTaskName(CodeGenerationMessages.GenerateToStringOperation_description); 96 97 98 AbstractTypeDeclaration declaration= (AbstractTypeDeclaration)ASTNodes.findDeclaration(fContext.getTypeBinding(), fRewrite.getRoot()); 99 ListRewrite rewriter= fRewrite.getASTRewrite().getListRewrite(declaration, declaration.getBodyDeclarationsProperty()); 100 if (fContext.getTypeBinding() != null && rewriter != null) { 101 102 MethodDeclaration toStringMethod= fGenerator.generateToStringMethod(); 103 104 List<BodyDeclaration> list= declaration.bodyDeclarations(); 105 BodyDeclaration replace= findMethodToReplace(list, toStringMethod); 106 if (replace == null || ((Boolean)toStringMethod.getProperty(AbstractToStringGenerator.OVERWRITE_METHOD_PROPERTY)).booleanValue()) 107 insertMethod(toStringMethod, rewriter, replace); 108 109 for (MethodDeclaration method : fGenerator.generateHelperMethods()) { 110 replace= findMethodToReplace(list, method); 111 if (replace == null || ((Boolean)method.getProperty(AbstractToStringGenerator.OVERWRITE_METHOD_PROPERTY)).booleanValue()) { 112 insertMethod(method, rewriter, replace); 113 } 114 } 115 116 fEdit= fRewrite.createChange(true).getEdit(); 117 if (fApply) { 118 JavaModelUtil.applyEdit((ICompilationUnit)fUnit.getJavaElement(), fEdit, fSave, monitor); 119 } 120 } 121 122 } finally { 123 monitor.done(); 124 } 125 } 126 127 /** 128 * Returns the resulting text edit. 129 * 130 * @return the resulting edit 131 */ getResultingEdit()132 public final TextEdit getResultingEdit() { 133 return fEdit; 134 } 135 136 /** 137 * @return RefactoringStatus with eventual errors and warnings 138 */ checkConditions()139 public RefactoringStatus checkConditions() { 140 return fGenerator.checkConditions(); 141 } 142 143 144 insertMethod(MethodDeclaration method, ListRewrite rewriter, BodyDeclaration replace)145 protected void insertMethod(MethodDeclaration method, ListRewrite rewriter, BodyDeclaration replace) throws JavaModelException { 146 if (replace != null) { 147 rewriter.replace(replace, method, null); 148 } else { 149 ASTNode insertion= StubUtility2Core.getNodeToInsertBefore(rewriter, fInsert); 150 if (insertion != null) 151 rewriter.insertBefore(method, insertion, null); 152 else 153 rewriter.insertLast(method, null); 154 } 155 } 156 157 /** 158 * Determines if given method exists in a given list 159 * 160 * @param list list of method to search through 161 * @param method method to find 162 * @return declaration of method from the list that has the same name and parameter types, or 163 * null if not found 164 */ findMethodToReplace(final List<BodyDeclaration> list, MethodDeclaration method)165 protected BodyDeclaration findMethodToReplace(final List<BodyDeclaration> list, MethodDeclaration method) { 166 for (BodyDeclaration bodyDecl : list) { 167 if (bodyDecl instanceof MethodDeclaration) { 168 final MethodDeclaration method2= (MethodDeclaration)bodyDecl; 169 if (method2.getName().getIdentifier().equals(method.getName().getIdentifier()) && method2.parameters().size() == method.parameters().size()) { 170 Iterator<SingleVariableDeclaration> iterator1= method.parameters().iterator(); 171 Iterator<SingleVariableDeclaration> iterator2= method2.parameters().iterator(); 172 boolean ok= true; 173 while (iterator1.hasNext()) { 174 if (!iterator1.next().getType().subtreeMatch(new ASTMatcher(), iterator2.next().getType())) { 175 ok= false; 176 break; 177 } 178 } 179 if (ok) 180 return method2; 181 } 182 } 183 } 184 return null; 185 } 186 getSchedulingRule()187 public ISchedulingRule getSchedulingRule() { 188 return ResourcesPlugin.getWorkspace().getRoot(); 189 } 190 191 public static final int STRING_CONCATENATION= 0; 192 193 public static final int STRING_BUILDER= 1; 194 195 public static final int STRING_BUILDER_CHAINED= 2; 196 197 public static final int STRING_FORMAT= 3; 198 199 public static final int CUSTOM_BUILDER= 4; 200 201 private final static String[] hardcoded_styles= { 202 CodeGenerationMessages.GenerateToStringOperation_stringConcatenation_style_name, 203 CodeGenerationMessages.GenerateToStringOperation_stringBuilder_style_name, 204 CodeGenerationMessages.GenerateToStringOperation_StringBuilder_chained_style_name, 205 CodeGenerationMessages.GenerateToStringOperation_string_format_style_name, 206 CodeGenerationMessages.GenerateToStringOperation_customStringBuilder_style_name 207 }; 208 209 /** 210 * @return Array containing names of implemented code styles 211 */ getStyleNames()212 public static String[] getStyleNames() { 213 return hardcoded_styles; 214 } 215 216 /** 217 * 218 * @param toStringStyle id number of the code style (its position in the array returned by 219 * {@link #getStyleNames()} 220 * @return a toString() generator implementing given code style 221 */ createToStringGenerator(int toStringStyle)222 private static AbstractToStringGenerator createToStringGenerator(int toStringStyle) { 223 switch (toStringStyle) { 224 case STRING_CONCATENATION: 225 return new StringConcatenationGenerator(); 226 case STRING_BUILDER: 227 return new StringBuilderGenerator(); 228 case STRING_BUILDER_CHAINED: 229 return new StringBuilderChainGenerator(); 230 case STRING_FORMAT: 231 return new StringFormatGenerator(); 232 case CUSTOM_BUILDER: 233 return new CustomBuilderGenerator(); 234 default: 235 throw new IllegalArgumentException("Undefined toString() code style: " + toStringStyle); //$NON-NLS-1$ 236 } 237 } 238 239 /** 240 * @param toStringStyle id number of the style (its position in the array returned by 241 * {@link #getStyleNames()} 242 * @return a template parser that should be used with given code style 243 */ createTemplateParser(int toStringStyle)244 public static ToStringTemplateParser createTemplateParser(int toStringStyle) { 245 return new ToStringTemplateParser(); 246 } 247 248 /** 249 * Creates new <code>GenerateToStringOperation</code>, using <code>settings.toStringStyle</code> 250 * field to choose the right subclass. 251 * 252 * @param typeBinding binding for the type for which the toString() method will be created 253 * @param selectedBindings bindings for the typetype's members to be used in created method 254 * @param unit a compilation unit containing the type 255 * @param elementPosition at this position in the compilation unit created method will be added 256 * @param settings the settings for toString() generator 257 * @param apply <code>true</code> if the resulting edit should be applied, <code>false</code> otherwise 258 * @param save <code>true</code> if the changed compilation unit should be saved, <code>false</code> otherwise 259 * @return a ready to use <code>GenerateToStringOperation</code> object 260 */ createOperation(ITypeBinding typeBinding, Object[] selectedBindings, CompilationUnit unit, IJavaElement elementPosition, ToStringGenerationSettingsCore settings, boolean apply, boolean save)261 public static GenerateToStringOperation createOperation(ITypeBinding typeBinding, Object[] selectedBindings, CompilationUnit unit, IJavaElement elementPosition, 262 ToStringGenerationSettingsCore settings, boolean apply, boolean save) { 263 AbstractToStringGenerator generator= createToStringGenerator(settings.toStringStyle); 264 ToStringTemplateParser parser= createTemplateParser(settings.toStringStyle); 265 parser.parseTemplate(settings.stringFormatTemplate); 266 CompilationUnitRewrite rewrite= new CompilationUnitRewrite((ICompilationUnit)unit.getTypeRoot(), unit); 267 ToStringGenerationContext context= new ToStringGenerationContext(parser, selectedBindings, settings, typeBinding, rewrite); 268 generator.setContext(context); 269 return new GenerateToStringOperation(elementPosition, context, generator, unit, rewrite, apply, save); 270 } 271 272 273 } 274