package jj.resource;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import jj.ServerStopping;
import jj.event.Listener;
import jj.event.Subscriber;
/**
* <p>
* Cache of {@link Resource}s, keyed by their associated
* {@link ResourceIdentifier}s. Main purpose is to provide
* a type-safe heterogeneous collection, and to provide a
* way to get all known resources associated to a path
* @author jason
*
*/
@Singleton
@Subscriber
class ResourceCache {
private final ResourceCreators resourceCreators;
private final ConcurrentMap<ResourceIdentifier, Resource<?>> resourceCache = new ConcurrentHashMap<>(128, 0.75F, 4);
@Inject
ResourceCache(final ResourceCreators resourceCreators) {
this.resourceCreators = resourceCreators;
}
List<Resource<?>> findAllByPath(Path path) {
return Collections.unmodifiableList(
resourceCache.values().stream()
.filter(
resource ->
// only living resources
resource.alive() &&
// that are from the file system
resource instanceof FileSystemResource &&
// and have the same path
path.equals(((FileSystemResource)resource).path())
)
.collect(Collectors.toList())
);
}
/**
* Returns an unmodifiable snapshot of the current Resource instances. This information is
* immediately out of date, cannot be manipulated, and is in no particular order.
*/
List<Resource<?>> allResources() {
return Collections.unmodifiableList(new ArrayList<>(resourceCache.values()));
}
@Listener
void on(ServerStopping ignored) {
resourceCache.clear();
}
@SuppressWarnings("unchecked")
<T extends Resource<A>, A> ResourceCreator<T, A> getCreator(final Class<T> type) {
return (ResourceCreator<T, A>) resourceCreators.get(type);
}
<T extends Resource<A>, A> T get(ResourceIdentifier<T, A> identifier) {
return identifier.resourceClass.cast(resourceCache.get(identifier));
}
<T extends Resource<A>, A> T putIfAbsent(T resource) {
@SuppressWarnings("unchecked")
ResourceIdentifier<T, A> identifier = (ResourceIdentifier<T, A>) resource.identifier();
return identifier.resourceClass.cast(resourceCache.putIfAbsent(identifier, resource));
}
<T extends Resource<A>, A> boolean replace(Resource<T> newResource) {
return resourceCache.replace(newResource.identifier(), newResource) != null;
}
<T extends Resource<A>, A> boolean replace(Resource<T> currentResource, Resource<T> newResource) {
ResourceIdentifier<? ,?> identifier = currentResource.identifier();
assert identifier.equals(newResource.identifier()) : "RESOURCE REPLACEMENT MUST BE EQUIVALENT";
return resourceCache.replace(identifier, currentResource, newResource);
}
<T extends Resource<A>, A> boolean remove(T resource) {
return resourceCache.remove(resource.identifier(), resource);
}
int size() {
return resourceCache.size();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getName()).append(" {\n");
resourceCache.forEach((identifier, resource) ->
sb.append(" ").append(identifier).append(" = ").append(resource).append("\n")
);
return sb.append("}").toString();
}
}