/* * Copyright 2011 b1.org * * 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 org.b1.pack.standard.builder; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import org.b1.pack.api.builder.BuilderVolume; import org.b1.pack.api.builder.Writable; import org.b1.pack.standard.common.*; import java.util.List; import java.util.Map; public class VolumeBuilder { private final List<CompositeWritable> volumeContents = Lists.newArrayList(); private final String archiveId = Volumes.createArchiveId(); // TODO content should be deterministic private final long maxVolumeSize; private final Map<Writable, PbRecordPointer> pointerMap; private final RecordPointer catalogPointer; private final long volumeLimit; private long volumeNumber; private CompositeWritable volumeContent; public VolumeBuilder(long maxVolumeSize, Map<Writable, PbRecordPointer> pointerMap, long objectCount) { this.maxVolumeSize = maxVolumeSize; this.pointerMap = pointerMap; initVolume(objectCount); catalogPointer = new RecordPointer(volumeNumber, volumeContent.getSize(), 0); volumeLimit = maxVolumeSize - PbInt.NULL.getSize() - Volumes.createVolumeTail(false, catalogPointer, 0, null).length; } public void addContent(Writable content) { Preconditions.checkNotNull(volumeContent); long contentOffset = 0; while (contentOffset < content.getSize()) { long chunkSize = addChunk(content, contentOffset); if (chunkSize == 0) { completeVolume(false); initVolume(null); chunkSize = addChunk(content, contentOffset); Preconditions.checkArgument(chunkSize > 0, "Volume size too small"); } contentOffset += chunkSize; } } public List<BuilderVolume> getVolumes() { if (volumeContent != null) { completeVolume(true); } int volumeCount = volumeContents.size(); List<BuilderVolume> result = Lists.newArrayListWithCapacity(volumeCount); for (int i = 0; i < volumeCount; i++) { result.add(new StandardBuilderVolume(i + 1, volumeContents.get(i))); } return result; } private void initVolume(Long objectCount) { volumeNumber++; volumeContent = new CompositeWritable(); volumeContent.add(new ByteArrayWritable(Volumes.createVolumeHead(archiveId, volumeNumber, objectCount, null, null))); } private void completeVolume(boolean lastVolume) { volumeContent.add(PbInt.NULL); long minSize = lastVolume ? 0 : maxVolumeSize - volumeContent.getSize(); volumeContent.add(new ByteArrayWritable(Volumes.createVolumeTail(lastVolume, catalogPointer, minSize, null))); volumeContents.add(volumeContent); volumeContent = null; } private long addChunk(Writable content, long contentOffset) { long chunkSize = Math.min(content.getSize() - contentOffset, Constants.MAX_CHUNK_SIZE); Writable chunk = new PartialWritable(content, contentOffset, contentOffset + chunkSize); PbBlock block = PbBlock.wrapPlainBlock(new PbPlainBlock(chunk)); long freeSpace = volumeLimit - volumeContent.getSize(); if (block.getSize() > freeSpace) { chunkSize -= block.getSize() - freeSpace; if (chunkSize <= 0) { return 0; } chunk = new PartialWritable(content, contentOffset, contentOffset + chunkSize); block = PbBlock.wrapPlainBlock(new PbPlainBlock(chunk)); } if (contentOffset == 0) { updatePointers(content); } volumeContent.add(block); return chunk.getSize(); } private void updatePointers(Writable content) { PbRecordPointer pointer = pointerMap.get(content); if (pointer != null) { pointer.setVolumeNumber(volumeNumber); pointer.setBlockOffset(volumeContent.getSize()); pointer.setRecordOffset(0); } } }