/** 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 java.util.HashMap; import com.delcyon.capo.util.diff.Diff.Side; /** * @author jeremiah * This class is a window onto a tokenized data stream. It contains a hashmap of all of the window items it contains, * and a collection of 'chains' for each match it has with another window. * It also keeps track of it's current position with regards to the number of window items that have been stored in it. * This generally corresponds to the token number of a tokenized input stream. */ public class Window { private long streamPosition = -1l; private ArrayList<WindowItem> windowItems = null; private HashMap<Long, ArrayList<WindowItem>> windowItemHashMap = new HashMap<Long, ArrayList<WindowItem>>(); private ArrayList<ArrayList<WindowItemLink>> chains = new ArrayList<ArrayList<WindowItemLink>>(); private Side side; public Window(Side side,int windowSize) { this.side = side; this.windowItems = new ArrayList<WindowItem>(windowSize); } /** * @return the current windowItems within this window. */ public ArrayList<WindowItem> getWindowItems() { return windowItems; } /** * This will remove anything in the window associated with a windowItem up to and including the specified stream position. * @param windowStreamPosition */ public void removeUntil(long windowStreamPosition) { while(windowItems.isEmpty() == false && windowItems.get(0).getStreamPosition() <= windowStreamPosition) { WindowItem currentWindowItem = windowItems.get(0); currentWindowItem.clearMatches(); windowItems.remove(0); windowItemHashMap.get(currentWindowItem.getDataHashCode()).remove(currentWindowItem); } //now remove any of our own window level chains that are empty for (int chainIndex = 0; chainIndex < chains.size(); chainIndex++) { ArrayList<WindowItemLink> chain = chains.get(chainIndex); if(chain.isEmpty() == false && chain.get(0).getWindowItemForSide(side).getStreamPosition() <= windowStreamPosition) { chain.clear(); } if (chain.isEmpty() == true) { chains.remove(chainIndex); chainIndex--; } } } /** * * @param windowItem * @return any window items who's data is the same as the data from the WindowItem being passed in. */ public ArrayList<WindowItem> getMatches(WindowItem windowItem) { return windowItemHashMap.get(windowItem.getDataHashCode()); } /** * * @param windowItem * @return returns true if we have a windowItem with the same data */ public boolean hasMatch(WindowItem windowItem) { if (windowItem == null) { return false; } else { return windowItemHashMap.containsKey(windowItem.getDataHashCode()); } } /** * creates and adds a window items for this byte array. It will also link this window item's predecessor to this new windowItem * @param data */ public void addWindowItem(byte[] data) { streamPosition++; //make sure that each window item knows who it's neighbors are if (data != null) { WindowItem previousWindowItem = null; if (windowItems.size() > 0) { previousWindowItem = windowItems.get(windowItems.size()-1); } WindowItem windowItem = new WindowItem(data,previousWindowItem,this,streamPosition); windowItems.add(windowItem); addToHashMap(windowItem); } } /** * Sometimes you might use a window to match up object, and need a pointer to the original * @param data * @param object */ public void addWindowItem(byte[] data,Object object) { streamPosition++; //make sure that each window item knows who it's neighbors are if (data != null) { WindowItem previousWindowItem = null; if (windowItems.size() > 0) { previousWindowItem = windowItems.get(windowItems.size()-1); } WindowItem windowItem = new WindowItem(data,object,previousWindowItem,this,streamPosition); windowItems.add(windowItem); addToHashMap(windowItem); } } /** * add a window item to our internal hashmap, and expand the entry list if needed * @param windowItem */ private void addToHashMap(WindowItem windowItem) { if(windowItem == null) { return; } else if (windowItemHashMap.containsKey(windowItem.getDataHashCode())) { windowItemHashMap.get(windowItem.getDataHashCode()).add(windowItem); } else { ArrayList<WindowItem> windowItemArrayList = new ArrayList<WindowItem>(); windowItemArrayList.add(windowItem); windowItemHashMap.put(windowItem.getDataHashCode(), windowItemArrayList); } } public WindowItemLink getFirstMatch() { WindowItemLink firstMatch = null; for (WindowItem windowItem : windowItems) { if (windowItem.getMatches().size() > 0) { firstMatch = new WindowItemLink(windowItem, windowItem.getMatches().get(0)); break; } } return firstMatch; } /** * Some of the magic is here. Given two windows, this will return an array of WindowItemLinks that are equals with each other, * and closest to the start of each window. * @param window * @param window2 * @return */ public ArrayList<WindowItemLink> getCheapestChain(Window window, Window window2) { ArrayList<WindowItemLink> chain = null; long bestDistance = Long.MAX_VALUE; //printChains(); for (ArrayList<WindowItemLink> searchableChain : chains) { try { if (searchableChain.size() > 1) { long distance1 = searchableChain.get(0).getWindowItemForSide(window.side).getStreamPosition() - window.windowItems.get(0).getStreamPosition(); long distance2 = searchableChain.get(0).getWindowItemForSide(window2.side).getStreamPosition() - window2.windowItems.get(0).getStreamPosition(); long totalDistance = distance1 + distance2; if (bestDistance > totalDistance) { bestDistance = totalDistance; chain = searchableChain; } } } catch (IndexOutOfBoundsException indexOutOfBoundsException) { indexOutOfBoundsException.printStackTrace(); } } return chain; } /** * debugging */ public void printChains() { for (ArrayList<WindowItemLink> chain : chains) { System.out.print(chain.get(0)+"["+chain.size()+"],"); } System.out.println(); } /** * This will return which side the window belongs to. */ @Override public String toString() { return side.toString(); } /** * Returns an array list of arrayLists of all of the windowItemLinks. * Basically this will return every matching window item from both streams that is currently visible within both windows. * @return */ public ArrayList<ArrayList<WindowItemLink>> getChains() { return chains; } /** * return which side this window is looking at. * @return */ public Side getSide() { return side; } }