/*license*\ XBN-Java: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com) This software is dual-licensed under the: - Lesser General Public License (LGPL) version 3.0 or, at your option, any later version; - Apache Software License (ASL) version 2.0. Either license may be applied at your discretion. More information may be found at - http://en.wikipedia.org/wiki/Multi-licensing. The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at: - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt \*license*/ package com.github.xbn.experimental; import com.github.xbn.array.CrashIfIndex; import com.github.xbn.util.lock.AbstractOneWayLockable; import java.util.Iterator; import java.util.ArrayList; /** <p>Iterator for a <i>series</i> of iterators, treating each as if it were a column in an odometer--getting every possible combination. IterationOdometer properly deals with each column 'flipping' (from 9 back to 0), and incrementing the 'higher'-place column (its left neighbor) when the flip occurs.</p> <p><a href="http://www.whitegauges.net/pages/Odometer-Reset.html"><IMG SRC="doc-files/odometer.jpg" border="0"></a></p> <h3>Warning</h3> <p>This properly recycles column-values as necessary. Such as going from 1001 to 1002, the first three columns (the 1, 0, and 0) are all reused. <i><b>Therefore, you must avoid altering the column-objects that are returned by {@code next()}</b></i> (each of the items in the {@code ArrayList} as returned by {@code next()}). If a to-be-recycled object is altered, unpredictable things will happen. If a to-be-recycled object must be altered, then it needs to be cloned or otherwise duplicated before doing so.</p> **/ public class IterationOdometer<T> extends AbstractOneWayLockable implements Iterable<ArrayList<T>> { private boolean bLastColAdded = false; private ArrayList<Iterable<T>> vitrblt = new ArrayList<Iterable<T>>(); /** <p>Create a new IterationOdometer. Does nothing.</p> */ public IterationOdometer() { } /** <p>How many columns are in this IterationOdometer?.</p> * @return An <b>int</b> representing the number of times {@code addColumn(Iterable)} was called. */ public final int getColumnCount() { return vitrblt.size(); } /** <p>Get an odometer-column.</p> * @param index The column-index. Must be valid given {@link #getColumnCount() getColumnCount}{@code ()}. */ public final Iterable<T> getIterableColumn(int index) { try { return vitrblt.get(index); } catch(IndexOutOfBoundsException ibx) { CrashIfIndex.badForLength(index, getColumnCount(), "index", "getColumnCount()"); throw ibx; } } /** <p>Add a column. This appends a new column to the right of existing columns. When adding the final (right-most) column, either use {@code addLastColumn()}, or call {@link #wasLastColumnAdded() wasLastColumnAdded}{@code ()}.</p> * @exception LockException If {@code addLastColumn()} or {@code wasLastColumnAdded()} was already called. * @see #addColumnsFrom(IterationOdometer, boolean) addColumnsFrom(iod,b) */ public final IterationOdometer<T> addColumn(Iterable<T> itrblt_column) { ciLocked(); //System.out.println("itrblt_column=" + itrblt_column + ""); if(itrblt_column == null) { throw new NullPointerException(" itrblt_column"); } vitrblt.add((Iterable<T>)itrblt_column); return this; } /** <p>Add the final (right-most) column. For all other columns, use {@code addColumn(itrbl)}</p> * @exception LockException If {@link #wasLastColumnAdded() wasLastColumnAdded}{@code ()} is true. * @see #addColumnsFrom(IterationOdometer, boolean) addColumnsFrom(iod,b) */ public final IterationOdometer<T> addLastColumn(Iterable<T> itrblt_column) { addColumn(itrblt_column); lastColumnAdded(); return this; } /** <p>Add all columns existing in another IterationOdometer.</p> * @return {@code addColumnsFrom(itr_odometer, false)} */ public final IterationOdometer<T> addColumnsFrom(IterationOdometer<T> itr_odometer) { return addColumnsFrom(itr_odometer, false); } /** <p>Add all columns existing in another IterationOdometer.</p> * @param itr_odometer The columns to add. May not be null, and <code>itr_odometer.{@link #wasLastColumnAdded() wasLastColumnAdded}()</code> must be true. * @param does_containLastColumn If true, then {@link #lastColumnAdded() lastColumnAdded}{@code ()} is called after the columns are added. * @return <b>{@code this}</b> * @exception LockException If {@code wasLastColumnAdded()} is true. * @see #addColumnsFrom(IterationOdometer) addColumnsFrom(iod) * @see #addColumn(Iterable) */ public final IterationOdometer<T> addColumnsFrom(IterationOdometer<T> itr_odometer, boolean does_containLastColumn) { ciLocked(); try { itr_odometer.ciNotLocked(); } catch(NullPointerException npx) { throw new NullPointerException("itr_odometer"); } for(Iterable<T> itrbl : vitrblt) { addColumn(itrbl); } if(does_containLastColumn) { lastColumnAdded(); } return this; } /** <p>Get a new IteratorIOD, where each call to next returns a ArrayList of the elements as returned by each columns' iterator.</p> <p>See {@link #iteratorIOD() iteratorIOD}{@code ()}</p> */ public final Iterator<ArrayList<T>> iterator() { ciNotLocked(); return (Iterator<ArrayList<T>>)(new IteratorIOD<T>(vitrblt)); } /** <p>Get a new IteratorIOD, where each call to next returns a ArrayList of the elements as returned by each columns' iterator.</p> * @return <code>(IteratorIOD<T>){@link #iterator() iterator}()</code> */ public final IteratorIOD<T> iteratorIOD() { return (IteratorIOD<T>)iterator(); } public final boolean isLocked() { return wasLastColumnAdded(); } public final boolean wasLastColumnAdded() { return bLastColAdded; } public final IterationOdometer<T> lastColumnAdded() { bLastColAdded = true; return this; } public final void lock() { if(!isLocked()) { lastColumnAdded(); super.lock(); } } /** <p>Testing/diagnostic function.</p> * @return <code>test_get1stIdxIfNotAtCurrPos({@link #iteratorIOD() iteratorIOD}(), 0, string_elements)</code> */ public final int test_get1stIdxIfNotAtStart(String... string_elements) { return test_get1stIdxIfNotAtCurrPos(iteratorIOD(), 0, string_elements); } /** <p>Testing/diagnostic function. Get the index number of the first element not found at the <i>current point</i> in this IterationOdometer (its iterator).</p> <p>Note that this function uses {@code IteratorIOD.nextString()}, which concatenates all the {@code toString()} values, of all the ArrayList elements returned by {@code IteratorIOD.next()}. See the example code at the top of this class for usage.</p> * @param iod_itr An iterator as returned by {@link #iterator() iterator}{@code ()}. The next element retrieved is the point at which the first element in {@code string_elements} is expected to exist. May not be {@code null}. * @param idx_start The index in {@code string_elements} at which to start analysis. This is needed by the {@code test_get1stIdxIfNotInMiddle()} function (It finds the first element, and then passes the iterator to this function...so in that case, this needs to tbe set to 1, so we don't try and <u>re</u>-find the first [0-th] element again). * @return <b>{@code -1}</b> If all string elements in {@code string_elements} are the first elements in this IterationOdometer. <br/><b>{@code <i>the index</i>}</b> Of the first element not found. */ public final int test_get1stIdxIfNotAtCurrPos(IteratorIOD<T> iod_itr, int idx_start, String... string_elements) { int i = idx_start; for(; i < string_elements.length; i++) { try { if(!iod_itr.hasNext()) { return i; } } catch(NullPointerException npx) { throw new NullPointerException("iod_itr"); } String sNas = iod_itr.nextString(); try { if(!string_elements[i].equals(sNas)) { return i; } } catch(NullPointerException npx) { throw new NullPointerException("string_elements[" + i + ']'); } } return -1; } /** <p>Testing/diagnostic function.</p> * @return {@code test_get1stIdxIfNotInMiddle(0, string_elements)} */ public final int test_get1stIdxIfNotInMiddle(String... string_elements) { return test_get1stIdxIfNotInMiddle(0, string_elements); } /** <p>Testing/diagnostic function. Get the index of the first element in {@code string_elements} that is not found <i>anywhere</i> in this IterationOdometer (its iterator). Once the first item is found (at any point), all following strings in {@code string_elements} must be immediately following.</p> * @param elements_toBypassCount If you're obviously testing far into the iterator, then you can bypass a certain number of elements, to save a bit of processing power. * @return <b>{@code -1}</b> If all string elements in {@code string_elements} are found, sequentially, anywhere in this IterationOdometer. <br/><b>{@code <i>the index</i>}</b> Of the first element not found. */ public final int test_get1stIdxIfNotInMiddle(int elements_toBypassCount, String... string_elements) { IteratorIOD<T> itrt = (IteratorIOD<T>)iteratorIOD(); boolean bQ = false; int i = 0; while(itrt.hasNext()) { if(elements_toBypassCount > 0) { itrt.next(); elements_toBypassCount--; continue; } String s = itrt.nextString(); try { bQ = string_elements[i].equals(s); } catch(NullPointerException npx) { throw new NullPointerException("string_elements[" + i + ']'); } if(bQ) { return test_get1stIdxIfNotAtCurrPos(itrt, 1, string_elements); } } return 0; } /** <p>Testing/diagnostic function.</p> * @return {@code test_get1stIdxIfNotAtEnd(0, string_elements)} */ public final int test_get1stIdxIfNotAtEnd(String... string_elements) { return test_get1stIdxIfNotAtEnd(0, string_elements); } /** <p>Testing/diagnostic function. Get the index of the first element in {@code string_elements} that is not found <i>at the end</i> of this IterationOdometer (its iterator). The final string in {@code string_elements} must be the final element in this IterationOdometer. The second-to-last string must be the second-to-last element, and so on.</p> * @param elements_toBypassCount If you're obviously testing far into the iterator, then you can bypass a certain number of elements, to save a bit of processing power. * @return <b>{@code -1}</b> If all string elements in {@code string_elements} are found, sequentially, beginning anywhere in this IterationOdometer. <br/><b>{@code <i>the index</i>}</b> Of the first element not found. */ public final int test_get1stIdxIfNotAtEnd(int elements_toBypassCount, String... string_elements) { ArrayList<String> vs = new ArrayList<String>(string_elements.length); IteratorIOD<T> ioditrt = (IteratorIOD<T>)iterator(); // boolean bQ = false; while(ioditrt.hasNext()) { if(elements_toBypassCount > 0) { ioditrt.next(); elements_toBypassCount--; continue; } if(vs.size() >= string_elements.length) { vs.remove(0); } String s = ioditrt.nextString(); vs.add(s); } for(int i = 0; i < string_elements.length; i++) { if(vs.size() < string_elements.length) { return i; } try { if(!string_elements[i].equals(vs.get(i))) { return i; } } catch(NullPointerException npx) { throw new NullPointerException("string_elements[" + i + ']'); } } return -1; } }