Skip to content

Commit

Permalink
Generate hashCode and equals with prompt
Browse files Browse the repository at this point in the history
Signed-off-by: Jinbo Wang <[email protected]>
  • Loading branch information
testforstephen committed Feb 26, 2019
1 parent f3552fb commit 7170abe
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public interface JavaCodeActionKind {
*/
public static final String SOURCE_GENERATE_ACCESSORS = SOURCE_GENERATE + ".accessors";

/**
* Generate hashCode/equals kind
*/
public static final String SOURCE_GENERATE_HASHCODE_EQUALS = SOURCE_GENERATE + ".hashCodeEquals";

/**
* Override/Implement methods kind
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*******************************************************************************
* Copyright (c) 2019 Microsoft 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:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/

package org.eclipse.jdt.ls.core.internal.handlers;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
import org.eclipse.jdt.internal.corext.codemanipulation.GenerateHashCodeEqualsOperation;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.ls.core.internal.text.correction.SourceAssistProcessor;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.text.edits.TextEdit;

public class HashCodeEqualsHandler {
private static final String METHODNAME_HASH_CODE = "hashCode";
private static final String METHODNAME_EQUALS = "equals";

public static CheckHashCodeEqualsResponse checkHashCodeEqualsStatus(CodeActionParams params) {
CheckHashCodeEqualsResponse response = new CheckHashCodeEqualsResponse();
IType type = SourceAssistProcessor.getSelectionType(params);
if (type == null) {
return response;
}

try {
RefactoringASTParser astParser = new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL);
CompilationUnit astRoot = astParser.parse(type.getCompilationUnit(), true);
ITypeBinding typeBinding = ASTNodes.getTypeBinding(astRoot, type);
if (typeBinding == null) {
return response;
}

HashCodeEqualsInfo info = getTypeInfo(typeBinding);
IVariableBinding[] fields = typeBinding.getDeclaredFields();
List<VariableField> validFields = new ArrayList<>();
for (IVariableBinding field : fields) {
if (!Modifier.isStatic(field.getModifiers())) {
VariableField variableField = new VariableField();
variableField.bindingKey = field.getKey();
variableField.name = field.getName();
variableField.type = field.getType().getName();
validFields.add(variableField);
}
}

response.type = type.getTypeQualifiedName();
response.foundEquals = info.foundEquals;
response.foundHashCode = info.foundHashCode;
response.fields = validFields.toArray(new VariableField[0]);
response.settings = new GenerateHashCodeEqualsSettings();
response.settings.createComments = false;
response.settings.overrideAnnotation = true;
} catch (JavaModelException e) {
// do nothing.
}

return response;
}

public static WorkspaceEdit generateHashCodeEquals(GenerateHashCodeEqualsParams params) {
IType type = SourceAssistProcessor.getSelectionType(params.context);
if (type == null) {
return null;
}

try {
RefactoringASTParser astParser = new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL);
CompilationUnit astRoot = astParser.parse(type.getCompilationUnit(), true);
ITypeBinding typeBinding = ASTNodes.getTypeBinding(astRoot, type);
if (typeBinding == null) {
return null;
}

IVariableBinding[] variableBindings = convertToVariableBindings(typeBinding, params.fields);
boolean useInstanceOf = false;
boolean useJ7HashEquals = true;
boolean useBlocks = false;
boolean regenerate = false;
CodeGenerationSettings settings = new CodeGenerationSettings();
settings.createComments = false;
settings.overrideAnnotation = true;
if (params.settings != null) {
useInstanceOf = params.settings.useInstanceOf;
useJ7HashEquals = params.settings.useJ7HashEquals;
useBlocks = params.settings.useBlocks;
regenerate = params.settings.regenerate;
settings.setSettings(params.settings);
}

GenerateHashCodeEqualsOperation operation = new GenerateHashCodeEqualsOperation(typeBinding, variableBindings, astRoot, null, settings, useInstanceOf, useJ7HashEquals, regenerate, false, false);
operation.setUseBlocksForThen(useBlocks);
operation.run(null);
TextEdit edit = operation.getResultingEdit();
return SourceAssistProcessor.convertToWorkspaceEdit(type.getCompilationUnit(), edit);
} catch (CoreException e) {
return null;
}
}

private static HashCodeEqualsInfo getTypeInfo(ITypeBinding typeBinding) {
HashCodeEqualsInfo info = new HashCodeEqualsInfo();
IMethodBinding[] declaredMethods = typeBinding.getDeclaredMethods();
for (IMethodBinding method : declaredMethods) {
if (method.getName().equals(METHODNAME_EQUALS)) {
ITypeBinding[] b = method.getParameterTypes();
if ((b.length == 1) && (b[0].getQualifiedName().equals("java.lang.Object"))) {
info.foundEquals = true;
}
}

if (method.getName().equals(METHODNAME_HASH_CODE) && method.getParameterTypes().length == 0) {
info.foundHashCode = true;
}

if (info.foundEquals && info.foundHashCode) {
break;
}
}

return info;
}

private static IVariableBinding[] convertToVariableBindings(ITypeBinding typeBinding, VariableField[] fields) {
Set<String> bindingKeys = Stream.of(fields).map((field) -> field.bindingKey).collect(Collectors.toSet());
List<IVariableBinding> bindings = new ArrayList<>();
for (IVariableBinding declaredField : typeBinding.getDeclaredFields()) {
if (bindingKeys.contains(declaredField.getKey())) {
bindings.add(declaredField);
}
}

return bindings.toArray(new IVariableBinding[0]);
}

private static class HashCodeEqualsInfo {
public boolean foundHashCode = false;
public boolean foundEquals = false;
}

public static class VariableField {
public String bindingKey;
public String name;
public String type;
}

public static class GenerateHashCodeEqualsSettings extends CodeGenerationSettings {
public boolean useJ7HashEquals = true;
public boolean regenerate = false;
public boolean useBlocks = false;
public boolean useInstanceOf = false;
}

public static class CheckHashCodeEqualsResponse {
public String type;
public VariableField[] fields;
public boolean foundHashCode;
public boolean foundEquals;
public GenerateHashCodeEqualsSettings settings;
}

public static class GenerateHashCodeEqualsParams {
public CodeActionParams context;
public VariableField[] fields;
public GenerateHashCodeEqualsSettings settings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
import org.eclipse.jdt.ls.core.internal.JobHelpers;
import org.eclipse.jdt.ls.core.internal.LanguageServerWorkingCopyOwner;
import org.eclipse.jdt.ls.core.internal.ServiceStatus;
import org.eclipse.jdt.ls.core.internal.handlers.HashCodeEqualsHandler.CheckHashCodeEqualsResponse;
import org.eclipse.jdt.ls.core.internal.handlers.HashCodeEqualsHandler.GenerateHashCodeEqualsParams;
import org.eclipse.jdt.ls.core.internal.handlers.OverrideMethodsHandler.AddOverridableMethodParams;
import org.eclipse.jdt.ls.core.internal.handlers.OverrideMethodsHandler.OverridableMethodsResponse;
import org.eclipse.jdt.ls.core.internal.lsp.JavaProtocolExtensions;
Expand Down Expand Up @@ -770,7 +772,19 @@ public CompletableFuture<OverridableMethodsResponse> listOverridableMethods(Code
@Override
public CompletableFuture<WorkspaceEdit> addOverridableMethods(AddOverridableMethodParams params) {
logInfo(">> java/addOverridableMethods");
return computeAsync((montior) -> OverrideMethodsHandler.addOverridableMethods(params));
return computeAsync((monitor) -> OverrideMethodsHandler.addOverridableMethods(params));
}

@Override
public CompletableFuture<CheckHashCodeEqualsResponse> checkHashCodeEqualsStatus(CodeActionParams params) {
logInfo(">> java/checkHashCodeEqualsStatus");
return computeAsync((monitor) -> HashCodeEqualsHandler.checkHashCodeEqualsStatus(params));
}

@Override
public CompletableFuture<WorkspaceEdit> generateHashCodeEquals(GenerateHashCodeEqualsParams params) {
logInfo(">> java/generateHashCodeEquals");
return computeAsync((monitor) -> HashCodeEqualsHandler.generateHashCodeEquals(params));
}

public void sendStatus(ServiceStatus serverStatus, String status) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,9 @@

import java.util.List;

import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.codemanipulation.OverrideMethodsOperation;
import org.eclipse.jdt.ls.core.internal.codemanipulation.OverrideMethodsOperation.OverridableMethod;
import org.eclipse.jdt.ls.core.internal.corrections.DiagnosticsHelper;
import org.eclipse.jdt.ls.core.internal.corrections.InnovationContext;
import org.eclipse.jdt.ls.core.internal.text.correction.SourceAssistProcessor;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.WorkspaceEdit;
Expand All @@ -31,14 +24,14 @@
public class OverrideMethodsHandler {

public static OverridableMethodsResponse listOverridableMethods(CodeActionParams params) {
IType type = getSelectionType(params);
IType type = SourceAssistProcessor.getSelectionType(params);
String typeName = type == null ? "" : type.getTypeQualifiedName();
List<OverridableMethod> methods = OverrideMethodsOperation.listOverridableMethods(type);
return new OverridableMethodsResponse(typeName, methods);
}

public static WorkspaceEdit addOverridableMethods(AddOverridableMethodParams params) {
IType type = getSelectionType(params.context);
IType type = SourceAssistProcessor.getSelectionType(params.context);
TextEdit edit = OverrideMethodsOperation.addOverridableMethods(type, params.overridableMethods);
if (edit == null) {
return null;
Expand All @@ -47,20 +40,6 @@ public static WorkspaceEdit addOverridableMethods(AddOverridableMethodParams par
return SourceAssistProcessor.convertToWorkspaceEdit(type.getCompilationUnit(), edit);
}

private static IType getSelectionType(CodeActionParams params) {
final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(params.getTextDocument().getUri());
if (unit == null) {
return null;
}

int start = DiagnosticsHelper.getStartOffset(unit, params.getRange());
int end = DiagnosticsHelper.getEndOffset(unit, params.getRange());
InnovationContext context = new InnovationContext(unit, start, end - start);
CompilationUnit astRoot = CoreASTProvider.getInstance().getAST(unit, CoreASTProvider.WAIT_YES, new NullProgressMonitor());
context.setASTRoot(astRoot);
return SourceAssistProcessor.getSelectionType(context);
}

public static class OverridableMethodsResponse {
public String type;
public List<OverridableMethod> methods;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.concurrent.CompletableFuture;

import org.eclipse.jdt.ls.core.internal.BuildWorkspaceStatus;
import org.eclipse.jdt.ls.core.internal.handlers.HashCodeEqualsHandler.CheckHashCodeEqualsResponse;
import org.eclipse.jdt.ls.core.internal.handlers.HashCodeEqualsHandler.GenerateHashCodeEqualsParams;
import org.eclipse.jdt.ls.core.internal.handlers.OverrideMethodsHandler.AddOverridableMethodParams;
import org.eclipse.jdt.ls.core.internal.handlers.OverrideMethodsHandler.OverridableMethodsResponse;
import org.eclipse.lsp4j.CodeActionParams;
Expand Down Expand Up @@ -49,4 +51,10 @@ public interface JavaProtocolExtensions {

@JsonRequest
CompletableFuture<WorkspaceEdit> addOverridableMethods(AddOverridableMethodParams params);

@JsonRequest
CompletableFuture<CheckHashCodeEqualsResponse> checkHashCodeEqualsStatus(CodeActionParams params);

@JsonRequest
CompletableFuture<WorkspaceEdit> generateHashCodeEquals(GenerateHashCodeEqualsParams params);
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ public boolean isOverrideMethodsPromptSupported() {
return Boolean.parseBoolean(extendedClientCapabilities.getOrDefault("overrideMethodsPromptSupport", "false").toString());
}

public boolean isHashCodeEqualsPromptSupported() {
return Boolean.parseBoolean(extendedClientCapabilities.getOrDefault("hashCodeEqualsPromptSupport", "false").toString());
}

public boolean isSupportsCompletionDocumentationMarkdown() {
//@formatter:off
return v3supported && capabilities.getTextDocument().getCompletion() != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ private ActionMessages() {

public static String OverrideMethodsAction_label;
public static String GenerateGetterSetterAction_label;
public static String GenerateHashCodeEqualsAction_label;

static {
NLS.initializeMessages(BUNDLE_NAME, ActionMessages.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
###############################################################################

OverrideMethodsAction_label=Override/Implement Methods...
GenerateGetterSetterAction_label=Generate Getters and Setters
GenerateGetterSetterAction_label=Generate Getters and Setters
GenerateHashCodeEqualsAction_label=Generate hashCode() and equals()...
Loading

0 comments on commit 7170abe

Please sign in to comment.