| /* |
| * Copyright (c) 2007, 2008, 2011, 2012, 2015 Eike Stepper (Berlin, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.net4j.util.io; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public abstract class SortedFileMap<K extends Comparable<K>, V> implements Closeable |
| { |
| private File file; |
| |
| private RandomAccessFile randomAccessFile; |
| |
| private ExtendedDataInput input; |
| |
| private ExtendedDataOutput output; |
| |
| private long entrySize; |
| |
| private long entryCount; |
| |
| /** |
| * @see RandomAccessFile#RandomAccessFile(File, String) |
| */ |
| public SortedFileMap(File file, String mode) |
| { |
| try |
| { |
| this.file = file; |
| randomAccessFile = new RandomAccessFile(file, mode); |
| input = new DataInputExtender(randomAccessFile); |
| output = new DataOutputExtender(randomAccessFile); |
| entrySize = getKeySize() + getValueSize(); |
| entryCount = randomAccessFile.length() / entrySize; |
| } |
| catch (IOException ex) |
| { |
| throw new IORuntimeException(ex); |
| } |
| } |
| |
| public void close() throws IOException |
| { |
| IOUtil.close(randomAccessFile); |
| } |
| |
| public File getFile() |
| { |
| return file; |
| } |
| |
| public RandomAccessFile getRandomAccessFile() |
| { |
| return randomAccessFile; |
| } |
| |
| public long getEntryCount() |
| { |
| return entryCount; |
| } |
| |
| public int getEntrySize() |
| { |
| return (int)entrySize; |
| } |
| |
| public long getPosition(long index) |
| { |
| return index * entrySize; |
| } |
| |
| public long getValuePosition(long index) |
| { |
| return getPosition(index) + getKeySize(); |
| } |
| |
| public K getMaxKey() |
| { |
| if (entryCount == 0) |
| { |
| return null; |
| } |
| |
| return getKey(entryCount - 1); |
| } |
| |
| public K getKey(long index) |
| { |
| try |
| { |
| long pos = getPosition(index); |
| randomAccessFile.seek(pos); |
| return readKey(input); |
| } |
| catch (IOException ex) |
| { |
| throw new IORuntimeException(ex); |
| } |
| } |
| |
| public V getValue(long index) |
| { |
| try |
| { |
| long pos = getValuePosition(index); |
| randomAccessFile.seek(pos); |
| return readValue(input); |
| } |
| catch (IOException ex) |
| { |
| throw new IORuntimeException(ex); |
| } |
| } |
| |
| public V get(K key) |
| { |
| try |
| { |
| long index = search(key); |
| if (index < 0) |
| { |
| return null; |
| } |
| |
| return readValue(input); |
| } |
| catch (IOException ex) |
| { |
| throw new IORuntimeException(ex); |
| } |
| } |
| |
| public V put(K key, V value) |
| { |
| try |
| { |
| long index = search(key); |
| if (index >= 0) |
| { |
| long pos = getValuePosition(index); |
| randomAccessFile.seek(pos); |
| V oldValue = readValue(input); |
| randomAccessFile.seek(pos); |
| writeValue(output, value); |
| return oldValue; |
| } |
| |
| index = -index - 1; |
| for (long i = entryCount; i > index; --i) |
| { |
| randomAccessFile.seek(getPosition(i - 1)); |
| K k = readKey(input); |
| randomAccessFile.seek(getValuePosition(i - 1)); |
| V v = readValue(input); |
| |
| randomAccessFile.seek(getPosition(i)); |
| writeKey(output, k); |
| randomAccessFile.seek(getValuePosition(i)); |
| writeValue(output, v); |
| } |
| |
| ++entryCount; |
| randomAccessFile.seek(getPosition(index)); |
| writeKey(output, key); |
| randomAccessFile.seek(getValuePosition(index)); |
| writeValue(output, value); |
| return null; |
| } |
| catch (IOException ex) |
| { |
| throw new IORuntimeException(ex); |
| } |
| } |
| |
| /** |
| * @return The index of the entry with the given key if the key exists, <code>-(insertionIndex + 1)</code> otherwise. |
| */ |
| protected long search(K key) throws IOException |
| { |
| long low = 0; |
| long high = entryCount - 1; |
| |
| while (low <= high) |
| { |
| long mid = low + high >> 1; |
| randomAccessFile.seek(getPosition(mid)); |
| Comparable<K> midVal = readKey(input); |
| int cmp = midVal.compareTo(key); |
| |
| if (cmp < 0) |
| { |
| low = mid + 1; |
| } |
| else if (cmp > 0) |
| { |
| high = mid - 1; |
| } |
| else |
| { |
| return mid; // key found |
| } |
| } |
| |
| return -(low + 1); // key not found. |
| } |
| |
| public abstract int getKeySize(); |
| |
| protected abstract K readKey(ExtendedDataInput in) throws IOException; |
| |
| protected abstract void writeKey(ExtendedDataOutput out, K key) throws IOException; |
| |
| public abstract int getValueSize(); |
| |
| protected abstract V readValue(ExtendedDataInput in) throws IOException; |
| |
| protected abstract void writeValue(ExtendedDataOutput out, V value) throws IOException; |
| } |