Working on supporting pluggable descriptor.proto definitions.
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Strings_firstCharToLowerCase_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Strings_firstCharToLowerCase_Test.java
index 1b4b7a4..6453372 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Strings_firstCharToLowerCase_Test.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Strings_firstCharToLowerCase_Test.java
@@ -22,21 +22,15 @@
  */
 public class Strings_firstCharToLowerCase_Test {
 
-  private static Strings strings;
-  
-  @BeforeClass public static void setUpOnce() {
-    strings = new Strings();
-  }
-  
   @Test public void should_return_null() {
-    assertThat(strings.firstCharToLowerCase(null), nullValue());
+    assertThat(Strings.firstCharToLowerCase(null), nullValue());
   }
 
   @Test public void should_return_empty_String() {
-    assertThat(strings.firstCharToLowerCase(""), equalTo(""));
+    assertThat(Strings.firstCharToLowerCase(""), equalTo(""));
   }
   
   @Test public void should_return_String_with_first_char_lower_case() {
-    assertThat(strings.firstCharToLowerCase("HElLo"), equalTo("hElLo"));
+    assertThat(Strings.firstCharToLowerCase("HElLo"), equalTo("hElLo"));
   }
 }
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 a7f3e19..845fa54 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
@@ -12,6 +12,7 @@
 import static com.google.eclipse.protobuf.protobuf.ScalarType.STRING;
 import static com.google.eclipse.protobuf.ui.grammar.CommonKeyword.*;
 import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.*;
+import static com.google.eclipse.protobuf.ui.util.Strings.firstCharToLowerCase;
 import static java.lang.String.valueOf;
 import static java.util.Collections.emptyList;
 
@@ -54,7 +55,6 @@
   @Inject private Images images;
   @Inject private Literals literals;
   @Inject private Properties properties;
