package com.bizosys.hsearch.filter; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class MergedBlocks { public static Block add(int pos, byte[] data) { return add(null, pos, data); } /** * Add a new element to the block * @param existing * @param pos * @param data * @return Block with added record */ public static Block add(Block existing, int pos, byte[] data) { if ( pos <= 0 ) throw new ArrayIndexOutOfBoundsException("Starts from 1, Received" + pos); int headerLocation = pos*4; byte[] header = null; int dataPos = 0; if ( null == existing || null == existing.header) { header = new byte[headerLocation+4]; Arrays.fill(header,(byte)-1); } else if (existing.header.length < headerLocation) { header = new byte[headerLocation]; Arrays.fill(header,(byte)-1); System.arraycopy(existing.header, 0, header, 0, existing.header.length); dataPos = existing.data.length ; } else { header = existing.header; if ( null != existing.data ) dataPos = existing.data.length ; } writeHeader(header, pos, dataPos); byte[] newData = null; if ( dataPos != 0 ) { newData = new byte[dataPos + data.length]; System.arraycopy(existing.data, 0, newData, 0, existing.data.length); System.arraycopy(data, 0, newData, existing.data.length, data.length); } else { newData = data; } return new Block(header, newData); } /** * Mass addition. This avoids unnecessary array copy. * @param records Records * @param headerSpace We passed a header byte array space, which can be reused. * @return Block with added record */ public static Block add(Map<Integer, byte[]> records, byte[] headerSpace) { //Total Bytes data needed int dataLen = 0; for (byte[] de : records.values()) { dataLen = dataLen + de.length; } Block newBlock = new Block(headerSpace, new byte[dataLen]); int dataPos = 0; int maxDocPos = headerSpace.length / 4; for ( int i=1; i<= maxDocPos; i++) { if ( !records.containsKey(i)) continue; writeHeader(newBlock.header, i, dataPos); byte[] data = records.get(i); System.arraycopy(data, 0, newBlock.data, dataPos, data.length); dataPos = dataPos + data.length; } return newBlock; } /** * Update an existing entry * @param block * @param blank * @param updated * @param docPos * @return Block with updated record */ public static Block update(Block block, IStorable blank, IStorable updated, int docPos) { byte[] modifiedB = updated.toBytes(); return update(block,blank,modifiedB,docPos); } public static Block update(Block block, IStorable blank, byte[] modifiedB, int docPos) { IStorable existing = get(blank, block, docPos); //TODO :: Check this Logic of returning null if ( null == existing) return null; byte[] existingB = existing.toBytes(); int modifiedLen = modifiedB.length; int existingLen = existingB.length; int blockLen = block.data.length; if ( modifiedLen > existingLen) { //Delete and append in End byte[] newBlocks = new byte[blockLen + modifiedLen]; System.arraycopy(block.data, 0, newBlocks, 0, blockLen); System.arraycopy(modifiedB, 0, newBlocks, blockLen, modifiedLen); writeHeader(block.header, docPos, blockLen); block.data = newBlocks; } else { //Overwrite System.arraycopy(modifiedB, 0, block.data, readHeader(block.header, docPos), modifiedLen); } return block; } /** * Delete a specific data zone. * @param existing * @param docPos * @return Block with deleted record */ public static Block delete(Block existing, int docPos) { writeHeader(existing.header, docPos, -1); return existing; } public static void delete(byte[] header, int docPos) { writeHeader(header, docPos, -1); } /** * Get a record from the specified position of the block * @param storable * @param block * @param docPos * @return IStorable Object with storable interface */ public static IStorable get(IStorable storable, Block block, int docPos) { int dataLoc = readHeader(block.header, docPos); if ( -1 == dataLoc) return null; storable.fromBytes(block.data, dataLoc); return storable; } /** * Compact the existing blocks * @param block * @param storable * @return Block Compacted format */ public static Block compact(Block block, IStorable storable) { Map<Integer, byte[]> deList = fromBytes(block, storable); block.data = null; byte[] headerB = block.header; return add(deList, headerB); } public static Block merge(Block existingBlock, Map<Integer, byte[]> addedList, IStorable storable) { int existingMax = ( null == existingBlock.header ) ? 0 : (existingBlock.header.length / 4); int newMax = Integer.MIN_VALUE; for (Integer pos : addedList.keySet()) { if ( pos > newMax ) newMax = pos; } if ( existingMax < newMax) { //Header Expansion byte[] header = new byte[newMax*4]; if ( 0 != existingMax) { System.arraycopy(existingBlock.header, 0, header, 0, existingBlock.header.length); } existingBlock.header = header; } int totalBytes = 0; for (Integer docPos : addedList.keySet()) { byte[] data = addedList.get(docPos); if ( docPos < existingMax) { existingBlock = update(existingBlock,storable, data, docPos); continue; } else { totalBytes = totalBytes + data.length; } } int dataPos = (null == existingBlock) ? 0 : ( null == existingBlock.data) ? 0 : existingBlock.data.length; if ( totalBytes > 0 ) { byte[] newB = new byte[dataPos + totalBytes]; Arrays.fill(newB,(byte)-1); if ( dataPos > 0 ) System.arraycopy( existingBlock.data, 0, newB, 0, dataPos); existingBlock.data = newB; } byte[] header = ( null == existingBlock ) ? new byte[0] : existingBlock.header; byte[] data = ( null == existingBlock ) ? new byte[0] :existingBlock.data; for (Integer docPos : addedList.keySet()) { if ( docPos < existingMax) continue; byte[] objB = addedList.get(docPos); if ( null == objB) { writeHeader(header, docPos, -1); continue; } writeHeader(header, docPos, dataPos); System.arraycopy(objB, 0, data, dataPos, objB.length); dataPos = dataPos + objB.length; } return existingBlock; } public static int getTotalDocuments(Block block) { if ( null == block.header) return 0; else return block.header.length / 4; } public static Map<Integer, byte[]> fromBytes(Block block, IStorable storable) { int totalDocs = block.header.length / 4; Map<Integer, byte[]> deList = new HashMap<Integer, byte[]>(); for ( int i=1; i<= totalDocs; i++) { int dataLoc = readHeader(block.header, i); if ( -1 == dataLoc) continue; IStorable de = get(storable, block, i); if ( null == de) continue; deList.put(i, de.toBytes()); } return deList; } public static void writeHeader(byte[] header, int docPos, int dataPos) { int headerLoc = docPos * 4; if ( headerLoc > header.length) { System.err.println ( "Warning > MergedBlocks:ArrayIndexOutOfBoundsException: Header Size:" + header.length + " , docPos:" + docPos + " , dataPos:" + dataPos); return; } header[headerLoc-4] = (byte)(dataPos >> 24); header[headerLoc-3] = (byte)(dataPos >> 16 ); header[headerLoc-2] = (byte)(dataPos >> 8 ); header[headerLoc-1] = (byte)(dataPos); } public static int readHeader(byte[] header, int docPos) { int headerLoc = docPos * 4; if ( headerLoc > header.length ) return -1; return Storable.getInt(headerLoc-4, header); } public static class Block { public byte[] header; public byte[] data; public Block() { } public Block(byte[] header, byte[] data) { this.header = header; this.data = data; } } }