/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.map.diskmap;
import com.github.geophile.erdo.config.ConfigurationMap;
import com.github.geophile.erdo.map.diskmap.tree.Tree;
import com.github.geophile.erdo.map.diskmap.tree.TreeLevel;
import com.github.geophile.erdo.transaction.TimestampSet;
import com.github.geophile.erdo.util.Array;
import com.github.geophile.erdo.util.LongArray;
import java.io.File;
import java.io.IOException;
import java.util.StringTokenizer;
public class Manifest
{
public static Manifest write(File manifestFile, DiskMap map) throws IOException, InterruptedException
{
ConfigurationMap configurationMap = new ConfigurationMap();
configurationMap.value(TREE_ID, map.mapId());
boolean empty = map.recordCount() == 0;
if (empty) {
configurationMap.value(LEVELS, 0);
configurationMap.value(SEGMENTS, "0");
configurationMap.value(RECORD_COUNT, 0);
} else {
int levels = map.tree().levels();
configurationMap.value(LEVELS, levels);
configurationMap.value(SEGMENTS, levelSegmentCounts(map.tree()));
for (int level = 0; level < levels; level++) {
configurationMap.value(String.format(LEVEL_I_TEMPLATE, level),
segmentIds(map.tree(), level));
}
configurationMap.value(RECORD_COUNT, recordCount(map.tree()));
}
configurationMap.value(OBSOLETES, obsoleteTreeIds(map));
configurationMap.value(TIMESTAMPS, map.timestamps().toString());
configurationMap.write(manifestFile);
return new Manifest(configurationMap);
}
public static Manifest read(File manifestFile) throws IOException
{
Manifest manifest = null;
ConfigurationMap map = new ConfigurationMap();
if (manifestFile.exists()) {
map.read(manifestFile);
manifest = new Manifest(map);
}
return manifest;
}
public int treeId()
{
if (treeId == Integer.MIN_VALUE) {
treeId = configuration.intValue(TREE_ID);
}
return treeId;
}
public int levels()
{
if (levels == -1) {
levels = configuration.intValue(LEVELS);
}
return levels;
}
public LongArray segmentIds(int level)
{
if (segmentIds == null) {
segmentIds = new Array<>();
for (int i = 0; i < levels(); i++) {
segmentIds.append(null);
}
}
if (segmentIds.at(level) == null) {
segmentIds.at(level, configuration.longArray(String.format(LEVEL_I_TEMPLATE, level)));
}
return segmentIds.at(level);
}
public long recordCount()
{
if (recordCount == -1L) {
recordCount = configuration.longValue(RECORD_COUNT);
}
return recordCount;
}
public TimestampSet timestamps()
{
if (timestamps == null) {
timestamps = new TimestampSet();
StringTokenizer tokenizer = new StringTokenizer(configuration.value(TIMESTAMPS), ",");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
int dash = token.indexOf('-');
if (dash == -1) {
timestamps.append(Long.parseLong(token));
} else {
long start = Long.parseLong(token.substring(0, dash));
long end = Long.parseLong(token.substring(dash + 1));
timestamps.append(start, end);
}
}
}
return timestamps;
}
public LongArray obsoleteTreeIds()
{
if (obsoleteTreeIds == null) {
obsoleteTreeIds = new LongArray();
StringTokenizer tokenizer = new StringTokenizer(configuration.value(OBSOLETES), ",");
while (tokenizer.hasMoreTokens()) {
obsoleteTreeIds.append(Integer.parseInt(tokenizer.nextToken().trim()));
}
}
return obsoleteTreeIds;
}
// For use by this class
private static String levelSegmentCounts(Tree tree)
{
StringBuilder buffer = new StringBuilder();
for (int levelNumber = 0; levelNumber < tree.levels(); levelNumber++) {
if (levelNumber > 0) {
buffer.append(',');
}
buffer.append(tree.level(levelNumber).segments());
}
return buffer.toString();
}
private static String segmentIds(Tree tree, int levelNumber) throws IOException, InterruptedException
{
StringBuilder buffer = new StringBuilder();
TreeLevel level = tree.level(levelNumber);
for (int s = 0; s < level.segments(); s++) {
if (s > 0) {
buffer.append(',');
}
buffer.append(level.segment(s).segmentId());
}
return buffer.toString();
}
private static long recordCount(Tree tree)
{
long recordCount = 0;
TreeLevel level = tree.level(0);
for (int s = 0; s < level.segments(); s++) {
recordCount += level.segment(s).leafRecords();
}
return recordCount;
}
private static String obsoleteTreeIds(DiskMap map)
{
StringBuilder buffer = new StringBuilder();
for (Long id : map.obsoleteTreeIds()) {
if (buffer.length() > 0) {
buffer.append(',');
}
buffer.append(id);
}
return buffer.toString();
}
private Manifest(ConfigurationMap configuration)
{
this.configuration = configuration;
}
// Class state
private static final String TIMESTAMPS = "timestamps";
private static final String LEVELS = "levels";
private static final String SEGMENTS = "segments";
private static final String OBSOLETES = "obsoletes";
private static final String LEVEL_I_TEMPLATE = "level.%s";
private static final String RECORD_COUNT = "recordCount";
private static final String TREE_ID = "treeId";
// Object state
private final ConfigurationMap configuration;
private int treeId = Integer.MIN_VALUE;
private TimestampSet timestamps;
private int levels = -1;
private Array<LongArray> segmentIds;
private long recordCount = -1L;
private LongArray obsoleteTreeIds;
}