package org.eclipse.osgi.storage.bundlefile;
import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent;
import org.eclipse.osgi.container.ModuleRevision;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.storage.BundleInfo;
import org.eclipse.osgi.storage.Storage.StorageException;
import org.eclipse.osgi.util.NLS;
public abstract class CloseableBundleFile<E> extends BundleFile {
private final ReentrantLock openLock = new ReentrantLock();
private final Condition refCondition = openLock.newCondition();
private final MRUBundleFileList mruList;
protected final BundleInfo.Generation generation;
protected final Debug debug;
private volatile boolean closed = true;
private int referenceCount = 0;
public CloseableBundleFile(File basefile, BundleInfo.Generation generation, MRUBundleFileList mruList, Debug debug) {
super(basefile);
this.debug = debug;
this.generation = generation;
this.closed = true;
this.mruList = mruList;
}
private boolean lockOpen() {
try {
open(true);
return true;
} catch (IOException e) {
if (generation != null) {
ModuleRevision r = generation.getRevision();
if (r != null) {
ContainerEvent eventType = ContainerEvent.ERROR;
if (!r.getRevisions().getModuleRevisions().contains(r)) {
eventType = ContainerEvent.INFO;
}
generation.getBundleInfo().getStorage().getAdaptor().publishContainerEvent(eventType, r.getRevisions().getModule(), e);
}
}
return false;
}
}
private void open(boolean keepLock) throws IOException {
openLock.lock();
try {
if (closed) {
boolean needBackPressure = mruListAdd();
if (needBackPressure) {
openLock.unlock();
try {
mruListApplyBackPressure();
} finally {
openLock.lock();
}
}
if (closed) {
if (needBackPressure) {
mruListAdd();
}
doOpen();
closed = false;
}
} else {
mruListUse();
}
} finally {
if (!keepLock || closed) {
openLock.unlock();
}
}
}
protected abstract void doOpen() throws IOException;
File (String dirName) {
if (!lockOpen()) {
return null;
}
try {
for (String path : getPaths()) {
if (path.startsWith(dirName) && !path.endsWith("/"))
getFile(path, false);
}
return getExtractFile(dirName);
} finally {
openLock.unlock();
}
}
protected abstract Iterable<String> getPaths();
private File (String entryName) {
if (generation == null)
return null;
return generation.getExtractFile(".cp", entryName);
}
@Override
public File getFile(String entry, boolean nativeCode) {
if (!lockOpen()) {
return null;
}
try {
BundleEntry bEntry = getEntry(entry);
if (bEntry == null)
return null;
try {
File nested = getExtractFile(bEntry.getName());
if (nested != null) {
if (nested.exists()) {
if (debug.DEBUG_BUNDLE_FILE)
Debug.println("File already present: " + nested.getPath());
if (nested.isDirectory())
extractDirectory(bEntry.getName());
} else {
if (bEntry.getName().endsWith("/")) {
nested.mkdirs();
if (!nested.isDirectory()) {
if (debug.DEBUG_BUNDLE_FILE)
Debug.println("Unable to create directory: " + nested.getPath());
throw new IOException(NLS.bind(Msg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, nested.getAbsolutePath()));
}
extractDirectory(bEntry.getName());
} else {
InputStream in = bEntry.getInputStream();
if (in == null)
return null;
generation.storeContent(nested, in, nativeCode);
}
}
return nested;
}
} catch (IOException | StorageException e) {
if (debug.DEBUG_BUNDLE_FILE)
Debug.printStackTrace(e);
generation.getBundleInfo().getStorage().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Unable to extract content: " + generation.getRevision() + ": " + entry, e);
}
} finally {
openLock.unlock();
}
return null;
}
@Override
public boolean containsDir(String dir) {
if (!lockOpen()) {
return false;
}
try {
if (dir == null)
return false;
if (dir.length() == 0)
return true;
if (dir.charAt(0) == '/') {
if (dir.length() == 1)
return true;
dir = dir.substring(1);
}
if (dir.length() > 0 && dir.charAt(dir.length() - 1) != '/')
dir = dir + '/';
for (String entry : getPaths()) {
if (entry.startsWith(dir)) {
return true;
}
}
} finally {
openLock.unlock();
}
return false;
}
@Override
public BundleEntry getEntry(String path) {
if (!lockOpen()) {
return null;
}
try {
return findEntry(path);
} finally {
openLock.unlock();
}
}
protected abstract BundleEntry findEntry(String path);
@Override
public Enumeration<String> getEntryPaths(String path, boolean recurse) {
if (!lockOpen()) {
return null;
}
try {
if (path == null)
throw new NullPointerException();
if (path.length() > 0 && path.charAt(0) == '/')
path = path.substring(1);
if (path.length() > 0 && path.charAt(path.length() - 1) != '/')
path = new StringBuilder(path).append("/").toString();
LinkedHashSet<String> result = new LinkedHashSet<>();
for (String entryPath : getPaths()) {
if (entryPath.startsWith(path)) {
if (path.length() < entryPath.length()) {
getEntryPaths(path, entryPath.substring(path.length()), recurse, result);
}
}
}
return result.size() == 0 ? null : Collections.enumeration(result);
} finally {
openLock.unlock();
}
}
private void getEntryPaths(String path, String entry, boolean recurse, LinkedHashSet<String> entries) {
if (entry.length() == 0)
return;
int slash = entry.indexOf('/');
if (slash == -1)
entries.add(path + entry);
else {
path = path + entry.substring(0, slash + 1);
entries.add(path);
if (recurse)
getEntryPaths(path, entry.substring(slash + 1), true, entries);
}
}
@Override
public void close() throws IOException {
openLock.lock();
try {
if (!closed) {
if (referenceCount > 0 && isMruListClosing()) {
try {
refCondition.await(1000, TimeUnit.MICROSECONDS);
} catch (InterruptedException e) {
}
if (referenceCount != 0 || closed)
return;
}
closed = true;
doClose();
mruListRemove();
postClose();
}
} finally {
openLock.unlock();
}
}
protected abstract void doClose() throws IOException;
protected abstract void postClose();
private boolean isMruListClosing() {
return this.mruList != null && this.mruList.isClosing(this);
}
private boolean isMruEnabled() {
return this.mruList != null && this.mruList.isEnabled();
}
private void mruListRemove() {
if (this.mruList != null) {
this.mruList.remove(this);
}
}
private void mruListUse() {
if (this.mruList != null) {
mruList.use(this);
}
}
private void mruListApplyBackPressure() {
if (this.mruList != null) {
this.mruList.applyBackpressure();
}
}
private boolean mruListAdd() {
if (this.mruList != null) {
return mruList.add(this);
}
return false;
}
@Override
public void open() throws IOException {
open(false);
}
void incrementReference() {
openLock.lock();
try {
referenceCount += 1;
} finally {
openLock.unlock();
}
}
void decrementReference() {
openLock.lock();
try {
referenceCount = Math.max(0, referenceCount - 1);
if (referenceCount == 0)
refCondition.signal();
} finally {
openLock.unlock();
}
}
public InputStream getInputStream(E entry) throws IOException {
if (!lockOpen()) {
throw new IOException("Failed to lock bundle file.");
}
try {
InputStream in = doGetInputStream(entry);
if (isMruEnabled()) {
in = new BundleEntryInputStream(in);
}
return in;
} finally {
openLock.unlock();
}
}
protected abstract InputStream doGetInputStream(E entry) throws IOException;
private class BundleEntryInputStream extends FilterInputStream {
private boolean streamClosed = false;
public BundleEntryInputStream(InputStream stream) {
super(stream);
incrementReference();
}
@Override
public int available() throws IOException {
try {
return super.available();
} catch (IOException e) {
throw enrichExceptionWithBaseFile(e);
}
}
@Override
public void close() throws IOException {
try {
super.close();
} catch (IOException e) {
throw enrichExceptionWithBaseFile(e);
} finally {
synchronized (this) {
if (streamClosed)
return;
streamClosed = true;
}
decrementReference();
}
}
@Override
public int read() throws IOException {
try {
return super.read();
} catch (IOException e) {
throw enrichExceptionWithBaseFile(e);
}
}
@Override
public int read(byte[] var0, int var1, int var2) throws IOException {
try {
return super.read(var0, var1, var2);
} catch (IOException e) {
throw enrichExceptionWithBaseFile(e);
}
}
@Override
public int read(byte[] var0) throws IOException {
try {
return super.read(var0);
} catch (IOException e) {
throw enrichExceptionWithBaseFile(e);
}
}
@Override
public void reset() throws IOException {
try {
super.reset();
} catch (IOException e) {
throw enrichExceptionWithBaseFile(e);
}
}
@Override
public long skip(long var0) throws IOException {
try {
return super.skip(var0);
} catch (IOException e) {
throw enrichExceptionWithBaseFile(e);
}
}
private IOException enrichExceptionWithBaseFile(IOException e) {
return new IOException(getBaseFile().toString(), e);
}
}
}