blob: 5e789b9fc985a4b06877fc0c9a5b9506b46c1f42 [file] [log] [blame]
/*
* Copyright (c) 2014 Google Inc.
*
* 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
*/
package com.google.eclipse.protobuf.ui.quickfix;
import static com.google.eclipse.protobuf.protobuf.BOOL.FALSE;
import static com.google.eclipse.protobuf.protobuf.BOOL.TRUE;
import static com.google.eclipse.protobuf.ui.quickfix.Messages.changeValueDescription;
import static com.google.eclipse.protobuf.ui.quickfix.Messages.changeValueLabel;
import static com.google.eclipse.protobuf.ui.quickfix.Messages.regenerateTagNumberDescription;
import static com.google.eclipse.protobuf.ui.quickfix.Messages.regenerateTagNumberLabel;
import static com.google.eclipse.protobuf.ui.quickfix.Messages.removeDuplicatePackageLabel;
import static com.google.eclipse.protobuf.util.Strings.quote;
import static com.google.eclipse.protobuf.validation.DataTypeValidator.EXPECTED_BOOL_ERROR;
import static com.google.eclipse.protobuf.validation.DataTypeValidator.EXPECTED_STRING_ERROR;
import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.INVALID_FIELD_TAG_NUMBER_ERROR;
import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.MISSING_MODIFIER_ERROR;
import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.MORE_THAN_ONE_PACKAGE_ERROR;
import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.REQUIRED_IN_PROTO3_ERROR;
import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.SYNTAX_IS_NOT_KNOWN_ERROR;
import static org.eclipse.emf.ecore.util.EcoreUtil.remove;
import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.findActualNodeFor;
import com.google.eclipse.protobuf.grammar.CommonKeyword;
import com.google.eclipse.protobuf.model.util.INodes;
import com.google.eclipse.protobuf.model.util.IndexedElements;
import com.google.eclipse.protobuf.model.util.Syntaxes;
import com.google.eclipse.protobuf.naming.NameResolver;
import com.google.eclipse.protobuf.protobuf.BOOL;
import com.google.eclipse.protobuf.protobuf.BooleanLink;
import com.google.eclipse.protobuf.protobuf.FieldOption;
import com.google.eclipse.protobuf.protobuf.IndexedElement;
import com.google.eclipse.protobuf.protobuf.MessageField;
import com.google.eclipse.protobuf.protobuf.ModifierEnum;
import com.google.eclipse.protobuf.protobuf.Package;
import com.google.eclipse.protobuf.protobuf.ProtobufFactory;
import com.google.eclipse.protobuf.protobuf.StringLink;
import com.google.eclipse.protobuf.protobuf.StringLiteral;
import com.google.eclipse.protobuf.protobuf.Syntax;
import com.google.eclipse.protobuf.protobuf.Value;
import com.google.inject.Inject;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.edit.IModificationContext;
import org.eclipse.xtext.ui.editor.model.edit.ISemanticModification;
import org.eclipse.xtext.ui.editor.quickfix.DefaultQuickfixProvider;
import org.eclipse.xtext.ui.editor.quickfix.Fix;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionAcceptor;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.Issue;
/**
* @author alruiz@google.com (Alex Ruiz)
*/
public class ProtobufQuickfixProvider extends DefaultQuickfixProvider {
private static final String ICON_FOR_CHANGE = "change.gif";
@Inject private IndexedElements indexedElements;
@Inject private NameResolver nameResolver;
@Inject private INodes nodes;
@Inject private Syntaxes syntaxes;
@Fix(SYNTAX_IS_NOT_KNOWN_ERROR)
public void changeSyntaxToProto2(Issue issue, IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override public void apply(EObject element, IModificationContext context) throws Exception {
Syntax syntax = (Syntax) element;
syntaxes.setName(syntax, Syntaxes.PROTO2);
}
};
String description = String.format(changeValueDescription, "syntax", quote(Syntaxes.PROTO2));
String label = String.format(changeValueLabel, Syntaxes.PROTO2);
acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
@Fix(SYNTAX_IS_NOT_KNOWN_ERROR)
public void changeSyntaxToProto3(Issue issue, IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override public void apply(EObject element, IModificationContext context) throws Exception {
Syntax syntax = (Syntax) element;
syntaxes.setName(syntax, Syntaxes.PROTO3);
}
};
String description = String.format(changeValueDescription, "syntax", quote(Syntaxes.PROTO3));
String label = String.format(changeValueLabel, Syntaxes.PROTO3);
acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
@Fix(INVALID_FIELD_TAG_NUMBER_ERROR)
public void regenerateTagNumber(Issue issue, IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override public void apply(EObject element, IModificationContext context) throws Exception {
IndexedElement e = (IndexedElement) element;
long tagNumber = indexedElements.calculateNewIndexFor(e);
indexedElements.setIndexTo(e, tagNumber);
}
};
acceptor.accept(issue, regenerateTagNumberLabel, regenerateTagNumberDescription, "field.gif", modification);
}
@Fix(MORE_THAN_ONE_PACKAGE_ERROR)
public void removeDuplicatePackage(Issue issue, IssueResolutionAcceptor acceptor) {
final Package aPackage = element(issue, Package.class);
if (aPackage == null) {
return;
}
ISemanticModification modification = new ISemanticModification() {
@Override public void apply(EObject element, IModificationContext context) throws Exception {
if (element == aPackage) {
remove(aPackage);
}
}
};
INode node = findActualNodeFor(aPackage);
String description = nodes.textOf(node);
acceptor.accept(issue, removeDuplicatePackageLabel, description, "remove.gif", modification);
}
@Fix(MISSING_MODIFIER_ERROR)
public void changeModifierToRequired(Issue issue, IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override
public void apply(EObject element, IModificationContext context) throws Exception {
MessageField field = (MessageField) element;
field.setModifier(ModifierEnum.REQUIRED);
}
};
String description = String.format(changeValueDescription, "modifier", "required");
String label = String.format(changeValueLabel, "required");
acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
@Fix(MISSING_MODIFIER_ERROR)
public void changeModifierToRepeated(Issue issue, IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override
public void apply(EObject element, IModificationContext context) throws Exception {
MessageField field = (MessageField) element;
field.setModifier(ModifierEnum.REPEATED);
}
};
String description = String.format(changeValueDescription, "modifier", "repeated");
String label = String.format(changeValueLabel, "repeated");
acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
@Fix(MISSING_MODIFIER_ERROR)
public void changeModifierToOptional(Issue issue, IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override
public void apply(EObject element, IModificationContext context) throws Exception {
MessageField field = (MessageField) element;
field.setModifier(ModifierEnum.OPTIONAL);
}
};
String description = String.format(changeValueDescription, "modifier", "optional");
String label = String.format(changeValueLabel, "optional");
acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
@Fix(REQUIRED_IN_PROTO3_ERROR)
public void changeModifierToOptionalOnRequired(Issue issue, IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override
public void apply(EObject element, IModificationContext context) throws Exception {
MessageField field = (MessageField) element;
field.setModifier(ModifierEnum.OPTIONAL);
}
};
String description = String.format(changeValueDescription, "modifier", "optional");
String label = String.format(changeValueLabel, "optional");
acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
@Fix(EXPECTED_BOOL_ERROR)
public void changeValueToTrue(Issue issue, IssueResolutionAcceptor acceptor) {
EObject element = elementIn(issue);
if (element instanceof FieldOption) {
FieldOption option = (FieldOption) element;
changeValue(option, linkTo(TRUE), CommonKeyword.TRUE, issue, acceptor);
}
}
@Fix(EXPECTED_BOOL_ERROR)
public void changeValueToFalse(Issue issue, IssueResolutionAcceptor acceptor) {
EObject element = elementIn(issue);
if (element instanceof FieldOption) {
FieldOption option = (FieldOption) element;
changeValue(option, linkTo(FALSE), CommonKeyword.FALSE, issue, acceptor);
}
}
// TODO rename BooleanLink to BoolLink
private BooleanLink linkTo(BOOL value) {
BooleanLink link = ProtobufFactory.eINSTANCE.createBooleanLink();
link.setTarget(value);
return link;
}
@Fix(EXPECTED_STRING_ERROR)
public void changeValueToEmptyString(Issue issue, IssueResolutionAcceptor acceptor) {
EObject element = elementIn(issue);
if (element instanceof FieldOption) {
FieldOption option = (FieldOption) element;
String valueToPropose = "";
changeValue(option, linkTo(valueToPropose), valueToPropose, issue, acceptor);
}
}
private StringLink linkTo(String value) {
StringLink link = ProtobufFactory.eINSTANCE.createStringLink();
StringLiteral literal = ProtobufFactory.eINSTANCE.createStringLiteral();
literal.getChunks().add(value);
link.setTarget(literal);
return link;
}
private void changeValue(final FieldOption option, final Value newValue, Object proposedValue, Issue issue,
IssueResolutionAcceptor acceptor) {
ISemanticModification modification = new ISemanticModification() {
@Override public void apply(EObject element, IModificationContext context) throws Exception {
option.setValue(newValue);
}
};
String name = nameResolver.nameOf(option);
String description = String.format(changeValueDescription, name, proposedValue);
String label = String.format(changeValueLabel, proposedValue);
acceptor.accept(issue, label, description, ICON_FOR_CHANGE, modification);
}
private EObject elementIn(Issue issue) {
return element(issue, EObject.class);
}
private <T extends EObject> T element(final Issue issue, final Class<T> type) {
IModificationContext modificationContext = getModificationContextFactory().createModificationContext(issue);
IXtextDocument xtextDocument = modificationContext.getXtextDocument();
return xtextDocument.readOnly(new IUnitOfWork<T, XtextResource>() {
@Override public T exec(XtextResource state) throws Exception {
EObject e = state.getEObject(issue.getUriToProblem().fragment());
return (type.isInstance(e)) ? type.cast(e) : null;
}
});
}
}