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.