Extract a BlameCache interface to allow other implementations

Change-Id: I8c00cb8942fb6656233bf66bf69107dc7f6a4cf0
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/BlameCache.java b/gitiles-servlet/src/main/java/com/google/gitiles/BlameCache.java
index 1b3a2d3..cf105ad 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/BlameCache.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/BlameCache.java
@@ -15,14 +15,7 @@
 package com.google.gitiles;
 
 import com.google.common.base.Objects;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.cache.Weigher;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 
-import org.eclipse.jgit.blame.BlameGenerator;
 import org.eclipse.jgit.blame.BlameResult;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -30,23 +23,16 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
-/** Cache of blame data, weighted by number of blame regions. */
-public class BlameCache {
-  public static CacheBuilder<Object, Object> newBuilder() {
-    return CacheBuilder.newBuilder().maximumWeight(10 << 10);
-  }
-
-  static class Region {
+public interface BlameCache {
+  public static class Region {
     private final String sourcePath;
     private final ObjectId sourceCommit;
     private final PersonIdent sourceAuthor;
     private int count;
 
-    Region(BlameResult blame, int start) {
+    public Region(BlameResult blame, int start) {
       this.sourcePath = blame.getSourcePath(start);
       RevCommit c = blame.getSourceCommit(start);
       if (c != null) {
@@ -60,23 +46,30 @@
       this.count = 1;
     }
 
-    int getCount() {
+    public Region(String sourcePath, ObjectId sourceCommit, PersonIdent sourceAuthor, int count) {
+      this.sourcePath = sourcePath;
+      this.sourceCommit = sourceCommit;
+      this.sourceAuthor = sourceAuthor;
+      this.count = count;
+    }
+
+    public int getCount() {
       return count;
     }
 
-    String getSourcePath() {
+    public String getSourcePath() {
       return sourcePath;
     }
 
-    ObjectId getSourceCommit() {
+    public ObjectId getSourceCommit() {
       return sourceCommit;
     }
 
-    PersonIdent getSourceAuthor() {
+    public PersonIdent getSourceAuthor() {
       return sourceAuthor;
     }
 
-    private boolean growFrom(BlameResult blame, int i) {
+    public boolean growFrom(BlameResult blame, int i) {
       // Don't compare line numbers, so we collapse regions from the same source
       // but with deleted lines into one.
       if (Objects.equal(blame.getSourcePath(i), sourcePath)
@@ -89,91 +82,5 @@
     }
   }
 
-  private static class Key {
-    private final ObjectId commitId;
-    private final String path;
-    private Repository repo;
-
-    private Key(Repository repo, ObjectId commitId, String path) {
-      this.commitId = commitId;
-      this.path = path;
-      this.repo = repo;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (o instanceof Key) {
-        Key k = (Key) o;
-        return Objects.equal(commitId, k.commitId)
-            && Objects.equal(path, k.path);
-      }
-      return false;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hashCode(commitId, path);
-    }
-  }
-
-  private final LoadingCache<Key, List<BlameCache.Region>> cache;
-
-  public BlameCache() {
-    this(newBuilder());
-  }
-
-  public LoadingCache<?, ?> getCache() {
-    return cache;
-  }
-
-  public BlameCache(CacheBuilder<Object, Object> builder) {
-    this.cache = builder.weigher(new Weigher<Key, List<BlameCache.Region>>() {
-      @Override
-      public int weigh(Key key, List<BlameCache.Region> value) {
-        return value.size();
-      }
-    }).build(new CacheLoader<Key, List<BlameCache.Region>>() {
-      @Override
-      public List<BlameCache.Region> load(Key key) throws IOException {
-        return loadBlame(key);
-      }
-    });
-  }
-
-  List<BlameCache.Region> get(Repository repo, ObjectId commitId, String path)
-      throws IOException {
-    try {
-      return cache.get(new Key(repo, commitId, path));
-    } catch (ExecutionException e) {
-      throw new IOException(e);
-    }
-  }
-
-  private List<BlameCache.Region> loadBlame(Key key) throws IOException {
-    try {
-      BlameGenerator gen = new BlameGenerator(key.repo, key.path);
-      BlameResult blame;
-      try {
-        gen.push(null, key.commitId);
-        blame = gen.computeBlameResult();
-      } finally {
-        gen.release();
-      }
-      if (blame == null) {
-        return ImmutableList.of();
-      }
-      int lineCount = blame.getResultContents().size();
-      blame.discardResultContents();
-
-      List<BlameCache.Region> regions = Lists.newArrayList();
-      for (int i = 0; i < lineCount; i++) {
-        if (regions.isEmpty() || !regions.get(regions.size() - 1).growFrom(blame, i)) {
-          regions.add(new BlameCache.Region(blame, i));
-        }
-      }
-      return Collections.unmodifiableList(regions);
-    } finally {
-      key.repo = null;
-    }
-  }
+  public List<Region> get(Repository repo, ObjectId commitId, String path) throws IOException;
 }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/BlameCacheImpl.java b/gitiles-servlet/src/main/java/com/google/gitiles/BlameCacheImpl.java
new file mode 100644
index 0000000..d997938
--- /dev/null
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/BlameCacheImpl.java
@@ -0,0 +1,137 @@
+// Copyright (C) 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gitiles;
+
+import com.google.common.base.Objects;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.Weigher;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.eclipse.jgit.blame.BlameGenerator;
+import org.eclipse.jgit.blame.BlameResult;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+/** Guava implementation of BlameCache, weighted by number of blame regions. */
+public class BlameCacheImpl implements BlameCache {
+  public static CacheBuilder<Object, Object> newBuilder() {
+    return CacheBuilder.newBuilder().maximumWeight(10 << 10);
+  }
+
+  public static class Key {
+    private final ObjectId commitId;
+    private final String path;
+    private Repository repo;
+
+    public Key(Repository repo, ObjectId commitId, String path) {
+      this.commitId = commitId;
+      this.path = path;
+      this.repo = repo;
+    }
+
+    public ObjectId getCommitId() {
+      return commitId;
+    }
+
+    public String getPath() {
+      return path;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof Key) {
+        Key k = (Key) o;
+        return Objects.equal(commitId, k.commitId)
+            && Objects.equal(path, k.path);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(commitId, path);
+    }
+  }
+
+  private final LoadingCache<Key, List<BlameCache.Region>> cache;
+
+  public BlameCacheImpl() {
+    this(newBuilder());
+  }
+
+  public LoadingCache<?, ?> getCache() {
+    return cache;
+  }
+
+  public BlameCacheImpl(CacheBuilder<Object, Object> builder) {
+    this.cache = builder.weigher(new Weigher<Key, List<BlameCache.Region>>() {
+      @Override
+      public int weigh(Key key, List<BlameCache.Region> value) {
+        return value.size();
+      }
+    }).build(new CacheLoader<Key, List<BlameCache.Region>>() {
+      @Override
+      public List<BlameCache.Region> load(Key key) throws IOException {
+        return loadBlame(key);
+      }
+    });
+  }
+
+  @Override
+  public List<BlameCache.Region> get(Repository repo, ObjectId commitId, String path)
+      throws IOException {
+    try {
+      return cache.get(new Key(repo, commitId, path));
+    } catch (ExecutionException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private List<BlameCache.Region> loadBlame(Key key) throws IOException {
+    try {
+      BlameGenerator gen = new BlameGenerator(key.repo, key.path);
+      BlameResult blame;
+      try {
+        gen.push(null, key.commitId);
+        blame = gen.computeBlameResult();
+      } finally {
+        gen.release();
+      }
+      if (blame == null) {
+        return ImmutableList.of();
+      }
+      int lineCount = blame.getResultContents().size();
+      blame.discardResultContents();
+
+      List<BlameCache.Region> regions = Lists.newArrayList();
+      for (int i = 0; i < lineCount; i++) {
+        if (regions.isEmpty() || !regions.get(regions.size() - 1).growFrom(blame, i)) {
+          regions.add(new BlameCache.Region(blame, i));
+        }
+      }
+      return Collections.unmodifiableList(regions);
+    } finally {
+      key.repo = null;
+    }
+  }
+}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/BlameServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/BlameServlet.java
index 21ce0d5..0383b4f 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/BlameServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/BlameServlet.java
@@ -21,7 +21,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.gitiles.BlameCache.Region;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.http.server.ServletUtils;
@@ -71,7 +70,7 @@
       String title = "Blame - " + view.getPathPart();
       Map<String, ?> blobData = new BlobSoyData(rw, view).toSoyData(view.getPathPart(), blobId);
       if (blobData.get("data") != null) {
-        List<Region> regions = cache.get(repo, commit, view.getPathPart());
+        List<BlameCache.Region> regions = cache.get(repo, commit, view.getPathPart());
         if (regions.isEmpty()) {
           res.setStatus(SC_NOT_FOUND);
           return;
@@ -110,7 +109,7 @@
   }
 
   private static List<Map<String, ?>> toSoyData(GitilesView view, ObjectReader reader,
-      List<Region> regions, GitDateFormatter df) throws IOException {
+      List<BlameCache.Region> regions, GitDateFormatter df) throws IOException {
     Map<ObjectId, String> abbrevShas = Maps.newHashMap();
     List<Map<String, ?>> result = Lists.newArrayListWithCapacity(regions.size());
     for (BlameCache.Region r : regions) {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
index b878415..b4e78f6 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
@@ -401,9 +401,9 @@
   private void setDefaultBlameCache() {
     if (blameCache == null) {
       if (config.getSubsections("cache").contains("blame")) {
-        blameCache = new BlameCache(ConfigUtil.getCacheBuilder(config, "blame"));
+        blameCache = new BlameCacheImpl(ConfigUtil.getCacheBuilder(config, "blame"));
       } else {
-        blameCache = new BlameCache();
+        blameCache = new BlameCacheImpl();
       }
     }
   }