Add support for omitting modifiers for proto3

Change-Id: I4c74d0a31cc4b94c0e48d94409eebc5d941d60f9
Signed-off-by: Harry Terkelsen <het@google.com>
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkFieldModifiers_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkFieldModifiers_Test.java
new file mode 100644
index 0000000..c8c87a7
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkFieldModifiers_Test.java
@@ -0,0 +1,115 @@
+/*
+ * 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.validation;
+
+import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.MESSAGE_FIELD__MODIFIER;
+import static com.google.eclipse.protobuf.validation.Messages.mapWithModifier;
+import static com.google.eclipse.protobuf.validation.Messages.missingModifier;
+import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.MAP_WITH_MODIFIER_ERROR;
+import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.MISSING_MODIFIER_ERROR;
+import static org.eclipse.xtext.validation.ValidationMessageAcceptor.INSIGNIFICANT_INDEX;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.MessageField;
+import com.google.inject.Inject;
+
+import org.eclipse.xtext.validation.ValidationMessageAcceptor;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Tests for <code>{@link ProtobufJavaValidator#checkFieldModifiers(MessageField)}</code>
+ */
+public class ProtobufJavaValidator_checkFieldModifiers_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 Foo {
+  //   string bar = 1;
+  // }
+  @Test public void should_create_error_if_no_modifier_in_proto2() {
+    MessageField field = xtext.find("bar", MessageField.class);
+    validator.checkFieldModifiers(field);
+    verify(messageAcceptor).acceptError(missingModifier, field, MESSAGE_FIELD__MODIFIER,
+        INSIGNIFICANT_INDEX, MISSING_MODIFIER_ERROR);
+  }
+
+  // syntax = "proto3";
+  //
+  // message Foo {
+  //   string bar = 1;
+  // }
+  @Test public void should_not_create_error_if_no_modifier_in_proto3() {
+    MessageField field = xtext.find("bar", MessageField.class);
+    validator.checkFieldModifiers(field);
+    verifyZeroInteractions(messageAcceptor);
+  }
+
+  // syntax = "proto3";
+  //
+  // message Foo {
+  //   required string bar = 1;
+  // }
+  @Test public void should_create_error_if_required_modifier_in_proto3() {
+    MessageField field = xtext.find("bar", MessageField.class);
+    validator.checkFieldModifiers(field);
+    verify(messageAcceptor).acceptError(Messages.requiredInProto3, field, MESSAGE_FIELD__MODIFIER,
+        INSIGNIFICANT_INDEX, ProtobufJavaValidator.REQUIRED_IN_PROTO3_ERROR);
+  }
+
+  // syntax = "proto2";
+  //
+  // message Foo {
+  //   optional map<string, string> bar = 1;
+  // }
+  @Test public void should_create_error_if_modifier_on_map_in_proto2() {
+    MessageField field = xtext.find("bar", MessageField.class);
+    validator.checkFieldModifiers(field);
+    verify(messageAcceptor).acceptError(mapWithModifier, field, MESSAGE_FIELD__MODIFIER,
+        INSIGNIFICANT_INDEX, MAP_WITH_MODIFIER_ERROR);
+  }
+
+  // syntax = "proto3";
+  //
+  // message Foo {
+  //   optional map<string, string> bar = 1;
+  // }
+  @Test public void should_create_error_if_modifier_on_map_in_proto3() {
+    MessageField field = xtext.find("bar", MessageField.class);
+    validator.checkFieldModifiers(field);
+    verify(messageAcceptor).acceptError(mapWithModifier, field, MESSAGE_FIELD__MODIFIER,
+        INSIGNIFICANT_INDEX, MAP_WITH_MODIFIER_ERROR);
+  }
+
+  // syntax = "proto2";
+  //
+  // message Foo {
+  //   map<string, string> bar = 1;
+  // }
+  @Test public void should_not_create_error_if_no_modifier_on_map_in_proto2() {
+    MessageField field = xtext.find("bar", MessageField.class);
+    validator.checkFieldModifiers(field);
+    verifyZeroInteractions(messageAcceptor);
+  }
+}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkSyntaxIsKnown_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkSyntaxIsKnown_Test.java
index ff0ae0c..4ec6ede 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkSyntaxIsKnown_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkSyntaxIsKnown_Test.java
@@ -42,8 +42,10 @@
   @Test public void should_create_error_if_syntax_is_not_proto2_or_proto3() {
     when(syntax.getName()).thenReturn("proto5");
     validator.checkSyntaxIsKnown(syntax);
-    String message = "Unrecognized syntax identifier \"proto5\".  This parser only recognizes \"proto2\" or \"proto3\".";
-    verify(messageAcceptor).acceptError(message, syntax, SYNTAX__NAME, INSIGNIFICANT_INDEX, SYNTAX_IS_NOT_KNOWN_ERROR);
+    String message = "Unrecognized syntax identifier \"proto5\".  "
+        + "This parser only recognizes \"proto2\" and \"proto3\".";
+    verify(messageAcceptor).acceptError(message, syntax, SYNTAX__NAME, INSIGNIFICANT_INDEX,
+        SYNTAX_IS_NOT_KNOWN_ERROR);
   }
 
   @Test public void should_not_create_error_if_syntax_is_proto2() {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
index bbbce17..3a2fbfa 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
@@ -18,8 +18,8 @@
 import static com.google.eclipse.protobuf.grammar.CommonKeyword.OPENING_CURLY_BRACKET;
 import static com.google.eclipse.protobuf.grammar.CommonKeyword.SYNTAX;
 import static com.google.eclipse.protobuf.grammar.CommonKeyword.TRUE;
-import static com.google.eclipse.protobuf.protobuf.Modifier.OPTIONAL;
-import static com.google.eclipse.protobuf.protobuf.Modifier.REPEATED;
+import static com.google.eclipse.protobuf.protobuf.ModifierEnum.OPTIONAL;
+import static com.google.eclipse.protobuf.protobuf.ModifierEnum.REPEATED;
 import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.LITERAL;
 import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.OPTION;
 import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.DEFAULT_EQUAL;
@@ -28,8 +28,8 @@
 import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.DEFAULT_EQUAL_STRING_IN_BRACKETS;
 import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.EMPTY_STRING;
 import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.EQUAL_PROTO2_IN_QUOTES;
-import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.PROTO2_IN_QUOTES;
 import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.EQUAL_PROTO3_IN_QUOTES;
+import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.PROTO2_IN_QUOTES;
 import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.PROTO3_IN_QUOTES;
 import static com.google.eclipse.protobuf.util.CommonWords.space;
 import static java.lang.String.valueOf;
@@ -56,7 +56,7 @@
 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.Modifier;
+import com.google.eclipse.protobuf.protobuf.ModifierEnum;
 import com.google.eclipse.protobuf.protobuf.NativeFieldOption;
 import com.google.eclipse.protobuf.protobuf.NativeOption;
 import com.google.eclipse.protobuf.protobuf.Option;
@@ -314,7 +314,7 @@
     }
     if (model instanceof MessageField) {
       MessageField field = (MessageField) model;
-      Modifier modifier = field.getModifier();
+      ModifierEnum modifier = field.getModifier();
       if (OPTIONAL.equals(modifier)) {
         CompoundElement display = DEFAULT_EQUAL_IN_BRACKETS;
         int cursorPosition = display.indexOf(CLOSING_BRACKET);
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Images.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Images.java
index ae435bf..b0dbee3 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Images.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/labeling/Images.java
@@ -9,9 +9,9 @@
 package com.google.eclipse.protobuf.ui.labeling;
 
 import static com.google.common.collect.Sets.newHashSet;
-import static com.google.eclipse.protobuf.protobuf.Modifier.OPTIONAL;
-import static com.google.eclipse.protobuf.protobuf.Modifier.REPEATED;
-import static com.google.eclipse.protobuf.protobuf.Modifier.REQUIRED;
+import static com.google.eclipse.protobuf.protobuf.ModifierEnum.OPTIONAL;
+import static com.google.eclipse.protobuf.protobuf.ModifierEnum.REPEATED;
+import static com.google.eclipse.protobuf.protobuf.ModifierEnum.REQUIRED;
 import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.ENUM;
 import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.EXTENSIONS;
 import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.GROUP;
@@ -35,7 +35,7 @@
 import com.google.eclipse.protobuf.grammar.CommonKeyword;
 import com.google.eclipse.protobuf.protobuf.Import;
 import com.google.eclipse.protobuf.protobuf.MessageField;
-import com.google.eclipse.protobuf.protobuf.Modifier;
+import com.google.eclipse.protobuf.protobuf.ModifierEnum;
 import com.google.eclipse.protobuf.protobuf.Option;
 import com.google.inject.Singleton;
 
@@ -56,8 +56,8 @@
     addImages("imports", "options");
   }
 
-  private static void addImages(Modifier...modifiers) {
-    for (Modifier m : modifiers) {
+  private static void addImages(ModifierEnum...modifiers) {
+    for (ModifierEnum m : modifiers) {
       addImage(imageNameFrom(m));
     }
   }
@@ -90,7 +90,7 @@
       imageName = (String) o;
     } else if (o instanceof MessageField) {
       MessageField field = (MessageField) o;
-      Modifier modifier = field.getModifier();
+      ModifierEnum modifier = field.getModifier();
       imageName = imageNameFrom(modifier);
     } else if (o instanceof Option) {
       imageName = imageNameFrom(OPTION);
@@ -110,7 +110,7 @@
     return (IMAGES.contains(imageFileName)) ? imageFileName : defaultImage();
   }
 
-  private static String imageNameFrom(Modifier modifier) {
+  private static String imageNameFrom(ModifierEnum modifier) {
     return modifier.getName().toLowerCase();
   }
 
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties
index efbe0b8..66492dd 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/Messages.properties
@@ -2,4 +2,4 @@
 changeValueLabel=Change value to '%s'
 regenerateTagNumberLabel=Regenerate tag number
 regenerateTagNumberDescription=Regenerate tag number.
-removeDuplicatePackageLabel=Remove duplicate package declaration
\ No newline at end of file
+removeDuplicatePackageLabel=Remove duplicate package declaration
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java
index 32e012c..aa8f353 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/quickfix/ProtobufQuickfixProvider.java
@@ -21,7 +21,9 @@
 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;
@@ -34,11 +36,14 @@
 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.Syntax;
 import com.google.eclipse.protobuf.protobuf.Value;
+import com.google.eclipse.protobuf.validation.ProtobufJavaValidator;
 import com.google.inject.Inject;
 
 import org.eclipse.emf.ecore.EObject;
@@ -119,6 +124,62 @@
     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);
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 0ce144f..fb0a8e6 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
@@ -45,7 +45,7 @@
   Enum | ExtensibleType;
 
 Message:
-  'message' name=Name '{'
+  ->'message' name=Name '{'
   elements+=MessageElement*
   '}' (';')?;
 
@@ -58,43 +58,38 @@
 RangeMax:
   LONG | 'max';
 
-Group:
-  modifier=Modifier 'group' name=Name '=' index=(LONG | HEX)
-  ('[' (fieldOptions+=FieldOption (',' fieldOptions+=FieldOption)*) ']')? '{'
-  elements+=GroupElement*
-  '}' (';')?;
+// Hack to make the default modifier 'unspecified'
+enum ModifierEnum:
+  unspecified | optional | required | repeated
+;
 
-UnmodifiedGroup returns Group:
-  =>'group' name=Name '=' index=(LONG | HEX)
+enum Modifier returns ModifierEnum:
+  optional | required | repeated;
+
+Group:
+  (modifier=Modifier)? =>'group' name=Name '=' index=(LONG | HEX)
   ('[' (fieldOptions+=FieldOption (',' fieldOptions+=FieldOption)*) ']')? '{'
   elements+=GroupElement*
   '}' (';')?;
 
 GroupElement:
-  Option | IndexedElement | ComplexType | TypeExtension | Extensions;
+  Option | MessageField | ComplexType | TypeExtension | Extensions;
 
 OneOf:
-  (isRepeated?='repeated')? 'oneof' name=Name '{'
+  (isRepeated?='repeated')? =>'oneof' name=Name '{'
   elements+=OneOfElement+
   '}' (';')?;
 
 OneOfElement returns MessageElement:
-  Extensions | UnmodifiedGroup | UnmodifiedMessageField;
+  Extensions | Group;
 
 Extensions:
-  'extensions' ranges+=Range (',' ranges+=Range)* (';')+;
+  ->'extensions' ranges+=Range (',' ranges+=Range)* (';')+;
 
 MessageField:
-  =>modifier=Modifier type=TypeLink name=Name '=' index=(LONG | HEX)
+  (modifier=Modifier)? =>type=TypeLink name=Name '=' index=(LONG | HEX)
   ('[' (fieldOptions+=FieldOption (',' fieldOptions+=FieldOption)*)? ']')? (';')+;
 
-UnmodifiedMessageField returns MessageField:
-  type=TypeLink name=Name '=' index=(LONG | HEX)
-  ('[' (fieldOptions+=FieldOption (',' fieldOptions+=FieldOption)*)? ']')? (';')+;
-
-enum Modifier:
-  optional | required | repeated;
-
 TypeLink:
   ScalarTypeLink | ComplexTypeLink | MapTypeLink;
 
@@ -107,15 +102,15 @@
 
 ComplexTypeLink:
   target=[ComplexType|QualifiedName];
-  
+
 MapTypeLink:
   target=MapType;
 
 MapType:
-  =>'map' '<' fromType=TypeLink ',' toType=TypeLink '>';
+  ->'map' '<' fromType=TypeLink ',' toType=TypeLink '>';
 
 Enum:
-  =>'enum' name=Name '{'
+  ->'enum' name=Name '{'
   elements+=EnumElement*
   '}' ';'?;
 
@@ -123,14 +118,14 @@
   Option | Literal;
 
 Literal:
-  name=Name '=' index=(LONG | HEX) 
+  name=Name '=' index=(LONG | HEX)
   ('[' fieldOptions+=FieldOption (',' fieldOptions+=FieldOption)* ']')? (';')+;
 
 terminal HEX returns ecore::ELong:
-  ('-')? '0' ('x' | 'X') (NUMBER | 'a'..'f' | 'A'..'F')+;
+  ('-')? '0' ('x' | 'X') (DIGIT | 'a'..'f' | 'A'..'F')+;
 
 TypeExtension:
-  =>'extend' type=ExtensibleTypeLink '{'
+  ->'extend' type=ExtensibleTypeLink '{'
   elements+=MessageElement*
   '}' (';')?;
 
@@ -141,7 +136,7 @@
   Message | Group;
 
 Service:
-  'service' name=Name '{'
+  ->'service' name=Name '{'
   (elements+=ServiceElement)*
   '}' (';')?;
 
@@ -149,17 +144,17 @@
   Option | Rpc | Stream;
 
 Rpc:
-  'rpc' name=Name '(' argType=MessageLink ')' 'returns' '(' returnType=MessageLink ')'
+  ->'rpc' name=Name '(' argType=MessageLink ')' 'returns' '(' returnType=MessageLink ')'
   (('{' options+=Option* '}') (';')? | (';')+);
 
 Stream:
-  'stream' name=Name '(' clientMessage=MessageLink ',' serverMessage=MessageLink ')'
+  ->'stream' name=Name '(' clientMessage=MessageLink ',' serverMessage=MessageLink ')'
   (('{' options+=Option* '}') (';')? | (';')+);
 
 Name:
   IdOrReservedWord;
 
-IdOrReservedWord: 
+IdOrReservedWord:
   ID | ReservedWord;
 
 MessageLink:
@@ -178,9 +173,9 @@
   'option' source=OptionSource '=' value=Value (';')+;
 
 CustomOption:
-  'option' '(' source=OptionSource ')'
+  'option' =>'(' source=OptionSource ')'
   ('.' fields+=OptionField ('.' fields+=OptionField)*)? '=' value=Value (';')+;
- 
+
 FieldOption:
   DefaultValueFieldOption | NativeFieldOption | CustomFieldOption;
 
@@ -191,7 +186,7 @@
   source=OptionSource '=' value=Value;
 
 CustomFieldOption:
-  '(' source=OptionSource ')'
+  ->'(' source=OptionSource ')'
   ('.' fields+=OptionField ('.' fields+=OptionField)*)? '=' value=Value;
 
 OptionSource:
@@ -201,7 +196,7 @@
   MessageOptionField | '(' ExtensionOptionField ')';
 
 MessageOptionField:
-  target=[IndexedElement|Name];  
+  target=[IndexedElement|Name];
 
 ExtensionOptionField:
   target=[IndexedElement|QualifiedName];
@@ -217,11 +212,11 @@
 
 // { foo: 1, bar: 2 }
 ComplexValueCurlyBracket:
-  '{' {ComplexValueCurlyBracket} (fields+=ValueField (',')?)* '}';
+  ->'{' {ComplexValueCurlyBracket} (fields+=ValueField (',')?)* '}';
 
 // < foo: 1, bar: 2 >
 ComplexValueAngleBracket:
-  '<' {ComplexValueAngleBracket} (fields+=ValueField (',')?)* '>';
+  ->'<' {ComplexValueAngleBracket} (fields+=ValueField (',')?)* '>';
 
 ValueField:
   SimpleValueField | ComplexValueField;
@@ -278,18 +273,18 @@
   target=LONG;
 
 terminal LONG returns ecore::ELong:
-  ('-')? (NUMBER)+;
+  ('-')? (DIGIT)+;
 
 DoubleLink:
   target=DOUBLE;
 
 terminal DOUBLE returns ecore::EDouble:
-  ('-')? (NUMBER)* ('.' (NUMBER)+)? |
-  ('-')? (NUMBER)+ ('.') | 
-  ('-')? (NUMBER)+ ('.' (NUMBER)*)? (('e'|'E')('-'|'+')? (NUMBER)+) | 
+  ('-')? (DIGIT)* ('.' (DIGIT)+)? |
+  ('-')? (DIGIT)+ ('.') |
+  ('-')? (DIGIT)+ ('.' (DIGIT)*)? (('e'|'E')('-'|'+')? (DIGIT)+) |
   'nan' | 'inf' | '-inf';
 
-terminal NUMBER:
+terminal DIGIT:
   '0'..'9';
 
 StringLink:
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
index 3f500e2..3c09143 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/MessageFields.java
@@ -23,7 +23,7 @@
 import static com.google.eclipse.protobuf.grammar.CommonKeyword.STRING;
 import static com.google.eclipse.protobuf.grammar.CommonKeyword.UINT32;
 import static com.google.eclipse.protobuf.grammar.CommonKeyword.UINT64;
-import static com.google.eclipse.protobuf.protobuf.Modifier.OPTIONAL;
+import static com.google.eclipse.protobuf.protobuf.ModifierEnum.OPTIONAL;
 
 import com.google.eclipse.protobuf.grammar.CommonKeyword;
 import com.google.eclipse.protobuf.protobuf.ComplexType;
@@ -31,7 +31,6 @@
 import com.google.eclipse.protobuf.protobuf.Enum;
 import com.google.eclipse.protobuf.protobuf.Message;
 import com.google.eclipse.protobuf.protobuf.MessageField;
-import com.google.eclipse.protobuf.protobuf.Modifier;
 import com.google.eclipse.protobuf.protobuf.ScalarType;
 import com.google.eclipse.protobuf.protobuf.ScalarTypeLink;
 import com.google.eclipse.protobuf.protobuf.TypeLink;
@@ -44,7 +43,7 @@
  */
 @Singleton public class MessageFields {
   /**
-   * Indicates whether the modifier of the given field is <code>{@link Modifier#OPTIONAL}</code>.
+   * Indicates whether the modifier of the given field is <code>{@link ModifierEnum#OPTIONAL}</code>.
    * @param field the given field.
    * @return {@code true} if the modifier of the given field is "optional," {@code false} otherwise.
    */
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 66eee36..184ec86 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
@@ -28,8 +28,11 @@
   public static String importingUnsupportedSyntax;
   public static String importNotFound;
   public static String literalNotInEnum;
+  public static String mapWithModifier;
   public static String missingFieldNumber;
+  public static String missingModifier;
   public static String multiplePackages;
+  public static String requiredInProto3;
   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 38bccda..1c061db 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
@@ -12,8 +12,11 @@
 importingUnsupportedSyntax = Importing unsupported file (directly or indirectly.) This may cause errors related to unresolved references.
 importNotFound = Import \"%s\" was not found.
 literalNotInEnum = Enum type \"%s\" has no value named \"%s\".
+mapWithModifier = Field labels (required/optional/repeated) are not allowed on map fields.
 missingFieldNumber = Missing field number.
+missingModifier = Missing modifier: \"required\", \"optional\", or \"repeated\".
 multiplePackages = Multiple package definitions.
+requiredInProto3 = Required fields are not allowed in proto3.
 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 9bc3f57..3a83d80 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
@@ -8,31 +8,31 @@
  */
 package com.google.eclipse.protobuf.validation;
 
-import static java.lang.String.format;
-
+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.expectedFieldNumber;
 import static com.google.eclipse.protobuf.validation.Messages.expectedSyntaxIdentifier;
 import static com.google.eclipse.protobuf.validation.Messages.fieldNumberAlreadyUsed;
 import static com.google.eclipse.protobuf.validation.Messages.fieldNumbersMustBePositive;
+import static com.google.eclipse.protobuf.validation.Messages.mapWithModifier;
+import static com.google.eclipse.protobuf.validation.Messages.missingModifier;
 import static com.google.eclipse.protobuf.validation.Messages.multiplePackages;
+import static com.google.eclipse.protobuf.validation.Messages.requiredInProto3;
 import static com.google.eclipse.protobuf.validation.Messages.unknownSyntax;
 import static com.google.eclipse.protobuf.validation.Messages.unrecognizedSyntaxIdentifier;
-
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.xtext.naming.IQualifiedNameProvider;
-import org.eclipse.xtext.naming.QualifiedName;
-import org.eclipse.xtext.validation.Check;
-import org.eclipse.xtext.validation.ComposedChecks;
+import static java.lang.String.format;
 
 import com.google.eclipse.protobuf.grammar.Syntaxes;
 import com.google.eclipse.protobuf.model.util.IndexedElements;
 import com.google.eclipse.protobuf.model.util.Protobufs;
 import com.google.eclipse.protobuf.naming.NameResolver;
 import com.google.eclipse.protobuf.protobuf.IndexedElement;
+import com.google.eclipse.protobuf.protobuf.MapTypeLink;
 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.ModifierEnum;
 import com.google.eclipse.protobuf.protobuf.OneOf;
 import com.google.eclipse.protobuf.protobuf.Package;
 import com.google.eclipse.protobuf.protobuf.Protobuf;
@@ -40,6 +40,12 @@
 import com.google.eclipse.protobuf.protobuf.Syntax;
 import com.google.inject.Inject;
 
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.naming.IQualifiedNameProvider;
+import org.eclipse.xtext.naming.QualifiedName;
+import org.eclipse.xtext.validation.Check;
+import org.eclipse.xtext.validation.ComposedChecks;
+
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
@@ -48,6 +54,9 @@
   public static final String SYNTAX_IS_NOT_KNOWN_ERROR = "syntaxIsNotProto2";
   public static final String INVALID_FIELD_TAG_NUMBER_ERROR = "invalidFieldTagNumber";
   public static final String MORE_THAN_ONE_PACKAGE_ERROR = "moreThanOnePackage";
+  public static final String MISSING_MODIFIER_ERROR = "noModifier";
+  public static final String MAP_WITH_MODIFIER_ERROR = "mapWithModifier";
+  public static final String REQUIRED_IN_PROTO3_ERROR = "requiredInProto3";
 
   @Inject private IndexedElements indexedElements;
   @Inject private NameResolver nameResolver;
@@ -85,6 +94,47 @@
     }
   }
 
+  @Check public void checkFieldModifiers(MessageField field) {
+    if (field.getType() instanceof MapTypeLink) {
+      checkMapField(field);
+      return;
+    }
+    if (field.getModifier() == ModifierEnum.UNSPECIFIED && isProto2Field(field)) {
+      error(missingModifier, field, MESSAGE_FIELD__MODIFIER, MISSING_MODIFIER_ERROR);
+    } else if (field.getModifier() == ModifierEnum.REQUIRED && isProto3Field(field)) {
+      error(requiredInProto3, field, MESSAGE_FIELD__MODIFIER, REQUIRED_IN_PROTO3_ERROR);
+    }
+  }
+
+  private void checkMapField(MessageField field) {
+    // TODO(het): Add quickfix to delete the modifier
+    if (field.getModifier() != ModifierEnum.UNSPECIFIED) {
+      error(mapWithModifier, field, MESSAGE_FIELD__MODIFIER, MAP_WITH_MODIFIER_ERROR);
+    }
+  }
+
+  private boolean isProto2Field(MessageField field) {
+    EObject container = field.eContainer();
+    if (container != null && !(container instanceof Protobuf)) {
+      container = container.eContainer();
+    }
+    if (container instanceof Protobuf) {
+      return Syntaxes.isSpecifyingProto2Syntax(((Protobuf) container).getSyntax().getName());
+    }
+    return false;
+  }
+
+  private boolean isProto3Field(MessageField field) {
+    EObject container = field.eContainer();
+    while (container != null && !(container instanceof Protobuf)) {
+      container = container.eContainer();
+    }
+    if (container instanceof Protobuf) {
+      return Syntaxes.isSpecifyingProto3Syntax(((Protobuf) container).getSyntax().getName());
+    }
+    return false;
+  }
+
   private boolean checkTagNumberIsUnique(IndexedElement e, EObject message, 
       Iterable<MessageElement> elements) {
     long index = indexedElements.indexOf(e);