-  @Inject private Strings strings;
 
   /** {@inheritDoc} */
   @Override public void completeProtobuf_Syntax(EObject model, Assignment assignment, ContentAssistContext context,
@@ -310,7 +310,7 @@
 
   @Override public void completeProperty_Name(EObject model, Assignment assignment, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
-    String typeName = strings.firstCharToLowerCase(properties.typeNameOf((Property) model));
+    String typeName = firstCharToLowerCase(properties.typeNameOf((Property) model));
     int index = 1;
     String name = typeName + index;
     for (EObject o : model.eContainer().eContents()) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/syntaxcoloring/ProtobufSemanticHighlightingCalculator.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/syntaxcoloring/ProtobufSemanticHighlightingCalculator.java
index 8993736..c02e9ec 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/syntaxcoloring/ProtobufSemanticHighlightingCalculator.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/syntaxcoloring/ProtobufSemanticHighlightingCalculator.java
@@ -8,10 +8,12 @@
  */
 package com.google.eclipse.protobuf.ui.editor.syntaxcoloring;
 
-import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.findNodesForFeature;
 import static org.eclipse.xtext.ui.editor.syntaxcoloring.DefaultHighlightingConfiguration.DEFAULT_ID;
 
-import java.util.List;
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.protobuf.Package;
+import com.google.eclipse.protobuf.util.ModelNodes;
+import com.google.inject.Inject;
 
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.*;
@@ -19,14 +21,13 @@
 import org.eclipse.xtext.resource.XtextResource;
 import org.eclipse.xtext.ui.editor.syntaxcoloring.*;
 
-import com.google.eclipse.protobuf.protobuf.*;
-import com.google.eclipse.protobuf.protobuf.Package;
-
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
 public class ProtobufSemanticHighlightingCalculator implements ISemanticHighlightingCalculator {
 
+  @Inject private ModelNodes nodes;
+  
   public void provideHighlightingFor(XtextResource resource, IHighlightedPositionAcceptor acceptor) {
     if (resource == null) return;
     EList<EObject> contents = resource.getContents();
@@ -104,13 +105,8 @@
 
   private void highlightFirstFeature(EObject semantic, EStructuralFeature feature, String highlightId,
       IHighlightedPositionAcceptor acceptor) {
-    INode node = firstFeatureNode(semantic, feature);
+    INode node = nodes.firstNodeForFeature(semantic, feature);
     if (node == null || node.getText() == null) return;
     acceptor.addPosition(node.getOffset(), node.getText().length(), highlightId);
   }
-
-  public INode firstFeatureNode(EObject semantic, EStructuralFeature feature) {
-    List<INode> nodes = findNodesForFeature(semantic, feature);
-    return (nodes.size() == 1) ? nodes.get(0) : null;
-  }
 }
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 496ffd4..d8319e4 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
@@ -8,19 +8,16 @@
  */
 package com.google.eclipse.protobuf.ui.labeling;
 
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.IMPORT__IMPORT_URI;
 import static org.eclipse.jface.viewers.StyledString.DECORATIONS_STYLER;
-import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.findNodesForFeature;
-import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.*;
-
-import org.eclipse.jface.viewers.StyledString;
-import org.eclipse.xtext.nodemodel.INode;
-
-import java.util.List;
 
 import com.google.eclipse.protobuf.protobuf.*;
 import com.google.eclipse.protobuf.ui.util.Properties;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
+import com.google.eclipse.protobuf.util.ModelNodes;
+import com.google.inject.*;
+
+import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.xtext.nodemodel.INode;
 
 /**
  * Registry of commonly used text in the 'Protocol Buffer' editor.
@@ -30,6 +27,7 @@
 @Singleton
 public class Labels {
 
+  @Inject private ModelNodes nodes;
   @Inject private Properties properties;
 
   public Object labelFor(Object o) {
@@ -61,9 +59,8 @@
   }
 
   private Object labelFor(Import i) {
-    List<INode> nodes = findNodesForFeature(i, IMPORT__IMPORT_URI);
-    if (nodes.size() != 1) return i.getImportURI();
-    INode node = nodes.get(0);
+    INode node = nodes.firstNodeForFeature(i, IMPORT__IMPORT_URI);
+    if (node == null) return i.getImportURI();
     return node.getText();
   }
 
@@ -77,7 +74,7 @@
   private Object labelFor(Property p) {
     StyledString text = new StyledString(p.getName());
     String typeName = properties.typeNameOf(p);
-    if (typeName == null) typeName = "<unable to resolve type reference>";
+    if (typeName == null) typeName = "<unresolved reference>"; // TODO move to properties file
     String indexAndType = String.format(" [%d] : %s", p.getIndex(), typeName);
     text.append(indexAndType, DECORATIONS_STYLER);
     return text;
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Fields.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Fields.java
index a9e8d40..47e35fd 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Fields.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Fields.java
@@ -38,7 +38,7 @@
    *
    * The calculated tag number value for the field {@code PhoneNumber} will be 3.
    * </p>
-   * @param p the given field.
+   * @param f the given field.
    * @return the calculated value for the tag number of the given field.
    */
   public int calculateTagNumberOf(Field f) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Strings.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Strings.java
index 4b66918..af8b091 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Strings.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Strings.java
@@ -10,14 +10,11 @@
 
 import static java.lang.Character.toLowerCase;
 
-import com.google.inject.Singleton;
-
 /**
  * Utility methods related to {@code String}.
  *
  * @author alruiz@google.com (Alex Ruiz)
  */
-@Singleton
 public class Strings {
 
   /** Pattern to split CSVs. */
@@ -28,11 +25,13 @@
    * @param s the original {@code String}
    * @return a {@code String} with the same content as the given one, but with the first character in lower case.
    */
-  public String firstCharToLowerCase(String s) {
+  public static String firstCharToLowerCase(String s) {
     if (s == null) return null;
     if (s.length() == 0) return s;
     char[] chars = s.toCharArray();
     chars[0] = toLowerCase(chars[0]);
     return new String(chars);
   }
+  
+  private Strings() {}
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
index 2b9a399..b9ecf9a 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
@@ -8,24 +8,28 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.PROPERTY__TYPE;
 import static com.google.eclipse.protobuf.scoping.OptionType.*;
 import static com.google.eclipse.protobuf.util.Closeables.close;
 import static java.util.Collections.unmodifiableCollection;
 import static org.eclipse.emf.common.util.URI.createURI;
 import static org.eclipse.xtext.EcoreUtil2.*;
 import static org.eclipse.xtext.util.CancelIndicator.NullImpl;
+import static org.eclipse.xtext.util.Strings.isEmpty;
+
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.protobuf.Enum;
+import com.google.eclipse.protobuf.util.ModelNodes;
+import com.google.inject.Inject;
+
+import org.eclipse.xtext.nodemodel.INode;
+import org.eclipse.xtext.parser.*;
+import org.eclipse.xtext.resource.XtextResource;
 
 import java.io.*;
 import java.net.URL;
 import java.util.*;
 
-import org.eclipse.xtext.parser.*;
-import org.eclipse.xtext.resource.XtextResource;
-
-import com.google.eclipse.protobuf.protobuf.*;
-import com.google.eclipse.protobuf.protobuf.Enum;
-import com.google.inject.Inject;
-
 /**
  * Contains the elements from descriptor.proto (provided with protobuf's library.)
  *
@@ -34,13 +38,23 @@
 public class ProtoDescriptor implements IProtoDescriptor {
 
   private static final String DESCRIPTOR_URI = "platform:/plugin/com.google.eclipse.protobuf/descriptor.proto";
-
-  private final Map<OptionType, Map<String, Property>> options = new HashMap<OptionType, Map<String, Property>>();
-  private final Map<String, Enum> enums = new HashMap<String, Enum>();
+  private static final Map<String, OptionType> OPTION_DEFINITION_BY_NAME = new HashMap<String, OptionType>();
+  
+  static {
+    OPTION_DEFINITION_BY_NAME.put("FileOptions", FILE);
+    OPTION_DEFINITION_BY_NAME.put("MessageOptions", MESSAGE);
+    OPTION_DEFINITION_BY_NAME.put("FieldOptions", FIELD);
+  }
+  
+  private final Map<OptionType, Map<String, Property>> optionsByType = new HashMap<OptionType, Map<String, Property>>();
+  private final Map<String, Enum> enumsByName = new HashMap<String, Enum>();
 
   private Protobuf root;
 
-  @Inject public ProtoDescriptor(IParser parser) {
+  private final ModelNodes nodes;
+
+  @Inject public ProtoDescriptor(IParser parser, ModelNodes nodes) {
+    this.nodes = nodes;
     addOptionTypes();
     InputStreamReader reader = null;
     try {
@@ -61,7 +75,7 @@
 
   private void addOptionTypes() {
     for (OptionType type : OptionType.values())
-      options.put(type, new LinkedHashMap<String, Property>());
+      optionsByType.put(type, new LinkedHashMap<String, Property>());
   }
 
   private static InputStream globalScopeContents() throws IOException {
@@ -71,86 +85,34 @@
 
   private void initContents() {
     for (Message m : getAllContentsOfType(root, Message.class)) {
-      if (isFileOptionsMessage(m)) initFileOptions(m);
-      else if (isMessageOptionsMessage(m)) initMessageOptions(m);
-      else if (isFieldOptionsMessage(m)) initFieldOptions(m);
+      OptionType type = OPTION_DEFINITION_BY_NAME.get(m.getName());
+      if (type == null) continue;
+      initOptions(m, type);
     }
   }
 
-  private boolean isFileOptionsMessage(Message m) {
-    return "FileOptions".equals(m.getName());
-  }
-
-  private boolean isMessageOptionsMessage(Message m) {
-    return "MessageOptions".equals(m.getName());
-  }
-
-  private boolean isFieldOptionsMessage(Message m) {
-    return "FieldOptions".equals(m.getName());
-  }
-
-  private void initFileOptions(Message fileOptionsMessage) {
-    for (MessageElement e : fileOptionsMessage.getElements()) {
+  private void initOptions(Message optionGroup, OptionType type) {
+    for (MessageElement e : optionGroup.getElements()) {
       if (e instanceof Property) {
-        addFileOption((Property) e);
+        addOption((Property) e, type);
         continue;
       }
-      if (isEnumWithName(e, "OptimizeMode")) {
-        enums.put("optimize_for", (Enum) e);
-        continue;
+      if (e instanceof Enum) {
+        Enum anEnum = (Enum) e;
+        enumsByName.put(anEnum.getName(), anEnum);
       }
     }
   }
 
-  private void addFileOption(Property p) {
-    addOption(FILE, p);
-  }
-
-  private void initMessageOptions(Message messageOptionsMessage) {
-    for (MessageElement e : messageOptionsMessage.getElements()) {
-      if (e instanceof Property) {
-        addMessageOption((Property) e);
-        continue;
-      }
-    }
-  }
-
-  private void addMessageOption(Property p) {
-    addOption(MESSAGE, p);
-  }
-
-  private void initFieldOptions(Message fieldOptionsMessage) {
-    for (MessageElement e : fieldOptionsMessage.getElements()) {
-      if (e instanceof Property) {
-        addFieldOption((Property) e);
-        continue;
-      }
-      if (isEnumWithName(e, "CType")) {
-        enums.put("ctype", (Enum) e);
-        continue;
-      }
-    }
-  }
-
-  private void addFieldOption(Property p) {
-    addOption(FIELD, p);
-  }
-
-  private void addOption(OptionType type, Property p) {
-    if (shouldIgnore(p)) return;
-    options.get(type).put(p.getName(), p);
+  private void addOption(Property optionDefinition, OptionType type) {
+    if (shouldIgnore(optionDefinition)) return;
+    optionsByType.get(type).put(optionDefinition.getName(), optionDefinition);
   }
 
   private boolean shouldIgnore(Property property) {
     return "uninterpreted_option".equals(property.getName());
   }
 
-  private boolean isEnumWithName(MessageElement e, String name) {
-    if (!(e instanceof Enum)) return false;
-    Enum anEnum = (Enum) e;
-    return name.equals(anEnum.getName());
-  }
-
   /** {@inheritDoc} */
   public Collection<Property> fileOptions() {
     return optionsOfType(FILE);
@@ -158,9 +120,24 @@
 
   /** {@inheritDoc} */
   public Property lookupOption(String name) {
-    Property p = lookupOption(FILE, name);
-    if (p == null) lookupOption(MESSAGE, name);
-    return p;
+    return lookupOption(name, FILE, MESSAGE);
+  }
+  
+  public Property lookupOption(String name, OptionType...types) {
+    for (OptionType type : types) {
+      Property p = lookupOption(name, type);
+      if (p != null) return p;
+    }
+    return null;
+  }
+
+  /** {@inheritDoc} */
+  public Property lookupFieldOption(String name) {
+    return lookupOption(name, FIELD);
+  }
+
+  private Property lookupOption(String name, OptionType type) {
+    return optionsByType.get(type).get(name);
   }
 
   /** {@inheritDoc} */
@@ -174,25 +151,26 @@
   }
 
   private Collection<Property> optionsOfType(OptionType type) {
-    return unmodifiableCollection(options.get(type).values());
-  }
-
-  /** {@inheritDoc} */
-  public Property lookupFieldOption(String name) {
-    return lookupOption(FIELD, name);
-  }
-
-  private Property lookupOption(OptionType type, String name) {
-    return options.get(type).get(name);
+    return unmodifiableCollection(optionsByType.get(type).values());
   }
 
   /** {@inheritDoc} */
   public Enum enumTypeOf(Option option) {
-    return enums.get(option.getName());
+    String name = option.getName();
+    return enumTypeOf(lookupOption(name));
   }
 
   /** {@inheritDoc} */
   public Enum enumTypeOf(FieldOption option) {
-    return enums.get(option.getName());
+    String name = option.getName();
+    return enumTypeOf(lookupFieldOption(name));
+  }
+
+  private Enum enumTypeOf(Property p) {
+    if (p == null) return null;
+    INode node = nodes.firstNodeForFeature(p, PROPERTY__TYPE);
+    if (node == null) return null;
+    String typeName = node.getText();
+    return (isEmpty(typeName)) ? null : enumsByName.get(typeName.trim());
   }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
index 6734b2d..82aef7f 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptorProvider.java
@@ -8,10 +8,11 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
-import org.eclipse.xtext.parser.IParser;
-
+import com.google.eclipse.protobuf.util.ModelNodes;
 import com.google.inject.*;
 
+import org.eclipse.xtext.parser.IParser;
+
 /**
  * Provider of a singleton instance of <code>{@link ProtoDescriptor}</code>.
  *
@@ -21,11 +22,12 @@
 public class ProtoDescriptorProvider implements Provider<IProtoDescriptor> {
 
   @Inject private IParser parser;
+  @Inject private ModelNodes nodes;
 
   private IProtoDescriptor descriptor;
 
   public synchronized IProtoDescriptor get() {
-    if (descriptor == null) descriptor = new ProtoDescriptor(parser);
+    if (descriptor == null) descriptor = new ProtoDescriptor(parser, nodes);
     return descriptor;
   }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
new file mode 100644
index 0000000..a720813
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011 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.util;
+
+import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.findNodesForFeature;
+
+import com.google.inject.Singleton;
+
+import org.eclipse.emf.ecore.*;
+import org.eclipse.xtext.nodemodel.INode;
+
+import java.util.List;
+
+/**
+ * Utility methods related to <code>{@link INode}</code>s.
+ * 
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+@Singleton
+public class ModelNodes {
+
+  public INode firstNodeForFeature(EObject semanticObject, EStructuralFeature structuralFeature) {
+    List<INode> nodes = findNodesForFeature(semanticObject, structuralFeature);
+    if (nodes.isEmpty()) return null;
+    return nodes.get(0);
+  }
+}