Skip to content

Commit

Permalink
Improved caching (#3)
Browse files Browse the repository at this point in the history
* Improved caching

* Cache data objects instead of CompletableFutures
  • Loading branch information
robertlit authored Feb 10, 2021
1 parent 12897d1 commit c12eb5d
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 113 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ and use the artifact information that is in the ```pom.xml``` file as a dependen

# Code exmaples
``` Java
SpigotResourcesAPI api = new SpigotResourcesAPI(false);
// Construct an API and specify how long should data be cached for
SpigotResourcesAPI api = new SpigotResourcesAPI(1, TimeUnit.HOURS);

// Get Author by id
CompletableFuture<Author> future = api.getAuthor(740512);
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
</build>

<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,109 +3,56 @@
import me.robertlit.spigotresources.internal.AuthorManager;
import me.robertlit.spigotresources.internal.ResourceManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
* The main class of SpigotResourcesAPI
*/
public class SpigotResourcesAPI {

private final boolean fetchByDefault;
public final class SpigotResourcesAPI {

private final ResourceManager resourceManager;
private final AuthorManager authorManager;
/**
* Constructs an API
* @param fetchByDefault whether to fetch data or retrieve it from cache by default
* @param cacheDuration the duration for which data is cached
* @param cacheUnit the time unit for the duration
*/
public SpigotResourcesAPI(boolean fetchByDefault) {
this.fetchByDefault = fetchByDefault;
public SpigotResourcesAPI(long cacheDuration, TimeUnit cacheUnit) {
this.resourceManager = new ResourceManager(cacheDuration, cacheUnit);
this.authorManager = new AuthorManager(cacheDuration, cacheUnit);
}

private final ResourceManager resourceManager = new ResourceManager();
private final AuthorManager authorManager = new AuthorManager();

/**
* Gets the Resource with the given id
* <p>
* If this API is set to fetch by default or if the data is not in cache, the data will be fetched,
* otherwise the data will be gotten from cache
* </p>
* @param resourceId resource id
* @return a future, which is to be completed with the wanted Resource or null
*/
@NotNull
public CompletableFuture<Resource> getResource(int resourceId) {
return getResource(resourceId, fetchByDefault);
}

/**
* Gets the Resource with the given id
* <p>
* Note: If fetch is false but the data is not in cache, it will be fetched
* </p>
* @param resourceId resource id
* @param fetch whether to fetch data or get from cache
* @return a future, which is to be completed with the wanted Resource or null
*/
@NotNull
public CompletableFuture<Resource> getResource(int resourceId, boolean fetch) {
return resourceManager.getResource(resourceId, fetch);
}

/**
* Gets the Resources of a given Author
* <p>
* If this API is set to fetch by default or if the data is not in cache, the data will be fetched,
* otherwise the data will be gotten from cache
* </p>
* @param authorId id of the Author
* @return a future, which is to be completed with an unmodifiable collection representing the resources of the given Author
*/
@NotNull
public CompletableFuture<Collection<Resource>> getResourcesByAuthor(int authorId) {
return getResourcesByAuthor(authorId, fetchByDefault);
public CompletableFuture<@Nullable Resource> getResource(int resourceId) {
return resourceManager.getResource(resourceId);
}

/**
* Gets the Resources of a given Author
* <p>
* Note: If fetch is false but the data is not in cache, it will be fetched
* </p>
* @param authorId id of the Author
* @param fetch whether to fetch data or get from cache
* @return a future, which is to be completed with an unmodifiable collection representing the resources of the given Author
*/
@NotNull
public CompletableFuture<Collection<Resource>> getResourcesByAuthor(int authorId, boolean fetch) {
return resourceManager.getResourcesByAuthor(authorId, fetch);
}

/**
* Gets the Author with the given id
* <p>
* If this API is set to fetch by default or if the data is not in cache, the data will be fetched,
* otherwise the data will be gotten from cache
* </p>
* @param authorId author id
* @return a future, which is to be completed with the wanted Author or null
*/
@NotNull
public CompletableFuture<Author> getAuthor(int authorId) {
return getAuthor(authorId, fetchByDefault);
public CompletableFuture<@NotNull Collection<Resource>> getResourcesByAuthor(int authorId) {
return resourceManager.getResourcesByAuthor(authorId);
}

/**
* Gets the Author with the given id
* <p>
* Note: If fetch is false but the data is not in cache, it will be fetched
* </p>
* @param authorId author id
* @param fetch whether to fetch data or get from cache
* @return a future, which is to be completed with the wanted Author or null
*/
@NotNull
public CompletableFuture<Author> getAuthor(int authorId, boolean fetch) {
return authorManager.getAuthor(authorId, fetch);
public CompletableFuture<@Nullable Author> getAuthor(int authorId) {
return authorManager.getAuthor(authorId);
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
package me.robertlit.spigotresources.internal;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gson.Gson;
import me.robertlit.spigotresources.api.Author;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class AuthorManager {

private static final String GET_AUTHOR_URL = "https://api.spigotmc.org/simple/0.1/index.php?action=getAuthor&id=%d";

private final Map<Integer, Author> idToAuthorMap = Collections.synchronizedMap(new HashMap<>());

private final Gson gson = new Gson();
private final LoadingCache<Integer, Author> authorCache;

public AuthorManager(long duration, TimeUnit unit) {
this.authorCache = CacheBuilder.newBuilder()
.expireAfterWrite(duration, unit)
.build(new CacheLoader<Integer, Author>() {
@Override
public Author load(@NotNull Integer authorId) throws Exception {
Author author = gson.fromJson(HttpRequester.requestString(String.format(GET_AUTHOR_URL, authorId)), Author.class);
if (author == null) {
throw new Exception();
}
return author;
}
});
}

public CompletableFuture<Author> getAuthor(int authorId, boolean fetch) {
if (fetch || idToAuthorMap.get(authorId) == null) {
return CompletableFuture.supplyAsync(() -> {
Author author = gson.fromJson(HttpRequester.requestString(String.format(GET_AUTHOR_URL, authorId)), Author.class);
idToAuthorMap.put(authorId, author);
return author;
});
}
return CompletableFuture.completedFuture(idToAuthorMap.get(authorId));
public CompletableFuture<Author> getAuthor(int authorId) {
return CompletableFuture.supplyAsync(() -> {
try {
return authorCache.get(authorId);
} catch (ExecutionException e) {
return null;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,74 @@
package me.robertlit.spigotresources.internal;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import me.robertlit.spigotresources.api.Resource;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Type;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class ResourceManager {

private static final String GET_RESOURCE_URL = "https://api.spigotmc.org/simple/0.1/index.php?action=getResource&id=%d";
public static final String GET_RESOURCES_BY_AUTHOR_URL = "https://api.spigotmc.org/simple/0.1/index.php?action=getResourcesByAuthor&id=%d";

private final Map<Integer, Resource> idToResourceMap = Collections.synchronizedMap(new HashMap<>());
private final Map<Integer, Collection<Resource>> authorToResourcesMap = Collections.synchronizedMap(new HashMap<>());
private static final String GET_RESOURCES_BY_AUTHOR_URL = "https://api.spigotmc.org/simple/0.1/index.php?action=getResourcesByAuthor&id=%d";

private final Gson gson = new Gson();
private final LoadingCache<Integer, Resource> resourceCache;
private final LoadingCache<Integer, Collection<Resource>> authorResourcesCache;

public ResourceManager(long duration, TimeUnit unit) {
this.resourceCache = CacheBuilder.newBuilder()
.expireAfterWrite(duration, unit)
.build(new CacheLoader<Integer, Resource>() {
@Override
public Resource load(@NotNull Integer resourceId) throws Exception {
Resource resource = gson.fromJson(HttpRequester.requestString(String.format(GET_RESOURCE_URL, resourceId)), Resource.class);
if (resource == null) {
throw new Exception();
}
return resource;
}
});
this.authorResourcesCache = CacheBuilder.newBuilder()
.expireAfterWrite(duration, unit)
.build(new CacheLoader<Integer, Collection<Resource>>() {
@Override
public Collection<Resource> load(@NotNull Integer authorId) {
Type type = new TypeToken<Collection<Resource>>(){}.getType();
Collection<Resource> resources = gson.fromJson(HttpRequester.requestString(String.format(GET_RESOURCES_BY_AUTHOR_URL, authorId)), type);
if (resources == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(resources);
}
});
}

public CompletableFuture<Resource> getResource(int resourceId, boolean fetch) {
if (fetch || idToResourceMap.get(resourceId) == null) {
return CompletableFuture.supplyAsync(() -> {
Resource resource = gson.fromJson(HttpRequester.requestString(String.format(GET_RESOURCE_URL, resourceId)), Resource.class);
idToResourceMap.put(resourceId, resource);
return resource;
});
}
return CompletableFuture.completedFuture(idToResourceMap.get(resourceId));
public CompletableFuture<Resource> getResource(int resourceId) {
return CompletableFuture.supplyAsync(() -> {
try {
return resourceCache.get(resourceId);
} catch (ExecutionException e) {
return null;
}
});
}

public CompletableFuture<Collection<Resource>> getResourcesByAuthor(int authorId, boolean fetch) {
if (fetch || authorToResourcesMap.get(authorId) == null) {
return CompletableFuture.supplyAsync(() -> {
Type type = new TypeToken<Collection<Resource>>(){}.getType();
Collection<Resource> resources = gson.fromJson(HttpRequester.requestString(String.format(GET_RESOURCES_BY_AUTHOR_URL, authorId)), type);
if (resources == null) {
return Collections.emptyList();
}
Collection<Resource> unmodifiable = Collections.unmodifiableCollection(resources);
authorToResourcesMap.put(authorId, unmodifiable);
return unmodifiable;
});
}
return CompletableFuture.completedFuture(authorToResourcesMap.get(authorId));
public CompletableFuture<Collection<Resource>> getResourcesByAuthor(int authorId) {
return CompletableFuture.supplyAsync(() -> {
try {
return authorResourcesCache.get(authorId);
} catch (ExecutionException e) {
return Collections.emptyList();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertEquals;

class SpigotResourcesAPITest {

SpigotResourcesAPI api = new SpigotResourcesAPI(false);
SpigotResourcesAPI api = new SpigotResourcesAPI(1, TimeUnit.HOURS);

@Test
void testAuthor() {
Expand Down

0 comments on commit c12eb5d

Please sign in to comment.