/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* 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 com.intellij.util.io;
import com.intellij.util.containers.SLRUMap;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class FileAccessorCache<K, T> implements com.intellij.util.containers.hash.EqualityPolicy<K> {
/*@GuardedBy("myCacheLock")*/ private final SLRUMap<K, Handle<T>> myCache;
/*@GuardedBy("myCacheLock")*/ private final List<T> myElementsToBeDisposed = new ArrayList<T>();
private final Object myCacheLock = new Object();
private final Object myUpdateLock = new Object();
public FileAccessorCache(int protectedQueueSize, int probationalQueueSize) {
myCache = new SLRUMap<K, Handle<T>>(protectedQueueSize, probationalQueueSize, this) {
@Override
protected final void onDropFromCache(K key, Handle<T> value) {
value.release();
}
};
}
protected abstract T createAccessor(K key) throws IOException;
protected abstract void disposeAccessor(T fileAccessor) throws IOException;
@NotNull
public final Handle<T> get(K key) {
Handle<T> cached = getIfCached(key);
if (cached != null) return cached;
synchronized (myUpdateLock) {
cached = getIfCached(key);
if (cached != null) return cached;
return createHandle(key);
}
}
//private static final int FACTOR = 0xF;
//private static final AtomicLong myCreateTime = new AtomicLong();
//private static final AtomicInteger myCreateRequests = new AtomicInteger();
//private static final AtomicInteger myCloseRequests = new AtomicInteger();
//private static final AtomicLong myCloseTime = new AtomicLong();
@NotNull
private Handle<T> createHandle(K key) {
Handle<T> cached;
try {
//long started = System.nanoTime();
cached = new Handle<T>(createAccessor(key), this);
//myCreateTime.addAndGet(System.nanoTime() - started);
//int l = myCreateRequests.incrementAndGet();
//if ((l & FACTOR) == 0) {
// System.out.println("Opened for:" + this + ", " + l + " for " + (myCreateTime.get() / 1000000));
//}
cached.allocate();
synchronized (myCacheLock) {
myCache.put(key, cached);
}
disposeInvalidAccessors();
return cached;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void disposeInvalidAccessors() {
List<T> fileAccessorsToBeDisposed;
synchronized (myCacheLock) {
if (myElementsToBeDisposed.isEmpty()) return;
fileAccessorsToBeDisposed = new ArrayList<T>(myElementsToBeDisposed);
myElementsToBeDisposed.clear();
}
//assert Thread.holdsLock(myUpdateLock);
//long started = System.nanoTime();
for (T t : fileAccessorsToBeDisposed) {
try {
disposeAccessor(t);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
//myCloseTime.addAndGet(System.nanoTime() - started);
//int l = myCloseRequests.addAndGet(fileAccessorsToBeDisposed.size());
//if ((l & FACTOR) == 0) {
// System.out.println("Closed for:" + this + ", " + l + " for " + (myCloseTime.get() / 1000000));
//}
}
public Handle<T> getIfCached(K key) {
synchronized (myCacheLock) {
final Handle<T> value = myCache.get(key);
if (value != null) {
value.allocate();
}
return value;
}
}
public boolean remove(K key) {
try {
synchronized (myCacheLock) {
return myCache.remove(key);
}
}
finally {
synchronized (myUpdateLock) {
disposeInvalidAccessors();
}
}
}
public void clear() {
try {
synchronized (myCacheLock) {
myCache.clear();
}
}
finally {
synchronized (myUpdateLock) {
disposeInvalidAccessors();
}
}
}
@Override
public int getHashCode(K value) {
return value.hashCode();
}
@Override
public boolean isEqual(K val1, K val2) {
return val1.equals(val2);
}
public static final class Handle<T> {
private final FileAccessorCache<?, T> myOwner;
private final T myFileAccessor;
private final AtomicInteger myRefCount = new AtomicInteger(1);
public Handle(T fileAccessor, FileAccessorCache<?, T> owner) {
myFileAccessor = fileAccessor;
myOwner = owner;
}
private void allocate() {
myRefCount.incrementAndGet();
}
public final void release() {
if (myRefCount.decrementAndGet() == 0) {
synchronized (myOwner.myCacheLock) {
myOwner.myElementsToBeDisposed.add(myFileAccessor);
}
}
}
public T get() {
return myFileAccessor;
}
}
}