Merge branch 'master' of https://code.google.com/p/protobuf-dt/
diff --git a/com.google.eclipse.protobuf.cdt.test/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf.cdt.test/META-INF/MANIFEST.MF
index 09d731b..3b08b5f 100644
--- a/com.google.eclipse.protobuf.cdt.test/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf.cdt.test/META-INF/MANIFEST.MF
@@ -7,4 +7,6 @@
 Fragment-Host: com.google.eclipse.protobuf.cdt;bundle-version="1.0.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
 Require-Bundle: org.junit;bundle-version="4.8.2",
- org.mockito;bundle-version="1.8.5"
+ org.mockito;bundle-version="1.8.5",
+ org.hamcrest;bundle-version="1.1.0",
+ org.hamcrest.library;bundle-version="1.1.0"
diff --git a/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/PatternBuilder_patternToMatchFrom_Test.java b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/PatternBuilder_patternToMatchFrom_Test.java
new file mode 100644
index 0000000..ffe207e
--- /dev/null
+++ b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/PatternBuilder_patternToMatchFrom_Test.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012 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.cdt.matching;
+
+import static com.google.eclipse.protobuf.cdt.matching.PatternMatcher.matches;
+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;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.xtext.naming.IQualifiedNameConverter;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.cdt.mapping.CppToProtobufMapping;
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link PatternBuilder#patternToMatchFrom(CppToProtobufMapping)}</code>
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class PatternBuilder_patternToMatchFrom_Test {
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
+
+  @Inject private PatternBuilder builder;
+  @Inject private IQualifiedNameConverter converter;
+
+  @Test public void should_escape_dots() {
+    CppToProtobufMapping mapping = createMessageMapping("com.google.proto.test.Person");
+    Pattern pattern = builder.patternToMatchFrom(mapping);
+    assertThat(pattern.pattern(), equalTo("com\\.google\\.proto\\.test\\.Person"));
+    assertThat("com.google.proto.test.Person", matches(pattern));
+  }
+
+  @Test public void should_escape_underscore() {
+    CppToProtobufMapping mapping = createMessageMapping("com.google.proto.test.Person_PhoneType");
+    Pattern pattern = builder.patternToMatchFrom(mapping);
+    assertThat(pattern.pattern(), equalTo("com\\.google\\.proto\\.test\\.Person(\\.|_)PhoneType"));
+    assertThat("com.google.proto.test.Person.PhoneType", matches(pattern));
+    assertThat("com.google.proto.test.Person_PhoneType", matches(pattern));
+  }
+
+  private CppToProtobufMapping createMessageMapping(String qualifiedNameAsText) {
+    return new CppToProtobufMapping(converter.toQualifiedName(qualifiedNameAsText), MESSAGE);
+  }
+}
diff --git a/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/PatternMatcher.java b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/PatternMatcher.java
new file mode 100644
index 0000000..c7d4f07
--- /dev/null
+++ b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/PatternMatcher.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012 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.cdt.matching;
+
+import java.util.regex.Pattern;
+
+import org.hamcrest.*;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class PatternMatcher extends TypeSafeMatcher<String> {
+  private final Pattern pattern;
+
+  static PatternMatcher matches(Pattern pattern) {
+    return new PatternMatcher(pattern);
+  }
+
+  private PatternMatcher(Pattern pattern) {
+    super(String.class);
+    this.pattern = pattern;
+  }
+
+  @Override public boolean matchesSafely(String item) {
+    return pattern.matcher(item).matches();
+  }
+
+  @Override public void describeTo(Description description) {
+    description.appendValue(pattern.pattern());
+  }
+
+}
diff --git a/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test.java b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test.java
index ac4e225..15f9ec1 100644
--- a/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test.java
+++ b/com.google.eclipse.protobuf.cdt.test/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher_findUriOfMatchingProtobufElement_Test.java
@@ -10,6 +10,7 @@
 
 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;
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.*;
 
@@ -105,7 +106,7 @@
 
   private CppToProtobufMapping messageMapping(String qualifiedNameAsText) {
     QualifiedName qualifiedName = fqnConverter.toQualifiedName(qualifiedNameAsText);
-    return new CppToProtobufMapping(qualifiedName, Message.class);
+    return new CppToProtobufMapping(qualifiedName, MESSAGE);
   }
 
   private URI uriOfMessageWithName(String name) {
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/cpplang/CppKeywords.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/cpplang/CppKeywords.java
new file mode 100644
index 0000000..87604a6
--- /dev/null
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/cpplang/CppKeywords.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2012 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.cdt.cpplang;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+import java.util.List;
+
+import com.google.inject.Singleton;
+
+/**
+ * C++ keywords.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+@Singleton public class CppKeywords {
+
+  private static final List<String> KEYWORDS = newArrayList("and", "and_eq", "asm", "auto", "bitand", "bitor", "bool",
+      "break", "case", "catch", "char", "class", "compl", "const", "const_cast", "continue", "default", "delete",
+      "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend",
+      "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "not", "not_eq", "operator", "or", "or_eq",
+      "private", "protected", "public", "register", "reinterpret_cast", "return", "short", "signed", "sizeof",
+      "static", "static_cast", "struct", "switch", "template", "this", "throw", "true", "try", "typedef", "typeid",
+      "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq");
+
+  @SuppressWarnings("unused")
+  private static final List<String> CPP11_KEYWORDS = newArrayList("alignas", "alignof", "char16_t", "char32_t",
+      "constexpr", "decltype", "noexcept", "nullptr", "static_assert", "thread_local");
+
+  /**
+   * Indicates whether the content of the given {@code String} is a C++ keyword.
+   * @param s the given {@code String}.
+   * @return {@code true} if the content of the given {@code String} is a C++ keyword, {@code false} otherwise.
+   */
+  public boolean isKeyword(String s) {
+    return KEYWORDS.contains(s);
+  }
+}
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/ClassMappingStrategy.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/ClassMappingStrategy.java
index 00dacf4..2b9a1f5 100644
--- a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/ClassMappingStrategy.java
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/ClassMappingStrategy.java
@@ -8,13 +8,14 @@
  */
 package com.google.eclipse.protobuf.cdt.mapping;
 
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.MESSAGE;
+
 import org.eclipse.cdt.core.dom.IName;
 import org.eclipse.cdt.core.dom.ast.IBinding;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase;
 import org.eclipse.cdt.internal.core.dom.parser.cpp.*;
 import org.eclipse.xtext.naming.QualifiedName;
 
