<commit_before>package uk.co.todddavies.website.notes;

import uk.co.todddavies.website.cache.MemcacheKeys;
import uk.co.todddavies.website.cache.MemcacheKeys.MemcacheKey;
import uk.co.todddavies.website.notes.data.NotesDatastoreInterface;
import uk.co.todddavies.website.notes.data.NotesDocument;

import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gdata.util.common.base.Pair;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;

import java.io.IOException;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.LinkedList;

import javax.cache.Cache;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
@Singleton
final class NotesApiServlet extends HttpServlet { 
  
  private final NotesDatastoreInterface notesStorage;
  private final ObjectWriter jsonObjectWriter;
  private final Optional<Cache> memCache;
  
  // TODO(td): Don't hardcode this list
  private static final ImmutableList<String> TAGS = ImmutableList.of("Third Year");
  
  @Inject
  private NotesApiServlet(
      NotesDatastoreInterface notesStorage,
      ObjectWriter jsonObjectWriter,
      Optional<Cache> memCache) {
    this.notesStorage = notesStorage;
    this.jsonObjectWriter = jsonObjectWriter;
    this.memCache = memCache;
  }
  
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setContentType("text/plain");
    // Try and read the cached data.
    ImmutableMap<String, Serializable> responseData = get(memCache, MemcacheKey.NOTES_LIST);
    // If it wasn't read, then read & prepare it.
    if (responseData == null) {
      Pair<LinkedHashMap<String, LinkedList<NotesDocument>>, Integer> notes = listNotesByTag(TAGS);
      int totalDownloads = notes.getSecond();
      responseData = ImmutableMap.of("downloads", totalDownloads, "notes", notes.getFirst());
      put(memCache, MemcacheKey.NOTES_LIST, responseData);
    }
    // Serialise to JSON and send to the client.
    resp.getWriter().print(jsonObjectWriter.writeValueAsString(responseData));
  }
  
  /**
   * List the notes according to the tags they have.
   * @param tags The tags to list by.
   * @return A linked map of {@code tag->List<NotesDocument>} where the order of the tag iterator is
   *     the same as the input tag list. 
   */
  @VisibleForTesting
  public Pair<LinkedHashMap<String, LinkedList<NotesDocument>>, Integer>
      listNotesByTag(ImmutableList<String> tags) {
    LinkedHashMap<String, LinkedList<NotesDocument>> output = new LinkedHashMap<>();
    // Add the tag keys in order
    for (String tag : tags) {
      output.put(tag, new LinkedList<NotesDocument>());
    }
    // Keep track of how many downloads there were
    int downloads = 0;
    // For each notes document, add it to the map
    for (NotesDocument notes : notesStorage.listNotes()) {
      Optional<String> firstTag = getFirstTag(notes, tags);
      if (firstTag.isPresent()) {
        output.get(firstTag.get()).add(notes);
        downloads += notes.getDownloads();
      } else { /* Don't show notes that aren't in the tag list */ }
    }
    // Remove tags w/ no notes
    for (String tag : tags) {
      if (output.get(tag).isEmpty()) {
        output.remove(tag);
      }
    }
    return Pair.of(output, downloads);
  }

  @VisibleForTesting
  @SuppressWarnings("unchecked") /* Type checking is done manually */
  static <T> T get(Optional<Cache> cache, MemcacheKey key) {
    // Retrieve the object from the cache
    T out = cache.isPresent() ? (T) cache.get().get(MemcacheKeys.KEY_MAP.get(key)) : null;
    // The cache didn't contain that object
    if (out == null) {
      return null;
    } else {
      // Check that the retrieved object is of the correct type.
      TypeLiteral<?> expectedType = MemcacheKeys.EXPECTED_TYPES.get(key);
      if (expectedType.getRawType().isInstance(out)) {
        return out;
      } else {
        System.err.printf("Memcache key as of an unexpected type for key '%s'\n", key);
        System.err.printf("Expected type: '%s'\n", expectedType.toString());
        System.err.printf("Actual type: '%s'\n", out == null ? "null" : out.getClass().toString());
        System.err.printf("Value: '%s'\n", out == null ? "null" : out.toString());
        return null;
      }
    }
  }
  
  @VisibleForTesting<commit_msg>Added a comment to refactor out the caching logic for Notes<commit_after>package uk.co.todddavies.website.notes;

