package org.eclipse.osgi.storage.bundlefile;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.osgi.framework.eventmgr.EventDispatcher;
import org.eclipse.osgi.framework.eventmgr.EventManager;
import org.eclipse.osgi.framework.eventmgr.ListenerQueue;
import org.eclipse.osgi.internal.debug.Debug;
public class MRUBundleFileList implements EventDispatcher<Object, Object, BundleFile> {
private static final int MIN = 10;
private static final ThreadLocal<BundleFile> closingBundleFile = new ThreadLocal<>();
final private BundleFile[] bundleFileList;
final private long[] useStampList;
final private int fileLimit;
private EventManager bundleFileCloserManager = null;
final private Map<Object, Object> bundleFileCloser;
private int numOpen = 0;
private long curUseStamp = 0;
private boolean firstDispatch = true;
private final ReentrantLock pendingLock = new ReentrantLock();
private final Condition pendingCond = pendingLock.newCondition();
private final AtomicInteger pending = new AtomicInteger();
private final Debug debug;
public MRUBundleFileList(int fileLimit, Debug debug) {
this.fileLimit = fileLimit;
this.debug = debug;
if (fileLimit >= MIN) {
this.bundleFileList = new BundleFile[fileLimit];
this.useStampList = new long[fileLimit];
this.bundleFileCloser = Collections.<Object, Object> singletonMap(this, this);
} else {
this.bundleFileList = null;
this.useStampList = null;
this.bundleFileCloser = null;
}
}
public boolean add(BundleFile bundleFile) {
if (fileLimit < MIN)
return false;
BundleFile toRemove = null;
EventManager manager = null;
boolean backpressureNeeded = false;
synchronized (this) {
if (bundleFile.getMruIndex() >= 0)
return false;
int index = 0;
if (numOpen < fileLimit) {
for (int i = 0; i < fileLimit; i++)
if (bundleFileList[i] == null) {
index = i;
break;
}
} else {
index = 0;
for (int i = 1; i < fileLimit; i++)
if (useStampList[i] < useStampList[index])
index = i;
toRemove = bundleFileList[index];
if (toRemove.getMruIndex() != index)
throw new IllegalStateException("The BundleFile has the incorrect mru index: " + index + " != " + toRemove.getMruIndex());
removeInternal(toRemove);
backpressureNeeded = isBackPressureNeeded();
}
bundleFileList[index] = bundleFile;
bundleFile.setMruIndex(index);
incUseStamp(index);
numOpen++;
if (toRemove != null) {
if (bundleFileCloserManager == null)
bundleFileCloserManager = new EventManager("Bundle File Closer");
manager = bundleFileCloserManager;
}
}
closeBundleFile(toRemove, manager);
return backpressureNeeded;
}
public boolean remove(BundleFile bundleFile) {
if (fileLimit < MIN)
return false;
synchronized (this) {
int index = bundleFile.getMruIndex();
if ((index >= 0 && index < fileLimit) && bundleFileList[index] == bundleFile) {
removeInternal(bundleFile);
return true;
}
}
return false;
}
private void removeInternal(BundleFile bundleFile) {
int index = bundleFile.getMruIndex();
bundleFile.setMruIndex(-1);
bundleFileList[index] = null;
useStampList[index] = -1;
numOpen--;
}
public void use(BundleFile bundleFile) {
if (fileLimit < MIN)
return;
synchronized (this) {
int index = bundleFile.getMruIndex();
if ((index >= 0 && index < fileLimit) && bundleFileList[index] == bundleFile)
incUseStamp(index);
}
}
private void incUseStamp(int index) {
if (curUseStamp == Long.MAX_VALUE) {
for (int i = 0; i < fileLimit; i++)
useStampList[i] = 0;
curUseStamp = 0;
}
useStampList[index] = ++curUseStamp;
}
@Override
public final void dispatchEvent(Object eventListener, Object listenerObject, int eventAction, BundleFile eventObject) {
if (firstDispatch) {
Thread.currentThread().setContextClassLoader(null);
firstDispatch = false;
}
try {
closingBundleFile.set(eventObject);
eventObject.close();
} catch (IOException e) {
} finally {
closingBundleFile.set(null);
pendingLock.lock();
try {
if (pending.decrementAndGet() < fileLimit) {
pendingCond.signalAll();
}
} finally {
pendingLock.unlock();
}
}
}
private boolean isBackPressureNeeded() {
pendingLock.lock();
try {
return pending.incrementAndGet() > fileLimit;
} finally {
pendingLock.unlock();
}
}
public void applyBackpressure() {
pendingLock.lock();
try {
int pendingNum = pending.get();
if (pendingNum > fileLimit) {
if (debug.DEBUG_BUNDLE_FILE) {
Debug.println("MRUBundleFileList: Applying back pressure before opening: " + toString());
}
try {
pendingCond.await(Math.min(500, pendingNum), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} finally {
pendingLock.unlock();
}
}
private void closeBundleFile(BundleFile toRemove, EventManager manager) {
if (toRemove == null)
return;
if (debug.DEBUG_BUNDLE_FILE) {
Debug.println("MRUBundleFileList: about to close bundle file: " + toRemove);
}
try {
ListenerQueue<Object, Object, BundleFile> queue = new ListenerQueue<>(manager);
queue.queueListeners(bundleFileCloser.entrySet(), this);
queue.dispatchEventAsynchronous(0, toRemove);
} catch (Throwable t) {
if (debug.DEBUG_BUNDLE_FILE) {
Debug.printStackTrace(t);
}
}
}
public void shutdown() {
synchronized (this) {
if (bundleFileCloserManager != null)
bundleFileCloserManager.close();
bundleFileCloserManager = null;
}
}
public boolean isClosing(BundleFile bundleFile) {
if (fileLimit < MIN)
return false;
return closingBundleFile.get() == bundleFile;
}
public boolean isEnabled() {
return fileLimit >= MIN;
}
}