Validate that the start and end numbers of reserved and of extensions are
positive and that end >= start.

Change-Id: I94377f31f35a9fd15500bda93b2af1badd8efb39
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkIndexRangeBounds.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkIndexRangeBounds.java
new file mode 100644
index 0000000..a04cd27
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkIndexRangeBounds.java
@@ -0,0 +1,78 @@
+/*
+ * 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 org.mockito.Mockito.verifyNoMoreInteractions;
+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.IndexRange;
+import com.google.eclipse.protobuf.protobuf.ProtobufPackage;
+import com.google.inject.Inject;
+
+public class ProtobufJavaValidator_checkIndexRangeBounds {
+  @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 -2 to 2;
+  // }
+  @Test public void should_error_on_negative_bounds() {
+    List<IndexRange> indexRanges = xtext.findAll(IndexRange.class);
+    validator.checkIndexRangeBounds(indexRanges.get(0));
+    verifyError(
+        "Extensions and reserved numbers must be positive.",
+        indexRanges.get(0),
+        ProtobufPackage.Literals.INDEX_RANGE__FROM);
+    verifyNoMoreInteractions(messageAcceptor);
+  }
+
+  // syntax = "proto2";
+  //
+  // message Person {
+  //   reserved 3 to 1;
+  //   reserved 4 to 4;
+  // }
+  @Test public void should_error_on_end_less_than_start() {
+    List<IndexRange> indexRanges = xtext.findAll(IndexRange.class);
+    validator.checkIndexRangeBounds(indexRanges.get(0));
+    validator.checkIndexRangeBounds(indexRanges.get(1));
+    verifyError("End number must be greater than or equal to start number.", indexRanges.get(0));
+    verifyNoMoreInteractions(messageAcceptor);
+  }
+
+  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/src/com/google/eclipse/protobuf/model/util/IndexRanges.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java
index 47aee62..7eb1a56 100644
--- 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
@@ -20,6 +20,13 @@
  * @author jogl@google.com (John Glassmyer)
  */
 @Singleton public class IndexRanges {
+  /**
+   * Thrown to indicate that a range's end number is less than its start number.
+   */
+  public static class BackwardsRangeException extends Exception {
+    private static final long serialVersionUID = 1L;
+  }
+
   private ProtobufGrammarAccess protobufGrammarAccess;
 
   @Inject
@@ -27,7 +34,10 @@
     this.protobufGrammarAccess = protobufGrammarAccess;
   }
 
