package jj.resource;
import static java.util.concurrent.TimeUnit.SECONDS;
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.event.Publisher;
import jj.execution.CurrentTask;
import jj.logging.Warning;
import jj.util.Closer;
/**
* coordinates access to the resource cache for the outside
* world.
* @author jason
*
*/
@Singleton
class ResourceFinderImpl implements ResourceFinder {
private final ConcurrentMap<ResourceIdentifier<?, ?>, ResourceTask> resourcesInProgress =
new ConcurrentHashMap<>(16, 0.75f, 4);
private final ResourceCache resourceCache;
private final Publisher publisher;
private final CurrentTask currentTask;
private final ResourceIdentifierMaker resourceIdentifierMaker;
@Inject
ResourceFinderImpl(
ResourceCache resourceCache,
Publisher publisher,
CurrentTask currentTask,
ResourceIdentifierMaker resourceIdentifierMaker
) {
this.resourceCache = resourceCache;
this.publisher = publisher;
this.currentTask = currentTask;
this.resourceIdentifierMaker = resourceIdentifierMaker;
}
@Override
public <T extends Resource<Void>> T findResource(Class<T> resourceClass, Location location, String name) {
return findResource(resourceIdentifierMaker.make(resourceClass, location, name, null));
}
@Override
public <T extends Resource<A>, A> T findResource(Class<T> resourceClass, Location location, String name, A argument) {
return findResource(resourceIdentifierMaker.make(resourceClass, location, name, argument));
}
@Override
public <T extends Resource<A>, A> T findResource(ResourceIdentifier<T, A> identifier) {
return resourceIdentifiers(identifier).stream()
.map(resourceCache::get)
.filter(r -> r != null)
.findFirst()
.orElse(null);
}
@Override
@ResourceThread
public <T extends Resource<Void>> T loadResource(Class<T> resourceClass, Location location, String name) {
return loadResource(resourceIdentifierMaker.make(resourceClass, location, name, null));
}
@Override
@ResourceThread
public <T extends Resource<A>, A> T loadResource(Class<T> resourceClass, Location location, String name, A argument) {
return loadResource(resourceIdentifierMaker.make(resourceClass, location, name, argument));
}
@Override
@ResourceThread
public <T extends Resource<A>, A> T loadResource(ResourceIdentifier<T, A> identifier) {
assert currentTask.currentIs(ResourceTask.class) : "Can only call loadResource from a ResourceTask";
try (Closer ignored = acquire(identifier)) {
T resource = findResource(identifier);
if (resource == null) {
resource = resourceIdentifiers(identifier).stream()
.map(this::createResource)
.filter(r -> r != null)
.findFirst()
.orElse(null);
}
return resource;
} catch (InterruptedException ie) {
// this is a shutdown
}
return null;
}
private <T extends Resource<A>, A> T createResource(ResourceIdentifier<T, A> identifier) {
ResourceCreator<T, A> creator = findCreator(identifier);
try {
T resource = creator.create(identifier.base, identifier.name, identifier.argument);
// resource not found!
if (resource == null) {
publisher.publish(new ResourceNotFound(identifier));
// try to add it to the cache. shouldn't be in there!
// if it isn't, tell the world we loaded a new resource
} else if (resourceCache.putIfAbsent(resource) == null) {
publisher.publish(new ResourceLoaded(resource));
} else {
publisher.publish(new Warning("Resource identified by {} was created but already in the cache!"));
}
} catch (Exception e) {
publisher.publish(new ResourceError(identifier, e));
}
// return whatever is in the cache
return resourceCache.get(identifier);
}
private <T extends Resource<A>, A> ResourceCreator<T, A> findCreator(ResourceIdentifier<T, A> identifier) {
ResourceCreator<T, A> resourceCreator = resourceCache.getCreator(identifier.resourceClass);
assert resourceCreator != null : "no ResourceCreator for " + identifier.resourceClass;
return resourceCreator;
}
/**
* Converts a resource identifier with a list of locations into a list
* of resource identifiers with one location apiece
*/
private <T extends Resource<A>, A> List<ResourceIdentifier<T, A>> resourceIdentifiers(ResourceIdentifier<T, A> identifier) {
List<ResourceIdentifier<T, A>> attempts;
if (identifier.base.locations().size() > 1) {
attempts = identifier.base.locations().stream().map(
location -> resourceIdentifierMaker.make(identifier.resourceClass, location, identifier.name, identifier.argument)
).collect(Collectors.toList());
} else {
attempts = Collections.singletonList(identifier);
}
return attempts;
}
private Closer acquire(ResourceIdentifier<?, ?> identifier) throws InterruptedException {
ResourceTask owner = resourcesInProgress.putIfAbsent(identifier, currentTask.currentAs(ResourceTask.class));
if (owner != null) {
if (!owner.await(2, SECONDS)) {
return () -> {};
}
}
return () -> resourcesInProgress.remove(identifier, currentTask.currentAs(ResourceTask.class));
}
}