blob: e03374ab1e1c1551dcb14820df74e550e5b1b17a [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library index.page_node_manager;
import 'dart:collection';
import 'dart:typed_data';
import 'package:analysis_server/src/index/b_plus_tree.dart';
import 'package:analysis_server/src/index/lru_cache.dart';
/**
* A [NodeManager] that caches a specified number of index and leaf nodes.
*/
class CachingNodeManager<K, V, N> implements NodeManager<K, V, N> {
final NodeManager<K, V, N> _delegate;
LRUCache<N, IndexNodeData<K, N>> _indexCache;
LRUCache<N, LeafNodeData<K, V>> _leafCache;
CachingNodeManager(this._delegate, int indexNodeCacheSize,
int leafNodeCacheSize) {
_indexCache = new LRUCache<N, IndexNodeData<K, N>>(indexNodeCacheSize,
_delegate.writeIndex);
_leafCache = new LRUCache<N, LeafNodeData<K, V>>(leafNodeCacheSize,
_delegate.writeLeaf);
}
@override
int get maxIndexKeys => _delegate.maxIndexKeys;
@override
int get maxLeafKeys => _delegate.maxLeafKeys;
@override
N createIndex() {
return _delegate.createIndex();
}
@override
N createLeaf() {
return _delegate.createLeaf();
}
@override
void delete(N id) {
_indexCache.remove(id);
_leafCache.remove(id);
_delegate.delete(id);
}
@override
bool isIndex(N id) {
return _delegate.isIndex(id);
}
@override
IndexNodeData<K, N> readIndex(N id) {
IndexNodeData<K, N> data = _indexCache.get(id);
if (data == null) {
data = _delegate.readIndex(id);
_indexCache.put(id, data);
}
return data;
}
@override
LeafNodeData<K, V> readLeaf(N id) {
LeafNodeData<K, V> data = _leafCache.get(id);
if (data == null) {
data = _delegate.readLeaf(id);
_leafCache.put(id, data);
}
return data;
}
@override
void writeIndex(N id, IndexNodeData<K, N> data) {
_indexCache.put(id, data);
}
@override
void writeLeaf(N id, LeafNodeData<K, V> data) {
_leafCache.put(id, data);
}
}
/**
* A [Codec] encodes and decodes data.
*/
abstract class Codec<E> {
/**
* The size of the value in bytes.
*/
int get sizeInBytes;
/**
* Returns the value decoded from [buffer].
*
* The given [buffer] has exactly [sizeInBytes] bytes length.
*/
E decode(ByteData buffer);
/**
* Encodes [value] into [buffer].
*
* The given [buffer] has exactly [sizeInBytes] bytes length.
*/
void encode(ByteData buffer, E value);
}
/**
* A [Codec] for strings with a predefined maximum length.
*/
class FixedStringCodec implements Codec<String> {
final int maxLength;
final int sizeInBytes;
const FixedStringCodec(int maxLength)
: maxLength = maxLength,
sizeInBytes = 2 + 2 * maxLength;
@override
String decode(ByteData buffer) {
int length = buffer.getUint16(0);
int offset = 2;
List<int> codeUnits = new List<int>(length);
for (int i = 0; i < length; i++) {
codeUnits[i] = buffer.getUint16(offset);
offset += 2;
}
return new String.fromCharCodes(codeUnits);
}
@override
void encode(ByteData buffer, String value) {
int length = value.length;
if (length > maxLength) {
throw new ArgumentError(
'String $value length=$length is greater than allowed $maxLength');
}
buffer.setUint16(0, length);
int offset = 2;
List<int> codeUnits = value.codeUnits;
for (int i = 0; i < length; i++) {
buffer.setUint16(offset, codeUnits[i]);
offset += 2;
}
}
}
/**
* A [PageManager] that keeps all [Uint8List] pages in memory.
*/
class MemoryPageManager implements PageManager {
final int pageSizeInBytes;
int _nextPage = 0;
final Map<int, Uint8List> _pages = new HashMap<int, Uint8List>();
MemoryPageManager(this.pageSizeInBytes);
@override
int alloc() {
int id = _nextPage++;
Uint8List page = new Uint8List(pageSizeInBytes);
_pages[id] = page;
return id;
}
@override
void free(int id) {
Uint8List page = _pages.remove(id);
if (page == null) {
throw new StateError('Page $id has been already freed.');
}
}
@override
Uint8List read(int id) {
Uint8List page = _pages[id];
if (page == null) {
throw new StateError('Page $id does not exist.');
}
return page;
}
@override
void write(int id, Uint8List page) {
if (!_pages.containsKey(id)) {
throw new StateError('Page $id does not exist.');
}
if (page.length != pageSizeInBytes) {
throw new ArgumentError('Page $id has length ${page.length}, '
'but $pageSizeInBytes is expected.');
}
_pages[id] = page;
}
}
/**
* [PageManager] allows to allocate, read, write and free [Uint8List] pages.
*/
abstract class PageManager {
/**
* The size of pages provided by this [PageManager].
*/
int get pageSizeInBytes;
/**
* Allocates a new page and returns its identifier.
*/
int alloc();
/**
* Frees the page with the given identifier.
*/
void free(int id);
/**
* Reads the page with the given identifier and returns its content.
*
* An internal representation of the page is returned, any changes made to it
* may be accessible to other clients reading the same page.
*/
Uint8List read(int id);
/**
* Writes the given page.
*/
void write(int id, Uint8List page);
}
/**
* A [NodeManager] that keeps nodes in [PageManager].
*/
class PageNodeManager<K, V> implements NodeManager<K, V, int> {
static const int _INDEX_OFFSET_DATA = 4;
static const int _INDEX_OFFSET_KEY_COUNT = 0;
static const int _LEAF_OFFSET_DATA = 4;
static const int _LEAF_OFFSET_KEY_COUNT = 0;
final Set<int> _indexPages = new HashSet<int>();
Codec<K> _keyCodec;
final Set<int> _leafPages = new HashSet<int>();
PageManager _pageManager;
Codec<V> _valueCodec;
PageNodeManager(this._pageManager, this._keyCodec, this._valueCodec);
@override
int get maxIndexKeys {
int keySize = _keyCodec.sizeInBytes;
int childSize = 4;
int dataSize = _pageManager.pageSizeInBytes - _INDEX_OFFSET_DATA;
return (dataSize - childSize) ~/ (keySize + childSize);
}
@override
int get maxLeafKeys {
int keySize = _keyCodec.sizeInBytes;
int valueSize = _valueCodec.sizeInBytes;
int dataSize = _pageManager.pageSizeInBytes - _INDEX_OFFSET_DATA;
return dataSize ~/ (keySize + valueSize);
}
@override
int createIndex() {
int id = _pageManager.alloc();
_indexPages.add(id);
return id;
}
@override
int createLeaf() {
int id = _pageManager.alloc();
_leafPages.add(id);
return id;
}
@override
void delete(int id) {
_pageManager.free(id);
_indexPages.remove(id);
_leafPages.remove(id);
}
@override
bool isIndex(int id) {
return _indexPages.contains(id);
}
@override
IndexNodeData<K, int> readIndex(int id) {
Uint8List page = _pageManager.read(id);
// read header
int keyCount;
{
ByteData data = new ByteData.view(page.buffer);
keyCount = data.getInt32(_INDEX_OFFSET_KEY_COUNT);
}
// read keys/children
List<K> keys = new List<K>();
List<int> children = new List<int>();
int keySize = _keyCodec.sizeInBytes;
int offset = _INDEX_OFFSET_DATA;
for (int i = 0; i < keyCount; i++) {
// read child
{
ByteData byteData = new ByteData.view(page.buffer, offset);
int childPage = byteData.getUint32(0);
children.add(childPage);
offset += 4;
}
// read key
{
ByteData byteData = new ByteData.view(page.buffer, offset, keySize);
K key = _keyCodec.decode(byteData);
keys.add(key);
offset += keySize;
}
}
// read last child
{
ByteData byteData = new ByteData.view(page.buffer, offset);
int childPage = byteData.getUint32(0);
children.add(childPage);
}
// done
return new IndexNodeData<K, int>(keys, children);
}
@override
LeafNodeData<K, V> readLeaf(int id) {
Uint8List page = _pageManager.read(id);
// read header
int keyCount;
{
ByteData data = new ByteData.view(page.buffer);
keyCount = data.getInt32(_LEAF_OFFSET_KEY_COUNT);
}
// read keys/children
List<K> keys = new List<K>();
List<V> values = new List<V>();
int keySize = _keyCodec.sizeInBytes;
int valueSize = _valueCodec.sizeInBytes;
int offset = _LEAF_OFFSET_DATA;
for (int i = 0; i < keyCount; i++) {
// read key
{
ByteData byteData = new ByteData.view(page.buffer, offset, keySize);
K key = _keyCodec.decode(byteData);
keys.add(key);
offset += keySize;
}
// read value
{
ByteData byteData = new ByteData.view(page.buffer, offset);
V value = _valueCodec.decode(byteData);
values.add(value);
offset += valueSize;
}
}
// done
return new LeafNodeData<K, V>(keys, values);
}
@override
void writeIndex(int id, IndexNodeData<K, int> data) {
Uint8List page = new Uint8List(_pageManager.pageSizeInBytes);
// write header
int keyCount = data.keys.length;
{
ByteData byteData = new ByteData.view(page.buffer);
byteData.setUint32(PageNodeManager._INDEX_OFFSET_KEY_COUNT, keyCount);
}
// write keys/children
int keySize = _keyCodec.sizeInBytes;
int offset = PageNodeManager._INDEX_OFFSET_DATA;
for (int i = 0; i < keyCount; i++) {
// write child
{
ByteData byteData = new ByteData.view(page.buffer, offset);
byteData.setUint32(0, data.children[i]);
offset += 4;
}
// write key
{
ByteData byteData = new ByteData.view(page.buffer, offset, keySize);
_keyCodec.encode(byteData, data.keys[i]);
offset += keySize;
}
}
// write last child
{
ByteData byteData = new ByteData.view(page.buffer, offset);
byteData.setUint32(0, data.children.last);
}
// write page
_pageManager.write(id, page);
}
@override
void writeLeaf(int id, LeafNodeData<K, V> data) {
Uint8List page = new Uint8List(_pageManager.pageSizeInBytes);
// write header
int keyCount = data.keys.length;
{
ByteData byteData = new ByteData.view(page.buffer);
byteData.setUint32(PageNodeManager._LEAF_OFFSET_KEY_COUNT, keyCount);
}
// write keys/values
int keySize = _keyCodec.sizeInBytes;
int valueSize = _valueCodec.sizeInBytes;
int offset = PageNodeManager._LEAF_OFFSET_DATA;
for (int i = 0; i < keyCount; i++) {
// write key
{
ByteData byteData = new ByteData.view(page.buffer, offset, keySize);
_keyCodec.encode(byteData, data.keys[i]);
offset += keySize;
}
// write value
{
ByteData byteData = new ByteData.view(page.buffer, offset);
_valueCodec.encode(byteData, data.values[i]);
offset += valueSize;
}
}
// write page
_pageManager.write(id, page);
}
}
/**
* A [Codec] for unsigned 32-bit integers.
*/
class Uint32Codec implements Codec<int> {
static const Uint32Codec INSTANCE = const Uint32Codec._();
const Uint32Codec._();
@override
int get sizeInBytes => 4;
@override
int decode(ByteData buffer) {
return buffer.getUint32(0);
}
@override
void encode(ByteData buffer, int element) {
buffer.setUint32(0, element);
}
}