1 /*******************************************************************************
2  * Copyright (c) 2000, 2019 IBM Corporation 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  *     IBM Corporation - initial API and implementation
13  *     Pierre-Yves B. <pyvesdev@gmail.com> - Generation of equals and hashcode with java 7 Objects.equals and Objects.hashcode - https://bugs.eclipse.org/424214
14  *     Pierre-Yves B. <pyvesdev@gmail.com> - Different behaviour when generating hashCode and equals - https://bugs.eclipse.org/539589
15  *     Pierre-Yves B. <pyvesdev@gmail.com> - Confusing name when generating hashCode and equals with outer type - https://bugs.eclipse.org/539872
16  *     Red Hat Inc. - refactored to jdt.core.manipulation
17  *     Pierre-Yves B. <pyvesdev@gmail.com> - Allow hashCode and equals generation when no fields but a super/enclosing class that implements them - https://bugs.eclipse.org/539901
18  *     Pierre-Yves B. <pyvesdev@gmail.com> - [hashcode/equals] Redundant null check when instanceof is used - https://bugs.eclipse.org/545424
19  *******************************************************************************/
20 package org.eclipse.jdt.internal.corext.codemanipulation;
21 
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.List;
25 
26 import org.eclipse.core.runtime.Assert;
27 import org.eclipse.core.runtime.CoreException;
28 import org.eclipse.core.runtime.IProgressMonitor;
29 import org.eclipse.core.runtime.NullProgressMonitor;
30 import org.eclipse.core.runtime.OperationCanceledException;
31 import org.eclipse.core.runtime.jobs.ISchedulingRule;
32 
33 import org.eclipse.core.resources.IWorkspaceRunnable;
34 import org.eclipse.core.resources.ResourcesPlugin;
35 
36 import org.eclipse.text.edits.TextEdit;
37 
38 import org.eclipse.jdt.core.ICompilationUnit;
39 import org.eclipse.jdt.core.IJavaElement;
40 import org.eclipse.jdt.core.IJavaProject;
41 import org.eclipse.jdt.core.dom.AST;
42 import org.eclipse.jdt.core.dom.ASTNode;
43 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
44 import org.eclipse.jdt.core.dom.ArrayAccess;
45 import org.eclipse.jdt.core.dom.Assignment;
46 import org.eclipse.jdt.core.dom.Block;
47 import org.eclipse.jdt.core.dom.BodyDeclaration;
48 import org.eclipse.jdt.core.dom.CastExpression;
49 import org.eclipse.jdt.core.dom.CompilationUnit;
50 import org.eclipse.jdt.core.dom.ConditionalExpression;
51 import org.eclipse.jdt.core.dom.Expression;
52 import org.eclipse.jdt.core.dom.FieldAccess;
53 import org.eclipse.jdt.core.dom.ForStatement;
54 import org.eclipse.jdt.core.dom.IMethodBinding;
55 import org.eclipse.jdt.core.dom.ITypeBinding;
56 import org.eclipse.jdt.core.dom.IVariableBinding;
57 import org.eclipse.jdt.core.dom.IfStatement;
58 import org.eclipse.jdt.core.dom.InfixExpression;
59 import org.eclipse.jdt.core.dom.InfixExpression.Operator;
60 import org.eclipse.jdt.core.dom.InstanceofExpression;
61 import org.eclipse.jdt.core.dom.Javadoc;
62 import org.eclipse.jdt.core.dom.MethodDeclaration;
63 import org.eclipse.jdt.core.dom.MethodInvocation;
64 import org.eclipse.jdt.core.dom.Modifier;
65 import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
66 import org.eclipse.jdt.core.dom.Name;
67 import org.eclipse.jdt.core.dom.ParenthesizedExpression;
68 import org.eclipse.jdt.core.dom.PostfixExpression;
69 import org.eclipse.jdt.core.dom.PrefixExpression;
70 import org.eclipse.jdt.core.dom.PrimitiveType;
71 import org.eclipse.jdt.core.dom.PrimitiveType.Code;
72 import org.eclipse.jdt.core.dom.ReturnStatement;
73 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
74 import org.eclipse.jdt.core.dom.Statement;
75 import org.eclipse.jdt.core.dom.SuperMethodInvocation;
76 import org.eclipse.jdt.core.dom.TagElement;
77 import org.eclipse.jdt.core.dom.TextElement;
78 import org.eclipse.jdt.core.dom.ThisExpression;
79 import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
80 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
81 import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
82 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
83 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation;
84 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
85 import org.eclipse.jdt.core.manipulation.CodeGeneration;
86 
87 import org.eclipse.jdt.internal.core.manipulation.StubUtility;
88 import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
89 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
90 import org.eclipse.jdt.internal.corext.dom.Bindings;
91 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
92 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
93 
94 /**
95  * <p>
96  * Workspace runnable to add implementations for
97  * <code>{@link java.lang.Object#equals(java.lang.Object)}</code> and
98  * <code>{@link java.lang.Object#hashCode()}</code>.
99  * </p>
100  *
101  * <p>
102  * This implementation creates a hashCode() and an equals() method intended to
103  * be used in value types: The implementation assumes that two objects are equal
104  * (and provide the same hashCode) if all values of all fields are equal.
105  * </p>
106  *
107  * <p>
108  * About the implementation:
109  * <ul>
110  * <li>To deal with reference types of fields and with supertypes, the
111  * implementation calls hashCode() and equals() on reference types of fields and
112  * on the superclass (except if the superclass is Object). It is an error if
113  * these types do not provide an equals() and hashCode() implementation and the
114  * comparison or hash code creation <strong>will fail</strong> if these methods
115  * are not correctly implemented.</li>
116  * <li>The implementation for primitive typed fields is the same as in the JDK
117  * implementations in the wrapper types (and the java.util.Arrays class).</li>
118  * <li>The equals() implementation uses equality of the declaring class instead
119  * of an instanceof check.</li>
120  * <li>A test for null in equals() is only implemented in direct subclasses of
121  * Object. This is only sufficient if every subimplementation calls
122  * super.equals() before any argument checks.</li>
123  * <li>Both equals() and hashCode() use methods from java.util.Arrays to test
124  * for equality and to generate a hash code for arrays. Note that this has an
125  * implication for Double and Float values (consider values -0.0 and 0.0 as well
126  * as border values like NaN and their equality in bit patterns) - however, the
127  * implementation is consistent with the wrapper types java.lang.Float and
128  * java.lang.Double.</li>
129  * </ul>
130  * </p>
131  *
132  * @since 3.2
133  */
134 public final class GenerateHashCodeEqualsOperation implements IWorkspaceRunnable {
135 
136 	private interface IHashCodeAccessProvider {
137 
getThisAccess(String name)138 		public Expression getThisAccess(String name);
139 	}
140 
141 	private static final String JAVA_UTIL_ARRAYS= "java.util.Arrays"; //$NON-NLS-1$
142 
143 	private static final String BOOLEAN_TRUE_CONSTANT= "1231"; //$NON-NLS-1$
144 
145 	private static final String BOOLEAN_FALSE_CONSTANT= "1237"; //$NON-NLS-1$
146 
147 	private static final String JAVA_LANG_OBJECT= "java.lang.Object"; //$NON-NLS-1$
148 
149 	private static final String METHODNAME_GETCLASS= "getClass"; //$NON-NLS-1$
150 
151 	private static final String METHODNAME_EQUALS= "equals"; //$NON-NLS-1$
152 
153 	private static final String METHODNAME_DEEP_EQUALS= "deepEquals"; //$NON-NLS-1$
154 
155 	private static final String METHODNAME_HASH_CODE= "hashCode"; //$NON-NLS-1$
156 
157 	private static final String METHODNAME_HASH= "hash"; //$NON-NLS-1$
158 
159 	private static final String METHODNAME_DEEP_HASH_CODE= "deepHashCode"; //$NON-NLS-1$
160 
161 	private static final String METHODNAME_GET_ENCLOSING_INSTANCE= "getEnclosingInstance"; //$NON-NLS-1$
162 
163 	private static final String PRIME_NUMBER= "31"; //$NON-NLS-1$
164 
165 	private static final String INITIAL_HASHCODE_VALUE= "1"; //$NON-NLS-1$
166 
167 	private static final String VARIABLE_NAME_DOUBLE_TEMPORARY= "temp"; //$NON-NLS-1$
168 
169 	private static final String VARIABLE_NAME_PRIME= "prime"; //$NON-NLS-1$
170 
171 	private static final String VARIABLE_NAME_RESULT= "result"; //$NON-NLS-1$
172 
173 	private static final String VARIABLE_NAME_EQUALS_PARAM= "obj"; //$NON-NLS-1$
174 
175 	private static final String VARIABLE_NAME_HASHCODE_PARAM= "array"; //$NON-NLS-1$
176 
177 	private static final String VARIABLE_NAME_EQUALS_CASTED= "other"; //$NON-NLS-1$
178 
179 	private static final String VARIABLE_NAME_INDEX= "index"; //$NON-NLS-1$
180 
181 	private static final String JAVA_UTIL_OBJECTS= "java.util.Objects"; //$NON-NLS-1$
182 
183 	private static final String TYPE_NAME_CLONEABLE= "Cloneable"; //$NON-NLS-1$
184 
185 	private static final String TYPE_NAME_SERIALIZABLE= "Serializable"; //$NON-NLS-1$
186 
187 	private static final String TYPE_NAME_OBJECT= "Object"; //$NON-NLS-1$
188 
189 	/** Should the resulting edit be applied? */
190 	private final boolean fApply;
191 
192 	/** The resulting text edit */
193 	private TextEdit fEdit= null;
194 
195 	/** The insertion point, or <code>null</code> */
196 	private final IJavaElement fInsert;
197 
198 	/** The variable binding keys to implement */
199 	private final IVariableBinding[] fFields;
200 
201 	/** Should the regeneration of the methods be enforced? */
202 	private final boolean fForce;
203 
204 	/** Should the compilation unit content be saved? */
205 	private final boolean fSave;
206 
207 	/** The code generation settings to use */
208 	private final CodeGenerationSettings fSettings;
209 
210 	/** The type declaration to add the methods to */
211 	private final ITypeBinding fType;
212 
213 	/** The compilation unit ast node */
214 	private final CompilationUnit fUnit;
215 
216 	/** The CURewrite to be used */
217 	private final CompilationUnitRewrite fRewrite;
218 
219 	/** The ast to be used. Convenience accessor field */
220 	private final AST fAst;
221 
222 	/** The number of double-typed fields handled so far */
223 	private int fDoubleCount;
224 
225 	/** The primitive types to generate custom hashCode() methods for */
226 	private List<ITypeBinding> fCustomHashCodeTypes= new ArrayList<>();
227 
228 	/** <code>true</code> to use 'instanceof' to compare types, <code>false</code> otherwise */
229 	private final boolean fUseInstanceOf;
230 
231 	/**
232 	 * <code>true</code> to use newer hashcode and equals method generation, using Java 7+
233 	 * Objects.hash and Objects.equals, <code>false</code> to generate default methods
234 	 */
235 	private final boolean fUseJ7HashEquals;
236 
237 	/** <code>true</code> to use blocks for then */
238 	private boolean fUseBlocksForThen;
239 
240 	/** The import rewrite context, only initialized in {@link #run(IProgressMonitor)}. */
241 	private ImportRewriteContext fImportRewriteContext;
242 
243 	/**
244 	 * Creates a new add hash code equals operation.
245 	 *
246 	 * @param type the type to add the methods to
247 	 * @param fields the method binding keys to implement
248 	 * @param unit the compilation unit ast node
249 	 * @param insert the insertion point, or <code>null</code>
250 	 * @param settings the code generation settings to use
251 	 * @param useInstanceof <code>true</code> to use 'instanceof' to compare types, <code>false</code> otherwise
252 	 * @param useJ7HashEquals <code>true</code> to use Java 7+ Objects.hash and Objects.equals methods, <code>false</code> otherwise
253 	 * @param force <code>true</code> to force the regeneration of existing methods,
254 	 *            <code>false</code> otherwise
255 	 * @param apply <code>true</code> if the resulting edit should be applied,
256 	 *            <code>false</code> otherwise
257 	 * @param save <code>true</code> if the changed compilation unit should be
258 	 *            saved, <code>false</code> otherwise
259 	 */
GenerateHashCodeEqualsOperation(final ITypeBinding type, final IVariableBinding[] fields, final CompilationUnit unit, final IJavaElement insert, final CodeGenerationSettings settings, final boolean useInstanceof, final boolean useJ7HashEquals, final boolean force, final boolean apply, final boolean save)260 	public GenerateHashCodeEqualsOperation(final ITypeBinding type, final IVariableBinding[] fields, final CompilationUnit unit,
261 			final IJavaElement insert, final CodeGenerationSettings settings, final boolean useInstanceof, final boolean useJ7HashEquals, final boolean force, final boolean apply,
262 			final boolean save) {
263 		Assert.isNotNull(type);
264 		Assert.isNotNull(fields);
265 		Assert.isNotNull(unit);
266 		Assert.isNotNull(settings);
267 		Assert.isTrue(unit.getTypeRoot() instanceof ICompilationUnit);
268 
269 		fType= type;
270 		fInsert= insert;
271 		fUnit= unit;
272 		fFields= fields;
273 		fSettings= settings;
274 		fUseInstanceOf= useInstanceof;
275 		fUseJ7HashEquals= useJ7HashEquals;
276 		fSave= save;
277 		fApply= apply;
278 		fDoubleCount= 0;
279 		fRewrite= new CompilationUnitRewrite((ICompilationUnit) fUnit.getTypeRoot(), fUnit);
280 		fForce= force;
281 		fAst= fRewrite.getAST();
282 		fUseBlocksForThen= false;
283 	}
284 
285 	/**
286 	 * Defines if then statements should use blocks or not.
287 	 *
288 	 * @param useBlocksForThen if set, blocks are forced in if-then statements
289 	 */
setUseBlocksForThen(boolean useBlocksForThen)290 	public void setUseBlocksForThen(boolean useBlocksForThen) {
291 		fUseBlocksForThen= useBlocksForThen;
292 	}
293 
294 	/**
295 	 * Returns the resulting text edit.
296 	 *
297 	 * @return the resulting edit
298 	 */
getResultingEdit()299 	public final TextEdit getResultingEdit() {
300 		return fEdit;
301 	}
302 
303 	/**
304 	 * Returns the scheduling rule for this operation.
305 	 *
306 	 * @return the scheduling rule
307 	 */
getSchedulingRule()308 	public final ISchedulingRule getSchedulingRule() {
309 		return ResourcesPlugin.getWorkspace().getRoot();
310 	}
311 
312 	/*
313 	 * @see org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse.core.runtime.IProgressMonitor)
314 	 */
315 	@Override
run(IProgressMonitor monitor)316 	public final void run(IProgressMonitor monitor) throws CoreException {
317 		if (monitor == null)
318 			monitor= new NullProgressMonitor();
319 		try {
320 			monitor.beginTask("", 1); //$NON-NLS-1$
321 			monitor.setTaskName(CodeGenerationMessages.GenerateHashCodeEqualsOperation_description);
322 
323 			fCustomHashCodeTypes.clear();
324 
325 			// get the declaration and the rewrite
326 			AbstractTypeDeclaration declaration= (AbstractTypeDeclaration) ASTNodes.findDeclaration(fType, fRewrite.getRoot());
327 			ListRewrite rewriter= fRewrite.getASTRewrite().getListRewrite(declaration, declaration.getBodyDeclarationsProperty());
328 			List<BodyDeclaration> list= declaration.bodyDeclarations();
329 			if (fType != null && rewriter != null) {
330 
331 				ICompilationUnit cu= (ICompilationUnit) fUnit.getJavaElement();
332 
333 				ASTNode insertion= StubUtility2Core.getNodeToInsertBefore(rewriter, fInsert);
334 
335 				// equals(..)
336 				ITypeBinding[] objectAsParam= { declaration.getAST().resolveWellKnownType(JAVA_LANG_OBJECT) };
337 				BodyDeclaration oldEquals= fForce ? findMethodToReplace(list, METHODNAME_EQUALS, objectAsParam) : null;
338 
339 				fImportRewriteContext= new ContextSensitiveImportRewriteContext(declaration, fRewrite.getImportRewrite());
340 				MethodDeclaration equalsMethod= createEqualsMethod();
341 				addMethod(rewriter, insertion, equalsMethod, oldEquals);
342 
343 				if (monitor.isCanceled())
344 					throw new OperationCanceledException();
345 
346 				// hashCode()
347 				BodyDeclaration oldHash= fForce ? findMethodToReplace(list, METHODNAME_HASH_CODE, new ITypeBinding[0]) : null;
348 
349 				MethodDeclaration hashCodeMethod= createHashCodeMethod();
350 				addMethod(rewriter, equalsMethod, hashCodeMethod, oldHash);
351 
352 				// helpers
353 				for (ITypeBinding binding : fCustomHashCodeTypes) {
354 					if (findMethodToReplace(list, METHODNAME_HASH_CODE, objectAsParam) == null) {
355 						final MethodDeclaration helperDecl= createHashCodeHelper(binding);
356 						addHelper(rewriter, null, helperDecl);
357 					}
358 				}
359 
360 				if (isMemberType()) {
361 					if (findMethodToReplace(list, METHODNAME_GET_ENCLOSING_INSTANCE, new ITypeBinding[0]) == null) {
362 						final MethodDeclaration helperDecl= createGetEnclosingInstanceHelper();
363 						rewriter.insertLast(helperDecl, null);
364 					}
365 				}
366 
367 				fEdit= fRewrite.createChange(true).getEdit();
368 				if (fApply)
369 					JavaModelUtil.applyEdit(cu, fEdit, fSave, monitor);
370 			}
371 		} finally {
372 			monitor.done();
373 		}
374 	}
375 
isMemberType()376 	private boolean isMemberType() {
377 		return fType.isMember() && !Modifier.isStatic(fType.getModifiers());
378 	}
379 
findMethodToReplace(final List<BodyDeclaration> list, String name, ITypeBinding[] paramTypes)380 	private BodyDeclaration findMethodToReplace(final List<BodyDeclaration> list, String name, ITypeBinding[] paramTypes) {
381 		for (BodyDeclaration bodyDecl : list) {
382 			if (bodyDecl instanceof MethodDeclaration) {
383 				final MethodDeclaration method= (MethodDeclaration) bodyDecl;
384 				final IMethodBinding binding= method.resolveBinding();
385 				if (binding != null && binding.getName().equals(name)) {
386 					if (Bindings.equals(binding.getParameterTypes(), paramTypes)) {
387 						return method;
388 					}
389 				}
390 			}
391 		}
392 		return null;
393 	}
394 
addHelper(ListRewrite rewriter, ASTNode insertion, MethodDeclaration stub)395 	private void addHelper(ListRewrite rewriter, ASTNode insertion, MethodDeclaration stub) {
396 		if (insertion != null)
397 			rewriter.insertBefore(stub, insertion, null);
398 		else
399 			rewriter.insertFirst(stub, null);
400 	}
401 
addMethod(ListRewrite rewriter, ASTNode insertion, MethodDeclaration stub, BodyDeclaration replace)402 	private void addMethod(ListRewrite rewriter, ASTNode insertion, MethodDeclaration stub, BodyDeclaration replace) {
403 		if (replace != null) {
404 			rewriter.replace(replace, stub, null);
405 		} else {
406 		if (insertion != null)
407 			rewriter.insertBefore(stub, insertion, null);
408 		else
409 			rewriter.insertLast(stub, null);
410 		}
411 	}
412 
413 	// ******************* HASHCODE *******************
414 
createHashCodeMethod()415 	private MethodDeclaration createHashCodeMethod() throws CoreException {
416 
417 		MethodDeclaration hashCodeMethod= fAst.newMethodDeclaration();
418 		hashCodeMethod.modifiers().addAll(ASTNodeFactory.newModifiers(fAst, Modifier.PUBLIC));
419 		hashCodeMethod.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
420 		hashCodeMethod.setConstructor(false);
421 		hashCodeMethod.setReturnType2(fAst.newPrimitiveType(PrimitiveType.INT));
422 
423 		Block body= fAst.newBlock();
424 		hashCodeMethod.setBody(body);
425 
426 		boolean needsNoSuperCall= needsNoSuperCall(fType, METHODNAME_HASH_CODE, new ITypeBinding[0]);
427 		boolean memberType= isMemberType();
428 		ReturnStatement endReturn= fAst.newReturnStatement();
429 		if (!memberType && fFields.length == 0) {
430 			// return super.hashCode();
431 			SuperMethodInvocation invoc= fAst.newSuperMethodInvocation();
432 			invoc.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
433 			endReturn.setExpression(invoc);
434 		} else if (fUseJ7HashEquals && needsNoSuperCall && !memberType && containsNoArrays()) {
435 			// return Objects.hash(...);
436 			endReturn.setExpression(createStandaloneJ7HashCall());
437 		} else {
438 			// final int prime = 31;
439 			VariableDeclarationFragment frag= fAst.newVariableDeclarationFragment();
440 			frag.setName(fAst.newSimpleName(VARIABLE_NAME_PRIME));
441 			frag.setInitializer(fAst.newNumberLiteral(PRIME_NUMBER));
442 
443 			VariableDeclarationStatement primeNumberDeclaration= fAst.newVariableDeclarationStatement(frag);
444 			primeNumberDeclaration.modifiers().add(fAst.newModifier(ModifierKeyword.FINAL_KEYWORD));
445 			primeNumberDeclaration.setType(fAst.newPrimitiveType(PrimitiveType.INT));
446 			body.statements().add(primeNumberDeclaration);
447 
448 			VariableDeclarationFragment fragment= fAst.newVariableDeclarationFragment();
449 			fragment.setName(fAst.newSimpleName(VARIABLE_NAME_RESULT));
450 
451 			VariableDeclarationStatement resultDeclaration= fAst.newVariableDeclarationStatement(fragment);
452 			resultDeclaration.setType(fAst.newPrimitiveType(PrimitiveType.INT));
453 			body.statements().add(resultDeclaration);
454 
455 			if (needsNoSuperCall) {
456 				// int result = 1;
457 				fragment.setInitializer(fAst.newNumberLiteral(INITIAL_HASHCODE_VALUE));
458 			} else {
459 				// int result = super.hashCode();
460 				SuperMethodInvocation invoc= fAst.newSuperMethodInvocation();
461 				invoc.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
462 				fragment.setInitializer(invoc);
463 			}
464 
465 			if (memberType) {
466 				// result = prime * result + getEnclosingInstance().hashCode();
467 				body.statements().add(createAddEnclosingInstanceHashCode());
468 			}
469 
470 			MethodInvocation j7Invoc= fAst.newMethodInvocation();
471 			for (IVariableBinding field : fFields) {
472 				if (field.getType().isArray()) {
473 					body.statements().add(createAddArrayHashCode(field));
474 				} else if (fUseJ7HashEquals) {
475 					j7Invoc.arguments().add(fAst.newSimpleName(field.getName()));
476 				} else if (field.getType().isPrimitive()) {
477 					Statement[] sts= createAddSimpleHashCode(field.getType(), this::getThisAccessForHashCode, field.getName(), false);
478 					for (Statement st : sts) {
479 						body.statements().add(st);
480 					}
481 				} else {
482 					body.statements().add(createAddQualifiedHashCode(field));
483 				}
484 			}
485 			if (!j7Invoc.arguments().isEmpty()) {
486 				j7Invoc.setExpression(getQualifiedName(JAVA_UTIL_OBJECTS));
487 				j7Invoc.setName(fAst.newSimpleName(METHODNAME_HASH));
488 				body.statements().add(prepareAssignment(j7Invoc));
489 			}
490 			endReturn.setExpression(fAst.newSimpleName(VARIABLE_NAME_RESULT));
491 		}
492 
493 		// the last return:
494 		body.statements().add(endReturn);
495 
496 		// method comment
497 		if (fSettings != null) {
498 			ITypeBinding object= fAst.resolveWellKnownType(JAVA_LANG_OBJECT);
499 			IMethodBinding objectMethod= null;
500 			for (IMethodBinding objm : object.getDeclaredMethods()) {
501 				if (objm.getName().equals(METHODNAME_HASH_CODE) && objm.getParameterTypes().length == 0) {
502 					objectMethod= objm;
503 				}
504 			}
505 			createMethodComment(hashCodeMethod, objectMethod);
506 		}
507 
508 		return hashCodeMethod;
509 	}
510 
containsNoArrays()511 	private boolean containsNoArrays() {
512 		return Arrays.stream(fFields).map(IVariableBinding::getType).noneMatch(ITypeBinding::isArray);
513 	}
514 
createStandaloneJ7HashCall()515 	private MethodInvocation createStandaloneJ7HashCall() {
516 		MethodInvocation j7Invoc= fAst.newMethodInvocation();
517 		for (IVariableBinding field : fFields) {
518 			j7Invoc.arguments().add(fAst.newSimpleName(field.getName()));
519 		}
520 		j7Invoc.setExpression(getQualifiedName(JAVA_UTIL_OBJECTS));
521 		j7Invoc.setName(fAst.newSimpleName(METHODNAME_HASH));
522 		return j7Invoc;
523 	}
524 
createAddEnclosingInstanceHashCode()525 	private Statement createAddEnclosingInstanceHashCode() {
526 		MethodInvocation enclosing= fAst.newMethodInvocation();
527 		enclosing.setName(fAst.newSimpleName(METHODNAME_GET_ENCLOSING_INSTANCE));
528 		MethodInvocation hashAccess= fAst.newMethodInvocation();
529 		hashAccess.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
530 		hashAccess.setExpression(enclosing);
531 		return prepareAssignment(hashAccess);
532 	}
533 
createAddSimpleHashCode(ITypeBinding type, IHashCodeAccessProvider provider, String name, boolean singleTemp)534 	private Statement[] createAddSimpleHashCode(ITypeBinding type, IHashCodeAccessProvider provider, String name, boolean singleTemp) {
535 
536 		List<Statement> statements= new ArrayList<>();
537 
538 		if (!type.isPrimitive()) {
539 			// (element == null ? 0 : element.hashCode())
540 			ConditionalExpression ce= fAst.newConditionalExpression();
541 			InfixExpression exp= fAst.newInfixExpression();
542 			ArrayAccess access= fAst.newArrayAccess();
543 			access.setArray(fAst.newSimpleName(VARIABLE_NAME_HASHCODE_PARAM));
544 			access.setIndex(fAst.newSimpleName(VARIABLE_NAME_INDEX));
545 			exp.setLeftOperand(access);
546 			exp.setOperator(Operator.EQUALS);
547 			exp.setRightOperand(fAst.newNullLiteral());
548 			ce.setExpression(exp);
549 			ce.setThenExpression(fAst.newNumberLiteral("0")); //$NON-NLS-1$
550 			MethodInvocation invoc= fAst.newMethodInvocation();
551 			access= fAst.newArrayAccess();
552 			access.setArray(fAst.newSimpleName(VARIABLE_NAME_HASHCODE_PARAM));
553 			access.setIndex(fAst.newSimpleName(VARIABLE_NAME_INDEX));
554 			invoc.setExpression(access);
555 			invoc.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
556 			ce.setElseExpression(invoc);
557 			statements.add(prepareAssignment(parenthesize(ce)));
558 		} else if (isPrimitiveType(type, PrimitiveType.BOOLEAN)) {
559 			ConditionalExpression ce= fAst.newConditionalExpression();
560 			ce.setExpression(provider.getThisAccess(name));
561 			// see Boolean.hashCode(boolean)
562 			ce.setThenExpression(fAst.newNumberLiteral(BOOLEAN_TRUE_CONSTANT));
563 			ce.setElseExpression(fAst.newNumberLiteral(BOOLEAN_FALSE_CONSTANT));
564 			statements.add(prepareAssignment(parenthesize(ce)));
565 		} else if (isPrimitiveType(type, new PrimitiveType.Code[] { PrimitiveType.CHAR, PrimitiveType.INT, PrimitiveType.SHORT, PrimitiveType.BYTE })) {
566 			statements.add(prepareAssignment(provider.getThisAccess(name)));
567 		} else if (isPrimitiveType(type, PrimitiveType.FLOAT)) {
568 			// Float.floatToIntBits(aFloat)
569 			statements.add(prepareAssignment(createFloatInvocation(provider.getThisAccess(name))));
570 		} else if (isPrimitiveType(type, PrimitiveType.LONG)) {
571 			statements.add(prepareAssignment(createShiftAssignment(provider.getThisAccess(name), provider.getThisAccess(name))));
572 		} else if (isPrimitiveType(type, PrimitiveType.DOUBLE)) {
573 
574 			VariableDeclarationFragment fragment= null;
575 			if (singleTemp || fDoubleCount == 0) {
576 				fragment= fAst.newVariableDeclarationFragment();
577 				fragment.setName(fAst.newSimpleName(VARIABLE_NAME_DOUBLE_TEMPORARY));
578 
579 				VariableDeclarationStatement st2= fAst.newVariableDeclarationStatement(fragment);
580 				st2.setType(fAst.newPrimitiveType(PrimitiveType.LONG));
581 				statements.add(st2);
582 			}
583 			fDoubleCount++;
584 
585 			// Double.doubleToIntBits(aDouble)
586 			Expression comparison= createDoubleInvocation(provider.getThisAccess(name));
587 
588 			if (singleTemp)
589 				fragment.setInitializer(comparison);
590 			else {
591 				Assignment ass= fAst.newAssignment();
592 				ass.setLeftHandSide(fAst.newSimpleName(VARIABLE_NAME_DOUBLE_TEMPORARY));
593 				ass.setRightHandSide(comparison);
594 				statements.add(fAst.newExpressionStatement(ass));
595 			}
596 			statements.add(prepareAssignment(createShiftAssignment(fAst.newSimpleName(VARIABLE_NAME_DOUBLE_TEMPORARY), fAst.newSimpleName(VARIABLE_NAME_DOUBLE_TEMPORARY))));
597 		}
598 
599 		return statements.toArray(new Statement[statements.size()]);
600 	}
601 
createAddArrayHashCode(IVariableBinding binding)602 	private Statement createAddArrayHashCode(IVariableBinding binding) {
603 		MethodInvocation invoc= fAst.newMethodInvocation();
604 		if (JavaModelUtil.is50OrHigher(fRewrite.getCu().getJavaProject())) {
605 			if (needsDeepMethod(binding.getType())) {
606 				invoc.setName(fAst.newSimpleName(METHODNAME_DEEP_HASH_CODE));
607 			} else {
608 				invoc.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
609 			}
610 			invoc.setExpression(getQualifiedName(JAVA_UTIL_ARRAYS));
611 			invoc.arguments().add(getThisAccessForHashCode(binding.getName()));
612 		} else {
613 			invoc.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
614 			final IJavaElement element= fType.getJavaElement();
615 			if (element != null && !"".equals(element.getElementName())) //$NON-NLS-1$
616 				invoc.setExpression(fAst.newSimpleName(element.getElementName()));
617 			invoc.arguments().add(getThisAccessForHashCode(binding.getName()));
618 			ITypeBinding type= binding.getType().getElementType();
619 			if (!Bindings.isVoidType(type)) {
620 				if (!type.isPrimitive() || binding.getType().getDimensions() >= 2)
621 					type= fAst.resolveWellKnownType(JAVA_LANG_OBJECT);
622 				if (!fCustomHashCodeTypes.contains(type))
623 					fCustomHashCodeTypes.add(type);
624 			}
625 		}
626 		return prepareAssignment(invoc);
627 	}
628 
createGetEnclosingInstanceHelper()629 	private MethodDeclaration createGetEnclosingInstanceHelper() {
630 		String enclosingTypeName= fType.getDeclaringClass().getTypeDeclaration().getName();
631 
632 		MethodDeclaration helperMethod= fAst.newMethodDeclaration();
633 		helperMethod.modifiers().addAll(ASTNodeFactory.newModifiers(fAst, Modifier.PRIVATE));
634 		helperMethod.setName(fAst.newSimpleName(METHODNAME_GET_ENCLOSING_INSTANCE));
635 		helperMethod.setConstructor(false);
636 		helperMethod.setReturnType2(fAst.newSimpleType(fAst.newSimpleName(enclosingTypeName)));
637 
638 		Block body= fAst.newBlock();
639 		helperMethod.setBody(body);
640 
641 		ThisExpression thisExpression= fAst.newThisExpression();
642 		thisExpression.setQualifier(fAst.newSimpleName(enclosingTypeName));
643 
644 		ReturnStatement endReturn= fAst.newReturnStatement();
645 		endReturn.setExpression(thisExpression);
646 		body.statements().add(endReturn);
647 
648 		return helperMethod;
649 	}
650 
651 
createHashCodeHelper(ITypeBinding binding)652 	private MethodDeclaration createHashCodeHelper(ITypeBinding binding) {
653 		Assert.isTrue(!binding.isArray());
654 
655 		MethodDeclaration hashCodeMethod= fAst.newMethodDeclaration();
656 		hashCodeMethod.modifiers().addAll(ASTNodeFactory.newModifiers(fAst, Modifier.PRIVATE | Modifier.STATIC));
657 		hashCodeMethod.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
658 		hashCodeMethod.setConstructor(false);
659 		hashCodeMethod.setReturnType2(fAst.newPrimitiveType(PrimitiveType.INT));
660 
661 		// ARGUMENTS
662 		List<SingleVariableDeclaration> parameters= hashCodeMethod.parameters();
663 		SingleVariableDeclaration hashCodeParam= fAst.newSingleVariableDeclaration();
664 		if (!binding.isPrimitive())
665 			hashCodeParam.setType(fAst.newArrayType(fAst.newSimpleType(getQualifiedName(JAVA_LANG_OBJECT)), 1));
666 		else
667 			hashCodeParam.setType(fAst.newArrayType(fAst.newPrimitiveType(PrimitiveType.toCode(binding.getName())), 1));
668 		hashCodeParam.setName(fAst.newSimpleName(VARIABLE_NAME_HASHCODE_PARAM));
669 		parameters.add(hashCodeParam);
670 
671 		Block body= fAst.newBlock();
672 		hashCodeMethod.setBody(body);
673 
674 		// PRIME NUMBER
675 		VariableDeclarationFragment frag= fAst.newVariableDeclarationFragment();
676 		frag.setName(fAst.newSimpleName(VARIABLE_NAME_PRIME));
677 		frag.setInitializer(fAst.newNumberLiteral(PRIME_NUMBER));
678 
679 		VariableDeclarationStatement primeNumberDeclaration= fAst.newVariableDeclarationStatement(frag);
680 		primeNumberDeclaration.setType(fAst.newPrimitiveType(PrimitiveType.INT));
681 		body.statements().add(primeNumberDeclaration);
682 
683 		// IF STATEMENT
684 		IfStatement ifStatement= fAst.newIfStatement();
685 		final InfixExpression newInfixExpression= fAst.newInfixExpression();
686 		newInfixExpression.setLeftOperand(fAst.newSimpleName(VARIABLE_NAME_HASHCODE_PARAM));
687 		newInfixExpression.setRightOperand(fAst.newNullLiteral());
688 		newInfixExpression.setOperator(Operator.EQUALS);
689 		ifStatement.setExpression(newInfixExpression);
690 		final ReturnStatement returnStatement= fAst.newReturnStatement();
691 		returnStatement.setExpression(fAst.newNumberLiteral("0")); //$NON-NLS-1$
692 		ifStatement.setThenStatement(getThenStatement(returnStatement));
693 		body.statements().add(ifStatement);
694 
695 		// RESULT
696 		VariableDeclarationFragment resultFragment= fAst.newVariableDeclarationFragment();
697 		resultFragment.setName(fAst.newSimpleName(VARIABLE_NAME_RESULT));
698 		resultFragment.setInitializer(fAst.newNumberLiteral(INITIAL_HASHCODE_VALUE));
699 		VariableDeclarationStatement resultDeclaration= fAst.newVariableDeclarationStatement(resultFragment);
700 		resultDeclaration.setType(fAst.newPrimitiveType(PrimitiveType.INT));
701 		body.statements().add(resultDeclaration);
702 
703 		// FOR LOOP
704 		ForStatement forStatement= fAst.newForStatement();
705 
706 		VariableDeclarationFragment indexDeclaration= fAst.newVariableDeclarationFragment();
707 		indexDeclaration.setName(fAst.newSimpleName(VARIABLE_NAME_INDEX));
708 		indexDeclaration.setInitializer(fAst.newNumberLiteral("0")); //$NON-NLS-1$
709 		final VariableDeclarationExpression declExpression= fAst.newVariableDeclarationExpression(indexDeclaration);
710 		declExpression.setType(fAst.newPrimitiveType(PrimitiveType.INT));
711 		forStatement.initializers().add(declExpression);
712 		InfixExpression infixExpr= fAst.newInfixExpression();
713 		infixExpr.setLeftOperand(fAst.newSimpleName(VARIABLE_NAME_INDEX));
714 		FieldAccess access= fAst.newFieldAccess();
715 		access.setExpression(fAst.newSimpleName(VARIABLE_NAME_HASHCODE_PARAM));
716 		access.setName(fAst.newSimpleName("length")); //$NON-NLS-1$
717 		infixExpr.setRightOperand(access);
718 		infixExpr.setOperator(Operator.LESS);
719 		forStatement.setExpression(infixExpr);
720 		PostfixExpression postfixExpr= fAst.newPostfixExpression();
721 		postfixExpr.setOperand(fAst.newSimpleName(VARIABLE_NAME_INDEX));
722 		postfixExpr.setOperator(org.eclipse.jdt.core.dom.PostfixExpression.Operator.INCREMENT);
723 		forStatement.updaters().add(postfixExpr);
724 		body.statements().add(forStatement);
725 
726 		Block forBody= fAst.newBlock();
727 		Statement[] statements= createAddSimpleHashCode(binding, name -> {
728 			ArrayAccess a= fAst.newArrayAccess();
729 			a.setArray(fAst.newSimpleName(VARIABLE_NAME_HASHCODE_PARAM));
730 			a.setIndex(fAst.newSimpleName(name));
731 			return a;
732 		}, VARIABLE_NAME_INDEX, true);
733 		for (Statement statement : statements) {
734 			forBody.statements().add(statement);
735 		}
736 		forStatement.setBody(forBody);
737 
738 		// END RETURN
739 		ReturnStatement endReturn= fAst.newReturnStatement();
740 		endReturn.setExpression(fAst.newSimpleName(VARIABLE_NAME_RESULT));
741 		body.statements().add(endReturn);
742 
743 		// COMMENT
744 		if (fSettings != null && fSettings.createComments) {
745 			Javadoc javadoc= fAst.newJavadoc();
746 			final TagElement tagComment= fAst.newTagElement();
747 			TextElement text= fAst.newTextElement();
748 			text.setText(CodeGenerationMessages.GenerateHashCodeEqualsOperation_hash_code_comment);
749 			tagComment.fragments().add(text);
750 			javadoc.tags().add(tagComment);
751 			final TagElement tagParam= fAst.newTagElement();
752 			tagParam.setTagName(CodeGenerationMessages.GenerateHashCodeEqualsOperation_tag_param);
753 			tagParam.fragments().add(fAst.newSimpleName(VARIABLE_NAME_HASHCODE_PARAM));
754 			text= fAst.newTextElement();
755 			text.setText(CodeGenerationMessages.GenerateHashCodeEqualsOperation_hash_code_argument);
756 			tagParam.fragments().add(text);
757 			javadoc.tags().add(tagParam);
758 			final TagElement tagReturn= fAst.newTagElement();
759 			tagReturn.setTagName(CodeGenerationMessages.GenerateHashCodeEqualsOperation_tag_return);
760 			text= fAst.newTextElement();
761 			text.setText(CodeGenerationMessages.GenerateHashCodeEqualsOperation_return_comment);
762 			tagReturn.fragments().add(text);
763 			javadoc.tags().add(tagReturn);
764 			hashCodeMethod.setJavadoc(javadoc);
765 		}
766 		return hashCodeMethod;
767 	}
768 
createAddQualifiedHashCode(IVariableBinding binding)769 	private Statement createAddQualifiedHashCode(IVariableBinding binding) {
770 
771 		MethodInvocation invoc= fAst.newMethodInvocation();
772 		invoc.setExpression(getThisAccessForHashCode(binding.getName()));
773 		invoc.setName(fAst.newSimpleName(METHODNAME_HASH_CODE));
774 
775 		InfixExpression expr= fAst.newInfixExpression();
776 		expr.setOperator(Operator.EQUALS);
777 		expr.setLeftOperand(getThisAccessForHashCode(binding.getName()));
778 		expr.setRightOperand(fAst.newNullLiteral());
779 
780 		ConditionalExpression cexpr= fAst.newConditionalExpression();
781 		cexpr.setThenExpression(fAst.newNumberLiteral("0")); //$NON-NLS-1$
782 		cexpr.setElseExpression(invoc);
783 		cexpr.setExpression(parenthesize(expr));
784 
785 		return prepareAssignment(parenthesize(cexpr));
786 	}
787 
createShiftAssignment(Expression shift1, Expression shift2)788 	private Expression createShiftAssignment(Expression shift1, Expression shift2) {
789 		// (int)(element ^ (element >>> 32));
790 		// see implementation in Arrays.hashCode(), Double.hashCode() and
791 		// Long.hashCode()
792 		CastExpression ce= fAst.newCastExpression();
793 		ce.setType(fAst.newPrimitiveType(PrimitiveType.INT));
794 
795 		InfixExpression unsignedShiftRight= fAst.newInfixExpression();
796 		unsignedShiftRight.setLeftOperand(shift1);
797 		unsignedShiftRight.setRightOperand(fAst.newNumberLiteral("32")); //$NON-NLS-1$
798 		unsignedShiftRight.setOperator(Operator.RIGHT_SHIFT_UNSIGNED);
799 
800 		InfixExpression xor= fAst.newInfixExpression();
801 		xor.setLeftOperand(shift2);
802 		xor.setRightOperand(parenthesize(unsignedShiftRight));
803 		xor.setOperator(InfixExpression.Operator.XOR);
804 
805 		ce.setExpression(parenthesize(xor));
806 		return ce;
807 	}
808 
prepareAssignment(Expression rightHand)809 	private Statement prepareAssignment(Expression rightHand) {
810 		// result = PRIME*result + (...)
811 		InfixExpression mul= fAst.newInfixExpression();
812 		mul.setLeftOperand(fAst.newSimpleName(VARIABLE_NAME_PRIME));
813 		mul.setRightOperand(fAst.newSimpleName(VARIABLE_NAME_RESULT));
814 		mul.setOperator(Operator.TIMES);
815 
816 		Assignment ass= fAst.newAssignment();
817 		ass.setLeftHandSide(fAst.newSimpleName(VARIABLE_NAME_RESULT));
818 
819 		InfixExpression plus= fAst.newInfixExpression();
820 		plus.setLeftOperand(mul);
821 		plus.setOperator(Operator.PLUS);
822 		plus.setRightOperand(rightHand);
823 
824 		ass.setRightHandSide(plus);
825 
826 		return fAst.newExpressionStatement(ass);
827 	}
828 
829 	// *************** EQUALS ***************
830 
createEqualsMethod()831 	private MethodDeclaration createEqualsMethod() throws CoreException {
832 
833 		MethodDeclaration equalsMethodDeclaration= fAst.newMethodDeclaration();
834 		equalsMethodDeclaration.modifiers().addAll(ASTNodeFactory.newModifiers(fAst, Modifier.PUBLIC));
835 		equalsMethodDeclaration.setName(fAst.newSimpleName(METHODNAME_EQUALS));
836 		equalsMethodDeclaration.setConstructor(false);
837 		equalsMethodDeclaration.setReturnType2(fAst.newPrimitiveType(PrimitiveType.BOOLEAN));
838 
839 		List<SingleVariableDeclaration> parameters= equalsMethodDeclaration.parameters();
840 		SingleVariableDeclaration equalsParam= fAst.newSingleVariableDeclaration();
841 		equalsParam.setType(fRewrite.getImportRewrite().addImport(fAst.resolveWellKnownType(JAVA_LANG_OBJECT), fAst, fImportRewriteContext, TypeLocation.PARAMETER));
842 		equalsParam.setName(fAst.newSimpleName(VARIABLE_NAME_EQUALS_PARAM));
843 		parameters.add(equalsParam);
844 
845 		Block body= fAst.newBlock();
846 		equalsMethodDeclaration.setBody(body);
847 
848 		// if (this == obj) return true;
849 		body.statements().add(
850 				createReturningIfStatement(fAst.newThisExpression(), fAst.newSimpleName(VARIABLE_NAME_EQUALS_PARAM), Operator.EQUALS, true));
851 
852 		if (needsNoSuperCall(fType, METHODNAME_EQUALS, new ITypeBinding[] {fAst.resolveWellKnownType(JAVA_LANG_OBJECT)})) {
853 			if (!fUseInstanceOf) {
854 				// if (obj == null) return false;
855 				body.statements().add(
856 						createReturningIfStatement(fAst.newSimpleName(VARIABLE_NAME_EQUALS_PARAM), fAst.newNullLiteral(), Operator.EQUALS, false));
857 			}
858 		} else {
859 			// if (!super.equals(obj)) return false;
860 			SuperMethodInvocation superEqualsCall= fAst.newSuperMethodInvocation();
861 			superEqualsCall.setName(fAst.newSimpleName(METHODNAME_EQUALS));
862 			superEqualsCall.arguments().add(fAst.newSimpleName(VARIABLE_NAME_EQUALS_PARAM));
863 
864 			PrefixExpression pe= fAst.newPrefixExpression();
865 			pe.setOperator(PrefixExpression.Operator.NOT);
866 			pe.setOperand(superEqualsCall);
867 
868 			IfStatement superEqualsIf= fAst.newIfStatement();
869 			superEqualsIf.setExpression(pe);
870 			superEqualsIf.setThenStatement(getThenStatement(getReturnFalse()));
871 
872 			body.statements().add(superEqualsIf);
873 		}
874 
875 		if (fUseInstanceOf) {
876 			// if (!(obj instanceof Type)) return false;
877 			InstanceofExpression expression= fAst.newInstanceofExpression();
878 			expression.setLeftOperand(fAst.newSimpleName(VARIABLE_NAME_EQUALS_PARAM));
879 			expression.setRightOperand(fRewrite.getImportRewrite().addImport(fType, fAst, fImportRewriteContext, TypeLocation.INSTANCEOF));
880 
881 			PrefixExpression notExpression= fAst.newPrefixExpression();
882 			notExpression.setOperator(org.eclipse.jdt.core.dom.PrefixExpression.Operator.NOT);
883 
884 			ParenthesizedExpression parenthesizedExpression= fAst.newParenthesizedExpression();
885 			parenthesizedExpression.setExpression(expression);
886 
887 			notExpression.setOperand(parenthesizedExpression);
888 
889 			body.statements().add(createReturningIfStatement(false, notExpression));
890 		} else {
891 			// if (getClass() != obj.getClass()) return false;
892 			MethodInvocation thisClass= fAst.newMethodInvocation();
893 			thisClass.setName(fAst.newSimpleName(METHODNAME_GETCLASS));
894 
895 			MethodInvocation objGetClass= fAst.newMethodInvocation();
896 			objGetClass.setExpression(fAst.newSimpleName(VARIABLE_NAME_EQUALS_PARAM));
897 			objGetClass.setName(fAst.newSimpleName(METHODNAME_GETCLASS));
898 
899 			body.statements().add(createReturningIfStatement(thisClass, objGetClass, Operator.NOT_EQUALS, false));
900 		}
901 
902 		boolean memberType= isMemberType();
903 		if (memberType || fFields.length > 0) {
904 			// Type other= (Type) obj;
905 			VariableDeclarationFragment sd= fAst.newVariableDeclarationFragment();
906 			sd.setName(fAst.newSimpleName(VARIABLE_NAME_EQUALS_CASTED));
907 
908 			CastExpression cast= fAst.newCastExpression();
909 			cast.setType(fAst.newSimpleType(fAst.newSimpleName(fType.getName())));
910 			cast.setExpression(fAst.newSimpleName(VARIABLE_NAME_EQUALS_PARAM));
911 			sd.setInitializer(cast);
912 
913 			VariableDeclarationStatement otherDeclaration= fAst.newVariableDeclarationStatement(sd);
914 			otherDeclaration.setType(fAst.newSimpleType(fAst.newSimpleName(fType.getName())));
915 
916 			body.statements().add(otherDeclaration);
917 		}
918 
919 		if (memberType) {
920 			body.statements().add(createEnclosingInstanceComparison());
921 		}
922 
923 		if (fUseJ7HashEquals && fFields.length > 0) {
924 			body.statements().add(createJ7EqualsStatement());
925 		} else {
926 			for (IVariableBinding field : fFields) {
927 				ITypeBinding type= field.getType();
928 				if (type.isPrimitive() || type.isEnum())
929 					body.statements().add(createSimpleComparison(field));
930 				else if (type.isArray()) {
931 					IJavaProject project= fUnit.getJavaElement().getJavaProject();
932 					if (needsDeepMethod(type) && JavaModelUtil.is50OrHigher(project)) {
933 						body.statements().add(createMultiArrayComparison(field.getName()));
934 					} else {
935 						body.statements().add(createArrayComparison(field.getName()));
936 					}
937 				} else
938 					body.statements().add(createQualifiedComparison(field.getName()));
939 			}
940 
941 			// the last return true:
942 			ReturnStatement endReturn= fAst.newReturnStatement();
943 			endReturn.setExpression(fAst.newBooleanLiteral(true));
944 
945 			body.statements().add(endReturn);
946 		}
947 
948 		// method comment
949 		if (fSettings != null) {
950 			ITypeBinding object= fAst.resolveWellKnownType(JAVA_LANG_OBJECT);
951 			IMethodBinding objectMethod= null;
952 			for (IMethodBinding objm : object.getDeclaredMethods()) {
953 				if (objm.getName().equals(METHODNAME_EQUALS) && objm.getParameterTypes().length == 1 && objm.getParameterTypes()[0].getQualifiedName().equals(JAVA_LANG_OBJECT)) {
954 					objectMethod= objm;
955 				}
956 			}
957 			createMethodComment(equalsMethodDeclaration, objectMethod);
958 		}
959 
960 		return equalsMethodDeclaration;
961 	}
962 
createJ7EqualsStatement()963 	private Statement createJ7EqualsStatement() {
964 		// return Object.equals(anObjet, other.anObject) && ... ;
965 		ReturnStatement returnStatement= fAst.newReturnStatement();
966 
967 		Expression equalsExp= createJ7EqualsExpression(fFields[0]);
968 		if (fFields.length == 1) {
969 			returnStatement.setExpression(equalsExp);
970 		} else {
971 			InfixExpression exp= fAst.newInfixExpression();
972 			exp.setLeftOperand(equalsExp);
973 			InfixExpression refExp= exp;
974 			for (int i= 1; i < fFields.length; i++) {
975 				equalsExp= createJ7EqualsExpression(fFields[i]);
976 
977 				refExp.setOperator(Operator.CONDITIONAL_AND);
978 				if (i == fFields.length - 1) {
979 					refExp.setRightOperand(equalsExp);
980 				} else {
981 					InfixExpression infixExp= fAst.newInfixExpression();
982 					infixExp.setLeftOperand(equalsExp);
983 					refExp.setRightOperand(infixExp);
984 					refExp= infixExp;
985 				}
986 			}
987 			returnStatement.setExpression(exp);
988 		}
989 		return returnStatement;
990 	}
991 
createJ7EqualsExpression(IVariableBinding variable)992 	private Expression createJ7EqualsExpression(IVariableBinding variable) {
993 		ITypeBinding type= variable.getType();
994 		String name= variable.getName();
995 		if (type.isPrimitive() || type.isEnum()) {
996 			InfixExpression expression= fAst.newInfixExpression();
997 			expression.setOperator(Operator.EQUALS);
998 			if (isPrimitiveType(type, PrimitiveType.FLOAT)) {
999 				// Float.floatToIntBits(aFloat) == Float.floatToIntBits(other.aFloat)
1000 				expression.setLeftOperand(createFloatInvocation(getThisAccessForEquals(name)));
1001 				expression.setRightOperand(createFloatInvocation(getOtherAccess(name)));
1002 			} else if (isPrimitiveType(type, PrimitiveType.DOUBLE)) {
1003 				// Double.doubleToLongBits(aDouble) == Double.doubleToLongBits(other.aDouble)
1004 				expression.setLeftOperand(createDoubleInvocation(getThisAccessForEquals(name)));
1005 				expression.setRightOperand(createDoubleInvocation(getOtherAccess(name)));
1006 			} else {
1007 				// anInt == other.anInt
1008 				expression.setLeftOperand(getThisAccessForEquals(name));
1009 				expression.setRightOperand(getOtherAccess(name));
1010 			}
1011 			return expression;
1012 		} else {
1013 			MethodInvocation invoc= fAst.newMethodInvocation();
1014 			if (type.isArray()) {
1015 				// Arrays.equals(anArray, other.anArray) or Arrays.deepEquals(anArray, other.anArray)
1016 				invoc.setExpression(getQualifiedName(JAVA_UTIL_ARRAYS));
1017 				invoc.setName(needsDeepMethod(type) ? fAst.newSimpleName(METHODNAME_DEEP_EQUALS) : fAst.newSimpleName(METHODNAME_EQUALS));
1018 			} else {
1019 				// Objects.equals(anObj, other.anObj)
1020 				invoc.setExpression(getQualifiedName(JAVA_UTIL_OBJECTS));
1021 				invoc.setName(fAst.newSimpleName(METHODNAME_EQUALS));
1022 			}
1023 			invoc.arguments().add(getThisAccessForEquals(name));
1024 			invoc.arguments().add(getOtherAccess(name));
1025 			return invoc;
1026 		}
1027 	}
1028 
createEnclosingInstanceComparison()1029 	private Statement createEnclosingInstanceComparison() {
1030 		MethodInvocation enclosing1= fAst.newMethodInvocation();
1031 		enclosing1.setName(fAst.newSimpleName(METHODNAME_GET_ENCLOSING_INSTANCE));
1032 
1033 		MethodInvocation enclosing2= fAst.newMethodInvocation();
1034 		enclosing2.setName(fAst.newSimpleName(METHODNAME_GET_ENCLOSING_INSTANCE));
1035 		enclosing2.setExpression(fAst.newSimpleName(VARIABLE_NAME_EQUALS_CASTED));
1036 
1037 		MethodInvocation enclosingEql= fAst.newMethodInvocation();
1038 		enclosingEql.setName(fAst.newSimpleName(METHODNAME_EQUALS));
1039 		enclosingEql.setExpression(enclosing1);
1040 		enclosingEql.arguments().add(enclosing2);
1041 
1042 		PrefixExpression not= fAst.newPrefixExpression();
1043 		not.setOperand(enclosingEql);
1044 		not.setOperator(PrefixExpression.Operator.NOT);
1045 
1046 		IfStatement notEqNull= fAst.newIfStatement();
1047 		notEqNull.setExpression(not);
1048 		notEqNull.setThenStatement(getThenStatement(getReturnFalse()));
1049 		return notEqNull;
1050 	}
1051 
createSimpleComparison(IVariableBinding binding)1052 	private Statement createSimpleComparison(IVariableBinding binding) {
1053 		if (isPrimitiveType(binding.getType(), PrimitiveType.FLOAT)) {
1054 			return createReturningIfStatement(createFloatInvocation(getThisAccessForEquals(binding.getName())), createFloatInvocation(getOtherAccess(binding
1055 					.getName())), Operator.NOT_EQUALS, false);
1056 		} else if (isPrimitiveType(binding.getType(), PrimitiveType.DOUBLE)) {
1057 			return createReturningIfStatement(createDoubleInvocation(getThisAccessForEquals(binding.getName())), createDoubleInvocation(getOtherAccess(binding
1058 					.getName())), Operator.NOT_EQUALS, false);
1059 		} else
1060 			return createReturningIfStatement(getThisAccessForEquals(binding.getName()), getOtherAccess(binding.getName()), Operator.NOT_EQUALS, false);
1061 	}
1062 
createArrayComparison(String name)1063 	private Statement createArrayComparison(String name) {
1064 		MethodInvocation invoc= fAst.newMethodInvocation();
1065 		invoc.setName(fAst.newSimpleName(METHODNAME_EQUALS));
1066 		invoc.setExpression(getQualifiedName(JAVA_UTIL_ARRAYS));
1067 		invoc.arguments().add(getThisAccessForEquals(name));
1068 		invoc.arguments().add(getOtherAccess(name));
1069 
1070 		PrefixExpression pe= fAst.newPrefixExpression();
1071 		pe.setOperator(PrefixExpression.Operator.NOT);
1072 		pe.setOperand(invoc);
1073 
1074 		IfStatement ifSt= fAst.newIfStatement();
1075 		ifSt.setExpression(pe);
1076 		ifSt.setThenStatement(getThenStatement(getReturnFalse()));
1077 
1078 		return ifSt;
1079 	}
1080 
createMultiArrayComparison(String name)1081 	private Statement createMultiArrayComparison(String name) {
1082 		MethodInvocation invoc= fAst.newMethodInvocation();
1083 		invoc.setName(fAst.newSimpleName(METHODNAME_DEEP_EQUALS));
1084 		invoc.setExpression(getQualifiedName(JAVA_UTIL_ARRAYS));
1085 		invoc.arguments().add(getThisAccessForEquals(name));
1086 		invoc.arguments().add(getOtherAccess(name));
1087 
1088 		PrefixExpression pe= fAst.newPrefixExpression();
1089 		pe.setOperator(PrefixExpression.Operator.NOT);
1090 		pe.setOperand(invoc);
1091 
1092 		IfStatement ifSt= fAst.newIfStatement();
1093 		ifSt.setExpression(pe);
1094 		ifSt.setThenStatement(getThenStatement(getReturnFalse()));
1095 
1096 		return ifSt;
1097 	}
1098 
1099 	/**
1100 	 * Creates a comparison of reference types.
1101 	 *
1102 	 * <pre>
1103 	 * if (this.a == null) {
1104 	 * 	if (other.a != null)
1105 	 * 		return false;
1106 	 * } else {
1107 	 * 	if (!this.a.equals(other.a))
1108 	 * 		return false;
1109 	 * }
1110 	 * </pre>
1111 	 * @param name the field name
1112 	 * @return the comparison statement
1113 	 */
createQualifiedComparison(String name)1114 	private Statement createQualifiedComparison(String name) {
1115 		InfixExpression newCondition= fAst.newInfixExpression();
1116 		newCondition.setOperator(Operator.EQUALS);
1117 		newCondition.setLeftOperand(getThisAccessForEquals(name));
1118 		newCondition.setRightOperand(fAst.newNullLiteral());
1119 
1120 		// THEN
1121 		InfixExpression notEqNull= fAst.newInfixExpression();
1122 		notEqNull.setOperator(Operator.NOT_EQUALS);
1123 		notEqNull.setLeftOperand(getOtherAccess(name));
1124 		notEqNull.setRightOperand(fAst.newNullLiteral());
1125 
1126 		IfStatement thenPart= fAst.newIfStatement();
1127 		thenPart.setExpression(notEqNull);
1128 		thenPart.setThenStatement(getThenStatement(getReturnFalse()));
1129 
1130 		Block thenPart2= fAst.newBlock();
1131 		thenPart2.statements().add(thenPart);
1132 
1133 		// ELSE
1134 		MethodInvocation invoc= fAst.newMethodInvocation();
1135 		invoc.setName(fAst.newSimpleName(METHODNAME_EQUALS));
1136 		invoc.setExpression(getThisAccessForEquals(name));
1137 		invoc.arguments().add(getOtherAccess(name));
1138 
1139 		PrefixExpression pe= fAst.newPrefixExpression();
1140 		pe.setOperator(PrefixExpression.Operator.NOT);
1141 		pe.setOperand(invoc);
1142 
1143 		IfStatement elsePart= fAst.newIfStatement();
1144 		elsePart.setExpression(pe);
1145 		elsePart.setThenStatement(getThenStatement(getReturnFalse()));
1146 
1147 		// ALL
1148 		IfStatement isNull= fAst.newIfStatement();
1149 		isNull.setExpression(newCondition);
1150 		isNull.setThenStatement(thenPart2);
1151 		isNull.setElseStatement(elsePart);
1152 
1153 		return isNull;
1154 	}
1155 
1156 	// ************************ HELPERS **************************
1157 
createReturningIfStatement(Expression left, Expression right, Operator operator, boolean whatToReturn)1158 	private Statement createReturningIfStatement(Expression left, Expression right, Operator operator, boolean whatToReturn) {
1159 		InfixExpression newCondition= fAst.newInfixExpression();
1160 		newCondition.setOperator(operator);
1161 		newCondition.setLeftOperand(left);
1162 		newCondition.setRightOperand(right);
1163 		return createReturningIfStatement(whatToReturn, newCondition);
1164 	}
1165 
createReturningIfStatement(boolean result, Expression condition)1166 	private Statement createReturningIfStatement(boolean result, Expression condition) {
1167 		IfStatement firstIf= fAst.newIfStatement();
1168 		firstIf.setExpression(condition);
1169 
1170 		ReturnStatement returner= fAst.newReturnStatement();
1171 		returner.setExpression(fAst.newBooleanLiteral(result));
1172 		firstIf.setThenStatement(getThenStatement(returner));
1173 		return firstIf;
1174 	}
1175 
createMethodComment(MethodDeclaration newDeclaration, IMethodBinding copyFrom)1176 	private void createMethodComment(MethodDeclaration newDeclaration, IMethodBinding copyFrom) throws CoreException {
1177 		if (fSettings.createComments) {
1178 			String string= CodeGeneration.getMethodComment(fRewrite.getCu(), fType.getQualifiedName(), newDeclaration, copyFrom, StubUtility.getLineDelimiterUsed(fRewrite.getCu()));
1179 			if (string != null) {
1180 				Javadoc javadoc= (Javadoc) fRewrite.getASTRewrite().createStringPlaceholder(string, ASTNode.JAVADOC);
1181 				newDeclaration.setJavadoc(javadoc);
1182 			}
1183 		}
1184 		IJavaProject project= fUnit.getJavaElement().getJavaProject();
1185 		StubUtility2Core.addOverrideAnnotation(fSettings, project, fRewrite.getASTRewrite(), fRewrite.getImportRewrite(), newDeclaration, copyFrom.getDeclaringClass().isInterface(), null);
1186 	}
1187 
needsNoSuperCall(ITypeBinding typeBinding, String name, ITypeBinding[] parameters)1188 	private boolean needsNoSuperCall(ITypeBinding typeBinding, String name, ITypeBinding[] parameters) {
1189 		Assert.isNotNull(typeBinding);
1190 		IMethodBinding binding= Bindings.findMethodInHierarchy(typeBinding.getSuperclass(), name, parameters);
1191 		if (binding != null && !Modifier.isAbstract(binding.getModifiers())) {
1192 			ITypeBinding declaring= binding.getDeclaringClass();
1193 			return declaring.getQualifiedName().equals(JAVA_LANG_OBJECT);
1194 		}
1195 		return true;
1196 	}
1197 
1198 
getThisAccessForEquals(String name)1199 	private Expression getThisAccessForEquals(String name) {
1200 		return getThisAccess(name, false);
1201 	}
1202 
getThisAccessForHashCode(String name)1203 	private Expression getThisAccessForHashCode(String name) {
1204 		return getThisAccess(name, true);
1205 	}
1206 
getThisAccess(String name, boolean forHashCode)1207 	private Expression getThisAccess(String name, boolean forHashCode) {
1208 		if (fSettings.useKeywordThis || needsThisQualification(name, forHashCode)) {
1209 			FieldAccess fa= fAst.newFieldAccess();
1210 			fa.setExpression(fAst.newThisExpression());
1211 			fa.setName(fAst.newSimpleName(name));
1212 			return fa;
1213 		}
1214 		return fAst.newSimpleName(name);
1215 	}
1216 
getOtherAccess(String name)1217 	private Expression getOtherAccess(String name) {
1218 		return fAst.newQualifiedName(fAst.newSimpleName(VARIABLE_NAME_EQUALS_CASTED), fAst.newSimpleName(name));
1219 	}
1220 
isPrimitiveType(ITypeBinding binding, PrimitiveType.Code code)1221 	private boolean isPrimitiveType(ITypeBinding binding, PrimitiveType.Code code) {
1222 		return (binding.getName().equals(code.toString()));
1223 	}
1224 
isPrimitiveType(ITypeBinding type, PrimitiveType.Code[] codes)1225 	private boolean isPrimitiveType(ITypeBinding type, PrimitiveType.Code[] codes) {
1226 		for (Code code : codes) {
1227 			if (isPrimitiveType(type, code))
1228 				return true;
1229 		}
1230 		return false;
1231 	}
1232 
getQualifiedName(String name)1233 	private Name getQualifiedName(String name) {
1234 		String importedType= fRewrite.getImportRewrite().addImport(name, fImportRewriteContext);
1235 		return ASTNodeFactory.newName(fAst, importedType);
1236 	}
1237 
getReturnFalse()1238 	private ReturnStatement getReturnFalse() {
1239 		ReturnStatement falseReturn= fAst.newReturnStatement();
1240 		falseReturn.setExpression(fAst.newBooleanLiteral(false));
1241 		return falseReturn;
1242 	}
1243 
getThenStatement(Statement statement)1244 	private Statement getThenStatement(Statement statement) {
1245 		if (fUseBlocksForThen && !(statement instanceof Block)) {
1246 			Block block= fAst.newBlock();
1247 			block.statements().add(statement);
1248 			return block;
1249 		}
1250 		return statement;
1251 	}
1252 
1253 
parenthesize(Expression expression)1254 	private Expression parenthesize(Expression expression) {
1255 		ParenthesizedExpression pe= fAst.newParenthesizedExpression();
1256 		pe.setExpression(expression);
1257 		return pe;
1258 	}
1259 
createFloatInvocation(Expression access)1260 	private Expression createFloatInvocation(Expression access) {
1261 		return createMethodInvocation(access, "java.lang.Float", "floatToIntBits"); //$NON-NLS-1$ //$NON-NLS-2$
1262 	}
1263 
createDoubleInvocation(Expression access)1264 	private Expression createDoubleInvocation(Expression access) {
1265 		return createMethodInvocation(access, "java.lang.Double", "doubleToLongBits"); //$NON-NLS-1$ //$NON-NLS-2$
1266 	}
1267 
createMethodInvocation(Expression access, String qualifiedClassName, String methodName)1268 	private Expression createMethodInvocation(Expression access, String qualifiedClassName, String methodName) {
1269 		MethodInvocation invoc= fAst.newMethodInvocation();
1270 		invoc.setExpression(getQualifiedName(qualifiedClassName));
1271 		invoc.setName(fAst.newSimpleName(methodName));
1272 		invoc.arguments().add(access);
1273 		return invoc;
1274 	}
1275 
needsThisQualification(String name, boolean isHashCode)1276 	private boolean needsThisQualification(String name, boolean isHashCode) {
1277 		if (isHashCode)
1278 			return ( (fDoubleCount > 0 && name.equals(VARIABLE_NAME_DOUBLE_TEMPORARY)) || (name.equals(VARIABLE_NAME_PRIME)) || (name
1279 					.equals(VARIABLE_NAME_RESULT)));
1280 		return ( (name.equals(VARIABLE_NAME_EQUALS_CASTED)) || (name.equals(VARIABLE_NAME_EQUALS_PARAM)));
1281 	}
1282 
needsDeepMethod(ITypeBinding type)1283 	private boolean needsDeepMethod(ITypeBinding type) {
1284 		String elementTypeName= type.getErasure().getElementType().getName();
1285 		return type.getDimensions() > 1 || TYPE_NAME_CLONEABLE.equals(elementTypeName) || TYPE_NAME_SERIALIZABLE.equals(elementTypeName) || TYPE_NAME_OBJECT.equals(elementTypeName);
1286 	}
1287 
1288 }
1289