-import com.google.eclipse.protobuf.protobuf.Message;
 import com.google.inject.Singleton;
 
 /**
@@ -28,7 +29,7 @@
     if (isMessage(classType)) {
       String[] segments = classType.getQualifiedName();
       QualifiedName qualifiedName = QualifiedName.create(segments);
-      return new CppToProtobufMapping(qualifiedName, Message.class);
+      return new CppToProtobufMapping(qualifiedName, MESSAGE);
     }
     return null;
   }
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java
index 35df4c4..b600a3b 100644
--- a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/CppToProtobufMapping.java
@@ -8,7 +8,7 @@
  */
 package com.google.eclipse.protobuf.cdt.mapping;
 
-import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EClass;
 import org.eclipse.xtext.naming.QualifiedName;
 
 /**
@@ -18,9 +18,9 @@
  */
 public class CppToProtobufMapping {
   private final QualifiedName qualifiedName;
-  private final Class<? extends EObject> type;
+  private final EClass type;
 
-  public CppToProtobufMapping(QualifiedName qualifiedName, Class<? extends EObject> type) {
+  public CppToProtobufMapping(QualifiedName qualifiedName, EClass type) {
     this.qualifiedName = qualifiedName;
     this.type = type;
   }
@@ -37,7 +37,7 @@
    * Returns the type of the protocol buffer element.
    * @return the type of the protocol buffer element.
    */
-  public Class<? extends EObject> type() {
+  public EClass type() {
     return type;
   }
 }
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/EnumMappingStrategy.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/EnumMappingStrategy.java
index 9c0edef..5c30caf 100644
--- a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/EnumMappingStrategy.java
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/EnumMappingStrategy.java
@@ -8,11 +8,12 @@
  */
 package com.google.eclipse.protobuf.cdt.mapping;
 
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.ENUM;
+
 import org.eclipse.cdt.core.dom.ast.IBinding;
 import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPEnumeration;
 import org.eclipse.xtext.naming.QualifiedName;
 
-import com.google.eclipse.protobuf.protobuf.Enum;
 import com.google.inject.Singleton;
 
 /**
@@ -25,7 +26,7 @@
     CPPEnumeration enumeration = typeOfSupportedBinding().cast(binding);
     String[] segments = enumeration.getQualifiedName();
     QualifiedName qualifiedName = QualifiedName.create(segments);
-    return new CppToProtobufMapping(qualifiedName, Enum.class);
+    return new CppToProtobufMapping(qualifiedName, ENUM);
   }
 
   @Override public Class<CPPEnumeration> typeOfSupportedBinding() {
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/MethodMappingStrategy.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/MethodMappingStrategy.java
index db5150a..5dce848 100644
--- a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/MethodMappingStrategy.java
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/mapping/MethodMappingStrategy.java
@@ -8,13 +8,13 @@
  */
 package com.google.eclipse.protobuf.cdt.mapping;
 
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.MESSAGE_FIELD;
+
 import org.eclipse.cdt.core.dom.ast.*;
 import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
 import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPMethod;
 import org.eclipse.xtext.naming.QualifiedName;
 
-import com.google.eclipse.protobuf.protobuf.MessageField;
-
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
@@ -32,7 +32,7 @@
       return null;
     }
     QualifiedName qualifiedName = QualifiedName.create(method.getQualifiedName());
-    return new CppToProtobufMapping(qualifiedName, MessageField.class);
+    return new CppToProtobufMapping(qualifiedName, MESSAGE_FIELD);
   }
 
   @Override public Class<CPPMethod> typeOfSupportedBinding() {
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/PatternBuilder.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/PatternBuilder.java
new file mode 100644
index 0000000..f080950
--- /dev/null
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/PatternBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 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.cdt.matching;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.xtext.naming.IQualifiedNameConverter;
+
+import com.google.eclipse.protobuf.cdt.mapping.CppToProtobufMapping;
+import com.google.inject.Inject;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class PatternBuilder {
+  @Inject private IQualifiedNameConverter converter;
+
+  Pattern patternToMatchFrom(CppToProtobufMapping mapping) {
+    String qualifiedNameAsText = converter.toString(mapping.qualifiedName());
+    StringBuilder regex = new StringBuilder();
+    int size = qualifiedNameAsText.length();
+    // escape existing "."
+    // replace "_" with "(\.|_)"
+    for (int i = 0; i < size; i++) {
+      char c = qualifiedNameAsText.charAt(i);
+      switch (c) {
+        case '.':
+          regex.append("\\.");
+          break;
+        case '_':
+          regex.append("(\\.|_)");
+          break;
+        default:
+          regex.append(c);
+      }
+    }
+    return Pattern.compile(regex.toString());
+  }
+}
diff --git a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java
index f7a254b..94655af 100644
--- a/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java
+++ b/com.google.eclipse.protobuf.cdt/src/com/google/eclipse/protobuf/cdt/matching/ProtobufElementMatcher.java
@@ -8,13 +8,15 @@
  */
 package com.google.eclipse.protobuf.cdt.matching;
 
+import static com.google.eclipse.protobuf.resource.filter.MatchingQualifiedNameAndTypeFilter.matchingQualifiedNameAndType;
+
 import java.util.List;
 import java.util.regex.Pattern;
 
 import org.eclipse.emf.common.util.URI;
-import org.eclipse.xtext.naming.*;
 import org.eclipse.xtext.resource.*;
 
+import com.google.common.base.Predicate;
 import com.google.eclipse.protobuf.cdt.mapping.CppToProtobufMapping;
 import com.google.eclipse.protobuf.resource.ResourceDescriptions;
 import com.google.inject.Inject;
@@ -23,7 +25,7 @@
  * @author alruiz@google.com (Alex Ruiz)
  */
 public class ProtobufElementMatcher {
-  @Inject private IQualifiedNameConverter converter;
+  @Inject private PatternBuilder patternBuilder;
   @Inject private ResourceDescriptions descriptions;
 
   /**
@@ -34,21 +36,12 @@
    * @return the found URI, or {@code null} if it was not possible to find a matching protocol buffer element.
    */
   public URI findUriOfMatchingProtobufElement(IResourceDescription resource, CppToProtobufMapping mapping) {
-    QualifiedName qualifiedName = mapping.qualifiedName();
-    Pattern pattern = patternToMatchFrom(qualifiedName);
-    List<IEObjectDescription> matches = descriptions.matchingQualifiedNames(resource, pattern, mapping.type());
+    Pattern pattern = patternBuilder.patternToMatchFrom(mapping);
+    Predicate<IEObjectDescription> filter = matchingQualifiedNameAndType(pattern, mapping.type());
+    List<IEObjectDescription> matches = descriptions.filterModelObjects(resource, filter);
     if (!matches.isEmpty()) {
       return matches.get(0).getEObjectURI();
     }
     return null;
   }
-
-  private Pattern patternToMatchFrom(QualifiedName qualifiedName) {
-    String qualifiedNameAsText = converter.toString(qualifiedName);
-    // escape existing "."
-    // replace "_" with "(\.|_)"
-    String regex = qualifiedNameAsText.replaceAll("\\.", "\\\\.")
-                                      .replaceAll("_", "\\(\\\\.|_\\)");
-    return Pattern.compile(regex);
-  }
 }
diff --git a/com.google.eclipse.protobuf.feature/feature.xml b/com.google.eclipse.protobuf.feature/feature.xml
index 1782178..e5f61f9 100644
--- a/com.google.eclipse.protobuf.feature/feature.xml
+++ b/com.google.eclipse.protobuf.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="com.google.eclipse.protobuf"
       label="%featureName"
-      version="1.2.2.qualifier"
+      version="1.2.3.qualifier"
       provider-name="%providerName">
 
    <description url="https://code.google.com/p/protobuf-dt/">
@@ -22,6 +22,7 @@
    </url>
 
    <requires>
+      <import plugin="org.eclipse.xtext" version="2.2.1" match="greaterOrEqual"/>
       <import plugin="org.eclipse.xtext.util"/>
       <import plugin="org.eclipse.emf.ecore"/>
       <import plugin="org.eclipse.emf.common"/>
@@ -42,7 +43,6 @@
       <import plugin="org.eclipse.compare.core" version="3.5.200" match="greaterOrEqual"/>
       <import plugin="org.eclipse.ui.console" version="3.5.100" match="greaterOrEqual"/>
       <import plugin="org.eclipse.ui.workbench.texteditor"/>
-      <import plugin="org.eclipse.xtext" version="2.2.1" match="greaterOrEqual"/>
    </requires>
 
    <plugin
diff --git a/com.google.eclipse.protobuf.integration.test/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf.integration.test/META-INF/MANIFEST.MF
index 3136f73..5726298 100644
--- a/com.google.eclipse.protobuf.integration.test/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf.integration.test/META-INF/MANIFEST.MF
@@ -9,5 +9,7 @@
  org.eclipse.xtext.junit;bundle-version="2.0.0",
  org.eclipse.xtext.junit4;bundle-version="2.0.0",
  org.mockito;bundle-version="1.8.5",
- org.eclipse.core.resources;bundle-version="3.7.100"
+ org.eclipse.core.resources;bundle-version="3.7.100",
+ org.hamcrest;bundle-version="1.1.0",
+ org.hamcrest.library;bundle-version="1.1.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFields.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFields.java
index 18b486c..83db9ee 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFields.java
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFields.java
@@ -23,7 +23,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class ContainAllFields extends BaseMatcher<IEObjectDescriptions> {
+public class ContainAllFields extends TypeSafeMatcher<IEObjectDescriptions> {
   private final Collection<MessageField> fields = newArrayList();
 
   public static ContainAllFields containAll(Collection<MessageField> fields) {
@@ -31,20 +31,17 @@
   }
 
   private ContainAllFields(Collection<MessageField> fields) {
+    super(IEObjectDescriptions.class);
     this.fields.addAll(fields);
   }
 
-  @Override public boolean matches(Object arg) {
-    if (!(arg instanceof IEObjectDescriptions)) {
-      return false;
-    }
-    IEObjectDescriptions descriptions = (IEObjectDescriptions) arg;
-    if (descriptions.size() != fields.size()) {
+  @Override public boolean matchesSafely(IEObjectDescriptions item) {
+    if (item.size() != fields.size()) {
       return false;
     }
     for (MessageField field : fields) {
       String name = field.getName();
-      EObject described = descriptions.objectDescribedAs(name);
+      EObject described = item.objectDescribedAs(name);
       if (described != field) {
         return false;
       }
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFieldsInMessage.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFieldsInMessage.java
index a4fb223..670969c 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFieldsInMessage.java
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllFieldsInMessage.java
@@ -23,7 +23,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class ContainAllFieldsInMessage extends BaseMatcher<IEObjectDescriptions> {
+public class ContainAllFieldsInMessage extends TypeSafeMatcher<IEObjectDescriptions> {
   private final EObject container;
 
   public static ContainAllFieldsInMessage containAllFieldsIn(Group group) {
@@ -35,21 +35,18 @@
   }
 
   private ContainAllFieldsInMessage(EObject container) {
+    super(IEObjectDescriptions.class);
     this.container = container;
   }
 
-  @Override public boolean matches(Object arg) {
-    if (!(arg instanceof IEObjectDescriptions)) {
-      return false;
-    }
-    IEObjectDescriptions descriptions = (IEObjectDescriptions) arg;
+  @Override public boolean matchesSafely(IEObjectDescriptions item) {
     List<IndexedElement> elements = allIndexedElements();
-    if (descriptions.size() != elements.size()) {
+    if (item.size() != elements.size()) {
       return false;
     }
     for (IndexedElement e : elements) {
       String name = nameOf(e);
-      EObject described = descriptions.objectDescribedAs(name);
+      EObject described = item.objectDescribedAs(name);
       if (described != e) {
         return false;
       }
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllLiteralsInEnum.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllLiteralsInEnum.java
index 40312dd..89ded43 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllLiteralsInEnum.java
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllLiteralsInEnum.java
@@ -24,7 +24,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class ContainAllLiteralsInEnum extends BaseMatcher<IEObjectDescriptions> {
+public class ContainAllLiteralsInEnum extends TypeSafeMatcher<IEObjectDescriptions> {
   private final Enum anEnum;
 
   public static ContainAllLiteralsInEnum containAllLiteralsIn(Enum anEnum) {
@@ -32,21 +32,18 @@
   }
 
   private ContainAllLiteralsInEnum(Enum anEnum) {
+    super(IEObjectDescriptions.class);
     this.anEnum = anEnum;
   }
 
-  @Override public boolean matches(Object arg) {
-    if (!(arg instanceof IEObjectDescriptions)) {
-      return false;
-    }
-    IEObjectDescriptions descriptions = (IEObjectDescriptions) arg;
+  @Override public boolean matchesSafely(IEObjectDescriptions item) {
     List<Literal> literals = allLiterals();
-    if (descriptions.size() != literals.size()) {
+    if (item.size() != literals.size()) {
       return false;
     }
     for (Literal literal : literals) {
       String name = literal.getName();
-      EObject described = descriptions.objectDescribedAs(name);
+      EObject described = item.objectDescribedAs(name);
       if (described != literal) {
         return false;
       }
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllNames.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllNames.java
index 2fb0457..b26569f 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllNames.java
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainAllNames.java
@@ -19,7 +19,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class ContainAllNames extends BaseMatcher<IEObjectDescriptions> {
+public class ContainAllNames extends TypeSafeMatcher<IEObjectDescriptions> {
   private final String[] expectedNames;
 
   public static ContainAllNames containAll(String... names) {
@@ -27,15 +27,12 @@
   }
 
   private ContainAllNames(String... names) {
+    super(IEObjectDescriptions.class);
     expectedNames = names;
   }
 
-  @Override public boolean matches(Object arg) {
-    if (!(arg instanceof IEObjectDescriptions)) {
-      return false;
-    }
-    IEObjectDescriptions descriptions = (IEObjectDescriptions) arg;
-    List<String> names = newArrayList(descriptions.names());
+  @Override public boolean matchesSafely(IEObjectDescriptions item) {
+    List<String> names = newArrayList(item.names());
     if (names.size() != expectedNames.length) {
       return false;
     }
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainNames.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainNames.java
index 456e079..3dfc2af 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainNames.java
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/junit/matchers/ContainNames.java
@@ -10,7 +10,7 @@
 
 import static java.util.Arrays.asList;
 
-import java.util.List;
+import java.util.*;
 
 import org.hamcrest.*;
 
@@ -19,7 +19,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class ContainNames extends BaseMatcher<IEObjectDescriptions> {
+public class ContainNames extends TypeSafeMatcher<IEObjectDescriptions> {
   private final List<String> expectedNames;
 
   public static ContainNames contain(String... names) {
@@ -27,15 +27,13 @@
   }
 
   private ContainNames(String... names) {
+    super(IEObjectDescriptions.class);
     expectedNames = asList(names);
   }
 
-  @Override public boolean matches(Object arg) {
-    if (!(arg instanceof IEObjectDescriptions)) {
-      return false;
-    }
-    IEObjectDescriptions descriptions = (IEObjectDescriptions) arg;
-    return descriptions.names().containsAll(expectedNames);
+  @Override public boolean matchesSafely(IEObjectDescriptions item) {
+    Collection<String> names = item.names();
+    return names.containsAll(expectedNames);
   }
 
   @Override public void describeTo(Description description) {
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/validation/ImportValidator_checkNonProto2Imports_Test.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/validation/ImportValidator_checkNonProto2Imports_Test.java
new file mode 100644
index 0000000..5b9a99b
--- /dev/null
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/validation/ImportValidator_checkNonProto2Imports_Test.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2012 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.IntegrationTestModule.integrationTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+import static org.mockito.Mockito.*;
+
+import org.eclipse.xtext.validation.ValidationMessageAcceptor;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.junit.core.XtextRule;
+import com.google.eclipse.protobuf.protobuf.Protobuf;
+import com.google.inject.Inject;
+
+/**
+ * Tests for <code>{@link ImportValidator#checkNonProto2Imports(Protobuf)}</code>
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ImportValidator_checkNonProto2Imports_Test {
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule());
+
+  @Inject private ImportValidator validator;
+  private ValidationMessageAcceptor messageAcceptor;
+
+  @Before public void setUp() {
+    messageAcceptor = mock(ValidationMessageAcceptor.class);
+    validator.setMessageAcceptor(messageAcceptor);
+  }
+
+  // // Create file C.proto
+  //
+  // syntax = 'proto2';
+
+  // // Create file B.proto
+  //
+  // syntax = 'proto2';
+  //
+  // import "C.proto";
+
+  // syntax = "proto2";
+  //
+  // import "B.proto";
+  // import "C.proto";
+  @Test public void should_not_add_warnings_if_imported_files_are_proto2() {
+    validator.checkNonProto2Imports(xtext.root());
+    verifyNoMoreInteractions(messageAcceptor);
+  }
+
+  // // Create file C.proto
+  //
+  // syntax = 'proto2';
+  //
+  // import "B.proto";
+
+  // // Create file B.proto
+  //
+  // syntax = 'proto2';
+  //
+  // import "C.proto";
+
+  // syntax = "proto2";
+  //
+  // import "B.proto";
+  // import "C.proto";
+  @Test public void should_not_add_warnings_if_imported_files_are_proto2_even_with_circular_dependencies() {
+    validator.checkNonProto2Imports(xtext.root());
+    verifyNoMoreInteractions(messageAcceptor);
+  }
+}
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/validation/ImportValidator_checkNonProto2Imports_withNonProto2Imports_Tests.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/validation/ImportValidator_checkNonProto2Imports_withNonProto2Imports_Tests.java
new file mode 100644
index 0000000..d8f8aa9
--- /dev/null
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/validation/ImportValidator_checkNonProto2Imports_withNonProto2Imports_Tests.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2012 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.IntegrationTestModule.integrationTestModule;
+import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith;
+import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.IMPORT__IMPORT_URI;
+import static com.google.eclipse.protobuf.validation.Messages.importingNonProto2;
+import static org.eclipse.xtext.validation.ValidationMessageAcceptor.INSIGNIFICANT_INDEX;
+import static org.mockito.Mockito.*;
+
+import java.util.List;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.xtext.validation.ValidationMessageAcceptor;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.junit.core.*;
+import com.google.eclipse.protobuf.model.util.Protobufs;
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.inject.*;
+
+/**
+ * Tests for <code>{@link ImportValidator#checkNonProto2Imports(Protobuf)}</code>
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class ImportValidator_checkNonProto2Imports_withNonProto2Imports_Tests {
+  @Rule public XtextRule xtext = overrideRuntimeModuleWith(integrationTestModule(), new TestModule());
+
+  @Inject private ProtobufsStub protobufs;
+  @Inject private ImportValidator validator;
+
+  private ValidationMessageAcceptor messageAcceptor;
+
+  @Before public void setUp() {
+    messageAcceptor = mock(ValidationMessageAcceptor.class);
+    validator.setMessageAcceptor(messageAcceptor);
+  }
+
+  // // Create file C.proto
+  //
+  // syntax = 'proto2';
+
+  // // Create file B.proto
+  //
+  // syntax = 'proto2';
+
+  // syntax = "proto2";
+  //
+  // import "B.proto";
+  // import "C.proto";
+  @Test public void should_add_warning_if_import_if_import_refers_directly_to_non_proto2() {
+    protobufs.nonProto2FileName = "B.proto";
+    validator.checkNonProto2Imports(xtext.root());
+    Import importWithNonProto2File = findImportReferreringToFile(protobufs.nonProto2FileName);
+    verifyThatImportingNonProto2FileCreatedWarning(importWithNonProto2File);
+  }
+
+  // // Create file C.proto
+  //
+  // syntax = 'proto2';
+
+  // // Create file B.proto
+  //
+  // syntax = 'proto2';
+  //
+  // import "C.proto";
+
+  // syntax = "proto2";
+  //
+  // import "B.proto";
+  @Test public void should_add_warning_if_import_if_import_refers_indirectly_to_non_proto2() {
+    protobufs.nonProto2FileName = "C.proto";
+    validator.checkNonProto2Imports(xtext.root());
+    Import importWithNonProto2File = findImportReferreringToFile("B.proto");
+    verifyThatImportingNonProto2FileCreatedWarning(importWithNonProto2File);
+  }
+
+  private Import findImportReferreringToFile(String fileName) {
+    List<Import> imports = protobufs.importsIn(xtext.root());
+    for (Import anImport : imports) {
+      if (anImport.getImportURI().endsWith(fileName)) {
+        return anImport;
+      }
+    }
+    return null;
+  }
+
+  private void verifyThatImportingNonProto2FileCreatedWarning(Import anImport) {
+    verify(messageAcceptor).acceptWarning(importingNonProto2, anImport, IMPORT__IMPORT_URI, INSIGNIFICANT_INDEX, null,
+        new String[0]);
+  }
+
+  private static class TestModule extends AbstractTestModule {
+    @Override protected void configure() {
+      binder().bind(Protobufs.class).to(ProtobufsStub.class);
+    }
+  }
+
+  @Singleton private static class ProtobufsStub extends Protobufs {
+    String nonProto2FileName;
+
+    @Override public boolean isProto2(Protobuf protobuf) {
+      URI resourceUri = protobuf.eResource().getURI();
+      if (resourceUri.toString().endsWith(nonProto2FileName)) {
+        return false;
+      }
+      return super.isProto2(protobuf);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf.test/.gitignore b/com.google.eclipse.protobuf.test/.gitignore
new file mode 100644
index 0000000..42859ab
--- /dev/null
+++ b/com.google.eclipse.protobuf.test/.gitignore
@@ -0,0 +1 @@
+/test-protos
diff --git a/com.google.eclipse.protobuf.test/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf.test/META-INF/MANIFEST.MF
index dbf6497..4d98f42 100644
--- a/com.google.eclipse.protobuf.test/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf.test/META-INF/MANIFEST.MF
@@ -9,7 +9,9 @@
  org.eclipse.xtext.junit;bundle-version="2.0.0",
  org.eclipse.xtext.junit4;bundle-version="2.0.0",
  org.mockito;bundle-version="1.8.5",
- org.eclipse.core.resources;bundle-version="3.7.100"
+ org.eclipse.core.resources;bundle-version="3.7.100",
+ org.hamcrest;bundle-version="1.1.0",
+ org.hamcrest.library;bundle-version="1.1.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
 Export-Package: com.google.eclipse.protobuf.junit.core,
  com.google.eclipse.protobuf.junit.matchers,
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java
index ba26024..21ef88d 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java
@@ -23,7 +23,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class EnumHasLiterals extends BaseMatcher<Enum> {
+public class EnumHasLiterals extends TypeSafeMatcher<Enum> {
   private final String[] literalNames;
 
   public static EnumHasLiterals hasLiterals(String... literalNames) {
@@ -31,15 +31,12 @@
   }
 
   private EnumHasLiterals(String... literalNames) {
+    super(Enum.class);
     this.literalNames = literalNames;
   }
 
-  @Override public boolean matches(Object item) {
-    if (!(item instanceof Enum)) {
-      return false;
-    }
-    Enum anEnum = (Enum) item;
-    List<String> actualNames = newArrayList(literalNames(anEnum));
+  @Override public boolean matchesSafely(Enum item) {
+    List<String> actualNames = newArrayList(literalNames(item));
     for (String name : literalNames) {
       actualNames.remove(name);
     }
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/FieldHasType.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/FieldHasType.java
index c02ed9f..9996ded 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/FieldHasType.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/FieldHasType.java
@@ -15,7 +15,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class FieldHasType extends BaseMatcher<MessageField> {
+public class FieldHasType extends TypeSafeMatcher<MessageField> {
   private final String typeName;
 
   public static FieldHasType isBool() {
@@ -31,15 +31,12 @@
   }
 
   private FieldHasType(String typeName) {
+    super(MessageField.class);
     this.typeName = typeName;
   }
 
-  @Override public boolean matches(Object arg) {
-    if (!(arg instanceof MessageField)) {
-      return false;
-    }
-    MessageField field = (MessageField) arg;
-    return typeName.equals(typeNameOf(field));
+  @Override public boolean matchesSafely(MessageField item) {
+    return typeName.equals(typeNameOf(item));
   }
 
   private String typeNameOf(MessageField field) {
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/stubs/resources/ResourceStub.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/stubs/resources/ResourceStub.java
deleted file mode 100644
index f69bd43..0000000
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/stubs/resources/ResourceStub.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.junit.stubs.resources;
-
-import static org.eclipse.emf.common.util.URI.createURI;
-
-import java.io.*;
-import java.util.Map;
-
-import org.eclipse.emf.common.notify.*;
-import org.eclipse.emf.common.util.*;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.resource.*;
-
-/**
- * @author alruiz@google.com (Alex Ruiz)
- */
-public class ResourceStub implements Resource {
-  private URI uri;
-
-  public ResourceStub() {}
-
-  public ResourceStub(String uri) {
-    setURI(createURI(uri));
-  }
-
-  /** {@inheritDoc} */
-  @Override public EList<Adapter> eAdapters() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public boolean eDeliver() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void eSetDeliver(boolean deliver) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void eNotify(Notification notification) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public ResourceSet getResourceSet() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public URI getURI() {
-    return uri;
-  }
-
-  /** {@inheritDoc} */
-  @Override public void setURI(URI uri) {
-    this.uri = uri;
-  }
-
-  /** {@inheritDoc} */
-  @Override public long getTimeStamp() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void setTimeStamp(long timeStamp) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public EList<EObject> getContents() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public TreeIterator<EObject> getAllContents() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public String getURIFragment(EObject eObject) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public EObject getEObject(String uriFragment) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void save(Map<?, ?> options) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void load(Map<?, ?> options) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void save(OutputStream outputStream, Map<?, ?> options) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void load(InputStream inputStream, Map<?, ?> options) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public boolean isTrackingModification() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void setTrackingModification(boolean isTrackingModification) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public boolean isModified() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void setModified(boolean isModified) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public boolean isLoaded() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void unload() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public void delete(Map<?, ?> options) {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public EList<Diagnostic> getErrors() {
-    throw new UnsupportedOperationException();
-  }
-
-  /** {@inheritDoc} */
-  @Override public EList<Diagnostic> getWarnings() {
-    throw new UnsupportedOperationException();
-  }
-
-}
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/linking/ProtobufDiagnosticMatcher.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/linking/ProtobufDiagnosticMatcher.java
index 162a5e4..9b35c37 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/linking/ProtobufDiagnosticMatcher.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/linking/ProtobufDiagnosticMatcher.java
@@ -18,7 +18,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-class ProtobufDiagnosticMatcher extends BaseMatcher<ProtobufDiagnostic> {
+class ProtobufDiagnosticMatcher extends TypeSafeMatcher<ProtobufDiagnostic> {
   private final DiagnosticMessage message;
 
   static ProtobufDiagnosticMatcher wasCreatedFrom(DiagnosticMessage message) {
@@ -26,16 +26,14 @@
   }
 
   private ProtobufDiagnosticMatcher(DiagnosticMessage message) {
+    super(ProtobufDiagnostic.class);
     this.message = message;
   }
 
-  @Override public boolean matches(Object item) {
-    if (!(item instanceof ProtobufDiagnostic)) {
-      return false;
-    }
-    ProtobufDiagnostic d = (ProtobufDiagnostic) item;
-    return equal(message.getIssueCode(), d.getCode()) && Arrays.equals(message.getIssueData(), d.getData())
-        && equal(message.getMessage(), d.getMessage());
+  @Override public boolean matchesSafely(ProtobufDiagnostic item) {
+    return equal(message.getIssueCode(), item.getCode()) &&
+        Arrays.equals(message.getIssueData(), item.getData()) &&
+        equal(message.getMessage(), item.getMessage());
   }
 
   @Override public void describeTo(Description description) {
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkOnlyOnePackageDefinition_Test.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkOnlyOnePackageDefinition_Test.java
index 3d3a260..0ea1264 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkOnlyOnePackageDefinition_Test.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkOnlyOnePackageDefinition_Test.java
@@ -11,6 +11,7 @@
 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.PACKAGE__NAME;
+import static com.google.eclipse.protobuf.validation.Messages.multiplePackages;
 import static com.google.eclipse.protobuf.validation.ProtobufJavaValidator.MORE_THAN_ONE_PACKAGE_ERROR;
 import static org.eclipse.xtext.validation.ValidationMessageAcceptor.INSIGNIFICANT_INDEX;
 import static org.mockito.Mockito.*;
@@ -45,8 +46,7 @@
   @Test public void should_create_error_if_there_are_more_than_one_package_definitions() {
     Package p = xtext.find("com.google.eclipse", Package.class);
     validator.checkOnlyOnePackageDefinition(p);
-    String message = "Multiple package definitions.";
-    verify(messageAcceptor).acceptError(message, p, PACKAGE__NAME, INSIGNIFICANT_INDEX, MORE_THAN_ONE_PACKAGE_ERROR);
+    verify(messageAcceptor).acceptError(multiplePackages, p, PACKAGE__NAME, INSIGNIFICANT_INDEX, MORE_THAN_ONE_PACKAGE_ERROR);
   }
 
   // syntax = "proto2";
diff --git a/com.google.eclipse.protobuf.ui.test/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf.ui.test/META-INF/MANIFEST.MF
index a90dbbd..5af6725 100644
--- a/com.google.eclipse.protobuf.ui.test/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf.ui.test/META-INF/MANIFEST.MF
@@ -10,4 +10,6 @@
  org.eclipse.xtext.junit;bundle-version="2.0.0",
  org.eclipse.xtext.junit4;bundle-version="2.0.0",
  org.eclipse.xtext.ui.junit;bundle-version="2.0.0",
- org.mockito;bundle-version="1.8.5"
+ org.mockito;bundle-version="1.8.5",
+ org.hamcrest;bundle-version="1.1.0",
+ org.hamcrest.library;bundle-version="1.1.0"
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/contentassist/IEObjectDescriptionsHaveNames.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/contentassist/IEObjectDescriptionsHaveNames.java
index 392b247..7d2b688 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/contentassist/IEObjectDescriptionsHaveNames.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/contentassist/IEObjectDescriptionsHaveNames.java
@@ -19,7 +19,7 @@
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class IEObjectDescriptionsHaveNames extends BaseMatcher<Collection<IEObjectDescription>> {
+public class IEObjectDescriptionsHaveNames extends TypeSafeMatcher<Collection<IEObjectDescription>> {
   private final List<String> qualifiedNames;
 
   public static IEObjectDescriptionsHaveNames containOnly(String...names) {
@@ -30,18 +30,12 @@
     this.qualifiedNames = newArrayList(qualifiedNames);
   }
 
-  /** {@inheritDoc} */
-  @Override @SuppressWarnings("unchecked")
-  public boolean matches(Object arg) {
-    if (!(arg instanceof Collection)) {
-      return false;
-    }
-    Collection<IEObjectDescription> descriptions = (Collection<IEObjectDescription>) arg;
+  @Override public boolean matchesSafely(Collection<IEObjectDescription> item) {
     List<String> copyOfNames = newArrayList(qualifiedNames);
-    if (copyOfNames.size() != descriptions.size()) {
+    if (copyOfNames.size() != item.size()) {
       return false;
     }
-    for (IEObjectDescription description : descriptions) {
+    for (IEObjectDescription description : item) {
       QualifiedName qualifiedName = description.getName();
       if (qualifiedName == null) {
         continue;
diff --git a/com.google.eclipse.protobuf/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
index caafc24..d306d0f 100644
--- a/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
@@ -33,6 +33,7 @@
  com.google.eclipse.protobuf.protobuf.impl,

  com.google.eclipse.protobuf.protobuf.util,

  com.google.eclipse.protobuf.resource,

+ com.google.eclipse.protobuf.resource.filter,

  com.google.eclipse.protobuf.scoping,

  com.google.eclipse.protobuf.serializer,

  com.google.eclipse.protobuf.services,

diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/Imports.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/Imports.java
index 43af49e..408118c 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/Imports.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/Imports.java
@@ -13,10 +13,12 @@
 import static org.eclipse.xtext.util.Strings.*;
 
 import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.*;
 import org.eclipse.xtext.nodemodel.INode;
 import org.eclipse.xtext.scoping.impl.ImportUriResolver;
 
 import com.google.eclipse.protobuf.protobuf.Import;
+import com.google.eclipse.protobuf.resource.ResourceSets;
 import com.google.eclipse.protobuf.scoping.ProtoDescriptorProvider;
 import com.google.inject.Inject;
 
@@ -28,6 +30,7 @@
 public class Imports {
   @Inject private ProtoDescriptorProvider descriptorProvider;
   @Inject private INodes nodes;
+  @Inject private ResourceSets resourceSets;
   @Inject private ImportUriResolver uriResolver;
 
   /**
@@ -98,6 +101,21 @@
   }
 
   /**
+   * Returns the resource referred by the URI of the given {@code Import}.
+   * @param anImport the given {@code Import}.
+   * @return the resource referred by the URI of the given {@code Import}, or {@code null} if the URI has not been
+   * resolved.
+   */
+  public Resource importedResource(Import anImport) {
+    URI resolvedUri = resolvedUriOf(anImport);
+    if (resolvedUri != null) {
+      ResourceSet resourceSet = anImport.eResource().getResourceSet();
+      return resourceSets.findResource(resourceSet, resolvedUri);
+    }
+    return null;
+  }
+
+  /**
    * Returns the resolved URI of the given {@code Import}.
    * @param anImport the the given {@code Import}.
    * @return the resolved URI of the given {@code Import}, or {@code null} if the URI was not successfully resolved.
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java
index ae13715..61b72a2 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/ResourceDescriptions.java
@@ -8,19 +8,18 @@
  */
 package com.google.eclipse.protobuf.resource;
 
-import static com.google.common.base.Objects.equal;
+import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.util.Collections.unmodifiableList;
 
 import java.util.List;
-import java.util.regex.*;
 
 import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.*;
-import org.eclipse.xtext.naming.*;
+import org.eclipse.xtext.naming.QualifiedName;
 import org.eclipse.xtext.resource.*;
 
-import com.google.inject.*;
+import com.google.common.base.Predicate;
+import com.google.inject.Singleton;
 
 /**
  * Utility methods related to <code>{@link IResourceDescription}</code>s.
@@ -28,8 +27,6 @@
  * @author alruiz@google.com (Alex Ruiz)
  */
 @Singleton public class ResourceDescriptions {
-  @Inject private IQualifiedNameConverter converter;
-
   /**
    * Finds the URI of a model object, in the given resource, whose qualified name matches the given one.
    * @param resource the given resource.
@@ -48,30 +45,13 @@
   }
 
   /**
-   * Finds the URIs of the model objects, in the given resource, that:
-   * <ol>
-   * <li>have a qualified name that match the given pattern, and</li>
-   * <li>have an {@code EClass} whose name is equal to the simple name of the given type.
-   * </ol>
-   * @param resource the given resource.
-   * @param pattern the pattern to match.
-   * @param type the type of model object we are looking for.
-   * @return the URI of the matching object models, or an empty list if matches could not be found.
+   * Returns the model objects that match the criteria specified in the given filter.
+   * @param resource the resource containing model objects.
+   * @param filter the filter to use.
+   * @return  the model objects that match the criteria specified in the given filter.
    */
-  public List<IEObjectDescription> matchingQualifiedNames(IResourceDescription resource, Pattern pattern,
-      Class<? extends EObject> type) {
-    List<IEObjectDescription> descriptions = newArrayList();
-    for (IEObjectDescription exported : resource.getExportedObjects()) {
-      QualifiedName qualifiedName = exported.getQualifiedName();
-      Matcher matcher = pattern.matcher(converter.toString(qualifiedName));
-      if (haveMatchingNames(exported.getEClass(), type) && matcher.matches()) {
-        descriptions.add(exported);
-      }
-    }
-    return unmodifiableList(descriptions);
-  }
-
-  private boolean haveMatchingNames(EClass eClass, Class<? extends EObject> type) {
-    return equal(eClass.getName(), type.getSimpleName());
+  public List<IEObjectDescription> filterModelObjects(IResourceDescription resource, Predicate<IEObjectDescription> filter) {
+    List<IEObjectDescription> filtered = newArrayList(filter(resource.getExportedObjects(), filter));
+    return unmodifiableList(filtered);
   }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/filter/MatchingQualifiedNameAndTypeFilter.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/filter/MatchingQualifiedNameAndTypeFilter.java
new file mode 100644
index 0000000..3bdac63
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/resource/filter/MatchingQualifiedNameAndTypeFilter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2012 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.resource.filter;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.xtext.naming.QualifiedName;
+import org.eclipse.xtext.resource.IEObjectDescription;
+
+import com.google.common.base.Predicate;
+
+/**
+ * Indicates whether the qualified name and {@code EClass} of a <code>{@link IEObjectDescription}</code> match the given
+ * pattern and type, respectively.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class MatchingQualifiedNameAndTypeFilter implements Predicate<IEObjectDescription> {
+  private final Pattern pattern;
+  private final EClass type;
+
+  /**
+   * Creates a new <code>{@link MatchingQualifiedNameAndTypeFilter}</code>.
+   * @param pattern the pattern that qualified names should match.
+   * @param type the type of model object to match.
+   * @return the created filter.
+   */
+  public static MatchingQualifiedNameAndTypeFilter matchingQualifiedNameAndType(Pattern pattern, EClass type) {
+    return new MatchingQualifiedNameAndTypeFilter(pattern, type);
+  }
+
+  private MatchingQualifiedNameAndTypeFilter(Pattern pattern, EClass type) {
+    this.pattern = pattern;
+    this.type = type;
+  }
+
+  @Override public boolean apply(IEObjectDescription input) {
+    if (!type.equals(input.getEClass())) {
+      return false;
+    }
+    QualifiedName qualifiedName = input.getQualifiedName();
+    return pattern.matcher(qualifiedName.toString()).matches();
+  }
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ImportValidator.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ImportValidator.java
index 9f978ac..d852659 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ImportValidator.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ImportValidator.java
@@ -8,7 +8,7 @@
  */
 package com.google.eclipse.protobuf.validation;
 
-import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
 import static com.google.common.collect.Sets.newHashSet;
 import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.IMPORT__IMPORT_URI;
 import static com.google.eclipse.protobuf.validation.Messages.*;
@@ -17,15 +17,13 @@
 
 import java.util.*;
 
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.resource.*;
+import org.eclipse.emf.ecore.resource.Resource;
 import org.eclipse.xtext.scoping.impl.ImportUriResolver;
 import org.eclipse.xtext.util.Pair;
 import org.eclipse.xtext.validation.*;
 
 import com.google.eclipse.protobuf.model.util.*;
 import com.google.eclipse.protobuf.protobuf.*;
-import com.google.eclipse.protobuf.resource.ResourceSets;
 import com.google.inject.Inject;
 
 /**
@@ -37,85 +35,75 @@
   @Inject private Imports imports;
   @Inject private Protobufs protobufs;
   @Inject private Resources resources;
-  @Inject private ResourceSets resourceSets;
   @Inject private ImportUriResolver uriResolver;
 
   @Override public void register(EValidatorRegistrar registrar) {}
 
   /**
    * Verifies that {@code Import}s in the given root only refer to "proto2" files. If non-proto2 {@code Import}s are
-   * found, this validator will create warning markers for such "imports".
+   * found, this validator will create warning markers for such {@code Import}s.
    * @param root the root containing the imports to check.
    */
   @Check public void checkNonProto2Imports(Protobuf root) {
-    warnIfNonProto2ImportsFound(root.eResource());
-  }
-
-  private void warnIfNonProto2ImportsFound(Resource resource) {
-    Protobuf root = resources.rootOf(resource);
     if (!protobufs.isProto2(root)) {
       return;
     }
-    ResourceSet resourceSet = resource.getResourceSet();
-    boolean hasNonProto2 = false;
-    List<Pair<Import, Resource>> resourcesToCheck = newArrayList();
-    Set<URI> checked = newHashSet();
-    checked.add(resource.getURI());
+    Set<Protobuf> currentlyChecking = newHashSet(root);
+    HashMap<Protobuf, IsProto2> alreadyChecked = newHashMap();
+    hasNonProto2Imports(root, currentlyChecking, alreadyChecked);
+  }
+
+  private boolean hasNonProto2Imports(Protobuf root, Set<Protobuf> currentlyChecking,
+      Map<Protobuf, IsProto2> alreadyChecked) {
+    IsProto2 isProto2 = alreadyChecked.get(root);
+    if (isProto2 != null) {
+      return isProto2 == IsProto2.NO;
+    }
+    currentlyChecking.add(root);
+    Set<Pair<Import, Protobuf>> importsToCheck = newHashSet();
+    boolean hasNonProto2Imports = false;
     for (Import anImport : protobufs.importsIn(root)) {
-      Resource imported = importedResource(resourceSet, anImport);
-      checked.add(imported.getURI());
-      if (!protobufs.isProto2(resources.rootOf(imported))) {
-        hasNonProto2 = true;
+      Resource imported = imports.importedResource(anImport);
+      if (imported == null) {
+        continue;
+      }
+      Protobuf importedRoot = resources.rootOf(imported);
+      isProto2 = alreadyChecked.get(importedRoot);
+      if (isProto2 != null) {
+        // resource was already checked.
+        if (isProto2 == IsProto2.NO) {
+          hasNonProto2Imports = true;
+          warnNonProto2ImportFoundIn(anImport);
+        }
+        continue;
+      }
+      if (!protobufs.isProto2(importedRoot)) {
+        alreadyChecked.put(importedRoot, IsProto2.NO);
+        hasNonProto2Imports = true;
         warnNonProto2ImportFoundIn(anImport);
         continue;
       }
-      resourcesToCheck.add(pair(anImport, imported));
-    }
-    if (hasNonProto2) {
-      return;
-    }
-    for (Pair<Import, Resource> p : resourcesToCheck) {
-      if (hasNonProto2(p, checked, resourceSet)) {
-        warnNonProto2ImportFoundIn(p.getFirst());
-        break;
-      }
-    }
-  }
-
-  private boolean hasNonProto2(Pair<Import, Resource> toCheck, Set<URI> alreadyChecked, ResourceSet resourceSet) {
-    Protobuf root = resources.rootOf(toCheck.getSecond());
-    if (!protobufs.isProto2(root)) {
-      return false;
-    }
-    List<Pair<Import, Resource>> resourcesToCheck = newArrayList();
-    for (Import anImport : protobufs.importsIn(root)) {
-      Resource imported = importedResource(resourceSet, anImport);
-      if (alreadyChecked.contains(imported.getURI())) {
+      // we have a circular dependency
+      if (currentlyChecking.contains(importedRoot)) {
         continue;
       }
-      if (!protobufs.isProto2(resources.rootOf(imported))) {
-        return true;
-      }
-      resourcesToCheck.add(pair(toCheck.getFirst(), imported));
+      // this is a proto2 file. Need to check its imports.
+      importsToCheck.add(pair(anImport, importedRoot));
     }
-    for (Pair<Import, Resource> p : resourcesToCheck) {
-      if (hasNonProto2(p, alreadyChecked, resourceSet)) {
-        return true;
+    for (Pair<Import, Protobuf> importToCheck : importsToCheck) {
+      if (hasNonProto2Imports(importToCheck.getSecond(), currentlyChecking, alreadyChecked)) {
+        hasNonProto2Imports = true;
+        warnNonProto2ImportFoundIn(importToCheck.getFirst());
       }
     }
-    return false;
-  }
-
-  private Resource importedResource(ResourceSet resourceSet, Import anImport) {
-    URI resolvedUri = imports.resolvedUriOf(anImport);
-    if (resolvedUri != null) {
-      return resourceSets.findResource(resourceSet, resolvedUri);
-    }
-    return null;
+    isProto2 = hasNonProto2Imports ? IsProto2.NO : IsProto2.YES;
+    alreadyChecked.put(root, isProto2);
+    currentlyChecking.remove(root);
+    return hasNonProto2Imports;
   }
 
   private void warnNonProto2ImportFoundIn(Import anImport) {
-    acceptWarning(importingNonProto2, anImport, IMPORT__IMPORT_URI, INSIGNIFICANT_INDEX, null);
+    warning(importingNonProto2, anImport, IMPORT__IMPORT_URI, INSIGNIFICANT_INDEX);
   }
 
   /**
@@ -132,4 +120,8 @@
       error(format(importNotFound, anImport.getImportURI()), IMPORT__IMPORT_URI);
     }
   }
+
+  private static enum IsProto2 {
+    YES, NO;
+  }
 }