blob: 623c93491c54b9ec12d1f206058b44ca03d9c9a8 [file] [log] [blame]
/*
* Copyright (c) 2016 Audi AG
* 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
*/
package org.eclipse.mdm.mdfsorter.mdf4;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.zip.DataFormatException;
import org.eclipse.mdm.mdfsorter.AbstractDataProvider;
import org.eclipse.mdm.mdfsorter.ReadDataCache;
/**
* This Class is used to read Data from a data section, with an given offset. If
* Data is stored in a linked list or in a zipped block, it is this classes job
* to provide the data needed.
*
* @author Tobias Leemann
*
*/
/**
* @author EU2IYD9
*
*/
public class MDF4DataProvider implements AbstractDataProvider {
private FileChannel reader;
private MDF4GenBlock datasectionhead;
/**
* What type of Data section is given? 'l' = linked list, not zipped. 'h' =
* linked list, zipped. 'd' = normal data block;. 'z' = zipped data block
*/
private final char sectype;
private ZippedDataCache cache;
private ReadDataCache ReadCache;
/**
* LastProcessed block.
*/
private MDF4GenBlock lastprocessed;
/**
* LastProcessed block's start in this data section
*/
private long lastprocessedstart;
/**
* LastProcessed block's start in this data section
*/
private long lastprocessedend;
/**
* Length of this data section.
*/
private long sectionlength;
private byte[] dataarr;
/**
* This constructer creates a new DataProvider with the given head. Possible
* blocktypes are: ##HL, ##DL, ##DZ, ##DT, ##SD, ##RD
*
* @param datasectionhead
* The header of the data section
* @param reader
* FileChannel to the input file.
*/
public MDF4DataProvider(MDF4GenBlock datasectionhead, FileChannel reader) {
// empty data section
if (datasectionhead == null) {
sectype = '0';
return;
}
this.datasectionhead = datasectionhead;
this.reader = reader;
cache = new ZippedDataCache(reader);
switch (datasectionhead.getId()) {
case "##DT":
case "##SD":
case "##RD":
sectype = 'd';
break;
case "##DL":
sectype = 'l';
break;
case "##HL":
sectype = 'h';
// skip HL-Block
this.datasectionhead = this.datasectionhead.getLink(0);
break;
case "##DZ":
sectype = 'z';
break;
default:
sectype = '0';
System.err.println("Unknown blocktype in DataProvider.");
}
sectionlength = calculateLength();
cache = new ZippedDataCache(reader);
ReadCache = new ReadDataCache(this);
}
public MDF4DataProvider(byte[] data) {
dataarr = data;
sectype = '0';
sectionlength = data.length;
}
public void setDataArray(byte[] dataArray) {
dataarr = dataArray;
sectionlength = dataArray.length;
}
/**
* Read length bytes from this datasection, starting at globaloffset (The
* offset in the datasection) This method physically reads data, and does
* not check the read data cache.
*
* @param globaloffset
* The offset from the start of this section, e.g. DTBLOCK
* @param data
* The ByteBuffer to read in. data.limit bytes are read.
* @throws IOException
* If an I/O error occurs.
* @throws DataFormatException
* If zipped data is in an invalid format.
*/
@Override
public void read(long globaloffset, ByteBuffer data) throws IOException, DataFormatException {
if (globaloffset + data.capacity() > sectionlength) {
throw new IllegalArgumentException(
"Invalid read access on Data Provider. Section is only " + sectionlength + " bytes long.");
}
if (dataarr != null) {
data.put(dataarr, (int) globaloffset, data.capacity());
data.rewind();
return;
}
MDF4GenBlock blk = null;
long blkoffset = 0;
// check if block was last processed block (performance optimization)
if (lastprocessed != null) {
if (lastprocessedstart <= globaloffset && lastprocessedend > globaloffset) {
blk = lastprocessed;
blkoffset = globaloffset - lastprocessedstart;
}
}
// we need to search for the block to read from
if (blk == null) {
Object[] bo = getBlockWithOffset(globaloffset);
blk = (MDF4GenBlock) bo[0];
blkoffset = (long) bo[1];
lastprocessedstart = (long) bo[2];
lastprocessed = blk;
long lpdatasize = lastprocessed instanceof DZBLOCK ? ((DZBLOCK) lastprocessed).getOrg_data_length()
: lastprocessed.getLength() - 24L;
lastprocessedend = lastprocessedstart + lpdatasize;
}
// length check
long datasize = blk instanceof DZBLOCK ? ((DZBLOCK) blk).getOrg_data_length() : blk.getLength() - 24L;
if (blkoffset + data.capacity() > datasize) {
int readablesize = (int) (datasize - blkoffset);
ByteBuffer readable = ByteBuffer.allocate(readablesize);
// divide and conquer: Read available section first
read(globaloffset, readable);
// read unavailable section
ByteBuffer unreadable = ByteBuffer.allocate(data.capacity() - readablesize);
read(globaloffset + readablesize, unreadable);
// merge sections
data.put(readable);
data.put(unreadable);
data.rewind();
return;
}
if (blk.getId().equals("##DZ")) {
cache.read((DZBLOCK) blk, blkoffset, data);
} else {
reader.position(blk.getPos() + 24L + blkoffset);
reader.read(data);
}
data.rewind();
}
/**
* Returns a ByteBuffer with position at globaloffset, where length bytes
* can be read from. If the data is available in the cache, it is taken from
* there.
*
* @param globaloffset
* The place in the data section, where the read should start.
* @param length
* The length to read.
* @return A ByteBuffer with the position at global offset, from which
* length bytes can be read.
* @throws IOException
* If an I/O error occurs.
* @throws DataFormatException
* If zipped data is in an invalid format.
*/
@Override
public ByteBuffer cachedRead(long globaloffset, int length) throws IOException, DataFormatException {
// argument check
if (globaloffset + length > sectionlength) {
throw new IllegalArgumentException(
"Invalid read access on Data Provider. Section is only " + sectionlength + " bytes long.");
}
if (dataarr != null) {
ByteBuffer data = ByteBuffer.allocate(length);
data.put(dataarr, (int) globaloffset, data.capacity());
data.rewind();
return data;
} else {
return ReadCache.read(globaloffset, length);
}
}
/**
* Reads data.limit() bytes from Block blk, starting at blockoffset, from
* the start of blk's data. Using this Method is more efficent if the block,
* that the section to read from is contained in, is already known.
* (Blockwise read) This method does not use the read data cache.
*
* @param blockoffset
* The offset to start reading in the block.
* @param data
* The Bytebuffer that the data should be read in.
* @param blk
* The block to read from.
* @throws IOException
* If an I/O error occurs.
* @throws DataFormatException
* If zipped data is in an invalid format.
*/
public void read(long blockoffset, ByteBuffer data, MDF4GenBlock blk) throws IOException, DataFormatException {
// argument check
long datalength = blk instanceof DZBLOCK ? ((DZBLOCK) blk).getOrg_data_length() : blk.getLength() - 24L;
if (blockoffset + data.capacity() > datalength) {
throw new IllegalArgumentException(
"Invalid read access on Data Provider. Block is only " + datalength + " bytes long.");
}
if (blk.getId().equals("##DZ")) {
cache.read((DZBLOCK) blk, blockoffset, data);
} else {
reader.position(blk.getPos() + 24L + blockoffset);
reader.read(data);
data.rewind();
}
}
/**
* Returns the block which contains the data starting at globaloffset and
* the internal offset, where to start reading in the block.
*
* @param globaloffset
* The offset that is looked for in this data section
* @return An Array with length 2. The Block (Type MDF4GenBlock) is stored
* at index 0, the offset (long) at index 1.
*/
private Object[] getBlockWithOffset(long globaloffset) {
Object[] ret = new Object[3];
// No list walking needed, only a single block
if (sectype == 'd' || sectype == 'z') {
ret[0] = datasectionhead;
ret[1] = globaloffset;
ret[2] = 0L;
return ret;
} else {
// list... Datasection head has to be a DL, because HLs are skipped
// in the constructor
DLBLOCK curr = (DLBLOCK) datasectionhead;
if (curr.isEqualLengthFlag()) {
long blknum = globaloffset / curr.getEqualLength(); // block
// we
// need
long pastblocks = 0;
DLBLOCK drag = null;
// skip to next list if needed.
while (pastblocks <= blknum) {
pastblocks += curr.getCount();
drag = curr;
curr = (DLBLOCK) curr.getLink(0);
}
ret[0] = drag.getLink((int) (blknum - (pastblocks - drag.getCount()) + 1));
ret[1] = globaloffset % drag.getEqualLength();
ret[2] = drag.getEqualLength() * blknum;
} else {
long curroff = -1;
int listblknum = 0; // Block number in active list
DLBLOCK actlist = curr; // active list.
MDF4GenBlock drag = null;
long draglength = 0; // length of drag block.
while (curroff <= globaloffset) {
drag = actlist.getLink(listblknum + 1);
draglength = drag.getId().equals("##DZ") ? ((DZBLOCK) drag).getOrg_data_length()
: drag.getLength() - 24L;
curroff = actlist.getOffset()[listblknum] + draglength;
listblknum++;
// switch to next list, if end is reached.
if (listblknum == actlist.getCount()) {
actlist = (DLBLOCK) actlist.getLnkDlNext();
listblknum = 0;
}
}
ret[0] = drag;
ret[1] = globaloffset - (curroff - draglength);
ret[2] = curroff - draglength;
}
}
return ret;
}
@Override
public long getLength() {
return sectionlength;
}
/**
* Calculate the length of this data section.
*
* @return The length.
*/
private long calculateLength() {
if (datasectionhead == null) {
return 0;
}
long newlength = 0;
if (sectype == 'd') {
return datasectionhead.getLength() - 24L;
} else if (sectype == 'z') {
return ((DZBLOCK) datasectionhead).getOrg_data_length();
} else {
DLBLOCK drag = null;
DLBLOCK blk = (DLBLOCK) datasectionhead;
MDF4GenBlock lastchld;
do {
lastchld = blk.getLink((int) blk.getCount());
newlength += (blk.getCount() - 1) * blk.getEqualLength();
if (lastchld.getId().equals("##DZ")) {
newlength += ((DZBLOCK) lastchld).getOrg_data_length();
} else {
newlength += lastchld.getLength() - 24L;
}
drag = blk;
} while ((blk = (DLBLOCK) blk.links[0]) != null);
if (!drag.isEqualLengthFlag()) {
// We were wrong, calculate newlength otherwise (last offset +
// length of last datablock)
newlength = drag.getOffset()[(int) drag.getCount() - 1];
if (lastchld.getId().equals("##DZ")) {
newlength += ((DZBLOCK) lastchld).getOrg_data_length();
} else {
newlength += lastchld.getLength() - 24L;
}
}
}
return newlength;
}
}