com.cinchapi.concourse.server.storage.db.BlockIndex.java Source code

Java tutorial

Introduction

Here is the source code for com.cinchapi.concourse.server.storage.db.BlockIndex.java

Source

/*
 * Copyright (c) 2013-2016 Cinchapi Inc.
 * 
 * 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 com.cinchapi.concourse.server.storage.db;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.Iterator;
import java.util.Map;

import com.cinchapi.concourse.server.io.Byteable;
import com.cinchapi.concourse.server.io.ByteableCollections;
import com.cinchapi.concourse.server.io.Composite;
import com.cinchapi.concourse.server.io.FileSystem;
import com.cinchapi.concourse.server.io.Syncable;
import com.cinchapi.concourse.util.ByteBuffers;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;

/**
 * A reference that stores the start and end position for sequences of bytes
 * that relate to a key that is described by one or more {@link Byteable}
 * objects. A BlockIndex is associated with each {@link Block} to determine
 * where to look on disk for a particular {@code locator} or {@code locator}/
 * {@code key} pair.
 * 
 * @author Jeff Nelson
 */
public class BlockIndex implements Byteable, Syncable {

    // CON-256: The BlockIndex does not need to perform locking for concurrency
    // control since writes only happen from a single thread (the
    // BufferTransportThread requesting a sync on the parent Block) and reads
    // only happen when the parent Block has been synced and the BlockIndex is
    // no longer mutable.

    /**
     * Return a newly created BlockIndex.
     * 
     * @param file
     * @param expectedInsertions
     * @return the BlockIndex
     */
    public static BlockIndex create(String file, int expectedInsertions) {
        return new BlockIndex(file, expectedInsertions);
    }

    /**
     * Return the BlockIndex that is stored in {@code file}.
     * 
     * @param file
     * @return the BlockIndex
     */
    public static BlockIndex open(String file) {
        return new BlockIndex(file);
    }

    /**
     * Represents an entry that has not been recorded.
     */
    public static final int NO_ENTRY = -1;

    /**
     * The entries contained in the index.
     */
    private Map<Composite, Entry> entries;

    /**
     * The file where the BlockIndex is stored during an diskSync.
     */
    private final String file;

    /**
     * A flag that indicates if this index is mutable. An index is no longer
     * mutable after it has been synced.
     */
    private boolean mutable;

    /**
     * The running size of the index in bytes.
     */
    private transient int size = 0;

    /**
     * A {@link SoftReference} to the entries contained in the index that is
     * used to reduce memory overhead.
     */
    private SoftReference<Map<Composite, Entry>> softEntries;

    /**
     * Lazily construct an existing instance from the data in {@code file}.
     * 
     * @param file
     */
    public BlockIndex(String file) {
        this.file = file;
        this.mutable = false;
        this.entries = null;
        this.softEntries = null;
    }

    /**
     * Construct a new instance.
     * 
     * @param expectedInsertions
     */
    private BlockIndex(String file, int expectedInsertions) {
        this.file = file;
        this.entries = Maps.newHashMapWithExpectedSize(expectedInsertions);
        this.softEntries = null;
        this.mutable = true;
    }

    @Override
    public ByteBuffer getBytes() {
        Preconditions.checkState(mutable);
        ByteBuffer bytes = ByteBuffer.allocate(size());
        copyTo(bytes);
        bytes.rewind();
        return bytes;
    }

    /**
     * Return the end position for {@code byteables} if it exists, otherwise
     * return {@link #NO_ENTRY}.
     * 
     * @param byteables
     * @return the end position
     */
    public int getEnd(Byteable... byteables) {
        Composite composite = Composite.create(byteables);
        Entry entry = entries().get(composite);
        if (entry != null) {
            return entry.getEnd();
        } else {
            return NO_ENTRY;
        }
    }

    /**
     * Return the start position for {@code byteables} if it exists, otherwise
     * return {@code #NO_ENTRY}.
     * 
     * @param byteables
     * @return the start position
     */
    public int getStart(Byteable... byteables) {
        Composite composite = Composite.create(byteables);
        Entry entry = entries().get(composite);
        if (entry != null) {
            return entry.getStart();
        } else {
            return NO_ENTRY;
        }
    }

    /**
     * Record the end position for the {@code byteables}.
     * 
     * @param end
     * @param byteables
     */
    public void putEnd(int end, Byteable... byteables) {
        Preconditions.checkArgument(end >= 0, "Cannot have negative index. Tried to put %s", end);
        Preconditions.checkState(mutable);
        Composite composite = Composite.create(byteables);
        Entry entry = entries().get(composite);
        Preconditions.checkState(entry != null,
                "Cannot set the end position before setting " + "the start position. Tried to put %s", end);
        entry.setEnd(end);
    }