-  public Range<Long> toLongRange(IndexRange indexRange) {
+  /**
+   * @throws BackwardsRangeException if the end number is less than the start number
+   */
+  public Range<Long> toLongRange(IndexRange indexRange) throws BackwardsRangeException {
     long from = indexRange.getFrom();
 
     Range<Long> range;
@@ -37,7 +47,13 @@
     } else if (toString.equals(getMaxKeyword())) {
       range = Range.atLeast(from);
     } else {
-      range = Range.closed(from, Long.valueOf(toString));
+      Long to = Long.valueOf(toString);
+
+      if (to < from) {
+        throw new BackwardsRangeException();
+      }
+
+      range = Range.closed(from, to);
     }
 
     return range;
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 1b995d4..f0cd083 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
@@ -21,6 +21,7 @@
 
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Range;
+import com.google.eclipse.protobuf.model.util.IndexRanges.BackwardsRangeException;
 import com.google.eclipse.protobuf.protobuf.FieldOption;
 import com.google.eclipse.protobuf.protobuf.Group;
 import com.google.eclipse.protobuf.protobuf.IndexRange;
@@ -67,20 +68,24 @@
   private long findMaxIndex(Iterable<? extends EObject> elements) {
     long maxIndex = 0;
 
-    for (EObject e : elements) {
-      if (e instanceof OneOf) {
-        maxIndex = max(maxIndex, findMaxIndex(((OneOf) e).getElements()));
-      } else if (e instanceof IndexedElement) {
-        maxIndex  = max(maxIndex, indexOf((IndexedElement) e));
-        if (e instanceof Group) {
-          maxIndex = max(maxIndex, findMaxIndex(((Group) e).getElements()));
+    for (EObject element : elements) {
+      if (element instanceof OneOf) {
+        maxIndex = max(maxIndex, findMaxIndex(((OneOf) element).getElements()));
+      } else if (element instanceof IndexedElement) {
+        maxIndex  = max(maxIndex, indexOf((IndexedElement) element));
+        if (element instanceof Group) {
+          maxIndex = max(maxIndex, findMaxIndex(((Group) element).getElements()));
         }
-      } else if (e instanceof Reserved) {
+      } else if (element 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());
+            Iterables.filter(((Reserved) element).getReservations(), IndexRange.class)) {
+          try {
+            Range<Long> range = indexRanges.toLongRange(indexRange);
+            if (range.hasUpperBound()) {
+              maxIndex = max(maxIndex, range.upperEndpoint());
+            }
+          } catch (BackwardsRangeException e) {
+            // Do not consider reserved ranges that are invalid.
           }
         }
       }
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 85c2f9d..bda1d9f 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
@@ -31,6 +31,8 @@
   public static String fieldNumbersMustBePositive;
   public static String importingUnsupportedSyntax;
   public static String importNotFound;
+  public static String indexRangeEndLessThanStart;
+  public static String indexRangeNonPositive;
   public static String invalidMapKeyType;
   public static String invalidMapValueType;
   public static String literalNotInEnum;
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 15aaf6a..60934f8 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
@@ -15,6 +15,8 @@
 fieldNumbersMustBePositive = Field numbers must be positive integers.
 importingUnsupportedSyntax = Importing unsupported file (directly or indirectly.) This may cause errors related to unresolved references.
 importNotFound = Import \"%s\" was not found.
+indexRangeEndLessThanStart = End number must be greater than or equal to start number.
+indexRangeNonPositive = Extensions and reserved numbers must be positive.
 invalidMapKeyType = Key in a map field must be of integral or string type.
 invalidMapValueType = Value in a map field cannot be a map.
 literalNotInEnum = Enum type \"%s\" has no value named \"%s\".
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 d37b92d..3c6e173 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
@@ -16,6 +16,8 @@
 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.fieldNumbersMustBePositive;
+import static com.google.eclipse.protobuf.validation.Messages.indexRangeEndLessThanStart;
+import static com.google.eclipse.protobuf.validation.Messages.indexRangeNonPositive;
 import static com.google.eclipse.protobuf.validation.Messages.invalidMapKeyType;
 import static com.google.eclipse.protobuf.validation.Messages.invalidMapValueType;
 import static com.google.eclipse.protobuf.validation.Messages.mapWithModifier;
@@ -49,6 +51,7 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Range;
 import com.google.eclipse.protobuf.model.util.IndexRanges;
+import com.google.eclipse.protobuf.model.util.IndexRanges.BackwardsRangeException;
 import com.google.eclipse.protobuf.model.util.IndexedElements;
 import com.google.eclipse.protobuf.model.util.Protobufs;
 import com.google.eclipse.protobuf.model.util.StringLiterals;
@@ -124,22 +127,44 @@
     error(msg, syntax, SYNTAX__NAME, SYNTAX_IS_NOT_KNOWN_ERROR);
   }
 
+  @Check public void checkIndexRangeBounds(IndexRange indexRange) {
+    Range<Long> range;
+    try {
+      range = indexRanges.toLongRange(indexRange);
+    } catch (BackwardsRangeException e) {
+      error(indexRangeEndLessThanStart, indexRange, null);
+      return;
+    }
+
+    if (range.lowerEndpoint() <= 0) {
+      error(indexRangeNonPositive, indexRange, ProtobufPackage.Literals.INDEX_RANGE__FROM);
+    }
+  }
+
   @Check public void checkForIndexConflicts(Message message) {
     Multimap<EObject, Range<Long>> rangeUsages = LinkedHashMultimap.create();
 
     for (Reserved reserved : getOwnedElements(message, Reserved.class)) {
       for (IndexRange indexRange : Iterables.filter(reserved.getReservations(), IndexRange.class)) {
-        Range<Long> range = indexRanges.toLongRange(indexRange);
-        errorOnConflicts(range, rangeUsages, indexRange, null);
-        rangeUsages.put(reserved, range);
+        try {
+          Range<Long> range = indexRanges.toLongRange(indexRange);
+          errorOnConflicts(range, rangeUsages, indexRange, null);
+          rangeUsages.put(reserved, range);
+        } catch (BackwardsRangeException e) {
+          // Do not try to find conflicts with invalid ranges.
+        }
       }
     }
 
     for (Extensions extensions : getOwnedElements(message, Extensions.class)) {
       for (IndexRange indexRange : extensions.getRanges()) {
-        Range<Long> range = indexRanges.toLongRange(indexRange);
-        errorOnConflicts(range, rangeUsages, indexRange, null);
-        rangeUsages.put(extensions, range);
+        try {
+          Range<Long> range = indexRanges.toLongRange(indexRange);
+          errorOnConflicts(range, rangeUsages, indexRange, null);
+          rangeUsages.put(extensions,  range);
+        } catch (BackwardsRangeException e) {
+          // Do not try to find conflicts with invalid ranges.
+        }
       }
     }