package org.netbeans.gradle.project.model; import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import org.jtrim.concurrent.GenericUpdateTaskExecutor; import org.jtrim.concurrent.TaskExecutor; import org.jtrim.concurrent.UpdateTaskExecutor; import org.jtrim.utils.ExceptionHelper; public final class LazyPersistentModelStoreFactory<T> { private static final Logger LOGGER = Logger.getLogger(LazyPersistentModelStoreFactory.class.getName()); private final ModelPersister<? super T> modelPersister; private final UpdateTaskExecutor persisterExecutor; private final ReentrantLock queueLock; private final Queue<Path> taskQueue; private final Map<Path, T> toSave; public LazyPersistentModelStoreFactory(ModelPersister<? super T> modelPersister, TaskExecutor persisterExecutor) { ExceptionHelper.checkNotNullArgument(modelPersister, "modelPersister"); this.modelPersister = modelPersister; this.persisterExecutor = new GenericUpdateTaskExecutor(persisterExecutor); this.queueLock = new ReentrantLock(); this.taskQueue = new LinkedList<>(); this.toSave = new HashMap<>(); } public PersistentModelStore<T> createStore(PersistentModelRetriever<? extends T> modelRetriever) { return new LazyPersistentModelStore(modelRetriever); } private final class LazyPersistentModelStore implements PersistentModelStore<T> { private final PersistentModelRetriever<? extends T> modelRetriever; public LazyPersistentModelStore(PersistentModelRetriever<? extends T> modelRetriever) { ExceptionHelper.checkNotNullArgument(modelRetriever, "modelRetriever"); this.modelRetriever = modelRetriever; } @Override public void persistModel(T model, Path dest) throws IOException { ExceptionHelper.checkNotNullArgument(model, "model"); ExceptionHelper.checkNotNullArgument(dest, "dest"); queueLock.lock(); try { if (toSave.put(dest, model) == null) { taskQueue.add(dest); } } finally { queueLock.unlock(); } persisterExecutor.execute(new Runnable() { @Override public void run() { persistQueue(); } }); } private void fixEmptyQueue() { assert queueLock.isHeldByCurrentThread(); assert taskQueue.isEmpty() : "This method may only be called on empty taskQueue"; // This method is only to recover if there are some inconsistencies // between `toSave` and `taskQueue`. Inconsistencies should never happen // except in case of a bug. if (!toSave.isEmpty()) { LOGGER.log(Level.WARNING, "Internal error: Task queue is empty while there are models to save."); taskQueue.addAll(toSave.keySet()); } } private void persistQueue() { while (true) { Path dest; T model; queueLock.lock(); try { dest = taskQueue.poll(); if (dest == null) { fixEmptyQueue(); return; } model = toSave.remove(dest); } finally { queueLock.unlock(); } if (model == null) { LOGGER.log(Level.WARNING, "There is no model to save for path: {0}", dest); continue; } try { modelPersister.persistModel(model, dest); } catch (IOException ex) { LOGGER.log(Level.INFO, "Failed to save into the persistent cache.", ex); } catch (Throwable ex) { LOGGER.log(Level.SEVERE, "Unexpected error while saving to the persistent cache.", ex); } } } @Override public T tryLoadModel(Path src) throws IOException { queueLock.lock(); try { T model = toSave.get(src); if (model != null) { return model; } } finally { queueLock.unlock(); } return modelRetriever.tryLoadModel(src); } } }