/** * Copyright 2013 Benjamin Lerer * * 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 io.horizondb.db.series; import io.horizondb.db.commitlog.ReplayPosition; import io.horizondb.io.ByteReader; import io.horizondb.io.ByteWriter; import io.horizondb.io.encoding.VarInts; import io.horizondb.io.serialization.Parser; import io.horizondb.io.serialization.Serializable; import io.horizondb.model.core.Field; import io.horizondb.model.core.util.SerializationUtils; import io.horizondb.model.schema.BlockPosition; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import javax.annotation.concurrent.Immutable; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import com.google.common.collect.Range; import static io.horizondb.model.core.util.SerializationUtils.computeRangeSerializedSize; import static io.horizondb.model.core.util.SerializationUtils.writeRange; /** * @author Benjamin * */ @Immutable public final class TimeSeriesPartitionMetaData implements Serializable { /** * The parser instance. */ private static final Parser<TimeSeriesPartitionMetaData> PARSER = new Parser<TimeSeriesPartitionMetaData>() { /** * {@inheritDoc} */ @Override public TimeSeriesPartitionMetaData parseFrom(ByteReader reader) throws IOException { Range<Field> range = SerializationUtils.parseRangeFrom(reader); ReplayPosition replayPosition = null; if (reader.readBoolean()) { replayPosition = ReplayPosition.parseFrom(reader); } long fileSize = VarInts.readUnsignedLong(reader); int numberOfBlocks = VarInts.readUnsignedInt(reader); LinkedHashMap<Range<Field>, BlockPosition> map = new LinkedHashMap<>(); for (int i = 0; i < numberOfBlocks; i++) { Range<Field> timeRange = SerializationUtils.parseRangeFrom(reader); BlockPosition position = BlockPosition.parseFrom(reader); map.put(timeRange, position); } return new TimeSeriesPartitionMetaData(range, map, replayPosition, fileSize); } }; /** * The partition time range. */ private final Range<Field> range; /** * The replay position of the latest data written on the disk. */ private final ReplayPosition replayPosition; /** * The expected file size */ private final long fileSize; /** * The positions of the block within the file. */ private final LinkedHashMap<Range<Field>, BlockPosition> blockPositions; /** * Returns the partition time range. * * @return the partition time range. */ public Range<Field> getRange() { return this.range; } /** * Return the replay position of the latest data written on the disk. * * @return the replay position of the latest data written on the disk */ public ReplayPosition getReplayPosition() { return this.replayPosition; } /** * Returns the expected file size. * * @return the expected file size */ public long getFileSize() { return this.fileSize; } /** * Returns the block positions. * * @return the block positions. */ public LinkedHashMap<Range<Field>, BlockPosition> getBlockPositions() { return this.blockPositions; } /** * Creates a new <code>TimeSeriesPartitionMetaData</code> by reading the data from the specified reader. * * @param reader the reader to read from. * @throws IOException if an I/O problem occurs */ public static TimeSeriesPartitionMetaData parseFrom(ByteReader reader) throws IOException { return getParser().parseFrom(reader); } /** * Returns the parser that can be used to deserialize <code>TimeSeriesPartitionMetaData</code> instances. * * @return the parser that can be used to deserialize <code>TimeSeriesPartitionMetaData</code> instances. */ public static Parser<TimeSeriesPartitionMetaData> getParser() { return PARSER; } /** * {@inheritDoc} */ @Override public int computeSerializedSize() { int size = computeRangeSerializedSize(this.range) + VarInts.computeUnsignedLongSize(this.fileSize) + 1; if (this.replayPosition != null) { size += this.replayPosition.computeSerializedSize(); } int numberOfBlocks = this.blockPositions.size(); size += VarInts.computeUnsignedIntSize(numberOfBlocks); for (Map.Entry<Range<Field>, BlockPosition> entry : this.blockPositions.entrySet()) { size += SerializationUtils.computeRangeSerializedSize(entry.getKey()); size += entry.getValue().computeSerializedSize(); } return size; } /** * {@inheritDoc} */ @Override public void writeTo(ByteWriter writer) throws IOException { writeRange(writer, this.range); if (this.replayPosition == null) { writer.writeBoolean(false); } else { writer.writeBoolean(true).writeObject(this.replayPosition); } VarInts.writeUnsignedLong(writer, this.fileSize); int numberOfBlocks = this.blockPositions.size(); VarInts.writeUnsignedInt(writer, numberOfBlocks); for (Map.Entry<Range<Field>, BlockPosition> entry : this.blockPositions.entrySet()) { SerializationUtils.writeRange(writer, entry.getKey()); entry.getValue().writeTo(writer); } } /** * Creates a new <code>Builder</code> instance. * * @return a new <code>Builder</code> instance. */ public static Builder newBuilder(Range<Field> range) { return new Builder(range); } /** * Creates a new <code>TimeSeriesPartitionMetaData</code> instance using the specified <code>Builder</code>. * * @param Builder return a new <code>TimeSeriesPartitionMetaData</code> instance. */ private TimeSeriesPartitionMetaData(Builder builder) { this(builder.range, builder.blockPositions, builder.replayPosition, builder.fileSize); } /** * Creates a new <code>TimeSeriesPartitionMetaData</code> instance using the specified <code>Builder</code>. * * @param range the partition time range * @param blockPositions the positions of the block * @param replayPosition the replay position of the latest data written on the disk * @param fileSize the expected file size. */ private TimeSeriesPartitionMetaData(Range<Field> range, LinkedHashMap<Range<Field>, BlockPosition> blockPositions, ReplayPosition replayPosition, long fileSize) { this.range = range; this.blockPositions = blockPositions; this.replayPosition = replayPosition; this.fileSize = fileSize; } /** * {@inheritDoc} */ @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("range", this.range) .append("blockPositions", this.blockPositions) .append("replayPosition", this.replayPosition) .append("fileSize", this.fileSize) .toString(); } /** * Builds instance of <code>TimeSeriesPartitionMetaData</code>. * */ public static class Builder { /** * The partition time range. */ private final Range<Field> range; /** * The replay position of the latest data written on the disk. */ private ReplayPosition replayPosition; /** * The positions of the blocks. */ private LinkedHashMap<Range<Field>, BlockPosition> blockPositions = new LinkedHashMap<>(); /** * The expected file size. */ private long fileSize = 0; /** * Creates a new <code>TimeSeriesPartitionMetaData</code> instance. * * @return a new <code>TimeSeriesPartitionMetaData</code> instance. */ public TimeSeriesPartitionMetaData build() { return new TimeSeriesPartitionMetaData(this); } /** * Specifies the block positions. * * @param blockPositions the block positions * @return this <code>Builder</code> */ public Builder blockPositions(Map<Range<Field>, BlockPosition> blockPositions) { this.blockPositions = new LinkedHashMap<>(blockPositions); return this; } /** * Sets the expected file size * * @param fileSize the expected file size * @return this <code>Builder</code> */ public Builder fileSize(long fileSize) { this.fileSize = fileSize; return this; } /** * Sets the replay position of the latest data written on the disk. * * @param replayPosition the replay position of the latest data written on the disk * @return this <code>Builder</code> */ public Builder replayPosition(ReplayPosition replayPosition) { this.replayPosition = replayPosition; return this; } /** * Must not be called from outside the enclosing class. */ private Builder(Range<Field> range) { this.range = range; } } }