/*
* Copyright 2004 - 2008 Christian Sprajc, Dennis Waldherr. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.util.delta;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import de.dal33t.powerfolder.util.Reject;
import de.dal33t.powerfolder.util.RingBuffer;
/**
* Creates arrays of PartInfos given the algorithms to use and a data set.
*
* @author Dennis "Dante" Waldherr
* @version $Revision: 4280 $
*/
public class PartInfoMatcher extends FilterInputStream {
private static final int BUFFER_SIZE = 16384;
private final RollingChecksum chksum;
private final MessageDigest digester;
private final RingBuffer rbuf;
private final byte[] buf = new byte[BUFFER_SIZE];
private final Map<Long, List<PartInfo>> partCache =
new HashMap<Long, List<PartInfo>>();
private final byte[] dbuf;
private long pos;
public PartInfoMatcher(InputStream in, RollingChecksum chksum, MessageDigest digester, PartInfo[] partInfos) {
super(in);
Reject.noNullElements(chksum, digester, partInfos);
this.chksum = chksum;
this.digester = digester;
rbuf = new RingBuffer(chksum.getFrameSize());
dbuf = new byte[chksum.getFrameSize()];
for (PartInfo info: partInfos) {
List<PartInfo> pList = partCache.get(info.getChecksum());
if (pList == null) {
partCache.put(info.getChecksum(), pList = new LinkedList<PartInfo>());
}
pList.add(info);
}
}
public MatchInfo nextMatch() throws IOException, InterruptedException {
// Step 1: Fill buffer for matching
int rem = rbuf.remaining();
while (rem > 0) {
int amount = Math.min(rem, BUFFER_SIZE);
int read = read(buf, 0, amount);
if (read == -1) {
break;
}
pos += read;
rem -= read;
chksum.update(buf, 0, read);
rbuf.write(buf, 0, read);
}
// Step 2: If the buffer is full, try to find a match or EOF
while (rbuf.remaining() == 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
List<PartInfo> lookup = partCache.get(chksum.getValue());
if (lookup != null) {
rbuf.peek(dbuf, 0, chksum.getFrameSize());
byte[] digest = digester.digest(dbuf);
for (PartInfo info: lookup) {
if (Arrays.equals(digest, info.getDigest())) {
MatchInfo retval = new MatchInfo(info, pos - chksum.getFrameSize());
rbuf.reset();
return retval;
}
}
}
rbuf.skip(1);
int data = read();
if (data == -1) {
break;
}
pos++;
rbuf.write(data);
chksum.update(data);
}
// Step 3: If we got on EOF before try to finalize the result or return null if all is done
rem = (int) (pos % chksum.getFrameSize());
if (rem > 0) {
pos -= rem;
int av = rbuf.available();
rbuf.peek(dbuf, 0, av);
digester.update(dbuf, 0, av);
rem = chksum.getFrameSize() - rem;
for (int i = 0; i < rem; i++) {
chksum.update(0);
digester.update((byte) 0);
}
byte[] digest = digester.digest();
List<PartInfo> mList = partCache.get(chksum.getValue());
if (mList != null) {
for (PartInfo info: mList) {
if (Arrays.equals(digest, info.getDigest())) {
return new MatchInfo(info, pos);
}
}
}
}
return null;
}
}