Support "reserved" field numbers and names.
Change-Id: I48f2c796178fc44aa7295b84f974e5acbe833eda
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java
index e20c5c6..31a87a6 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/core/XtextRule.java
@@ -10,10 +10,10 @@
package com.google.eclipse.protobuf.junit.core;
import static java.util.Arrays.asList;
-
import static org.eclipse.xtext.util.Strings.isEmpty;
import java.io.File;
+import java.util.Collection;
import java.util.List;
import org.eclipse.emf.ecore.EObject;
@@ -133,8 +133,11 @@
return finder.find(text);
}
+ public <T extends EObject> List<T> findAll(Class<T> type) {
+ return EcoreUtil2.getAllContentsOfType(root, type);
+ }
+
public <T extends EObject> T findFirst(Class<T> type) {
- List<T> contents = EcoreUtil2.getAllContentsOfType(root, type);
- return (contents.isEmpty()) ? null : contents.get(0);
+ return findAll(type).get(0);
}
}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForIndexRangeConflicts_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForIndexRangeConflicts_Test.java
new file mode 100644
index 0000000..a29c8c3
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForIndexRangeConflicts_Test.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2015 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.validation;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.xtext.validation.ValidationMessageAcceptor;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.Group;
+import com.google.eclipse.protobuf.protobuf.IndexRange;
+import com.google.eclipse.protobuf.protobuf.Message;
+import com.google.eclipse.protobuf.protobuf.MessageField;
+import com.google.eclipse.protobuf.protobuf.ProtobufPackage;
+import com.google.inject.Inject;
+
+public class ProtobufJavaValidator_checkForIndexRangeConflicts_Test {
+ @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
+
+ @Inject private ProtobufJavaValidator validator;
+ private ValidationMessageAcceptor messageAcceptor;
+
+ @Before public void setUp() {
+ messageAcceptor = mock(ValidationMessageAcceptor.class);
+ validator.setMessageAcceptor(messageAcceptor);
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved 10, 20 to 30;
+ // reserved 10;
+ // reserved 20;
+ // reserved 25 to 26;
+ // reserved 30 to 31;
+ // }
+ @Test public void should_error_on_conflict_between_reserved_and_reserved() {
+ validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class));
+ List<IndexRange> indexRanges = xtext.findAll(IndexRange.class);
+ verifyError("10 conflicts with reserved 10", indexRanges.get(2));
+ verifyError("20 conflicts with reserved 20 to 30", indexRanges.get(3));
+ verifyError("25 to 26 conflicts with reserved 20 to 30", indexRanges.get(4));
+ verifyError("30 to 31 conflicts with reserved 20 to 30", indexRanges.get(5));
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved 10, 20 to 30;
+ // extensions 10, 15 to max;
+ // }
+ @Test public void should_error_on_conflict_between_reserved_and_extensions() {
+ validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class));
+ List<IndexRange> indexRanges = xtext.findAll(IndexRange.class);
+ verifyError("10 conflicts with reserved 10", indexRanges.get(2));
+ verifyError("15 to max conflicts with reserved 20 to 30", indexRanges.get(3));
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved 10, 20 to 30;
+ // optional bool foo = 10;
+ // optional bool bar = 25;
+ // group baz = 26 {
+ // optional bool a = 27;
+ // }
+ // }
+ @Test public void should_error_on_conflict_between_reserved_and_indexed_element() {
+ validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class));
+ verifyError(
+ "10 conflicts with reserved 10",
+ xtext.findAll(MessageField.class).get(0),
+ ProtobufPackage.Literals.MESSAGE_FIELD__INDEX);
+ verifyError(
+ "25 conflicts with reserved 20 to 30",
+ xtext.findAll(MessageField.class).get(1),
+ ProtobufPackage.Literals.MESSAGE_FIELD__INDEX);
+ verifyError(
+ "26 conflicts with reserved 20 to 30",
+ xtext.findAll(Group.class).get(0),
+ ProtobufPackage.Literals.GROUP__INDEX);
+ verifyError(
+ "27 conflicts with reserved 20 to 30",
+ xtext.findAll(MessageField.class).get(2),
+ ProtobufPackage.Literals.MESSAGE_FIELD__INDEX);
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // extensions 10, 20 to 30;
+ // optional bool foo = 10;
+ // optional bool bar = 25;
+ // group baz = 26 {
+ // optional bool a = 27;
+ // }
+ // }
+ @Test public void should_error_on_conflict_between_extensions_and_indexed_element() {
+ validator.checkForIndexRangeConflicts(xtext.findFirst(Message.class));
+ verifyError(
+ "10 conflicts with extensions 10",
+ xtext.findAll(MessageField.class).get(0),
+ ProtobufPackage.Literals.MESSAGE_FIELD__INDEX);
+ verifyError(
+ "25 conflicts with extensions 20 to 30",
+ xtext.findAll(MessageField.class).get(1),
+ ProtobufPackage.Literals.MESSAGE_FIELD__INDEX);
+ verifyError(
+ "26 conflicts with extensions 20 to 30",
+ xtext.findAll(Group.class).get(0),
+ ProtobufPackage.Literals.GROUP__INDEX);
+ verifyError(
+ "27 conflicts with extensions 20 to 30",
+ xtext.findAll(MessageField.class).get(2),
+ ProtobufPackage.Literals.MESSAGE_FIELD__INDEX);
+ }
+
+ private void verifyError(String message, EObject errorSource) {
+ verifyError(message, errorSource, null);
+ }
+
+ private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) {
+ verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null);
+ }
+}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedIndexAndName_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedIndexAndName_Test.java
new file mode 100644
index 0000000..9316595
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedIndexAndName_Test.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015 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.validation;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.xtext.validation.ValidationMessageAcceptor;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.Reserved;
+import com.google.inject.Inject;
+
+public class ProtobufJavaValidator_checkForReservedIndexAndName_Test {
+ private static final String ERROR_MESSAGE = "A reserved declaration may not include both numbers and names.";
+
+ @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
+
+ @Inject private ProtobufJavaValidator validator;
+ private ValidationMessageAcceptor messageAcceptor;
+
+ @Before public void setUp() {
+ messageAcceptor = mock(ValidationMessageAcceptor.class);
+ validator.setMessageAcceptor(messageAcceptor);
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved 1, "foo";
+ // }
+ @Test public void should_error_on_reserved_index_and_name() {
+ validator.checkForReservedIndexAndName(xtext.findFirst(Reserved.class));
+ verifyError(ERROR_MESSAGE, xtext.findFirst(Reserved.class), null);
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved "foo", 1;
+ // }
+ @Test public void should_error_on_reserved_name_and_index() {
+ validator.checkForReservedIndexAndName(xtext.findFirst(Reserved.class));
+ verifyError(ERROR_MESSAGE, xtext.findFirst(Reserved.class), null);
+ }
+
+ private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) {
+ verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null);
+ }
+}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedNameConflicts_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedNameConflicts_Test.java
new file mode 100644
index 0000000..307d127
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedNameConflicts_Test.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2015 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.validation;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.xtext.validation.ValidationMessageAcceptor;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.Group;
+import com.google.eclipse.protobuf.protobuf.Message;
+import com.google.eclipse.protobuf.protobuf.MessageField;
+import com.google.eclipse.protobuf.protobuf.ProtobufPackage;
+import com.google.eclipse.protobuf.protobuf.StringLiteral;
+import com.google.inject.Inject;
+
+public class ProtobufJavaValidator_checkForReservedNameConflicts_Test {
+ @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
+
+ @Inject private ProtobufJavaValidator validator;
+ private ValidationMessageAcceptor messageAcceptor;
+
+ @Before public void setUp() {
+ messageAcceptor = mock(ValidationMessageAcceptor.class);
+ validator.setMessageAcceptor(messageAcceptor);
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved "foo", "bar";
+ // reserved "foo", 'b' 'a' 'r';
+ // }
+ @Test public void should_error_on_conflict_between_reserved_and_reserved() {
+ validator.checkForReservedNameConflicts(xtext.findFirst(Message.class));
+ List<StringLiteral> stringLiterals = xtext.findAll(StringLiteral.class);
+ verifyError("\"foo\" conflicts with reserved \"foo\"", stringLiterals.get(3));
+ verifyError("\"bar\" conflicts with reserved \"bar\"", stringLiterals.get(4));
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved "foo", "bar", "baz";
+ // optional bool foo = 1;
+ // group bar = 2 {
+ // optional bool baz = 3;
+ // }
+ // }
+ @Test public void should_error_on_conflict_between_reserved_and_indexed_element() {
+ validator.checkForReservedNameConflicts(xtext.findFirst(Message.class));
+ verifyError(
+ "\"foo\" conflicts with reserved \"foo\"",
+ xtext.findAll(MessageField.class).get(0),
+ ProtobufPackage.Literals.MESSAGE_FIELD__NAME);
+ verifyError(
+ "\"bar\" conflicts with reserved \"bar\"",
+ xtext.findAll(Group.class).get(0),
+ ProtobufPackage.Literals.COMPLEX_TYPE__NAME);
+ verifyError(
+ "\"baz\" conflicts with reserved \"baz\"",
+ xtext.findAll(MessageField.class).get(1),
+ ProtobufPackage.Literals.MESSAGE_FIELD__NAME);
+ }
+
+ private void verifyError(String message, EObject errorSource) {
+ verifyError(message, errorSource, null);
+ }
+
+ private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) {
+ verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null);
+ }
+}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedToMax_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedToMax_Test.java
new file mode 100644
index 0000000..201a2b4
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkForReservedToMax_Test.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015 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.validation;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.xtext.validation.ValidationMessageAcceptor;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.IndexRange;
+import com.google.eclipse.protobuf.protobuf.ProtobufPackage;
+import com.google.eclipse.protobuf.protobuf.Reserved;
+import com.google.inject.Inject;
+
+public class ProtobufJavaValidator_checkForReservedToMax_Test {
+ @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
+
+ @Inject private ProtobufJavaValidator validator;
+ private ValidationMessageAcceptor messageAcceptor;
+
+ @Before public void setUp() {
+ messageAcceptor = mock(ValidationMessageAcceptor.class);
+ validator.setMessageAcceptor(messageAcceptor);
+ }
+
+ // syntax = "proto2";
+ //
+ // message Person {
+ // reserved 1, 2 to max;
+ // }
+ @Test public void should_error_on_reserved_to_max() {
+ validator.checkForReservedToMax(xtext.findFirst(Reserved.class));
+ verifyError(
+ "Reserved index range must have finite upper bound.",
+ xtext.findAll(IndexRange.class).get(1),
+ ProtobufPackage.Literals.INDEX_RANGE__TO);
+ }
+
+ private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) {
+ verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null);
+ }
+}
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java
index 1956883..def91de 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandlerTest.java
@@ -11,7 +11,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.StyledTextContent;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
@@ -19,7 +18,6 @@
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.Mock;
import org.mockito.Mockito;
import com.google.eclipse.protobuf.junit.core.XtextRule;
@@ -117,6 +115,32 @@
// syntax = "proto2";
//
// message Message {
+ // optional bool foo = 1;
+ // reserved 3;
+ // optional bool incomplete
+ // }
+ @Test public void shouldDetermineCorrectIndexWithSingleReserved() {
+ MessageField incomplete = xtext.find("incomplete", MessageField.class);
+ assertThat(handler.determineNewIndex(incomplete), is(4L));
+ }
+
+ // // ignore errors
+ // syntax = "proto2";
+ //
+ // message Message {
+ // optional bool foo = 1;
+ // reserved 3, 5 to 7;
+ // optional bool incomplete
+ // }
+ @Test public void shouldDetermineCorrectIndexWithReservedRange() {
+ MessageField incomplete = xtext.find("incomplete", MessageField.class);
+ assertThat(handler.determineNewIndex(incomplete), is(8L));
+ }
+
+ // // ignore errors
+ // syntax = "proto2";
+ //
+ // message Message {
// optional bool incomplete
// }
@Test public void shouldComplete() {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java
index 69ca6ad..89c2d00 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Labels.java
@@ -21,12 +21,12 @@
import com.google.eclipse.protobuf.protobuf.ExtensibleTypeLink;
import com.google.eclipse.protobuf.protobuf.Extensions;
import com.google.eclipse.protobuf.protobuf.Import;
+import com.google.eclipse.protobuf.protobuf.IndexRange;
import com.google.eclipse.protobuf.protobuf.IndexedElement;
import com.google.eclipse.protobuf.protobuf.Literal;
import com.google.eclipse.protobuf.protobuf.MessageField;
import com.google.eclipse.protobuf.protobuf.MessageLink;
import com.google.eclipse.protobuf.protobuf.OptionField;
-import com.google.eclipse.protobuf.protobuf.Range;
import com.google.eclipse.protobuf.protobuf.Rpc;
import com.google.eclipse.protobuf.protobuf.Stream;
import com.google.eclipse.protobuf.protobuf.TypeExtension;
@@ -110,13 +110,13 @@
private Object labelFor(Extensions extensions) {
StringBuilder builder = new StringBuilder();
- EList<Range> ranges = extensions.getRanges();
+ EList<IndexRange> ranges = extensions.getRanges();
int rangeCount = ranges.size();
for (int i = 0; i < rangeCount; i++) {
if (i > 0) {
builder.append(", ");
}
- Range range = ranges.get(i);
+ IndexRange range = ranges.get(i);
builder.append(range.getFrom());
String to = range.getTo();
if (to != null) {
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext
index 0627375..fe0b46f 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext
@@ -50,12 +50,12 @@
'}' (';')?;
MessageElement:
- =>Option | Extensions | ComplexType | MessageField | TypeExtension | OneOf;
+ =>Option | Extensions | ComplexType | MessageField | TypeExtension | OneOf | Reserved;
-Range:
- from=LONG ('to' to=RangeMax)?;
+IndexRange:
+ from=LONG ('to' to=IndexRangeMax)?;
-RangeMax:
+IndexRangeMax:
LONG | 'max';
// Hack to make the default modifier 'unspecified'
@@ -84,7 +84,7 @@
Extensions | Group | MessageField;
Extensions:
- ->'extensions' ranges+=Range (',' ranges+=Range)* (';')+;
+ ->'extensions' ranges+=IndexRange (',' ranges+=IndexRange)* (';')+;
MessageField:
(modifier=Modifier)? =>type=TypeLink name=Name '=' index=(LONG | HEX)
@@ -135,6 +135,12 @@
ExtensibleType:
Message | Group;
+Reserved:
+ 'reserved' reservations+=Reservation (',' reservations+=Reservation)* ';'+;
+
+Reservation:
+ IndexRange | StringLiteral;
+
Service:
->'service' name=Name '{'
(elements+=ServiceElement)*
@@ -250,7 +256,7 @@
'package' | 'import' | 'public' | 'option' | 'extend' | 'message' | 'optional' | 'required' | 'repeated' |
'enum' | 'service' | 'rpc' | 'stream' | 'returns' | 'default' | 'extensions' | 'to' | 'max' | 'true' | 'false' |
'double' | 'float' | 'int32' | 'int64' | 'uint32' | 'uint64' | 'sint32' | 'sint64' | 'fixed32' | 'fixed64' |
- 'sfixed32' | 'sfixed64' | 'bool' | 'string' | 'bytes' | 'weak' | 'map';
+ 'sfixed32' | 'sfixed64' | 'bool' | 'string' | 'bytes' | 'weak' | 'map' | 'reserved';
SimpleValueLink:
LiteralLink | BooleanLink | NumberLink | StringLink;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java
new file mode 100644
index 0000000..47aee62
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015 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.model.util;
+
+import com.google.common.collect.Range;
+import com.google.eclipse.protobuf.protobuf.IndexRange;
+import com.google.eclipse.protobuf.services.ProtobufGrammarAccess;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Utility methods related to <code>{@link IndexRange}</code>.
+ *
+ * @author jogl@google.com (John Glassmyer)
+ */
+@Singleton public class IndexRanges {
+ private ProtobufGrammarAccess protobufGrammarAccess;
+
+ @Inject
+ IndexRanges(ProtobufGrammarAccess protobufGrammarAccess) {
+ this.protobufGrammarAccess = protobufGrammarAccess;
+ }
+
+ public Range<Long> toLongRange(IndexRange indexRange) {
+ long from = indexRange.getFrom();
+
+ Range<Long> range;
+ String toString = indexRange.getTo();
+ if (toString == null) {
+ range = Range.singleton(from);
+ } else if (toString.equals(getMaxKeyword())) {
+ range = Range.atLeast(from);
+ } else {
+ range = Range.closed(from, Long.valueOf(toString));
+ }
+
+ return range;
+ }
+
+ public String getMaxKeyword() {
+ return protobufGrammarAccess.getIndexRangeMaxAccess().getMaxKeyword_1().getValue();
+ }
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java
index 3061b1f..1b995d4 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java
@@ -19,13 +19,15 @@
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.util.SimpleAttributeResolver;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Range;
import com.google.eclipse.protobuf.protobuf.FieldOption;
import com.google.eclipse.protobuf.protobuf.Group;
+import com.google.eclipse.protobuf.protobuf.IndexRange;
import com.google.eclipse.protobuf.protobuf.IndexedElement;
import com.google.eclipse.protobuf.protobuf.Message;
-import com.google.eclipse.protobuf.protobuf.MessageElement;
-import com.google.eclipse.protobuf.protobuf.MessageField;
import com.google.eclipse.protobuf.protobuf.OneOf;
+import com.google.eclipse.protobuf.protobuf.Reserved;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -37,6 +39,7 @@
@Singleton public class IndexedElements {
private final static SimpleAttributeResolver<EObject, Long> INDEX_RESOLVER = newResolver(long.class, "index");
+ @Inject private IndexRanges indexRanges;
@Inject private ModelObjects modelObjects;
/**
@@ -72,6 +75,14 @@
if (e instanceof Group) {
maxIndex = max(maxIndex, findMaxIndex(((Group) e).getElements()));
}
+ } else if (e instanceof Reserved) {
+ for (IndexRange indexRange :
+ Iterables.filter(((Reserved) e).getReservations(), IndexRange.class)) {
+ Range<Long> range = indexRanges.toLongRange(indexRange);
+ if (range.hasUpperBound()) {
+ maxIndex = max(maxIndex, range.upperEndpoint());
+ }
+ }
}
}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java
index 3f6c7da..a54836c 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java
@@ -14,6 +14,8 @@
* @author alruiz@google.com (Alex Ruiz)
*/
public class Messages extends NLS {
+ public static String conflictsWithExtensions;
+ public static String conflictsWithReserved;
public static String expectedFieldName;
public static String expectedFieldNumber;
public static String expectedIdentifier;
@@ -37,6 +39,8 @@
public static String multiplePackages;
public static String oneofFieldWithModifier;
public static String requiredInProto3;
+ public static String reservedIndexAndName;
+ public static String reservedToMax;
public static String scopingError;
public static String unknownSyntax;
public static String unrecognizedSyntaxIdentifier;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties
index 4bdeb24..7f46818 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties
@@ -20,7 +20,11 @@
missingModifier = Missing modifier: \"required\", \"optional\", or \"repeated\".
oneofFieldWithModifier = Fields inside "oneof" cannot have a modifier.
multiplePackages = Multiple package definitions.
+conflictsWithExtensions = %s conflicts with extensions %s
+conflictsWithReserved = %s conflicts with reserved %s
requiredInProto3 = Required fields are not allowed in proto3.
+reservedIndexAndName = A reserved declaration may not include both numbers and names.
+reservedToMax = Reserved index range must have finite upper bound.
scopingError = It may be caused by an imported non-proto2 file.
unknownSyntax = Unknown syntax. This parser only recognizes \"proto2\" or \"proto3\".
unrecognizedSyntaxIdentifier = Unrecognized syntax identifier \"%s\". This parser only recognizes \"proto2\" and \"proto3\".
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
index bcbbab0..00b0350 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
@@ -13,6 +13,8 @@
import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.MESSAGE_FIELD__MODIFIER;
import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.PACKAGE__NAME;
import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.SYNTAX__NAME;
+import static com.google.eclipse.protobuf.validation.Messages.conflictsWithExtensions;
+import static com.google.eclipse.protobuf.validation.Messages.conflictsWithReserved;
import static com.google.eclipse.protobuf.validation.Messages.expectedFieldNumber;
import static com.google.eclipse.protobuf.validation.Messages.expectedSyntaxIdentifier;
import static com.google.eclipse.protobuf.validation.Messages.fieldNumberAlreadyUsed;
@@ -25,14 +27,28 @@
import static com.google.eclipse.protobuf.validation.Messages.multiplePackages;
import static com.google.eclipse.protobuf.validation.Messages.oneofFieldWithModifier;
import static com.google.eclipse.protobuf.validation.Messages.requiredInProto3;
+import static com.google.eclipse.protobuf.validation.Messages.reservedIndexAndName;
+import static com.google.eclipse.protobuf.validation.Messages.reservedToMax;
import static com.google.eclipse.protobuf.validation.Messages.unknownSyntax;
import static com.google.eclipse.protobuf.validation.Messages.unrecognizedSyntaxIdentifier;
import static java.lang.String.format;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Range;
+import com.google.eclipse.protobuf.model.util.IndexRanges;
import com.google.eclipse.protobuf.model.util.IndexedElements;
import com.google.eclipse.protobuf.model.util.Protobufs;
+import com.google.eclipse.protobuf.model.util.StringLiterals;
import com.google.eclipse.protobuf.model.util.Syntaxes;
import com.google.eclipse.protobuf.naming.NameResolver;
+import com.google.eclipse.protobuf.protobuf.Extensions;
+import com.google.eclipse.protobuf.protobuf.IndexRange;
import com.google.eclipse.protobuf.protobuf.IndexedElement;
import com.google.eclipse.protobuf.protobuf.MapType;
import com.google.eclipse.protobuf.protobuf.MapTypeLink;
@@ -44,17 +60,24 @@
import com.google.eclipse.protobuf.protobuf.Package;
import com.google.eclipse.protobuf.protobuf.Protobuf;
import com.google.eclipse.protobuf.protobuf.ProtobufElement;
+import com.google.eclipse.protobuf.protobuf.ProtobufPackage;
+import com.google.eclipse.protobuf.protobuf.Reservation;
+import com.google.eclipse.protobuf.protobuf.Reserved;
import com.google.eclipse.protobuf.protobuf.ScalarType;
import com.google.eclipse.protobuf.protobuf.ScalarTypeLink;
+import com.google.eclipse.protobuf.protobuf.StringLiteral;
import com.google.eclipse.protobuf.protobuf.Syntax;
import com.google.eclipse.protobuf.protobuf.TypeExtension;
import com.google.eclipse.protobuf.protobuf.TypeLink;
import com.google.inject.Inject;
+import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.naming.IQualifiedNameProvider;
import org.eclipse.xtext.naming.QualifiedName;
+import org.eclipse.xtext.util.SimpleAttributeResolver;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.ComposedChecks;
@@ -74,7 +97,9 @@
public static final String ONEOF_FIELD_WITH_MODIFIER_ERROR = "oneofFieldWithModifier";
@Inject private IndexedElements indexedElements;
+ @Inject private IndexRanges indexRanges;
@Inject private NameResolver nameResolver;
+ @Inject private StringLiterals stringLiterals;
@Inject private Protobufs protobufs;
@Inject private IQualifiedNameProvider qualifiedNameProvider;
@Inject private Syntaxes syntaxes;
@@ -94,6 +119,127 @@
error(msg, syntax, SYNTAX__NAME, SYNTAX_IS_NOT_KNOWN_ERROR);
}
+ @Check public void checkForIndexRangeConflicts(Message message) {
+ Collection<Range<Long>> reservedRanges = new ArrayList<>();
+ for (Reserved reserved : getOwnedElements(Message.class, message, Reserved.class)) {
+ for (IndexRange indexRange : Iterables.filter(reserved.getReservations(), IndexRange.class)) {
+ Range<Long> range = indexRanges.toLongRange(indexRange);
+ errorOnConflicts(range, reservedRanges, conflictsWithReserved, indexRange, null);
+ reservedRanges.add(range);
+ }
+ }
+
+ Collection<Range<Long>> extensionsRanges = new ArrayList<>();
+ for (Extensions extensions : getOwnedElements(Message.class, message, Extensions.class)) {
+ for (IndexRange indexRange : extensions.getRanges()) {
+ Range<Long> range = indexRanges.toLongRange(indexRange);
+ errorOnConflicts(range, reservedRanges, conflictsWithReserved, indexRange, null);
+ errorOnConflicts(range, extensionsRanges, conflictsWithExtensions, indexRange, null);
+ extensionsRanges.add(range);
+ }
+ }
+
+ for (IndexedElement element : getOwnedElements(Message.class, message, IndexedElement.class)) {
+ long index = indexedElements.indexOf(element);
+ Range<Long> range = Range.singleton(index);
+ EStructuralFeature indexFeature = indexedElements.indexFeatureOf(element);
+ errorOnConflicts(range, reservedRanges, conflictsWithReserved, element, indexFeature);
+ errorOnConflicts(range, extensionsRanges, conflictsWithExtensions, element, indexFeature);
+ }
+ }
+
+ private void errorOnConflicts(
+ Range<Long> range,
+ Iterable<Range<Long>> existingRanges,
+ String errorTemplate,
+ EObject errorSource,
+ EStructuralFeature errorFeature) {
+ for (Range<Long> existingRange : existingRanges) {
+ if (range.isConnected(existingRange)) {
+ String message =
+ String.format(errorTemplate, rangeToString(range), rangeToString(existingRange));
+ error(message, errorSource, errorFeature);
+ }
+ }
+ }
+
+ private String rangeToString(Range<Long> range) {
+ if (range.hasLowerBound() && range.hasUpperBound()
+ && range.lowerEndpoint() == range.upperEndpoint()) {
+ return String.valueOf(range.lowerEndpoint());
+ }
+
+ String upper =
+ range.hasUpperBound() ? String.valueOf(range.upperEndpoint()) : indexRanges.getMaxKeyword();
+ return String.format("%d to %s", range.lowerEndpoint(), upper);
+ }
+
+ @Check public void checkForReservedToMax(Reserved reserved) {
+ for (IndexRange range : Iterables.filter(reserved.getReservations(), IndexRange.class)) {
+ String to = range.getTo();
+ if (indexRanges.getMaxKeyword().equals(to)) {
+ error(reservedToMax, range, ProtobufPackage.Literals.INDEX_RANGE__TO);
+ }
+ }
+ }
+
+ @Check public void checkForReservedNameConflicts(Message message) {
+ Set<String> reservedNames = new HashSet<>();
+ for (Reserved reserved : getOwnedElements(Message.class, message, Reserved.class)) {
+ for (StringLiteral stringLiteral :
+ Iterables.filter(reserved.getReservations(), StringLiteral.class)) {
+ String name = stringLiterals.getCombinedString(stringLiteral);
+ reportReservedNameConflicts(name, reservedNames, stringLiteral, null);
+ reservedNames.add(name);
+ }
+ }
+
+ for (IndexedElement element : getOwnedElements(Message.class, message, IndexedElement.class)) {
+ String name = nameResolver.nameOf(element);
+ if (name != null) {
+ EAttribute nameAttribute = SimpleAttributeResolver.NAME_RESOLVER.getAttribute(element);
+ reportReservedNameConflicts(name, reservedNames, element, nameAttribute);
+ }
+ }
+ }
+
+ @Check public void checkForReservedIndexAndName(Reserved reserved) {
+ boolean hasIndexReservation = false;
+ boolean hasNameReservation = false;
+ for (Reservation reservation : reserved.getReservations()) {
+ if (reservation instanceof IndexRange) {
+ hasIndexReservation = true;
+ } else if (reservation instanceof StringLiteral) {
+ hasNameReservation = true;
+ }
+ }
+
+ if (hasIndexReservation && hasNameReservation) {
+ error(reservedIndexAndName, reserved, null);
+ }
+ }
+
+ private void reportReservedNameConflicts(
+ String name, Set<String> reservedNames, EObject errorSource, EAttribute errorFeature) {
+ if (reservedNames.contains(name)) {
+ String quotedName = '"' + name + '"';
+ String message = String.format(conflictsWithReserved, quotedName, quotedName);
+ error(message, errorSource, errorFeature);
+ }
+ }
+
+ private <E extends EObject, C extends EObject> Collection<E> getOwnedElements(
+ Class<C> containerType, C container, Class<E> elementType) {
+ List<E> allElements = EcoreUtil2.getAllContentsOfType(container, elementType);
+ List<E> ownedElements = new ArrayList<>(allElements.size());
+ for (E element : allElements) {
+ if (EcoreUtil2.getContainerOfType(element, containerType) == container) {
+ ownedElements.add(element);
+ }
+ }
+ return ownedElements;
+ }
+
@Check public void checkTagNumberIsUnique(IndexedElement e) {
if (isNameNull(e)) {
return; // we already show an error if name is null, no need to go further.