Added a cache to ModelElementFinder.

- Removes the UI freeze of one second or greater when opening or editing
a proto file.
- Reduces time to lookup scopes of imported proto files.

Change-Id: Id0b665a5f9ed776deecc54c322d6597c4c5e42b9
diff --git a/com.google.eclipse.protobuf/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
index f55ef48..e76431d 100644
--- a/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf/META-INF/MANIFEST.MF
@@ -9,15 +9,16 @@
 Require-Bundle: org.antlr.runtime,

  org.apache.commons.logging,

  org.apache.log4j,

- org.eclipse.emf.mwe2.launch;resolution:=optional,

  org.eclipse.core.resources,

  org.eclipse.core.runtime,

+ org.eclipse.emf.common,

+ org.eclipse.emf.ecore,

+ org.eclipse.emf.mwe2.launch;resolution:=optional,

+ org.eclipse.jdt.annotation;resolution:=optional,

  org.eclipse.xtext,

  org.eclipse.xtext.generator;resolution:=optional,

- org.eclipse.emf.common,

  org.eclipse.xtext.ui,

- org.eclipse.xtext.util,

- org.eclipse.emf.ecore

+ org.eclipse.xtext.util

 Bundle-RequiredExecutionEnvironment: JavaSE-1.8

 Export-Package: com.google.eclipse.protobuf,

  com.google.eclipse.protobuf.conversion,

diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ModelElementFinder.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ModelElementFinder.java
index bf24be1..4520d67 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ModelElementFinder.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ModelElementFinder.java
@@ -11,22 +11,8 @@
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 import static java.util.Collections.unmodifiableSet;
-
 import static org.eclipse.emf.ecore.util.EcoreUtil.getAllContents;
 
-import static com.google.common.collect.Sets.newHashSet;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.emf.common.util.TreeIterator;
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.resource.ResourceSet;
-import org.eclipse.xtext.resource.IEObjectDescription;
-
 import com.google.eclipse.protobuf.model.util.Imports;
 import com.google.eclipse.protobuf.model.util.ModelObjects;
 import com.google.eclipse.protobuf.model.util.Packages;
@@ -40,6 +26,23 @@
 import com.google.eclipse.protobuf.resource.ResourceSets;
 import com.google.inject.Inject;
 
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.xtext.resource.IEObjectDescription;
+import org.eclipse.xtext.util.OnChangeEvictingCache;
+import org.eclipse.xtext.util.OnChangeEvictingCache.CacheAdapter;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * @author alruiz@google.com (Alex Ruiz)
  */
@@ -51,16 +54,16 @@
   @Inject private Resources resources;
   @Inject private ResourceSets resourceSets;
 
-  // Start at an element
   <T> Collection<IEObjectDescription> find(EObject start, FinderStrategy<T> strategy, T criteria) {
-    Set<IEObjectDescription> descriptions = newHashSet();
+    Set<IEObjectDescription> descriptions = new HashSet<>();
     descriptions.addAll(getDescriptionsFromObjectAncestors(start, strategy, criteria));
     Protobuf root = modelObjects.rootOf(start);
     descriptions.addAll(getDescriptionsFromAllImports(root, strategy, criteria));
     return unmodifiableSet(descriptions);
   }
 
