HyCodeYourTale
classpublicPriority 3

IndexedStorageFile

com.hypixel.hytale.storage.IndexedStorageFile

implements Closeable

22

Methods

22

Public Methods

10

Fields

1

Constructors

Constants

intBLOB_COUNT_OFFSET= HOH.next(4)
intBLOB_HEADER_LENGTH= BOH.length()
IndexedStorageFile.OffsetHelperBOH= new IndexedStorageFile.OffsetHelper()
ThreadLocal<ByteBuffer>CACHED_TEMP_BUFFER= ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(HEADER_LENGTH))
intCOMPRESSED_LENGTH_OFFSET= BOH.next(4)
intDEFAULT_BLOB_COUNT= 1024
intDEFAULT_COMPRESSION_LEVEL= 3
intDEFAULT_SEGMENT_SIZE= 4096
StampedLock[]EMPTY_STAMPED_LOCKS= new StampedLock[0]
intFIRST_SEGMENT_INDEX= 1
intHEADER_LENGTH= HOH.length()
IndexedStorageFile.OffsetHelperHOH= new IndexedStorageFile.OffsetHelper()
intINDEX_SIZE= 4
ByteBufferMAGIC_BUFFER= ByteBuffer.wrap(MAGIC_BYTES)
byte[]MAGIC_BYTES= "HytaleIndexedStorage".getBytes(StandardCharsets.UTF_8)
intMAGIC_LENGTH= 20
intMAGIC_OFFSET= HOH.next(20)
StringMAGIC_STRING= "HytaleIndexedStorage"
MetricsRegistry<IndexedStorageFile>METRICS_REGISTRY= <complex>
intSEGMENT_SIZE_OFFSET= HOH.next(4)
intSRC_LENGTH_OFFSET= BOH.next(4)
intUNASSIGNED_INDEX= 0
intVERSION= 1
intVERSION_OFFSET= HOH.next(4)

Constructors

private
IndexedStorageFile(Path path, FileChannel fileChannel)

Methods

Public Methods (22)

public
void close()

throws IOException

@Override
public
void force(boolean metaData)

throws IOException

public
int getBlobCount()
public
int getCompressionLevel()
public
Path getPath()
@Nonnull
public
int getSegmentSize()
public
IntList keys()
@Nonnull
public
int length()
public
FileLock lock()

throws IOException

public
int next(int len)
public
ByteBuffer readBlob(int blobIndex)

throws IOException

@Nullable
public
void readBlob(int blobIndex, ByteBuffer dest)

throws IOException

public
int readBlobCompressedLength(int blobIndex)

throws IOException

public
int readBlobLength(int blobIndex)

throws IOException

public
void removeBlob(int blobIndex)

throws IOException

public
int segmentCount()
public
int segmentSize()
public
void setCompressionLevel(int compressionLevel)
public
void setFlushOnWrite(boolean flushOnWrite)
public
long size()

throws IOException

public
String toString()
@Nonnull@Override
public
void writeBlob(int blobIndex, ByteBuffer src)

throws IOException

Fields

Private/Package Fields (10)

privateint blobCount
privateint compressionLevel
privateFileChannel fileChannel
privateboolean flushOnWrite
privateStampedLock[] indexLocks
privateMappedByteBuffer mappedBlobIndexes
privatePath path
privateStampedLock[] segmentLocks
privateint segmentSize
privateint version

Inheritance

Parent
Current
Interface
Child

Use mouse wheel to zoom, drag to pan. Click nodes to navigate.

Related Classes

Source Code

package com.hypixel.hytale.storage;

