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