/*
* Copyright 2003-2011 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 jetbrains.mps.vfs.impl;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipFile;
/**
* A container for <code>JarFileData</code> instances.
* Stores a mapping from paths to jar file data, clears it up exploiting the
* <code>WeakReference</code> + <code>ReferenceQueue</code>mechanism.
*
* @author danilla, apyshkin
* @see WeakReference#WeakReference(Object, ReferenceQueue)
* @see #removeGCedReferences()
* <p>
* Also we use this mechanism to clean up the <code>JarFileData</code> resources,
* namely we close the <code>ZipFile</code> associated with the JarFileData instance.
*/
final class JarFileDataCache {
private static final Logger LOG = LogManager.getLogger(JarFileDataCache.class);
private static final JarFileDataCache ourInstance = new JarFileDataCache();
public static JarFileDataCache instance() {
return ourInstance;
}
private final ReferenceQueue<JarFileData> myQueue = new ReferenceQueue<>();
private final Map<String, JarFileDataWeakReference> myPathToRef = new HashMap<>();
private final Map<Reference<? extends JarFileData>, String> myRefToPath = new HashMap<>();
private final Object myLock = new Object();
private JarFileDataCache() {
}
public JarFileData getDataFor(File file) {
synchronized (myLock) {
removeGCedReferences();
String path = file.getAbsolutePath();
if (myPathToRef.containsKey(path)) {
JarFileDataWeakReference ref = myPathToRef.get(path);
JarFileData data = ref.get();
if (data != null) {
return data;
} else {
// 1) must clean myRefToPath before putting new data,
// otherwise stale ref keeps pointing to path, which points to another live ref (via myPathToRef)
// and removeRef() when called after enqueueing will cleanup the wrong (yet live) reference
// 2) it's nice to close ZipFile sooner rather than later
removeRef(ref);
}
}
JarFileData data = new JarFileData(new File(path));
JarFileDataWeakReference ref = new JarFileDataWeakReference(data);
myPathToRef.put(path, ref);
myRefToPath.put(ref, path);
return data;
}
}
private void removeGCedReferences() {
Reference<? extends JarFileData> ref;
while ((ref = myQueue.poll()) != null) {
removeRef(ref);
}
}
private void removeRef(Reference<? extends JarFileData> ref) {
String path = myRefToPath.remove(ref);
if (path == null) {
// already removed.
// we might be called twice, first by us when we see ref.get() == null, second when the ref is enqueued (it may happen later)
return;
}
JarFileDataWeakReference weakRef = myPathToRef.remove(path);
weakRef.cleanup();
}
/**
* Standard idiom to free resources associated with a weakly referenced data.
*/
private class JarFileDataWeakReference extends WeakReference<JarFileData> {
private final ZipFileContainer myZipFileContainer;
public JarFileDataWeakReference(JarFileData data) {
super(data, myQueue);
myZipFileContainer = data.getZipFileContainer();
}
public void cleanup() {
ZipFile zip = myZipFileContainer.zipFile;
if (zip == null) {
// zip file hasn't ever been opened in JarFileData
return;
}
try {
LOG.trace("GC triggered closing zip file " + zip.getName());
zip.close();
} catch (IOException e) {
LOG.error("Failed to close zip file " + zip.getName(), e);
}
}
}
}