-  private <T> Collection<IEObjectDescription> getDescriptionsFromObjectAncestors(EObject start, FinderStrategy<T> strategy, T criteria) {
+  private <T> Collection<IEObjectDescription> getDescriptionsFromObjectAncestors(
+      EObject start, FinderStrategy<T> strategy, T criteria) {
     UniqueDescriptions descriptions = new UniqueDescriptions();
     EObject current = start.eContainer();
     while (current != null) {
@@ -71,35 +74,43 @@
   }
 
   <T> Collection<IEObjectDescription> find(Protobuf start, FinderStrategy<T> strategy, T criteria) {
-    Set<IEObjectDescription> descriptions = newHashSet();
+    Set<IEObjectDescription> descriptions = new HashSet<>();
     descriptions.addAll(getDescriptionsFromObjectDescendants(start, strategy, criteria, 0));
     descriptions.addAll(getDescriptionsFromAllImports(start, strategy, criteria));
     return unmodifiableSet(descriptions);
   }
 
-  private <T> Collection<IEObjectDescription> getDescriptionsFromObjectDescendants(EObject start, FinderStrategy<T> strategy, T criteria, int level) {
+  private <T> Collection<IEObjectDescription> getDescriptionsFromObjectDescendants(
+      EObject start, FinderStrategy<T> strategy, T criteria, int level) {
     UniqueDescriptions descriptions = new UniqueDescriptions();
     for (EObject element : start.eContents()) {
       descriptions.addAll(strategy.local(element, criteria, level));
       if (element instanceof Message || element instanceof Group) {
-        descriptions.addAll(getDescriptionsFromObjectDescendants(element, strategy, criteria, level + 1));
+        descriptions.addAll(
+            getDescriptionsFromObjectDescendants(element, strategy, criteria, level + 1));
       }
     }
     return descriptions.values();
   }
 
-  private <T> Collection<IEObjectDescription> getDescriptionsFromAllImports(Protobuf start, FinderStrategy<T> strategy, T criteria) {
+  private <T> Collection<IEObjectDescription> getDescriptionsFromAllImports(
+      Protobuf start, FinderStrategy<T> strategy, T criteria) {
     List<Import> allImports = protobufs.importsIn(start);
     if (allImports.isEmpty()) {
       return emptyList();
     }
     ResourceSet resourceSet = start.eResource().getResourceSet();
-    return getDescriptionsFromImports(allImports, modelObjects.packageOf(start), resourceSet, strategy, criteria);
+    return getDescriptionsFromImports(
+        allImports, modelObjects.packageOf(start), resourceSet, strategy, criteria);
   }
 
-  private <T> Collection<IEObjectDescription> getDescriptionsFromImports(List<Import> allImports, Package fromImporter,
-      ResourceSet resourceSet, FinderStrategy<T> strategy, T criteria) {
-    Set<IEObjectDescription> descriptions = newHashSet();
+  private <T> Collection<IEObjectDescription> getDescriptionsFromImports(
+      List<Import> allImports,
+      Package fromImporter,
+      ResourceSet resourceSet,
+      FinderStrategy<T> strategy,
+      T criteria) {
+    Set<IEObjectDescription> descriptions = new HashSet<>();
     for (Import anImport : allImports) {
       if (imports.isImportingDescriptor(anImport)) {
         descriptions.addAll(strategy.inDescriptor(anImport, criteria));
@@ -118,23 +129,35 @@
         continue;
       }
       if (rootOfImported != null) {
-        descriptions.addAll(getDescriptionsFromPublicImports(rootOfImported, strategy, criteria));
-        if (arePackagesRelated(fromImporter, rootOfImported)) {
-          descriptions.addAll(getDescriptionsFromObjectDescendants(rootOfImported, strategy, criteria, 0));
-          continue;
+        CacheAdapter cache = new OnChangeEvictingCache().getOrCreate(imported);
+        Set<IEObjectDescription> descriptionsFromImport =
+            getFromCache(cache, resolvedUri, strategy, criteria);
+        if (descriptionsFromImport == null) {
+          descriptionsFromImport = new HashSet<>();
+          descriptionsFromImport.addAll(
+              getDescriptionsFromPublicImports(rootOfImported, strategy, criteria));
+          if (arePackagesRelated(fromImporter, rootOfImported)) {
+            descriptionsFromImport.addAll(
+                getDescriptionsFromObjectDescendants(rootOfImported, strategy, criteria, 0));
+          } else {
+            Package packageOfImported = modelObjects.packageOf(rootOfImported);
+            TreeIterator<Object> contents = getAllContents(imported, true);
+            while (contents.hasNext()) {
+              Object next = contents.next();
+              descriptionsFromImport.addAll(
+                  strategy.imported(fromImporter, packageOfImported, next, criteria));
+            }
+          }
+          putToCache(cache, resolvedUri, strategy, criteria, descriptionsFromImport);
         }
-        Package packageOfImported = modelObjects.packageOf(rootOfImported);
-        TreeIterator<Object> contents = getAllContents(imported, true);
-        while (contents.hasNext()) {
-          Object next = contents.next();
-          descriptions.addAll(strategy.imported(fromImporter, packageOfImported, next, criteria));
-        }
+        descriptions.addAll(descriptionsFromImport);
       }
     }
     return descriptions;
   }
 
-  private <T> Collection<IEObjectDescription> getDescriptionsFromPublicImports(Protobuf start, FinderStrategy<T> strategy, T criteria) {
+  private <T> Collection<IEObjectDescription> getDescriptionsFromPublicImports(
+      Protobuf start, FinderStrategy<T> strategy, T criteria) {
     if (!protobufs.hasKnownSyntax(start)) {
       return emptySet();
     }
@@ -143,7 +166,36 @@
       return emptyList();
     }
     ResourceSet resourceSet = start.eResource().getResourceSet();
-    return getDescriptionsFromImports(allImports, modelObjects.packageOf(start), resourceSet, strategy, criteria);
+    return getDescriptionsFromImports(
+        allImports, modelObjects.packageOf(start), resourceSet, strategy, criteria);
+  }
+
+  private <T> @Nullable Set<IEObjectDescription> getFromCache(
+      CacheAdapter cache, URI uri, FinderStrategy<T> strategy, T criteria) {
+    Map<FinderStrategy<T>, Map<T, Set<IEObjectDescription>>> strategyMap = cache.get(uri);
+    if (strategyMap != null) {
+      Map<T, Set<IEObjectDescription>> criteriaMap = strategyMap.get(strategy);
+      if (criteriaMap != null) {
+        return criteriaMap.get(criteria);
+      }
+    }
+    return null;
+  }
+
+  private <T> void putToCache(
+      CacheAdapter cache, URI uri, FinderStrategy<T> strategy, T criteria,
+      Set<IEObjectDescription> descriptions) {
+    Map<FinderStrategy<T>, Map<T, Set<IEObjectDescription>>> strategyMap = cache.get(uri);
+    if (strategyMap == null) {
+      strategyMap = new HashMap<>();
+      cache.set(uri, strategyMap);
+    }
+    Map<T, Set<IEObjectDescription>> criteriaMap = strategyMap.get(strategy);
+    if (criteriaMap == null) {
+      criteriaMap = new HashMap<>();
+      strategyMap.put(strategy, criteriaMap);
+    }
+    criteriaMap.put(criteria, descriptions);
   }
 
   private boolean arePackagesRelated(Package aPackage, EObject root) {
@@ -152,7 +204,8 @@
   }
 
   static interface FinderStrategy<T> {
-    Collection<IEObjectDescription> imported(Package fromImporter, Package fromImported, Object target, T criteria);
+    Collection<IEObjectDescription> imported(
+        Package fromImporter, Package fromImported, Object target, T criteria);
 
     Collection<IEObjectDescription> inDescriptor(Import anImport, T criteria);