private static void checkAttributesSanity(
      final int attributeRecordId,
      final IntArrayList usedAttributeRecordIds,
      final IntArrayList validAttributeIds)
      throws IOException {
    assert !usedAttributeRecordIds.contains(attributeRecordId);
    usedAttributeRecordIds.add(attributeRecordId);

    final DataInputStream dataInputStream = getAttributesStorage().readStream(attributeRecordId);
    try {
      while (dataInputStream.available() > 0) {
        int attId = DataInputOutputUtil.readINT(dataInputStream);
        int attDataRecordId = DataInputOutputUtil.readINT(dataInputStream);
        assert !usedAttributeRecordIds.contains(attDataRecordId);
        usedAttributeRecordIds.add(attDataRecordId);
        if (!validAttributeIds.contains(attId)) {
          assert getNames().valueOf(attId).length() > 0;
          validAttributeIds.add(attId);
        }
        getAttributesStorage().checkSanity(attDataRecordId);
      }
    } finally {
      dataInputStream.close();
    }
  }
  public static Pair<String[], int[]> listAll(int parentId) {
    try {
      r.lock();
      try {
        final DataInputStream input = readAttribute(parentId, CHILDREN_ATT);
        if (input == null)
          return Pair.create(ArrayUtil.EMPTY_STRING_ARRAY, ArrayUtil.EMPTY_INT_ARRAY);

        final int count = DataInputOutputUtil.readINT(input);
        final int[] ids = ArrayUtil.newIntArray(count);
        final String[] names = ArrayUtil.newStringArray(count);
        for (int i = 0; i < count; i++) {
          int id = DataInputOutputUtil.readINT(input);
          id = id >= 0 ? id + parentId : -id;
          ids[i] = id;
          names[i] = getName(id);
        }
        input.close();
        return Pair.create(names, ids);
      } finally {
        r.unlock();
      }
    } catch (Throwable e) {
      throw DbConnection.handleError(e);
    }
  }
  public static int findRootRecord(String rootUrl) throws IOException {
    try {
      w.lock();
      DbConnection.markDirty();
      final int root = getNames().enumerate(rootUrl);

      final DataInputStream input = readAttribute(1, CHILDREN_ATT);
      int[] names = ArrayUtil.EMPTY_INT_ARRAY;
      int[] ids = ArrayUtil.EMPTY_INT_ARRAY;

      if (input != null) {
        try {
          final int count = DataInputOutputUtil.readINT(input);
          names = ArrayUtil.newIntArray(count);
          ids = ArrayUtil.newIntArray(count);
          for (int i = 0; i < count; i++) {
            final int name = DataInputOutputUtil.readINT(input);
            final int id = DataInputOutputUtil.readINT(input);
            if (name == root) {
              return id;
            }

            names[i] = name;
            ids[i] = id;
          }
        } finally {
          input.close();
        }
      }

      final DataOutputStream output = writeAttribute(1, CHILDREN_ATT, false);
      int id;
      try {
        id = createRecord();
        DataInputOutputUtil.writeINT(output, names.length + 1);
        for (int i = 0; i < names.length; i++) {
          DataInputOutputUtil.writeINT(output, names[i]);
          DataInputOutputUtil.writeINT(output, ids[i]);
        }
        DataInputOutputUtil.writeINT(output, root);
        DataInputOutputUtil.writeINT(output, id);
      } finally {
        output.close();
      }

      return id;
    } finally {
      w.unlock();
    }
  }
  private static void deleteContentAndAttributes(int id) throws IOException {
    int content_page = getContentRecordId(id);
    if (content_page != 0) {
      getContentStorage().releaseRecord(content_page);
    }

    int att_page = getAttributeRecordId(id);
    if (att_page != 0) {
      final DataInputStream attStream = getAttributesStorage().readStream(att_page);
      while (attStream.available() > 0) {
        DataInputOutputUtil.readINT(attStream); // Attribute ID;
        int attAddress = DataInputOutputUtil.readINT(attStream);
        getAttributesStorage().deleteRecord(attAddress);
      }
      attStream.close();
      getAttributesStorage().deleteRecord(att_page);
    }
  }
  private static int findAttributePage(int fileId, String attrId, boolean toWrite)
      throws IOException {
    checkFileIsValid(fileId);

    Storage storage = getAttributesStorage();

    int encodedAttrId = DbConnection.getAttributeId(attrId);
    int recordId = getAttributeRecordId(fileId);

    if (recordId == 0) {
      if (!toWrite) return 0;

      recordId = storage.createNewRecord();
      setAttributeRecordId(fileId, recordId);
    } else {
      DataInputStream attrRefs = storage.readStream(recordId);
      try {
        while (attrRefs.available() > 0) {
          final int attIdOnPage = DataInputOutputUtil.readINT(attrRefs);
          final int attrAddress = DataInputOutputUtil.readINT(attrRefs);

          if (attIdOnPage == encodedAttrId) return attrAddress;
        }
      } finally {
        attrRefs.close();
      }
    }

    if (toWrite) {
      Storage.AppenderStream appender = storage.appendStream(recordId);
      DataInputOutputUtil.writeINT(appender, encodedAttrId);
      int attrAddress = storage.createNewRecord();
      DataInputOutputUtil.writeINT(appender, attrAddress);
      DbConnection.REASONABLY_SMALL.myAttrPageRequested = true;
      try {
        appender.close();
      } finally {
        DbConnection.REASONABLY_SMALL.myAttrPageRequested = false;
      }
      return attrAddress;
    }

    return 0;
  }
  public static void deleteRootRecord(int id) throws IOException {
    try {
      w.lock();
      DbConnection.markDirty();
      final DataInputStream input = readAttribute(1, CHILDREN_ATT);
      assert input != null;
      int count;
      int[] names;
      int[] ids;
      try {
        count = DataInputOutputUtil.readINT(input);

        names = ArrayUtil.newIntArray(count);
        ids = ArrayUtil.newIntArray(count);
        for (int i = 0; i < count; i++) {
          names[i] = DataInputOutputUtil.readINT(input);
          ids[i] = DataInputOutputUtil.readINT(input);
        }
      } finally {
        input.close();
      }

      final int index = ArrayUtil.find(ids, id);
      assert index >= 0;

      names = ArrayUtil.remove(names, index);
      ids = ArrayUtil.remove(ids, index);

      final DataOutputStream output = writeAttribute(1, CHILDREN_ATT, false);
      try {
        DataInputOutputUtil.writeINT(output, count - 1);
        for (int i = 0; i < names.length; i++) {
          DataInputOutputUtil.writeINT(output, names[i]);
          DataInputOutputUtil.writeINT(output, ids[i]);
        }
      } finally {
        output.close();
      }
    } finally {
      w.unlock();
    }
  }
  public static int[] listRoots() throws IOException {
    try {
      r.lock();
      final DataInputStream input = readAttribute(1, CHILDREN_ATT);
      if (input == null) return ArrayUtil.EMPTY_INT_ARRAY;

      try {
        final int count = DataInputOutputUtil.readINT(input);
        int[] result = ArrayUtil.newIntArray(count);
        for (int i = 0; i < count; i++) {
          DataInputOutputUtil.readINT(input); // Name
          result[i] = DataInputOutputUtil.readINT(input); // Id
        }
        return result;
      } finally {
        input.close();
      }
    } finally {
      r.unlock();
    }
  }
  public static int[] list(int id) {
    try {
      r.lock();
      try {
        final DataInputStream input = readAttribute(id, CHILDREN_ATT);
        if (input == null) return ArrayUtil.EMPTY_INT_ARRAY;

        final int count = DataInputOutputUtil.readINT(input);
        final int[] result = ArrayUtil.newIntArray(count);
        for (int i = 0; i < count; i++) {
          int childId = DataInputOutputUtil.readINT(input);
          childId = childId >= 0 ? childId + id : -childId;
          result[i] = childId;
        }
        input.close();
        return result;
      } finally {
        r.unlock();
      }
    } catch (Throwable e) {
      throw DbConnection.handleError(e);
    }
  }
    static {
      File snapshotInfoFile = new File(new File(getJarsDir()), "snapshots_info");

      int currentVersion = -1;
      File versionFile = getVersionFile(snapshotInfoFile);
      if (versionFile.exists()) {
        try {
          DataInputStream versionStream =
              new DataInputStream(new BufferedInputStream(new FileInputStream(versionFile)));
          try {
            currentVersion = DataInputOutputUtil.readINT(versionStream);
          } finally {
            versionStream.close();
          }
        } catch (IOException ignore) {
        }
      }

      if (currentVersion != VERSION) {
        PersistentHashMap.deleteFilesStartingWith(snapshotInfoFile);
        saveVersion(versionFile);
      }

      PersistentHashMap<String, CacheLibraryInfo> info = null;
      for (int i = 0; i < 2; ++i) {
        try {
          info =
              new PersistentHashMap<String, CacheLibraryInfo>(
                  snapshotInfoFile,
                  new EnumeratorStringDescriptor(),
                  new DataExternalizer<CacheLibraryInfo>() {

                    @Override
                    public void save(@NotNull DataOutput out, CacheLibraryInfo value)
                        throws IOException {
                      IOUtil.writeUTF(out, value.mySnapshotPath);
                      out.writeLong(value.myModificationTime);
                      out.writeLong(value.myFileLength);
                    }

                    @Override
                    public CacheLibraryInfo read(@NotNull DataInput in) throws IOException {
                      return new CacheLibraryInfo(IOUtil.readUTF(in), in.readLong(), in.readLong());
                    }
                  });

          if (i == 0) removeStaleJarFilesIfNeeded(snapshotInfoFile, info);
          break;
        } catch (IOException ex) {
          PersistentHashMap.deleteFilesStartingWith(snapshotInfoFile);
          saveVersion(versionFile);
        }
      }

      assert info != null;
      ourCachedLibraryInfo = info;
      FlushingDaemon.everyFiveSeconds(
          new Runnable() {
            @Override
            public void run() {
              flushCachedLibraryInfos();
            }
          });

      ShutDownTracker.getInstance()
          .registerShutdownTask(
              new Runnable() {
                @Override
                public void run() {
                  flushCachedLibraryInfos();
                }
              });
    }