    /**
     * Record the start position for the {@code byteables}.
     * 
     * @param start
     * @param byteables
     */
    public void putStart(int start, Byteable... byteables) {
        Preconditions.checkArgument(start >= 0, "Cannot have negative index. Tried to put %s", start);
        Preconditions.checkState(mutable);
        Composite composite = Composite.create(byteables);
        Entry entry = entries().get(composite);
        if (entry == null) {
            entry = new Entry(composite);
            entries.put(composite, entry);
            size += entry.size() + 4;
        }
        entry.setStart(start);
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void sync() {
        Preconditions.checkState(mutable);
        FileChannel channel = FileSystem.getFileChannel(file);
        try {
            channel.write(getBytes());
            channel.force(true);
            softEntries = new SoftReference<Map<Composite, Entry>>(entries);
            mutable = false;
            entries = null;
        } catch (IOException e) {
            throw Throwables.propagate(e);
        } finally {
            FileSystem.closeFileChannel(channel); // CON-162
        }
    }

    @Override
    public void copyTo(ByteBuffer buffer) {
        Preconditions.checkState(mutable);
        for (Entry entry : entries.values()) {
            buffer.putInt(entry.size());
            entry.copyTo(buffer);
        }
    }

    /**
     * Return {@code true} if this index is considered <em>loaded</em> meaning
     * all of its entries are available in memory.
     * 
     * @return {@code true} if the entries are loaded
     */
    protected boolean isLoaded() { // visible for testing
        return mutable || (softEntries != null && softEntries.get() != null);
    }

    /**
     * Return the entries in this index. This method will lazily load the
     * entries on demand if they do not currently exist in memory.
     * 
     * @return the entries
     */
    private synchronized Map<Composite, Entry> entries() {
        if (mutable && entries != null) {
            return entries;
        } else if (!mutable && (softEntries == null || softEntries.get() == null)) { // do
                                                                                     // lazy
                                                                                     // load
            ByteBuffer bytes = FileSystem.map(file, MapMode.READ_ONLY, 0, FileSystem.getFileSize(file));
            Iterator<ByteBuffer> it = ByteableCollections.iterator(bytes);
            Map<Composite, Entry> entries = Maps.newHashMapWithExpectedSize(bytes.capacity() / Entry.CONSTANT_SIZE);
            while (it.hasNext()) {
                Entry entry = new Entry(it.next());
                entries.put(entry.getKey(), entry);
            }
            softEntries = new SoftReference<Map<Composite, Entry>>(entries);
            return softEntries.get();
        } else if (!mutable && softEntries.get() != null) {
            return softEntries.get();
        } else {
            // "If i'm really an engineer thats worth a damn, we won't ever get
            // to this point" -jnelson
            throw new IllegalStateException();
        }
    }

    /**
     * Represents a single entry in the Index.
     * 
     * @author Jeff Nelson
     */
    private final class Entry implements Byteable {

        private static final int CONSTANT_SIZE = 8; // start(4), end(4)

        private int end = NO_ENTRY;
        private final Composite key;
        private int start = NO_ENTRY;

        /**
         * Construct an instance that represents an existing Entry from a
         * ByteBuffer. This constructor is public so as to comply with the
         * {@link Byteable} interface. Calling this constructor directly is not
         * recommend. Use {@link #fromByteBuffer(ByteBuffer)} instead to take
         * advantage of reference caching.
         * 
         * @param bytes
         */
        public Entry(ByteBuffer bytes) {
            this.start = bytes.getInt();
            this.end = bytes.getInt();
            this.key = Composite.fromByteBuffer(ByteBuffers.get(bytes, bytes.remaining()));
        }

        /**
         * Construct a new instance.
         * 
         * @param key
         */
        public Entry(Composite key) {
            this.key = key;
        }

        @Override
        public ByteBuffer getBytes() {
            ByteBuffer bytes = ByteBuffer.allocate(size());
            copyTo(bytes);
            bytes.rewind();
            return bytes;
        }

        /**
         * Return the end position.
         * 
         * @return the end
         */
        public int getEnd() {
            return end;
        }

        /**
         * Return the entry key
         * 
         * @return the key
         */
        public Composite getKey() {
            return key;
        }

        /**
         * Return the start position.
         * 
         * @return the start
         */
        public int getStart() {
            return start;
        }

        /**
         * Set the end position.
         * 
         * @param end the end to set
         */
        public void setEnd(int end) {
            this.end = end;
        }

        /**
         * Set the start position.
         * 
         * @param start the start to set
         */
        public void setStart(int start) {
            this.start = start;
        }

        @Override
        public int size() {
            return CONSTANT_SIZE + key.size();
        }

        @Override
        public void copyTo(ByteBuffer buffer) {
            buffer.putInt(start);
            buffer.putInt(end);
            key.copyTo(buffer);
        }

    }

}