/* * Copyright 2011 Future Systems * * 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.krakenapps.logdb.query; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Array; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.krakenapps.codec.CustomCodec; import org.krakenapps.codec.EncodingRule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Deprecated public class FileBufferMap<K, V> implements Map<K, V> { private static File BASE_DIR = new File(System.getProperty("kraken.data.dir"), "kraken-logdb/query/"); private static int BYTEBUFFER_CAPACITY = 655360; // 640KB private Logger logger = LoggerFactory.getLogger(FileBufferMap.class); private ByteBuffer bb; private CustomCodec cc; private File file; private RandomAccessFile raf; private long rafLength; private ConcurrentMap<K, V> cache = new ConcurrentHashMap<K, V>(); private int cacheSize; private Map<K, Block> fp = new HashMap<K, Block>(); private PriorityQueue<Block> freeBlock = new PriorityQueue<Block>(11, new BlockComparator()); // unused private V classinfo = null; public FileBufferMap() throws IOException { this(10000); } public FileBufferMap(int cacheSize) throws IOException { this(cacheSize, null); } public FileBufferMap(CustomCodec cc) throws IOException { this(10000, cc); } public FileBufferMap(int cacheSize, CustomCodec cc) throws IOException { if (!BASE_DIR.exists()) BASE_DIR.mkdirs(); this.bb = ByteBuffer.allocate(BYTEBUFFER_CAPACITY); this.file = File.createTempFile("fbm", ".buf", BASE_DIR); this.file.deleteOnExit(); this.raf = new RandomAccessFile(file, "rw"); this.cacheSize = cacheSize; this.cc = cc; } @Override public int size() { return fp.size(); } @Override public boolean isEmpty() { return fp.isEmpty(); } @Override public boolean containsKey(Object key) { return fp.containsKey(key); } @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public V get(Object key) { if (!cache.containsKey(key) && fp.containsKey(key)) { try { Block b = fp.get(key); raf.seek(b.fp); raf.read(bb.array(), 0, b.size); Object obj = EncodingRule.decode(bb, cc); bb.clear(); if (obj.getClass().isArray()) { Class<?> component = classinfo.getClass().getComponentType(); int length = Array.getLength(obj); Object r = Array.newInstance(component, length); for (int i = 0; i < length; i++) Array.set(r, i, component.cast(Array.get(obj, i))); cache.put((K) key, (V) r); } else cache.put((K) key, (V) obj); } catch (IOException e) { logger.error("kraken logdb: get error", e); } } return cache.get(key); } @Override public V put(K key, V value) { if (classinfo == null && value != null) classinfo = value; V prev = cache.get(key); cache.put(key, value); if (fp.containsKey(key) && fp.get(key) != null) freeBlock.add(fp.get(key)); fp.put(key, null); if (cache.size() > cacheSize) flush(); return prev; } public void flush() { long offset = rafLength; int pos = 0; for (K key : cache.keySet()) { V value = cache.get(key); try { EncodingRule.encode(bb, value, cc); } catch (BufferOverflowException e) { write(offset, pos); offset += pos; pos = 0; bb.clear(); EncodingRule.encode(bb, value, cc); } fp.put(key, new Block(offset + pos, bb.position() - pos)); pos = bb.position(); } write(offset, pos); bb.clear(); cache.clear(); } private void write(long offset, int length) { try { raf.seek(offset); raf.write(bb.array(), 0, length); rafLength += length; } catch (IOException e) { logger.error("kraken logdb: flush error", e); } } @Override public V remove(Object key) { V prev = cache.remove(key); if (fp.containsKey(key)) { Block b = fp.remove(key); if (b != null) freeBlock.add(b); } return prev; } @Override public void putAll(Map<? extends K, ? extends V> m) { for (K key : m.keySet()) put(key, m.get(key)); } @Override public void clear() { cache.clear(); fp.clear(); freeBlock.clear(); try { raf.seek(0); } catch (IOException e) { } } @Override public Set<K> keySet() { if (fp == null) { logger.debug("kraken logdb: file buffer map is already closed."); return new HashSet<K>(); } return fp.keySet(); } @Override public Collection<V> values() { throw new UnsupportedOperationException(); } @Override public Set<Entry<K, V>> entrySet() { throw new UnsupportedOperationException(); } public void close() { try { raf.close(); } catch (IOException e) { } cache = null; fp = null; freeBlock = null; file.delete(); } private class Block { private long fp; private int size; private Block(long fp, int size) { this.fp = fp; this.size = size; } } private class BlockComparator implements Comparator<Block> { @Override public int compare(Block o1, Block o2) { return o2.size - o1.size; } } }