/** Copyright (c) 2011 Delcyon, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package com.delcyon.capo.util.diff; import java.util.ArrayList; import com.delcyon.capo.util.diff.Diff.Side; /** * @author jeremiah * This is a holder for the data that was read in from a stream. * It knows it's position in a stream. * How long the data is * A hashcode for the data. * Which window it belongs to. * And contains a list of other window items that it is a match with. */ public class WindowItem { private Window window = null; private WindowItem previousWindowItem = null; private byte[] data; private long dataHashCode = 0l; private long streamPosition = 0l; private long dataLength = 0l; private ArrayList<WindowItem> matches = new ArrayList<WindowItem>(); private Object object = null; /** * * @param data * @param previousWindowItem * @param window * @param streamPosition */ public WindowItem(byte[] data, WindowItem previousWindowItem, Window window, long streamPosition) { this.data = data; this.dataHashCode = getHashcode(data); this.dataLength = data.length; this.previousWindowItem = previousWindowItem; this.window = window; this.streamPosition = streamPosition; } /** * * @param data * @param object to store along with data * @param previousWindowItem * @param window * @param streamPosition */ public WindowItem(byte[] data,Object object, WindowItem previousWindowItem, Window window, long streamPosition) { this.data = data; this.dataHashCode = getHashcode(data); this.dataLength = data.length; this.previousWindowItem = previousWindowItem; this.window = window; this.streamPosition = streamPosition; this.object = object; } /** * This is for debugging * @param windowItem * @return */ public String getChainID(WindowItem windowItem) { for (ArrayList<WindowItemLink> chain : window.getChains()) { if (chain.contains(windowItem)) { return chain.get(0).toString(windowItem.getSide()); } } return "--"; } /** * Adds any matches to both sides. This will add any window item not currently in a chain to he appropriate chains * The passed in array list should be from the opposing side of the window that this window item belongs to. * @param matches * @param requirePreviousMatch if this is true, a chain will only be constructed if the previous window items also match */ public void addMatches(ArrayList<WindowItem> matches) { for (WindowItem windowItem : matches) { if (this.matches.contains(windowItem) == false) { this.matches.add(windowItem); windowItem.addMatch(this); //check to see if this is part of a chain if (this.previousWindowItem != null && windowItem.previousWindowItem != null) { if (this.previousWindowItem.dataHashCode == windowItem.previousWindowItem.dataHashCode) { this.addToChain(windowItem); } } else { //we can end up here when dealing with chains that start on a zero stream position //do nothing } } else { //skip } } } /** * add this window item to any chain contained in the window. The assumption is made, that this window item is a match someplace. If it's not, things won't work * @param windowItem */ private void addToChain(WindowItem windowItem) { WindowItemLink windowItemLink = new WindowItemLink(this, windowItem); //figure out which chain to add to //this can be done, by looking at the head of the chain, and making sure that it's stream position matches the incoming item's stream position - chain length //add to chain for this window ArrayList<WindowItemLink> chain = null; //see if our parent has a chain that we are adding to //we will never already be a member of that chain for (ArrayList<WindowItemLink> searchableChain : this.window.getChains()) { //get last item in chain if (searchableChain.isEmpty() == false) { WindowItemLink previousLink = searchableChain.get(searchableChain.size() -1); if (previousLink.getWindowItemForSide(windowItem.getSide()).streamPosition == windowItem.streamPosition - 1l) { if(previousLink.getWindowItemForSide(this.getSide()).streamPosition == this.streamPosition - 1l) { searchableChain.add(windowItemLink); //add link to every matching chain we find? chain = searchableChain; //we are just using this as a flag to indicate we found one } else { //we can end up here when we have chains that start at a zero stream position, because they have no previos window item } } } } //if we didn't find a chain, make a new one, and make sure everyone has it if (chain == null) { chain = new ArrayList<WindowItemLink>(); windowItemLink.getBaseWindowItem().window.getChains().add(chain); windowItemLink.getOtherWindowItem().window.getChains().add(chain); WindowItemLink previousLink = new WindowItemLink(this.previousWindowItem, windowItem.previousWindowItem); chain.add(previousLink); //add the incoming windowItem to the chain chain.add(windowItemLink); } } /** * adds a match to only this side, should be kept private and internal, so we don't hit some kind of recursive loop */ private void addMatch(WindowItem windowItem) { if (this.matches.contains(windowItem) == false) { this.matches.add(windowItem); } else { //skip } } @Override public boolean equals(Object obj) { if (obj instanceof WindowItem) { if (streamPosition == ((WindowItem)obj).streamPosition) { return true; } else { return false; } } else if (obj instanceof WindowItemLink) { WindowItemLink windowItemLink = (WindowItemLink) obj; if (getSide() == Side.BASE && streamPosition == windowItemLink.getBaseWindowItem().streamPosition) { return true; } else if (getSide() == Side.MOD && streamPosition == windowItemLink.getOtherWindowItem().streamPosition) { return true; } else { return false; } } else { return false; } } public void clearMatches() { matches.clear(); } @Override public String toString() { return "[window = '"+window.getSide() +"', streamPosition = '"+streamPosition+"', dataHashCode = '"+Long.toHexString(dataHashCode)+"', data ='"+new String(data)+"', matchCount = '"+matches.size()+"', dataLength = '"+dataLength+"']"; } public Side getSide() { return window.getSide(); } public long getStreamPosition() { return streamPosition; } /** * returns a hash code based on a byte[]. This is based on sun's hashcode algorithm for strings, but has been changed to work for byte arrays * @param bytes * @return */ private long getHashcode(byte[] bytes) { if (bytes.length == 0) { return 0l; } else { long hashCode = 0l; for (int index = 0; index < bytes.length ; index++) { hashCode = bytes[index] + ((hashCode << 5) - hashCode); } return hashCode; } } /** * a hash code for the data, based on sun's hashcode algorithm for strings. * @return */ public long getDataHashCode() { return dataHashCode; } /** * the data * @return */ public byte[] getData() { return data; } public Object getObject() { return this.object; } public ArrayList<WindowItem> getMatches() { return matches; } }