import com.github.luben.zstd.Zstd;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.metrics.MetricsRegistry;
import com.hypixel.hytale.unsafe.UnsafeUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.StampedLock;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class IndexedStorageFile implements Closeable {
   public static final StampedLock[] EMPTY_STAMPED_LOCKS = new StampedLock[0];
   public static final MetricsRegistry<IndexedStorageFile> METRICS_REGISTRY = new MetricsRegistry<IndexedStorageFile>()
      .register("Size", file -> {
         try {
            return file.size();
         } catch (IOException var2) {
            return -1L;
         }
      }, Codec.LONG)
      .register("CompressionLevel", file -> file.getCompressionLevel(), Codec.INTEGER)
      .register("BlobCount", file -> file.getBlobCount(), Codec.INTEGER)
      .register("UsedBlobCount", file -> file.keys().size(), Codec.INTEGER)
      .register("SegmentSize", file -> file.segmentSize(), Codec.INTEGER)
      .register("SegmentCount", file -> file.segmentCount(), Codec.INTEGER);
   public static final String MAGIC_STRING = "HytaleIndexedStorage";
   public static final int VERSION = 1;
   public static final int DEFAULT_BLOB_COUNT = 1024;
   public static final int DEFAULT_SEGMENT_SIZE = 4096;
   public static final int DEFAULT_COMPRESSION_LEVEL = 3;
   static final IndexedStorageFile.OffsetHelper HOH = new IndexedStorageFile.OffsetHelper();
   public static final int MAGIC_LENGTH = 20;
   public static final int MAGIC_OFFSET = HOH.next(20);
   public static final int VERSION_OFFSET = HOH.next(4);
   public static final int BLOB_COUNT_OFFSET = HOH.next(4);
   public static final int SEGMENT_SIZE_OFFSET = HOH.next(4);
   public static final int HEADER_LENGTH = HOH.length();
   static final IndexedStorageFile.OffsetHelper BOH = new IndexedStorageFile.OffsetHelper();
   public static final int SRC_LENGTH_OFFSET = BOH.next(4);
   public static final int COMPRESSED_LENGTH_OFFSET = BOH.next(4);
   public static final int BLOB_HEADER_LENGTH = BOH.length();
   public static final int INDEX_SIZE = 4;
   public static final int UNASSIGNED_INDEX = 0;
   public static final int FIRST_SEGMENT_INDEX = 1;
   public static final FileAttribute<?>[] NO_ATTRIBUTES = new FileAttribute[0];
   static final byte[] MAGIC_BYTES = "HytaleIndexedStorage".getBytes(StandardCharsets.UTF_8);
   private static final ByteBuffer MAGIC_BUFFER = ByteBuffer.wrap(MAGIC_BYTES);
   private static final ThreadLocal<ByteBuffer> CACHED_TEMP_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(HEADER_LENGTH));
   @Nonnull
   private final Path path;
   private final FileChannel fileChannel;
   private boolean flushOnWrite = false;
   private int compressionLevel = 3;
   private int version;
   private int blobCount;
   private int segmentSize;
   private StampedLock[] indexLocks;
   private MappedByteBuffer mappedBlobIndexes;
   private final StampedLock segmentLocksLock = new StampedLock();
   private StampedLock[] segmentLocks = EMPTY_STAMPED_LOCKS;
   private final StampedLock usedSegmentsLock = new StampedLock();
   private final BitSet usedSegments = new BitSet();

   @Nonnull
   private static ByteBuffer getTempBuffer(int length) {
      ByteBuffer buffer = CACHED_TEMP_BUFFER.get();
      buffer.position(0);
      buffer.limit(length);
      return buffer;
   }

   @Nonnull
   private static ByteBuffer allocateDirect(int length) {
      return ByteBuffer.allocateDirect(length);
   }

   @Nonnull
   public static IndexedStorageFile open(@Nonnull Path path, OpenOption... options) throws IOException {
      return open(path, 1024, 4096, Set.of(options), NO_ATTRIBUTES);
   }

   @Nonnull
   public static IndexedStorageFile open(@Nonnull Path path, @Nonnull Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
      return open(path, 1024, 4096, options, attrs);
   }

   @Nonnull
   public static IndexedStorageFile open(@Nonnull Path path, int blobCount, int segmentSize, OpenOption... options) throws IOException {
      return open(path, blobCount, segmentSize, Set.of(options), NO_ATTRIBUTES);
   }

   @Nonnull
   public static IndexedStorageFile open(
      @Nonnull Path path, int blobCount, int segmentSize, @Nonnull Set<? extends OpenOption> options, FileAttribute<?>... attrs
   ) throws IOException {
      IndexedStorageFile storageFile = new IndexedStorageFile(path, FileChannel.open(path, options, attrs));
      if (options.contains(StandardOpenOption.CREATE_NEW)) {
         storageFile.create(blobCount, segmentSize);
         return storageFile;
      } else {
         if (options.contains(StandardOpenOption.CREATE) && storageFile.fileChannel.size() == 0L) {
            storageFile.create(blobCount, segmentSize);
         } else {
            if (storageFile.fileChannel.size() == 0L) {
               throw new IOException("file channel is empty");
            }

            storageFile.readHeader();
            storageFile.memoryMapBlobIndexes();
            if (storageFile.version == 0) {
               storageFile = migrateV0(path, blobCount, segmentSize, options, attrs, storageFile);
            } else {
               storageFile.readUsedSegments();
            }
         }

         return storageFile;
      }
   }

   private static IndexedStorageFile migrateV0(
      Path path, int blobCount, int segmentSize, Set<? extends OpenOption> options, FileAttribute<?>[] attrs, IndexedStorageFile storageFile
   ) throws IOException {
      storageFile.close();
      Path tempFile = path.resolveSibling(path.getFileName().toString() + ".old");
      Path tempPath = Files.move(path, tempFile, StandardCopyOption.REPLACE_EXISTING);
      HashSet<OpenOption> newOptions = new HashSet<>(options);
      newOptions.add(StandardOpenOption.CREATE);
      storageFile = new IndexedStorageFile(path, FileChannel.open(path, newOptions, attrs));
      storageFile.create(blobCount, segmentSize);

      try (IndexedStorageFile_v0 oldStorageFile = new IndexedStorageFile_v0(tempPath, FileChannel.open(tempPath, options, attrs))) {
         oldStorageFile.open();

         for (int blobIndex = 0; blobIndex < blobCount; blobIndex++) {
            ByteBuffer blob = oldStorageFile.readBlob(blobIndex);
            if (blob != null) {
               storageFile.writeBlob(blobIndex, blob);
            }
         }
      } finally {
         Files.delete(tempFile);
      }

      return storageFile;
   }

   private IndexedStorageFile(@Nonnull Path path, @Nonnull FileChannel fileChannel) {
      this.path = path;
      this.fileChannel = fileChannel;
   }

   @Nonnull
   public Path getPath() {
      return this.path;
   }

   public int getBlobCount() {
      return this.blobCount;
   }

   public int getSegmentSize() {
      return this.segmentSize;
   }

   public int getCompressionLevel() {
      return this.compressionLevel;
   }

   public void setFlushOnWrite(boolean flushOnWrite) {
      this.flushOnWrite = flushOnWrite;
   }

   public void setCompressionLevel(int compressionLevel) {
      this.compressionLevel = compressionLevel;
   }

   @Nonnull
   protected IndexedStorageFile create(int blobCount, int segmentSize) throws IOException {
      if (blobCount <= 0) {
         throw new IllegalArgumentException("blobCount must be > 0");
      } else if (segmentSize <= 0) {
         throw new IllegalArgumentException("segmentSize must be > 0");
      } else {
         this.blobCount = blobCount;
         this.segmentSize = segmentSize;
         if (this.fileChannel.size() != 0L) {
            throw new IOException("file channel is not empty");
         } else {
            this.writeHeader(blobCount, segmentSize);
            this.memoryMapBlobIndexes();
            return this;
         }
      }
   }

   protected void writeHeader(int blobCount, int segmentSize) throws IOException {
      ByteBuffer header = getTempBuffer(HEADER_LENGTH);
      header.put(MAGIC_BYTES);
      header.putInt(VERSION_OFFSET, 1);
      header.putInt(BLOB_COUNT_OFFSET, blobCount);
      header.putInt(SEGMENT_SIZE_OFFSET, segmentSize);
      header.position(0);
      if (this.fileChannel.write(header, 0L) != HEADER_LENGTH) {
         throw new IllegalStateException();
      }
   }

   protected void readHeader() throws IOException {
      ByteBuffer header = getTempBuffer(HEADER_LENGTH);
      if (this.fileChannel.read(header, 0L) != HEADER_LENGTH) {
         throw new IllegalStateException();
      } else {
         header.position(0);
         header.limit(20);
         if (!MAGIC_BUFFER.equals(header)) {
            header.position(0);
            byte[] dst = new byte[20];
            header.get(dst);
            throw new IOException("Invalid MAGIC! " + header + ", " + Arrays.toString(dst) + " expected " + Arrays.toString(MAGIC_BYTES));
         } else {
            header.limit(HEADER_LENGTH);
            this.version = header.getInt(VERSION_OFFSET);
            if (this.version >= 0 && this.version <= 1) {
               this.blobCount = header.getInt(BLOB_COUNT_OFFSET);
               this.segmentSize = header.getInt(SEGMENT_SIZE_OFFSET);
            } else {
               throw new IOException("Invalid version! " + this.version);
            }
         }
      }
   }

   protected void memoryMapBlobIndexes() throws IOException {
      this.indexLocks = new StampedLock[this.blobCount];

      for (int i = 0; i < this.blobCount; i++) {
         this.indexLocks[i] = new StampedLock();
      }

      this.mappedBlobIndexes = this.fileChannel.map(MapMode.READ_WRITE, (long)HEADER_LENGTH, (long)this.blobCount * 4L);
   }

   protected void readUsedSegments() throws IOException {
      long stamp = this.usedSegmentsLock.writeLock();

      try {
         for (int blobIndex = 0; blobIndex < this.blobCount; blobIndex++) {
            int indexPos = blobIndex * 4;
            long segmentStamp = this.indexLocks[blobIndex].readLock();

            int firstSegmentIndex;
            int compressedLength;
            try {
               firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos);
               if (firstSegmentIndex == 0) {
                  compressedLength = 0;
               } else {
                  ByteBuffer blobHeaderBuffer = this.readBlobHeader(firstSegmentIndex);
                  compressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET);
               }
            } finally {
               this.indexLocks[blobIndex].unlockRead(segmentStamp);
            }

            if (compressedLength > 0) {
               int segmentsCount = this.requiredSegments((long)(BLOB_HEADER_LENGTH + compressedLength));
               this.usedSegments.set(firstSegmentIndex, firstSegmentIndex + segmentsCount);
            }
         }
      } finally {
         this.usedSegmentsLock.unlockWrite(stamp);
      }
   }

   public long size() throws IOException {
      return this.fileChannel.size();
   }

   public int segmentSize() {
      try {
         return this.requiredSegments(this.fileChannel.size() - this.segmentsBase()) + 1;
      } catch (IOException var2) {
         return -1;
      }
   }

   public int segmentCount() {
      long stamp = this.usedSegmentsLock.tryOptimisticRead();
      int count = this.usedSegments.cardinality();
      if (this.usedSegmentsLock.validate(stamp)) {
         return count;
      } else {
         stamp = this.usedSegmentsLock.readLock();

         int var4;
         try {
            var4 = this.usedSegments.cardinality();
         } finally {
            this.usedSegmentsLock.unlockRead(stamp);
         }

         return var4;
      }
   }

   @Nonnull
   public IntList keys() {
      IntArrayList list = new IntArrayList(this.blobCount);

      for (int blobIndex = 0; blobIndex < this.blobCount; blobIndex++) {
         int indexPos = blobIndex * 4;
         StampedLock lock = this.indexLocks[blobIndex];
         long stamp = lock.tryOptimisticRead();
         int segmentIndex = this.mappedBlobIndexes.getInt(indexPos);
         if (lock.validate(stamp)) {
            if (segmentIndex != 0) {
               list.add(blobIndex);
            }
         } else {
            stamp = lock.readLock();

            try {
               if (this.mappedBlobIndexes.getInt(indexPos) != 0) {
                  list.add(blobIndex);
               }
            } finally {
               lock.unlockRead(stamp);
            }
         }
      }

      return list;
   }

   public int readBlobLength(int blobIndex) throws IOException {
      if (blobIndex >= 0 && blobIndex < this.blobCount) {
         int indexPos = blobIndex * 4;
         long stamp = this.indexLocks[blobIndex].readLock();

         byte blobHeaderBuffer;
         try {
            int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos);
            if (firstSegmentIndex != 0) {
               ByteBuffer blobHeaderBufferx = this.readBlobHeader(firstSegmentIndex);
               return blobHeaderBufferx.getInt(SRC_LENGTH_OFFSET);
            }

            blobHeaderBuffer = 0;
         } finally {
            this.indexLocks[blobIndex].unlockRead(stamp);
         }

         return blobHeaderBuffer;
      } else {
         throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount);
      }
   }

   public int readBlobCompressedLength(int blobIndex) throws IOException {
      if (blobIndex >= 0 && blobIndex < this.blobCount) {
         int indexPos = blobIndex * 4;
         long stamp = this.indexLocks[blobIndex].readLock();

         byte blobHeaderBuffer;
         try {
            int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos);
            if (firstSegmentIndex != 0) {
               ByteBuffer blobHeaderBufferx = this.readBlobHeader(firstSegmentIndex);
               return blobHeaderBufferx.getInt(COMPRESSED_LENGTH_OFFSET);
            }

            blobHeaderBuffer = 0;
         } finally {
            this.indexLocks[blobIndex].unlockRead(stamp);
         }

         return blobHeaderBuffer;
      } else {
         throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount);
      }
   }

   @Nullable
   public ByteBuffer readBlob(int blobIndex) throws IOException {
      if (blobIndex >= 0 && blobIndex < this.blobCount) {
         int indexPos = blobIndex * 4;
         long stamp = this.indexLocks[blobIndex].readLock();

         ByteBuffer src;
         int srcLength;
         label43: {
            ByteBuffer blobHeaderBuffer;
            try {
               int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos);
               if (firstSegmentIndex != 0) {
                  blobHeaderBuffer = this.readBlobHeader(firstSegmentIndex);
                  srcLength = blobHeaderBuffer.getInt(SRC_LENGTH_OFFSET);
                  int compressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET);
                  src = this.readSegments(firstSegmentIndex, compressedLength);
                  break label43;
               }

               blobHeaderBuffer = null;
            } finally {
               this.indexLocks[blobIndex].unlockRead(stamp);
            }

            return blobHeaderBuffer;
         }

         src.position(0);
         return Zstd.decompress(src, srcLength);
      } else {
         throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount);
      }
   }

   public void readBlob(int blobIndex, @Nonnull ByteBuffer dest) throws IOException {
      if (blobIndex >= 0 && blobIndex < this.blobCount) {
         int indexPos = blobIndex * 4;
         long stamp = this.indexLocks[blobIndex].readLock();

         ByteBuffer src;
         int srcLength;
         try {
            int firstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos);
            if (firstSegmentIndex == 0) {
               return;
            }

            ByteBuffer blobHeaderBuffer = this.readBlobHeader(firstSegmentIndex);
            srcLength = blobHeaderBuffer.getInt(SRC_LENGTH_OFFSET);
            int compressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET);
            if (srcLength > dest.remaining()) {
               throw new IllegalArgumentException("dest buffer is not large enough! required dest.remaining() >= " + srcLength);
            }

            src = this.readSegments(firstSegmentIndex, compressedLength);
         } finally {
            this.indexLocks[blobIndex].unlockRead(stamp);
         }

         src.position(0);
         if (dest.isDirect()) {
            Zstd.decompress(dest, src);
         } else {
            ByteBuffer tempDest = allocateDirect(srcLength);

            try {
               Zstd.decompress(tempDest, src);
               tempDest.position(0);
               dest.put(tempDest);
            } finally {
               if (UnsafeUtil.UNSAFE != null) {
                  UnsafeUtil.UNSAFE.invokeCleaner(tempDest);
               }
            }
         }
      } else {
         throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount);
      }
   }

   @Nonnull
   protected ByteBuffer readBlobHeader(int firstSegmentIndex) throws IOException {
      if (firstSegmentIndex == 0) {
         throw new IllegalArgumentException("Invalid segment index!");
      } else {
         ByteBuffer blobHeaderBuffer = getTempBuffer(BLOB_HEADER_LENGTH);
         if (this.fileChannel.read(blobHeaderBuffer, this.segmentPosition(firstSegmentIndex)) != BLOB_HEADER_LENGTH) {
            throw new IllegalStateException();
         } else {
            return blobHeaderBuffer;
         }
      }
   }

   @Nonnull
   protected ByteBuffer readSegments(int firstSegmentIndex, int compressedLength) throws IOException {
      ByteBuffer buffer = allocateDirect(compressedLength);
      long segmentPosition = this.segmentPosition(firstSegmentIndex);
      if (this.fileChannel.read(buffer, segmentPosition + (long)BLOB_HEADER_LENGTH) != compressedLength) {
         throw new IllegalStateException();
      } else if (buffer.remaining() != 0) {
         throw new IOException("Failed to read segments: " + firstSegmentIndex + ", " + compressedLength + ", " + buffer);
      } else {
         return buffer;
      }
   }

   public void writeBlob(int blobIndex, @Nonnull ByteBuffer src) throws IOException {
      if (blobIndex >= 0 && blobIndex < this.blobCount) {
         int srcLength = src.remaining();
         int maxCompressedLength = (int)Zstd.compressBound((long)srcLength);
         ByteBuffer dest = allocateDirect(BLOB_HEADER_LENGTH + maxCompressedLength);
         dest.putInt(SRC_LENGTH_OFFSET, srcLength);
         dest.position(BLOB_HEADER_LENGTH);
         int compressedLength;
         if (src.isDirect()) {
            compressedLength = Zstd.compress(dest, src, this.compressionLevel);
         } else {
            ByteBuffer tempSrc = allocateDirect(srcLength);

            try {
               tempSrc.put(src);
               tempSrc.position(0);
               compressedLength = Zstd.compress(dest, tempSrc, this.compressionLevel);
            } finally {
               if (UnsafeUtil.UNSAFE != null) {
                  UnsafeUtil.UNSAFE.invokeCleaner(tempSrc);
               }
            }
         }

         dest.putInt(COMPRESSED_LENGTH_OFFSET, compressedLength);
         dest.limit(dest.position());
         dest.position(0);
         int indexPos = blobIndex * 4;
         long stamp = this.indexLocks[blobIndex].writeLock();

         try {
            int oldSegmentLength = 0;
            int oldFirstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos);
            if (oldFirstSegmentIndex != 0) {
               ByteBuffer blobHeaderBuffer = this.readBlobHeader(oldFirstSegmentIndex);
               int oldCompressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET);
               oldSegmentLength = this.requiredSegments((long)(BLOB_HEADER_LENGTH + oldCompressedLength));
            }

            int firstSegmentIndex = this.writeSegments(dest);
            if (this.flushOnWrite) {
               this.fileChannel.force(false);
            }

            this.mappedBlobIndexes.putInt(indexPos, firstSegmentIndex);
            if (this.flushOnWrite) {
               this.mappedBlobIndexes.force(indexPos, 4);
            }

            if (oldSegmentLength > 0) {
               long usedSegmentsStamp = this.usedSegmentsLock.writeLock();

               try {
                  this.usedSegments.clear(oldFirstSegmentIndex, oldFirstSegmentIndex + oldSegmentLength);
               } finally {
                  this.usedSegmentsLock.unlockWrite(usedSegmentsStamp);
               }
            }
         } finally {
            this.indexLocks[blobIndex].unlockWrite(stamp);
         }
      } else {
         throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount);
      }
   }

   public void removeBlob(int blobIndex) throws IOException {
      if (blobIndex >= 0 && blobIndex < this.blobCount) {
         int indexPos = blobIndex * 4;
         long stamp = this.indexLocks[blobIndex].writeLock();

         try {
            int oldFirstSegmentIndex = this.mappedBlobIndexes.getInt(indexPos);
            if (oldFirstSegmentIndex != 0) {
               ByteBuffer blobHeaderBuffer = this.readBlobHeader(oldFirstSegmentIndex);
               int oldCompressedLength = blobHeaderBuffer.getInt(COMPRESSED_LENGTH_OFFSET);
               int oldSegmentLength = this.requiredSegments((long)(BLOB_HEADER_LENGTH + oldCompressedLength));
               this.mappedBlobIndexes.putInt(indexPos, 0);
               if (this.flushOnWrite) {
                  this.mappedBlobIndexes.force(indexPos, 4);
               }

               long usedSegmentsStamp = this.usedSegmentsLock.writeLock();

               try {
                  this.usedSegments.clear(oldFirstSegmentIndex, oldFirstSegmentIndex + oldSegmentLength);
               } finally {
                  this.usedSegmentsLock.unlockWrite(usedSegmentsStamp);
               }
            }
         } finally {
            this.indexLocks[blobIndex].unlockWrite(stamp);
         }
      } else {
         throw new IndexOutOfBoundsException("Index out of range: " + blobIndex + " blobCount: " + this.blobCount);
      }
   }

   protected int writeSegments(@Nonnull ByteBuffer data) throws IOException {
      int dataRemaining = data.remaining();
      int segmentsCount = this.requiredSegments((long)dataRemaining);
      IndexedStorageFile.SegmentRangeWriteLock segmentLock = this.findFreeSegment(segmentsCount);

      int var8;
      try {
         int firstSegmentIndex = segmentLock.segmentIndex;
         if (this.fileChannel.write(data, this.segmentPosition(firstSegmentIndex)) != dataRemaining) {
            throw new IllegalStateException();
         }

         long stamp = this.usedSegmentsLock.writeLock();

         try {
            this.usedSegments.set(firstSegmentIndex, firstSegmentIndex + segmentsCount);
         } finally {
            this.usedSegmentsLock.unlockWrite(stamp);
         }

         var8 = firstSegmentIndex;
      } finally {
         segmentLock.unlock();
      }

      return var8;
   }

   @Nonnull
   private IndexedStorageFile.SegmentRangeWriteLock findFreeSegment(int count) {
      long[] stamps = new long[count];
      int index = 1;

      label98:
      while (true) {
         long indexesStamp = this.usedSegmentsLock.readLock();

         try {
            int start = 0;
            int found = 0;

            while (found < count) {
               int nextUsedIndex = this.usedSegments.nextSetBit(index);
               if (nextUsedIndex < 0) {
                  start = index;
                  break;
               }

               if (index == nextUsedIndex) {
                  start = this.usedSegments.nextClearBit(index);
                  nextUsedIndex = this.usedSegments.nextSetBit(start + 1);
                  if (nextUsedIndex < 0) {
                     break;
                  }

                  found = nextUsedIndex - start;
                  index = nextUsedIndex + 1;
               } else {
                  start = index;
                  found = nextUsedIndex - index;
                  index = nextUsedIndex + 1;
               }
            }

            for (int i = count - 1; i >= 0; i--) {
               stamps[i] = this.getSegmentLock(start + i).tryWriteLock();
               if (stamps[i] == 0L) {
                  for (int j = count - 1; j > i; j--) {
                     this.getSegmentLock(start + j).unlockWrite(stamps[j]);
                  }

                  index = start + i + 1;
                  continue label98;
               }
            }

            return new IndexedStorageFile.SegmentRangeWriteLock(start, count, stamps);
         } finally {
            this.usedSegmentsLock.unlockRead(indexesStamp);
         }
      }
   }

   protected StampedLock getSegmentLock(int segmentIndex) {
      if (segmentIndex < this.segmentLocks.length) {
         return this.segmentLocks[segmentIndex];
      } else {
         long stamp = this.segmentLocksLock.writeLock();

         StampedLock newLength;
         try {
            if (segmentIndex >= this.segmentLocks.length) {
               int newLengthx = segmentIndex + 1;
               StampedLock[] newArray = Arrays.copyOf(this.segmentLocks, newLengthx);

               for (int i = this.segmentLocks.length; i < newLengthx; i++) {
                  newArray[i] = new StampedLock();
               }

               this.segmentLocks = newArray;
               return this.segmentLocks[segmentIndex];
            }

            newLength = this.segmentLocks[segmentIndex];
         } finally {
            this.segmentLocksLock.unlockWrite(stamp);
         }

         return newLength;
      }
   }

   protected long segmentsBase() {
      return (long)HEADER_LENGTH + (long)this.blobCount * 4L;
   }

   protected long segmentOffset(int segmentIndex) {
      if (segmentIndex == 0) {
         throw new IllegalArgumentException("Invalid segment index!");
      } else {
         return (long)(segmentIndex - 1) * (long)this.segmentSize;
      }
   }

   protected long segmentPosition(int segmentIndex) {
      return this.segmentOffset(segmentIndex) + this.segmentsBase();
   }

   protected int positionToSegment(long position) {
      long segmentOffset = position - this.segmentsBase();
      if (segmentOffset < 0L) {
         throw new IllegalArgumentException("position is before the segments start");
      } else {
         return (int)(segmentOffset / (long)this.segmentSize) + 1;
      }
   }

   protected int requiredSegments(long dataLength) {
      return (int)((dataLength + (long)this.segmentSize - 1L) / (long)this.segmentSize);
   }

   public FileLock lock() throws IOException {
      return this.fileChannel.lock();
   }

   public void force(boolean metaData) throws IOException {
      this.fileChannel.force(metaData);
      this.mappedBlobIndexes.force();
   }

   @Override
   public void close() throws IOException {
      this.fileChannel.close();
      if (UnsafeUtil.UNSAFE != null) {
         UnsafeUtil.UNSAFE.invokeCleaner(this.mappedBlobIndexes);
      }

      this.mappedBlobIndexes = null;
   }

   @Nonnull
   @Override
   public String toString() {
      return "IndexedStorageFile{fileChannel="
         + this.fileChannel
         + ", compressionLevel="
         + this.compressionLevel
         + ", blobCount="
         + this.blobCount
         + ", segmentSize="
         + this.segmentSize
         + ", mappedBlobIndexes="
         + this.mappedBlobIndexes
         + ", usedSegments="
         + this.usedSegments
         + "}";
   }

   static {
      MAGIC_BUFFER.position(0);
   }

   static class OffsetHelper {
      private int index;

      OffsetHelper() {
      }

      public int next(int len) {
         int cur = this.index;
         this.index += len;
         return cur;
      }

      public int length() {
         return this.index;
      }
   }

   protected class SegmentRangeWriteLock {
      private final int segmentIndex;
      private final int count;
      private final long[] stamps;

      public SegmentRangeWriteLock(int segmentIndex, int count, long[] stamps) {
         if (segmentIndex == 0) {
            throw new IllegalArgumentException("Invalid segment index!");
         } else if (count == 0) {
            throw new IllegalArgumentException("Invalid count!");
         } else {
            this.segmentIndex = segmentIndex;
            this.count = count;
            this.stamps = stamps;
         }
      }

      protected void unlock() {
         for (int i = 0; i < this.count; i++) {
            IndexedStorageFile.this.getSegmentLock(this.segmentIndex + i).unlockWrite(this.stamps[i]);
            this.stamps[i] = 0L;
         }
      }
   }
}