package com.redhat.ceylon.eclipse.code.correct;
import static com.redhat.ceylon.eclipse.code.complete.CodeCompletions.getRefinementTextFor;
import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.overloads;
import static com.redhat.ceylon.eclipse.code.complete.RefinementCompletionProposal.DEFAULT_REFINEMENT;
import static com.redhat.ceylon.eclipse.code.complete.RefinementCompletionProposal.getRefinedProducedReference;
import static com.redhat.ceylon.eclipse.code.correct.ImportProposals.importProposals;
import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentEditor;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.ltk.core.refactoring.DocumentChange;
import org.eclipse.ltk.core.refactoring.PerformChangeOperation;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.ui.IEditorPart;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.ClassDefinition;
import com.redhat.ceylon.eclipse.code.editor.CeylonEditor;
import com.redhat.ceylon.eclipse.util.Highlights;
import com.redhat.ceylon.ide.common.util.FindBodyContainerVisitor;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Reference;
import com.redhat.ceylon.model.typechecker.model.Scope;
import com.redhat.ceylon.model.typechecker.model.Unit;
class RefineEqualsHashProposal
implements ICompletionProposal,
ICompletionProposalExtension6 {
private final Tree.CompilationUnit rootNode;
private final String description;
private Node node;
public RefineEqualsHashProposal(Node node,
Tree.CompilationUnit rootNode,
String description) {
this.node = node;
this.description = description;
this.rootNode = rootNode;
}
@Override
public Point getSelection(IDocument doc) {
return null;
}
@Override
public Image getImage() {
return DEFAULT_REFINEMENT;
}
@Override
public String getDisplayString() {
return description;
}
@Override
public StyledString getStyledDisplayString() {
String hint =
CorrectionUtil.shortcut(
"com.redhat.ceylon.eclipse.ui.action.refineEqualsHash");
return Highlights.styleProposal(getDisplayString(), false)
.append(hint, StyledString.QUALIFIER_STYLER);
}
@Override
public IContextInformation getContextInformation() {
return null;
}
@Override
public String getAdditionalProposalInfo() {
//TODO: list the members that will be refined!
return null;
}
@Override
public void apply(IDocument doc) {
try {
refinEqualsHash(doc);
}
catch (ExecutionException e) {
e.printStackTrace();
}
}
private void refinEqualsHash(IDocument document)
throws ExecutionException {
if (rootNode==null) return;
TextChange change =
new DocumentChange("Refine Equals and Hash",
document);
change.setEdit(new MultiTextEdit());
//TODO: copy/pasted from CeylonQuickFixAssistant
Tree.Body body;
int offset;
if (node instanceof Tree.ClassDefinition) {
ClassDefinition classDefinition =
(Tree.ClassDefinition) node;
body = classDefinition.getClassBody();
offset = -1;
}
else if (node instanceof Tree.InterfaceDefinition) {
Tree.InterfaceDefinition interfaceDefinition =
(Tree.InterfaceDefinition) node;
body = interfaceDefinition.getInterfaceBody();
offset = -1;
}
else if (node instanceof Tree.ObjectDefinition) {
Tree.ObjectDefinition objectDefinition =
(Tree.ObjectDefinition) node;
body = objectDefinition.getClassBody();
offset = -1;
}
else if (node instanceof Tree.ObjectExpression) {
Tree.ObjectExpression objectExpression =
(Tree.ObjectExpression) node;
body = objectExpression.getClassBody();
offset = -1;
}
else if (node instanceof Tree.ClassBody ||
node instanceof Tree.InterfaceBody) {
body = (Tree.Body) node;
IEditorPart editor = getCurrentEditor();
if (editor instanceof CeylonEditor) {
CeylonEditor ce = (CeylonEditor) editor;
offset = ce.getSelection().getOffset();
}
else {
offset = -1;
}
}
else {
return;
}
if (body==null) {
return;
}
boolean isInterface =
body instanceof Tree.InterfaceBody;
List<Tree.Statement> statements =
body.getStatements();
String indent;
// String bodyIndent = getIndent(body, document);
String bodyIndent = utilJ2C().indents().getIndent(node, document);
String delim = utilJ2C().indents().getDefaultLineDelimiter(document);
if (statements.isEmpty()) {
indent = delim + bodyIndent + utilJ2C().indents().getDefaultIndent();
if (offset<0) {
offset = body.getStartIndex()+1;
}
}
else {
Tree.Statement statement =
statements.get(statements.size()-1);
indent = delim + utilJ2C().indents().getIndent(statement, document);
if (offset<0) {
offset = statement.getEndIndex();
}
}
StringBuilder result = new StringBuilder();
Set<Declaration> already =
new HashSet<Declaration>();
ClassOrInterface ci =
(ClassOrInterface)
node.getScope();
Unit unit = node.getUnit();
// Set<String> ambiguousNames = new HashSet<String>();
Declaration equals =
ci.getMember("equals", null, false);
Declaration hash =
ci.getMember("hash", null, false);
for (Declaration dec: Arrays.asList(equals, hash)) {
for (Declaration d: overloads(dec)) {
try {
if (ci.isInheritedFromSupertype(d)) {
appendRefinementText(isInterface,
indent, result, ci, unit, d);
importProposals().importSignatureTypes(d, rootNode, already);
// ambiguousNames.add(d.getName());
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
try {
if (document.getChar(offset)=='}' &&
result.length()>0) {
result.append(delim)
.append(bodyIndent);
}
}
catch (BadLocationException e) {
e.printStackTrace();
}
importProposals().applyImports(change, already, rootNode, document);
change.addEdit(new InsertEdit(offset, result.toString()));
change.initializeValidationData(null);
try {
getWorkspace()
.run(new PerformChangeOperation(change),
new NullProgressMonitor());
}
catch (CoreException ce) {
ce.printStackTrace();
}
}
private void appendRefinementText(boolean isInterface,
String indent, StringBuilder result,
ClassOrInterface ci, Unit unit, Declaration member) {
Reference pr =
getRefinedProducedReference(ci, member);
String rtext =
getRefinementTextFor(member, pr, unit,
isInterface, ci, indent, true);
result.append(indent)
.append(rtext)
.append(indent);
}
static void addRefineEqualsHashProposal(
Collection<ICompletionProposal> proposals,
Node n, Tree.CompilationUnit rootNode) {
for (ICompletionProposal p: proposals) {
if (p instanceof RefineEqualsHashProposal) {
return;
}
}
Node node;
if (n instanceof Tree.ClassBody ||
n instanceof Tree.InterfaceBody ||
n instanceof Tree.ClassDefinition ||
n instanceof Tree.InterfaceDefinition ||
n instanceof Tree.ObjectDefinition ||
n instanceof Tree.ObjectExpression) {
node = n;
}
else {
FindBodyContainerVisitor v =
new FindBodyContainerVisitor(n);
v.visit(rootNode);
node = v.getDeclaration();
}
if (node!=null) {
Scope scope = node.getScope();
if (scope instanceof ClassOrInterface) {
ClassOrInterface ci = (ClassOrInterface) scope;
String name = ci.getName();
if (name==null) {
return;
}
else if (name.startsWith("anonymous#")) {
name = "anonymous class";
}
else {
name = "'" + name + "'";
}
Declaration equals =
ci.getMember("equals", null, false);
Declaration hash =
ci.getMember("hash", null, false);
boolean hasEquals = true;
for (Declaration e: overloads(equals)) {
if (ci.isInheritedFromSupertype(e)) {
hasEquals = false;
}
}
boolean hasHash = true;
for (Declaration h: overloads(hash)) {
if (ci.isInheritedFromSupertype(h)) {
hasHash = false;
}
}
String desc;
if (hasEquals && hasHash) {
return;
}
else if (hasEquals) {
desc = "Refine 'hash' attribute of " + name;
}
else if (hasHash) {
desc = "Refine 'equals()' method of " + name;
}
else {
desc = "Refine 'equals()' and 'hash' of " + name;
}
proposals.add(new RefineEqualsHashProposal(node, rootNode, desc));
}
}
}
}