extracted PersistentEnumeratorBase to allow different enumerators

This commit is contained in:
Maxim Mossienko
2011-07-17 14:10:47 +04:00
committed by Maxim Mossienko
parent d68ffc98c8
commit 7b8d55ed9f
2 changed files with 496 additions and 432 deletions
@@ -15,35 +15,20 @@
*/
package com.intellij.util.io;
import com.intellij.openapi.Forceable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.containers.SLRUMap;
import com.intellij.util.containers.ShareableKey;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.io.File;
import java.io.IOException;
/**
* @author max
* @author jeka
*/
public class PersistentEnumerator<Data> implements Forceable, Closeable {
public class PersistentEnumerator<Data> extends PersistentEnumeratorBase<Data> {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.io.PersistentEnumerator");
protected static final int NULL_ID = 0;
protected static final int DATA_OFFSET = 8;
private static final int META_DATA_OFFSET = 4;
private static final int FIRST_VECTOR_OFFSET = 8;
private static final int DIRTY_MAGIC = 0xbabe0589;
private static final int VERSION = 5;
private static final int CORRECTLY_CLOSED_MAGIC = 0xebabafac + VERSION;
private static final int FIRST_VECTOR_OFFSET = DATA_START;
private static final int BITS_PER_LEVEL = 4;
private static final int SLOTS_PER_VECTOR = 1 << BITS_PER_LEVEL;
@@ -55,228 +40,19 @@ public class PersistentEnumerator<Data> implements Forceable, Closeable {
private static final int FIRST_LEVEL_MASK = SLOTS_PER_FIRST_VECTOR - 1;
private static final byte[] FIRST_VECTOR = new byte[SLOTS_PER_FIRST_VECTOR * 4];
protected final ResizeableMappedFile myStorage;
private final ResizeableMappedFile myKeyStorage;
private boolean myClosed = false;
private boolean myDirty = false;
private final KeyDescriptor<Data> myDataDescriptor;
private final byte[] myBuffer = new byte[RECORD_SIZE];
private static final CacheKey ourFlyweight = new FlyweightKey();
protected final File myFile;
private static final int COLLISION_OFFSET = 0;
private static final int KEY_HASHCODE_OFFSET = COLLISION_OFFSET + 4;
private static final int KEY_REF_OFFSET = KEY_HASHCODE_OFFSET + 4;
protected static final int RECORD_SIZE = KEY_REF_OFFSET + 4;
private boolean myCorrupted = false;
private final MyDataIS myKeyReadStream;
private static class CacheKey implements ShareableKey {
public PersistentEnumerator owner;
public Object key;
private CacheKey(final Object key, final PersistentEnumerator owner) {
this.key = key;
this.owner = owner;
}
public ShareableKey getStableCopy() {
return this;
}
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof CacheKey)) return false;
final CacheKey cacheKey = (CacheKey)o;
if (!key.equals(cacheKey.key)) return false;
if (!owner.equals(cacheKey.owner)) return false;
return true;
}
public int hashCode() {
return key.hashCode();
}
}
private static CacheKey sharedKey(Object key, PersistentEnumerator owner) {
ourFlyweight.key = key;
ourFlyweight.owner = owner;
return ourFlyweight;
}
protected static final PagedFileStorage.StorageLock ourLock = new PagedFileStorage.StorageLock();
private static final int ENUMERATION_CACHE_SIZE;
static {
String property = System.getProperty("idea.enumerationCacheSize");
ENUMERATION_CACHE_SIZE = property == null ? 8192 : Integer.valueOf(property);
}
private static final SLRUMap<Object, Integer> ourEnumerationCache = new SLRUMap<Object, Integer>(ENUMERATION_CACHE_SIZE, ENUMERATION_CACHE_SIZE);
@TestOnly
public static void clearCacheForTests() {
ourEnumerationCache.clear();
}
public static class CorruptedException extends IOException {
@SuppressWarnings({"HardCodedStringLiteral"})
public CorruptedException(File file) {
super("PersistentStringEnumerator storage corrupted " + file.getPath());
}
}
public PersistentEnumerator(File file, KeyDescriptor<Data> dataDescriptor, int initialSize) throws IOException {
myDataDescriptor = dataDescriptor;
myFile = file;
if (!file.exists()) {
FileUtil.delete(keystreamFile());
if (!FileUtil.createIfDoesntExist(file)) {
throw new IOException("Cannot create empty file: " + file);
}
}
myStorage = new ResizeableMappedFile(myFile, initialSize, ourLock);
synchronized (ourLock) {
if (myStorage.length() == 0) {
try {
markDirty(true);
putMetaData(0);
allocVector(FIRST_VECTOR);
}
catch (RuntimeException e) {
LOG.info(e);
myStorage.close();
if (e.getCause() instanceof IOException) {
throw (IOException)e.getCause();
}
throw e;
}
catch (IOException e) {
LOG.info(e);
myStorage.close();
throw e;
}
catch (Exception e) {
LOG.info(e);
myStorage.close();
throw new CorruptedException(file);
}
}
else {
int sign;
try {
sign = myStorage.getInt(0);
}
catch(Exception e) {
LOG.info(e);
sign = DIRTY_MAGIC;
}
if (sign != CORRECTLY_CLOSED_MAGIC) {
myStorage.close();
throw new CorruptedException(file);
}
}
}
if (myDataDescriptor instanceof InlineKeyDescriptor) {
myKeyStorage = null;
myKeyReadStream = null;
}
else {
myKeyStorage = new ResizeableMappedFile(keystreamFile(), initialSize, ourLock);
myKeyReadStream = new MyDataIS(myKeyStorage);
}
}
protected int tryEnumerate(Data value) throws IOException {
synchronized (ourEnumerationCache) {
final Integer cachedId = ourEnumerationCache.get(sharedKey(value, this));
if (cachedId != null) return cachedId.intValue();
}
final int id;
synchronized (this) {
synchronized (ourLock) {
id = enumerateImpl(value, false);
}
}
if (id != NULL_ID) {
synchronized (ourEnumerationCache) {
ourEnumerationCache.put(new CacheKey(value, this), id);
}
}
return id;
super(file, dataDescriptor, initialSize);
}
public int enumerate(Data value) throws IOException {
synchronized (ourEnumerationCache) {
final Integer cachedId = ourEnumerationCache.get(sharedKey(value, this));
if (cachedId != null) {
return cachedId.intValue();
}
}
final int id;
synchronized (this) {
synchronized (ourLock) {
id = enumerateImpl(value, true);
}
}
synchronized (ourEnumerationCache) {
ourEnumerationCache.put(new CacheKey(value, this), id);
}
return id;
}
public interface DataFilter {
boolean accept(int id);
}
protected final synchronized void putMetaData(int data) throws IOException {
synchronized (ourLock) {
myStorage.putInt(META_DATA_OFFSET, data);
}
}
protected final synchronized int getMetaData() throws IOException {
synchronized (ourLock) {
return myStorage.getInt(META_DATA_OFFSET);
}
}
public boolean processAllDataObject(final Processor<Data> processor, @Nullable final DataFilter filter) throws IOException {
return traverseAllRecords(new RecordsProcessor() {
public boolean process(final int record) throws IOException {
if (filter == null || filter.accept(record)) {
return processor.process(valueOf(record));
}
return true;
}
});
}
public Collection<Data> getAllDataObjects(@Nullable final DataFilter filter) throws IOException {
final List<Data> values = new ArrayList<Data>();
processAllDataObject(new CommonProcessors.CollectProcessor<Data>(values), filter);
return values;
}
public interface RecordsProcessor {
boolean process(int record) throws IOException;
protected void setupEmptyFile() throws IOException {
allocVector(FIRST_VECTOR);
}
public synchronized boolean traverseAllRecords(RecordsProcessor p) throws IOException {
@@ -300,7 +76,7 @@ public class PersistentEnumerator<Data> implements Forceable, Closeable {
}
}
private int enumerateImpl(final Data value, final boolean saveNewValue) throws IOException {
protected int enumerateImpl(final Data value, final boolean saveNewValue) throws IOException {
try {
int depth = 0;
final int valueHC = myDataDescriptor.getHashCode(value);
@@ -395,6 +171,11 @@ public class PersistentEnumerator<Data> implements Forceable, Closeable {
}
}
@Override
protected int recordWriteOffset(byte[] buf) {
return (int)myStorage.length();
}
private static int hcByte(int hashcode, int byteN) {
if (byteN == 0) {
return hashcode & FIRST_LEVEL_MASK;
@@ -402,7 +183,7 @@ public class PersistentEnumerator<Data> implements Forceable, Closeable {
hashcode >>>= BITS_PER_FIRST_LEVEL;
byteN--;
return (hashcode >>> (byteN * BITS_PER_LEVEL)) & LEVEL_MASK;
}
@@ -416,37 +197,6 @@ public class PersistentEnumerator<Data> implements Forceable, Closeable {
return -myStorage.getInt(idx);
}
private int writeData(final Data value, int hashCode) {
try {
markDirty(true);
final int dataOff = myKeyStorage != null ? (int)myKeyStorage.length() : ((InlineKeyDescriptor<Data>)myDataDescriptor).toInt(value);
byte[] buf = prepareEntryRecordBuf(hashCode, dataOff);
if (myKeyStorage != null) {
final BufferExposingByteArrayOutputStream bos = new BufferExposingByteArrayOutputStream();
DataOutput out = new DataOutputStream(bos);
myDataDescriptor.save(out, value);
myKeyStorage.put(dataOff, bos.getInternalBuffer(), 0, bos.size());
}
final ResizeableMappedFile storage = myStorage;
final int pos = (int)storage.length();
storage.put(pos, buf, 0, buf.length);
return pos;
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
private byte[] prepareEntryRecordBuf(int hashCode, int dataOffset) {
final byte[] buf = getRecordBuffer();
setupRecord(hashCode, dataOffset, buf);
return buf;
}
protected byte[] getRecordBuffer() {
return myBuffer;
}
@@ -457,175 +207,12 @@ public class PersistentEnumerator<Data> implements Forceable, Closeable {
Bits.putInt(buf, KEY_REF_OFFSET, dataOffset);
}
protected boolean iterateData(final Processor<Data> processor) throws IOException {
if (myKeyStorage == null) {
throw new UnsupportedOperationException("Iteration over InlineIntegerKeyDescriptors is not supported");
}
myKeyStorage.force();
DataInputStream keysStream = new DataInputStream(new BufferedInputStream(new LimitedInputStream(new FileInputStream(keystreamFile()),
(int)myKeyStorage.length())));
try {
try {
while (true) {
Data key = myDataDescriptor.read(keysStream);
if (!processor.process(key)) return false;
}
}
catch (EOFException e) {
// Done
}
return true;
}
finally {
keysStream.close();
}
}
private File keystreamFile() {
return new File(myFile.getPath() + ".keystream");
}
private int hashCodeOf(int idx) throws IOException {
return myStorage.getInt(idx + KEY_HASHCODE_OFFSET);
}
public synchronized Data valueOf(int idx) throws IOException {
synchronized (ourLock) {
try {
final ResizeableMappedFile storage = myStorage;
int addr = storage.getInt(idx + KEY_REF_OFFSET);
if (myKeyReadStream == null) return ((InlineKeyDescriptor<Data>)myDataDescriptor).fromInt(addr);
myKeyReadStream.setup(addr, myKeyStorage.length());
return myDataDescriptor.read(myKeyReadStream);
}
catch (IOException io) {
markCorrupted();
throw io;
}
catch (Throwable e) {
markCorrupted();
throw new RuntimeException(e);
}
}
@Override
protected int indexToAddr(int idx) {
return myStorage.getInt(idx + KEY_REF_OFFSET);
}
private static class MyDataIS extends DataInputStream {
private MyDataIS(ResizeableMappedFile raf) {
super(new MyBufferedIS(new MappedFileInputStream(raf, 0, 0)));
}
public void setup(long pos, long limit) {
((MyBufferedIS)in).setup(pos, limit);
}
}
private static class MyBufferedIS extends BufferedInputStream {
public MyBufferedIS(final InputStream in) {
super(in, 512);
}
public void setup(long pos, long limit) {
this.pos = 0;
count = 0;
((MappedFileInputStream)in).setup(pos, limit);
}
}
public synchronized void close() throws IOException {
synchronized (ourLock) {
if (!myClosed) {
myClosed = true;
try {
if (myKeyStorage != null) {
myKeyStorage.close();
}
flush();
}
finally {
myStorage.close();
}
}
}
}
public synchronized boolean isClosed() {
return myClosed;
}
public synchronized boolean isDirty() {
return myDirty;
}
private synchronized void flush() throws IOException {
synchronized (ourLock) {
if (myStorage.isDirty() || isDirty()) {
markDirty(false);
myStorage.force();
}
}
}
public synchronized void force() {
synchronized (ourLock) {
try {
if (myKeyStorage != null) {
myKeyStorage.force();
}
flush();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
protected final void markDirty(boolean dirty) throws IOException {
synchronized (ourLock) {
if (myDirty) {
if (!dirty) {
markClean();
}
}
else {
if (dirty) {
myStorage.putInt(0, DIRTY_MAGIC);
myDirty = true;
}
}
}
}
private void markCorrupted() {
if (!myCorrupted) {
myCorrupted = true;
try {
markDirty(true);
force();
}
catch (IOException e) {
// ignore...
}
}
}
protected void markClean() throws IOException {
if (!myCorrupted) {
myStorage.putInt(0, CORRECTLY_CLOSED_MAGIC);
myDirty = false;
}
}
private static class FlyweightKey extends CacheKey {
public FlyweightKey() {
super(null, null);
}
public ShareableKey getStableCopy() {
return new CacheKey(key, owner);
}
}
}
}
@@ -0,0 +1,477 @@
/*
* Copyright 2000-2009 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.openapi.Forceable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.containers.SLRUMap;
import com.intellij.util.containers.ShareableKey;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author max
* @author jeka
*/
abstract class PersistentEnumeratorBase<Data> implements Forceable, Closeable {
protected static final Logger LOG = Logger.getInstance("#com.intellij.util.io.PersistentEnumerator");
protected static final int NULL_ID = 0;
private static final int DIRTY_MAGIC = 0xbabe0589;
private static final int VERSION = 6;
private static final int CORRECTLY_CLOSED_MAGIC = 0xebabafac + VERSION;
private static final int META_DATA_OFFSET = 4;
protected static final int DATA_START = META_DATA_OFFSET + 4;
protected final ResizeableMappedFile myStorage;
private final ResizeableMappedFile myKeyStorage;
private boolean myClosed = false;
private boolean myDirty = false;
protected final KeyDescriptor<Data> myDataDescriptor;
private static final CacheKey ourFlyweight = new FlyweightKey();
protected final File myFile;
private boolean myCorrupted = false;
private final MyDataIS myKeyReadStream;
private static class CacheKey implements ShareableKey {
public PersistentEnumeratorBase owner;
public Object key;
private CacheKey(final Object key, final PersistentEnumeratorBase owner) {
this.key = key;
this.owner = owner;
}
public ShareableKey getStableCopy() {
return this;
}
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof CacheKey)) return false;
final CacheKey cacheKey = (CacheKey)o;
if (!key.equals(cacheKey.key)) return false;
if (!owner.equals(cacheKey.owner)) return false;
return true;
}
public int hashCode() {
return key.hashCode();
}
}
private static CacheKey sharedKey(Object key, PersistentEnumeratorBase owner) {
ourFlyweight.key = key;
ourFlyweight.owner = owner;
return ourFlyweight;
}
protected static final PagedFileStorage.StorageLock ourLock = new PagedFileStorage.StorageLock();
private static final int ENUMERATION_CACHE_SIZE;
static {
String property = System.getProperty("idea.enumerationCacheSize");
ENUMERATION_CACHE_SIZE = property == null ? 8192 : Integer.valueOf(property);
}
private static final SLRUMap<Object, Integer> ourEnumerationCache = new SLRUMap<Object, Integer>(ENUMERATION_CACHE_SIZE, ENUMERATION_CACHE_SIZE);
@TestOnly
public static void clearCacheForTests() {
ourEnumerationCache.clear();
}
public static class CorruptedException extends IOException {
@SuppressWarnings({"HardCodedStringLiteral"})
public CorruptedException(File file) {
super("PersistentStringEnumerator storage corrupted " + file.getPath());
}
}
public PersistentEnumeratorBase(File file, KeyDescriptor<Data> dataDescriptor, int initialSize) throws IOException {
myDataDescriptor = dataDescriptor;
myFile = file;
if (!file.exists()) {
FileUtil.delete(keystreamFile());
if (!FileUtil.createIfDoesntExist(file)) {
throw new IOException("Cannot create empty file: " + file);
}
}
myStorage = new ResizeableMappedFile(myFile, initialSize, ourLock);
synchronized (ourLock) {
if (myStorage.length() == 0) {
try {
markDirty(true);
putMetaData(0);
setupEmptyFile();
}
catch (RuntimeException e) {
LOG.info(e);
myStorage.close();
if (e.getCause() instanceof IOException) {
throw (IOException)e.getCause();
}
throw e;
}
catch (IOException e) {
LOG.info(e);
myStorage.close();
throw e;
}
catch (Exception e) {
LOG.info(e);
myStorage.close();
throw new CorruptedException(file);
}
}
else {
int sign;
try {
sign = myStorage.getInt(0);
}
catch(Exception e) {
LOG.info(e);
sign = DIRTY_MAGIC;
}
if (sign != CORRECTLY_CLOSED_MAGIC) {
myStorage.close();
throw new CorruptedException(file);
}
}
}
if (myDataDescriptor instanceof InlineKeyDescriptor) {
myKeyStorage = null;
myKeyReadStream = null;
}
else {
myKeyStorage = new ResizeableMappedFile(keystreamFile(), initialSize, ourLock);
myKeyReadStream = new MyDataIS(myKeyStorage);
}
}
protected abstract void setupEmptyFile() throws IOException;
protected int tryEnumerate(Data value) throws IOException {
synchronized (ourEnumerationCache) {
final Integer cachedId = ourEnumerationCache.get(sharedKey(value, this));
if (cachedId != null) return cachedId.intValue();
}
final int id;
synchronized (this) {
synchronized (ourLock) {
id = enumerateImpl(value, false);
}
}
if (id != NULL_ID) {
synchronized (ourEnumerationCache) {
ourEnumerationCache.put(new CacheKey(value, this), id);
}
}
return id;
}
public int enumerate(Data value) throws IOException {
synchronized (ourEnumerationCache) {
final Integer cachedId = ourEnumerationCache.get(sharedKey(value, this));
if (cachedId != null) {
return cachedId.intValue();
}
}
final int id;
synchronized (this) {
synchronized (ourLock) {
id = enumerateImpl(value, true);
}
}
synchronized (ourEnumerationCache) {
ourEnumerationCache.put(new CacheKey(value, this), id);
}
return id;
}
public interface DataFilter {
boolean accept(int id);
}
protected void putMetaData(int data) throws IOException {
synchronized (ourLock) {
myStorage.putInt(META_DATA_OFFSET, data);
}
}
protected int getMetaData() throws IOException {
synchronized (ourLock) {
return myStorage.getInt(META_DATA_OFFSET);
}
}
public boolean processAllDataObject(final Processor<Data> processor, @Nullable final DataFilter filter) throws IOException {
return traverseAllRecords(new RecordsProcessor() {
public boolean process(final int record) throws IOException {
if (filter == null || filter.accept(record)) {
return processor.process(valueOf(record));
}
return true;
}
});
}
public Collection<Data> getAllDataObjects(@Nullable final DataFilter filter) throws IOException {
final List<Data> values = new ArrayList<Data>();
processAllDataObject(new CommonProcessors.CollectProcessor<Data>(values), filter);
return values;
}
public interface RecordsProcessor {
boolean process(int record) throws IOException;
}
public abstract boolean traverseAllRecords(RecordsProcessor p) throws IOException;
protected abstract int enumerateImpl(final Data value, final boolean saveNewValue) throws IOException;
protected int writeData(final Data value, int hashCode) {
try {
markDirty(true);
final int dataOff = myKeyStorage != null ? (int)myKeyStorage.length() : ((InlineKeyDescriptor<Data>)myDataDescriptor).toInt(value);
byte[] buf = prepareEntryRecordBuf(hashCode, dataOff);
if (myKeyStorage != null) {
final BufferExposingByteArrayOutputStream bos = new BufferExposingByteArrayOutputStream();
DataOutput out = new DataOutputStream(bos);
myDataDescriptor.save(out, value);
myKeyStorage.put(dataOff, bos.getInternalBuffer(), 0, bos.size());
}
final int pos = recordWriteOffset(buf);
myStorage.put(pos, buf, 0, buf.length);
return pos;
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
protected abstract int recordWriteOffset(byte[] buf);
private byte[] prepareEntryRecordBuf(int hashCode, int dataOffset) {
final byte[] buf = getRecordBuffer();
setupRecord(hashCode, dataOffset, buf);
return buf;
}
protected abstract byte[] getRecordBuffer();
protected abstract void setupRecord(int hashCode, final int dataOffset, final byte[] buf);
protected boolean iterateData(final Processor<Data> processor) throws IOException {
if (myKeyStorage == null) {
throw new UnsupportedOperationException("Iteration over InlineIntegerKeyDescriptors is not supported");
}
myKeyStorage.force();
DataInputStream keysStream = new DataInputStream(new BufferedInputStream(new LimitedInputStream(new FileInputStream(keystreamFile()),
(int)myKeyStorage.length())));
try {
try {
while (true) {
Data key = myDataDescriptor.read(keysStream);
if (!processor.process(key)) return false;
}
}
catch (EOFException e) {
// Done
}
return true;
}
finally {
keysStream.close();
}
}
private File keystreamFile() {
return new File(myFile.getPath() + ".keystream");
}
public synchronized Data valueOf(int idx) throws IOException {
synchronized (ourLock) {
try {
int addr = indexToAddr(idx);
if (myKeyReadStream == null) return ((InlineKeyDescriptor<Data>)myDataDescriptor).fromInt(addr);
myKeyReadStream.setup(addr, myKeyStorage.length());
return myDataDescriptor.read(myKeyReadStream);
}
catch (IOException io) {
markCorrupted();
throw io;
}
catch (Throwable e) {
markCorrupted();
throw new RuntimeException(e);
}
}
}
protected abstract int indexToAddr(int idx);
private static class MyDataIS extends DataInputStream {
private MyDataIS(ResizeableMappedFile raf) {
super(new MyBufferedIS(new MappedFileInputStream(raf, 0, 0)));
}
public void setup(long pos, long limit) {
((MyBufferedIS)in).setup(pos, limit);
}
}
private static class MyBufferedIS extends BufferedInputStream {
public MyBufferedIS(final InputStream in) {
super(in, 512);
}
public void setup(long pos, long limit) {
this.pos = 0;
count = 0;
((MappedFileInputStream)in).setup(pos, limit);
}
}
public synchronized void close() throws IOException {
synchronized (ourLock) {
if (!myClosed) {
myClosed = true;
try {
if (myKeyStorage != null) {
myKeyStorage.close();
}
flush();
}
finally {
myStorage.close();
}
}
}
}
public synchronized boolean isClosed() {
return myClosed;
}
public synchronized boolean isDirty() {
return myDirty;
}
private synchronized void flush() throws IOException {
synchronized (ourLock) {
if (myStorage.isDirty() || isDirty()) {
markDirty(false);
myStorage.force();
}
}
}
public synchronized void force() {
synchronized (ourLock) {
try {
if (myKeyStorage != null) {
myKeyStorage.force();
}
flush();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
protected final void markDirty(boolean dirty) throws IOException {
synchronized (ourLock) {
if (myDirty) {
if (!dirty) {
markClean();
}
}
else {
if (dirty) {
myStorage.putInt(0, DIRTY_MAGIC);
myDirty = true;
}
}
}
}
protected void markCorrupted() {
if (!myCorrupted) {
myCorrupted = true;
try {
markDirty(true);
force();
}
catch (IOException e) {
// ignore...
}
}
}
protected void markClean() throws IOException {
if (!myCorrupted) {
myStorage.putInt(0, CORRECTLY_CLOSED_MAGIC);
myDirty = false;
}
}
private static class FlyweightKey extends CacheKey {
public FlyweightKey() {
super(null, null);
}
public ShareableKey getStableCopy() {
return new CacheKey(key, owner);
}
}
}