/* * Copyright 2015 Daniel Dittmar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package dan.dit.whatsthat.util.mosaic.matching; import java.util.Iterator; import java.util.NavigableMap; import java.util.TreeMap; /** * This class models a special iterator over a treemap. The treemap is divided * in a head and tail map at a certain marker key. The next method returns the next closest to * this marker alternatingly searching the head and tail map. * @author Daniel * * @param <K> The key type which must be comparable. * @param <T> The type of the entries in the treemap. */ class DividedTreeMapIterator<K extends Comparable<K>, T> { private Iterator<K> ascendingIt; private Iterator<K> descendingIt; private NavigableMap<K, T> tailMap; private NavigableMap<K, T> headMap; private boolean searchUp; private boolean searchDown; private boolean preferedUp; /** * Creates a new DividedTreeMapIterator dividing the given map at the given key. * @param map The map to divide and iterate through. * @param divideNearby All entries greater than or equal to this key will be in the tail map * and the rest in the head map. */ public DividedTreeMapIterator(TreeMap<K, T> map, K divideNearby) { this.tailMap = map.tailMap(divideNearby, true); this.ascendingIt = this.tailMap.navigableKeySet().iterator(); this.headMap = map.headMap(divideNearby, false); this.descendingIt = this.headMap.navigableKeySet().descendingIterator(); this.preferedUp = true; this.searchDown = true; this.searchUp = true; } /** * Returns the next key, which is either in the head or tail map and the closest to * the dividing marker which was not yet returned by next (inside the given head or tailmap). * If there is no more key in limit in one part map, the other part map will be used for iteration. * Being in limit means: The key that would be returned is smaller than the upper limit if it is * in the tail map or it is greater than the lower limit if it is in the head map. * @param lowerLimit The lower limit that is checked if the next key is in the head map. * @param upperLimit The higher limit that is checked if the next key is in the tail map. * @return The next element's key of the iteration. If there is no next element <code>null</code> is * returned. After <code>null</code> is being returned, the outcome of this method may yield * unexpected results as one or two entries will be skipped. This iterator is considered to have * no 'next' entry if the remaining head and tail map are empty or if the next key is not in limit. */ public K next(K lowerLimit, K upperLimit) { boolean hasUp = ascendingIt.hasNext(); boolean hasDown = descendingIt.hasNext(); K up; K down; // works similiar to a state machine with the three states being // SEARCH:UP&DOWN, fails if no higher in limit exists and no lower in limit exists, // changes to SEARCH:UP/DOWN, alternates between preferred up and down search // SEARCH:UP, fails if no higher in limit exists // SEARCH:DOWN, fails if no lower in limit exists if (searchUp && searchDown) { if (this.preferedUp) { if (hasUp) { up = ascendingIt.next(); if (up.compareTo(upperLimit) <= 0) { // has up in limit this.preferedUp = !this.preferedUp; return up; } } // has no up or has up not in limit if (hasDown) { down = descendingIt.next(); if (down.compareTo(lowerLimit) >= 0) { // has no up (in limit) and has down in limit searchUp = false; return down; } else { return null; // has no up (in limit) and down out of limit } } else { return null; // has up out of limit or no up and no down } } else { // prefers down if (hasDown) { down = descendingIt.next(); if (down.compareTo(lowerLimit) >= 0) { // has down in limit this.preferedUp = !this.preferedUp; return down; } } // has no down (in limit) if (hasUp) { up = ascendingIt.next(); if (up.compareTo(upperLimit) <= 0) { // has no down (in limit) and has up in limit searchDown = false; return up; } else { return null; // has no down (in limit) and up out of limit } } else { return null; // has down out of limit or no down and no up } } } if (searchUp) { // and not searchUp if (hasUp) { up = ascendingIt.next(); if (up.compareTo(upperLimit) <= 0) { return up; } else { return null; // has up but not in limit } } else { return null; // has no up } } if (searchDown) {//and not search up if (hasDown) { down = descendingIt.next(); if (down.compareTo(lowerLimit) >= 0) { return down; } else { return null; // has down but not in limit } } else { return null; // has no down } } return null; // will not happen } /** * Returns the next entry of the iteration. This uses the next() method and returns * the entry belonging to the returned key. * @param lowerLimit The lower limit. * @param upperLimit The upper limit. * @return The next entry or <code>null</code> if next() returned null. */ public T nextEntry(K lowerLimit, K upperLimit) { K nextKey = this.next(lowerLimit, upperLimit); if (nextKey != null) { return (this.headMap.containsKey(nextKey) ? this.headMap.get(nextKey) : this.tailMap.get(nextKey)); } else { return null; } } }