/**
* Copyright [2015] [Christian Loehnert]
*
* 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 de.ks.gallery.ui.thumbnail;
import de.ks.activity.executor.ActivityExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
@Singleton
public class ThumbnailCache {
private static final Logger log = LoggerFactory.getLogger(ThumbnailCache.class);
@Inject
protected ActivityExecutor executor;
@Inject
protected ActivityExecutor javaFXExecutor;
protected Set<Thumbnail> available = new HashSet<>();
protected Set<Thumbnail> reserved = new HashSet<>();
protected CompletableFuture<List<Thumbnail>> load(int amount) {
CompletableFuture<List<Thumbnail>> result = CompletableFuture.completedFuture(Collections.synchronizedList(new ArrayList<>(amount)));
for (int i = 0; i < amount; i++) {
CompletableFuture<Thumbnail> future = CompletableFuture.supplyAsync(this::load, executor).thenApply(t -> {
synchronized (this) {
reserved.add(t);
}
return t;
});
result = future.thenCombine(result, (thumbnail, thumbnails) -> {
thumbnails.add(thumbnail);
return thumbnails;
});
}
return result;
}
public synchronized CompletableFuture<List<Thumbnail>> reserve(int amount) {
// return load(amount);
int toLoad = amount - available.size();
int usedAvailable = amount - Math.max(0, toLoad);
log.debug("For requested amount {}, need to load {} using available {}/{}", amount, toLoad, usedAvailable, available.size());
List<Thumbnail> initiallyReserved;
if (usedAvailable > 0) {
initiallyReserved = getAvailablePushToReserved(usedAvailable);
} else {
initiallyReserved = Collections.emptyList();
}
Function<List<Thumbnail>, List<Thumbnail>> combiner = c -> {
HashSet<Thumbnail> all = new HashSet<>(c);
all.addAll(initiallyReserved);
return new ArrayList<>(all);
};
if (toLoad > 0) {
CompletableFuture<List<Thumbnail>> cf = load(toLoad);
return cf.thenApply(combiner);
} else {
return CompletableFuture.completedFuture(initiallyReserved);
}
}
protected List<Thumbnail> getAvailablePushToReserved(int amount) {
HashSet<Thumbnail> retval = new HashSet<>();
Iterator<Thumbnail> iterator = available.iterator();
for (int i = 0; i < amount; i++) {
if (!iterator.hasNext()) {
throw new IllegalStateException("Expected to have at least " + (amount - i) + " more elements but there are only " + available.size());
}
Thumbnail next = iterator.next();
iterator.remove();
retval.add(next);
reserved.add(next);
log.debug("Used {} from {} available to {} reserved.", amount, available.size(), reserved.size());
}
return new ArrayList<>(retval);
}
public synchronized void release(Collection<Thumbnail> thumbnails) {
thumbnails.forEach(t -> t.reset());
reserved.removeAll(thumbnails);
available.addAll(thumbnails);
}
protected Thumbnail load() {
return new Thumbnail();
}
public void clear() {
available.clear();
reserved.clear();
}
}