import uk.co.todddavies.website.cache.MemcacheKeys;
import uk.co.todddavies.website.cache.MemcacheKeys.MemcacheKey;
import uk.co.todddavies.website.notes.data.NotesDatastoreInterface;
import uk.co.todddavies.website.notes.data.NotesDocument;

import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gdata.util.common.base.Pair;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;

import java.io.IOException;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.LinkedList;

import javax.cache.Cache;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
@Singleton
final class NotesApiServlet extends HttpServlet { 
  
  private final NotesDatastoreInterface notesStorage;
  private final ObjectWriter jsonObjectWriter;
  private final Optional<Cache> memCache;
  
  // TODO(td): Don't hardcode this list
  private static final ImmutableList<String> TAGS = ImmutableList.of("Third Year");
  
  @Inject
  private NotesApiServlet(
      NotesDatastoreInterface notesStorage,
      ObjectWriter jsonObjectWriter,
      Optional<Cache> memCache) {
    this.notesStorage = notesStorage;
    this.jsonObjectWriter = jsonObjectWriter;
    this.memCache = memCache;
  }
  
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setContentType("text/plain");
    // Try and read the cached data.
    ImmutableMap<String, Serializable> responseData = get(memCache, MemcacheKey.NOTES_LIST);
    // If it wasn't read, then read & prepare it.
    if (responseData == null) {
      Pair<LinkedHashMap<String, LinkedList<NotesDocument>>, Integer> notes = listNotesByTag(TAGS);
      int totalDownloads = notes.getSecond();
      responseData = ImmutableMap.of("downloads", totalDownloads, "notes", notes.getFirst());
      put(memCache, MemcacheKey.NOTES_LIST, responseData);
    }
    // Serialise to JSON and send to the client.
    resp.getWriter().print(jsonObjectWriter.writeValueAsString(responseData));
  }
  
  /**
   * List the notes according to the tags they have.
   * @param tags The tags to list by.
   * @return A linked map of {@code tag->List<NotesDocument>} where the order of the tag iterator is
   *     the same as the input tag list. 
   */
  @VisibleForTesting
  public Pair<LinkedHashMap<String, LinkedList<NotesDocument>>, Integer>
      listNotesByTag(ImmutableList<String> tags) {
    LinkedHashMap<String, LinkedList<NotesDocument>> output = new LinkedHashMap<>();
    // Add the tag keys in order
    for (String tag : tags) {
      output.put(tag, new LinkedList<NotesDocument>());
    }
    // Keep track of how many downloads there were
    int downloads = 0;
    // For each notes document, add it to the map
    for (NotesDocument notes : notesStorage.listNotes()) {
      Optional<String> firstTag = getFirstTag(notes, tags);
      if (firstTag.isPresent()) {
        output.get(firstTag.get()).add(notes);
        downloads += notes.getDownloads();
      } else { /* Don't show notes that aren't in the tag list */ }
    }
    // Remove tags w/ no notes
    for (String tag : tags) {
      if (output.get(tag).isEmpty()) {
        output.remove(tag);
      }
    }
    return Pair.of(output, downloads);
  }

  // TODO(td): Move this logic into a dedicated class in uk.co.todddavies.website.cache
  @VisibleForTesting
  @SuppressWarnings("unchecked") /* Type checking is done manually */
  static <T> T get(Optional<Cache> cache, MemcacheKey key) {
    // Retrieve the object from the cache
    T out = cache.isPresent() ? (T) cache.get().get(MemcacheKeys.KEY_MAP.get(key)) : null;
    // The cache didn't contain that object
    if (out == null) {
      return null;
    } else {
      // Check that the retrieved object is of the correct type.
      TypeLiteral<?> expectedType = MemcacheKeys.EXPECTED_TYPES.get(key);
      if (expectedType.getRawType().isInstance(out)) {
        return out;
      } else {
        System.err.printf("Memcache key as of an unexpected type for key '%s'\n", key);
        System.err.printf("Expected type: '%s'\n", expectedType.toString());
        System.err.printf("Actual type: '%s'\n", out == null ? "null" : out.getClass().toString());
        System.err.printf("Value: '%s'\n", out == null ? "null" : out.toString());
        return null;
      }
    }
  }
  
  @VisibleForTesting
{'fraction_non_alphanumeric': 0.11393338954468803, 'fraction_numerical': 0.00021079258010118043, 'mean_word_length': 3.651470588235294, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1088226', 'n_tokens_mistral': 2846, 'n_tokens_neox': 2799, 'n_words': 729}