001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Cellersion 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY CellIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020package org.apache.hadoop.hbase.regionserver;
021
022import java.nio.ByteBuffer;
023
024import com.google.common.annotations.VisibleForTesting;
025import org.apache.hadoop.hbase.Cell;
026import org.apache.yetus.audience.InterfaceAudience;
027import org.apache.hadoop.hbase.util.Bytes;
028import org.apache.hadoop.hbase.util.ByteBufferUtils;
029import org.apache.hadoop.hbase.util.ClassSize;
030
031import java.util.Comparator;
032
033
034/**
035 * CellChunkMap is an array of serialized representations of Cell
036 * (pointing to Chunks with full Cell data) and can be allocated both off-heap and on-heap.
037 *
038 * CellChunkMap is a byte array (chunk) holding all that is needed to access a Cell, which
039 * is actually saved on another deeper chunk.
040 * Per Cell we have a reference to this deeper byte array B (chunk ID, integer),
041 * offset in bytes in B (integer), length in bytes in B (integer) and seqID of the cell (long).
042 * In order to save reference to byte array we use the Chunk's ID given by ChunkCreator.
043 *
044 * The CellChunkMap memory layout on chunk A relevant to a deeper byte array B,
045 * holding the actual cell data:
046 *
047 * < header > <---------------     first Cell     -----------------> <-- second Cell ...
048 * --------------------------------------------------------------------------------------- ...
049 *  integer  | integer      | integer      | integer     | long     |
050 *  4 bytes  | 4 bytes      | 4 bytes      | 4 bytes     | 8 bytes  |
051 *  ChunkID  | chunkID of   | offset in B  | length of   | sequence |          ...
052 *  of this  | chunk B with | where Cell's | Cell's      | ID of    |
053 *  chunk A  | Cell data    | data starts  | data in B   | the Cell |
054 * --------------------------------------------------------------------------------------- ...
055 */
056@InterfaceAudience.Private
057public class CellChunkMap extends CellFlatMap {
058
059  private final Chunk[] chunks;             // the array of chunks, on which the index is based
060
061  // number of cell-representations in a chunk
062  // depends on the size of the chunks (may be index chunks or regular data chunks)
063  // each chunk starts with its own ID following the cells data
064  private final int numOfCellRepsInChunk;
065
066  /**
067   * C-tor for creating CellChunkMap from existing Chunk array, which must be ordered
068   * (decreasingly or increasingly according to parameter "descending")
069   * @param comparator a tool for comparing cells
070   * @param chunks ordered array of index chunk with cell representations
071   * @param min the index of the first cell (usually 0)
072   * @param max number of Cells or the index of the cell after the maximal cell
073   * @param descending the order of the given array
074   */
075  public CellChunkMap(Comparator<? super Cell> comparator,
076      Chunk[] chunks, int min, int max, boolean descending) {
077    super(comparator, min, max, descending);
078    this.chunks = chunks;
079    if (chunks != null && chunks.length != 0 && chunks[0] != null) {
080      this.numOfCellRepsInChunk = (chunks[0].size - ChunkCreator.SIZEOF_CHUNK_HEADER) /
081              ClassSize.CELL_CHUNK_MAP_ENTRY;
082    } else { // In case the chunks array was not allocated
083      this.numOfCellRepsInChunk = 0;
084    }
085  }
086
087  /* To be used by base (CellFlatMap) class only to create a sub-CellFlatMap
088  * Should be used only to create only CellChunkMap from CellChunkMap */
089  @Override
090  protected CellFlatMap createSubCellFlatMap(int min, int max, boolean descending) {
091    return new CellChunkMap(this.comparator(), this.chunks, min, max, descending);
092  }
093
094
095  @Override
096  protected Cell getCell(int i) {
097    // get the index of the relevant chunk inside chunk array
098    int chunkIndex = (i / numOfCellRepsInChunk);
099    ByteBuffer block = chunks[chunkIndex].getData();// get the ByteBuffer of the relevant chunk
100    int j = i - chunkIndex * numOfCellRepsInChunk; // get the index of the cell-representation
101
102    // find inside the offset inside the chunk holding the index, skip bytes for chunk id
103    int offsetInBytes = ChunkCreator.SIZEOF_CHUNK_HEADER + j* ClassSize.CELL_CHUNK_MAP_ENTRY;
104
105    // find the chunk holding the data of the cell, the chunkID is stored first
106    int chunkId = ByteBufferUtils.toInt(block, offsetInBytes);
107    Chunk chunk = ChunkCreator.getInstance().getChunk(chunkId);
108    if (chunk == null) {
109      // this should not happen
110      throw new IllegalArgumentException("In CellChunkMap, cell must be associated with chunk."
111          + ". We were looking for a cell at index " + i);
112    }
113
114    // find the offset of the data of the cell, skip integer for chunkID, offset is stored second
115    int offsetOfCell = ByteBufferUtils.toInt(block, offsetInBytes + Bytes.SIZEOF_INT);
116    // find the length of the data of the cell, skip two integers for chunkID and offset,
117    // length is stored third
118    int lengthOfCell = ByteBufferUtils.toInt(block, offsetInBytes + 2*Bytes.SIZEOF_INT);
119    // find the seqID of the cell, skip three integers for chunkID, offset, and length
120    // the seqID is plain written as part of the cell representation
121    long cellSeqID = ByteBufferUtils.toLong(block, offsetInBytes + 3*Bytes.SIZEOF_INT);
122
123    ByteBuffer buf = chunk.getData();   // get the ByteBuffer where the cell data is stored
124    if (buf == null) {
125      // this should not happen
126      throw new IllegalArgumentException("In CellChunkMap, chunk must be associated with ByteBuffer."
127          + " Chunk: " + chunk + " Chunk ID: " + chunk.getId() + ", is from pool: "
128          + chunk.isFromPool() + ". We were looking for a cell at index " + i);
129    }
130
131    return new ByteBufferChunkKeyValue(buf, offsetOfCell, lengthOfCell, cellSeqID);
132  }
133}