/*
* Copyright 2016 the original author or authors.
*
* 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 org.gradle.api.internal.changedetection.state;
import com.google.common.cache.Cache;
import com.google.common.hash.HashCode;
import com.google.common.util.concurrent.Runnables;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.gradle.api.Transformer;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.cache.internal.FileLock;
import org.gradle.cache.internal.MultiProcessSafeAsyncPersistentIndexedCache;
import org.gradle.internal.UncheckedException;
import java.io.File;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
class InMemoryDecoratedCache<K, V> implements MultiProcessSafeAsyncPersistentIndexedCache<K, V> {
private final static Logger LOG = Logging.getLogger(InMemoryDecoratedCache.class);
private final static Object NULL = new Object();
private final MultiProcessSafeAsyncPersistentIndexedCache<K, V> delegate;
private final Cache<Object, Object> inMemoryCache;
private final String cacheId;
private final AtomicReference<FileLock.State> fileLockStateReference;
public InMemoryDecoratedCache(MultiProcessSafeAsyncPersistentIndexedCache<K, V> delegate, Cache<Object, Object> inMemoryCache, String cacheId, AtomicReference<FileLock.State> fileLockStateReference) {
this.delegate = delegate;
this.inMemoryCache = inMemoryCache;
this.cacheId = cacheId;
this.fileLockStateReference = fileLockStateReference;
}
@Override
public String toString() {
return "{in-memory-cache cache: " + delegate + "}";
}
@Override
public V get(final K key) {
assert key instanceof String || key instanceof Long || key instanceof File || key instanceof HashCode : "Unsupported key type: " + key;
Object value;
try {
value = inMemoryCache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
Object out = delegate.get(key);
return out == null ? NULL : out;
}
});
} catch (UncheckedExecutionException e) {
throw UncheckedException.throwAsUncheckedException(e.getCause());
} catch (ExecutionException e) {
throw UncheckedException.throwAsUncheckedException(e.getCause());
}
if (value == NULL) {
return null;
} else {
return (V) value;
}
}
@Override
public V get(final K key, final Transformer<? extends V, ? super K> producer, final Runnable completion) {
assert key instanceof String || key instanceof Long || key instanceof File || key instanceof HashCode : "Unsupported key type: " + key;
final AtomicReference<Runnable> completionRef = new AtomicReference<Runnable>(completion);
Object value;
try {
value = inMemoryCache.getIfPresent(key);
final boolean wasNull = value == NULL;
if (wasNull) {
inMemoryCache.invalidate(key);
} else if (value != null) {
return (V) value;
}
value = inMemoryCache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
if (!wasNull) {
Object out = delegate.get(key);
if (out != null) {
return out;
}
}
V value = producer.transform(key);
delegate.putLater(key, value, completion);
completionRef.set(Runnables.doNothing());
return value;
}
});
} catch (UncheckedExecutionException e) {
throw UncheckedException.throwAsUncheckedException(e.getCause());
} catch (ExecutionException e) {
throw UncheckedException.throwAsUncheckedException(e.getCause());
} finally {
completionRef.get().run();
}
if (value == NULL) {
return null;
} else {
return (V) value;
}
}
@Override
public void putLater(K key, V value, Runnable completion) {
inMemoryCache.put(key, value);
delegate.putLater(key, value, completion);
}
@Override
public void removeLater(K key, Runnable completion) {
inMemoryCache.put(key, NULL);
delegate.removeLater(key, completion);
}
@Override
public void afterLockAcquire(FileLock.State currentCacheState) {
boolean outOfDate = false;
FileLock.State previousState = fileLockStateReference.get();
if (previousState == null) {
outOfDate = true;
} else if (currentCacheState.hasBeenUpdatedSince(previousState)) {
LOG.info("Invalidating in-memory cache of {}", cacheId);
outOfDate = true;
}
if (outOfDate) {
inMemoryCache.invalidateAll();
}
delegate.afterLockAcquire(currentCacheState);
}
@Override
public void finishWork() {
delegate.finishWork();
}
@Override
public void beforeLockRelease(FileLock.State currentCacheState) {
fileLockStateReference.set(currentCacheState);
delegate.beforeLockRelease(currentCacheState);
}
}