/*
* Copyright 2007 Google Inc.
*
* 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 com.google.gwt.junit.client.impl;
import com.google.gwt.junit.client.Range;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Iterates over all the possible permutations available in a list of
* {@link com.google.gwt.junit.client.Range}s.
*
* <p>The simplest way to iterate over the permutations of multiple iterators
* is in a nested for loop. The PermutationIterator turns that for loop inside
* out into a single iterator, which enables you to access each permutation
* in a piecemeal fashion.</p>
*
*/
public class PermutationIterator implements Iterator {
/**
* A single permutation of all the iterators. Contains the current value
* of each iterator for the permutation.
*
*/
public static class Permutation {
private List values;
public Permutation( List values ) {
this.values = new ArrayList( values );
}
public List getValues() {
return values;
}
public String toString() {
return values.toString();
}
}
private static class ListRange implements Range {
private List list;
public ListRange( List list ) {
this.list = list;
}
public Iterator iterator() {
return list.iterator();
}
}
public static void main( String[] args ) {
List ranges = Arrays.asList(
new Range[] {
new ListRange( Arrays.asList( new String[] {"a", "b", "c" } ) ),
new ListRange( Arrays.asList( new String[] {"1", "2", "3" } ) ),
new ListRange( Arrays.asList( new String[] {"alpha", "beta", "gamma", "delta" } ) ),
}
);
System.out.println("Testing normal iteration.");
for ( Iterator it = new PermutationIterator(ranges); it.hasNext(); ) {
Permutation p = (Permutation) it.next();
System.out.println(p);
}
System.out.println("\nTesting skipping iteration.");
Iterator skipIterator = Arrays.asList( new String[] {"alpha", "beta", "gamma", "delta" } ).iterator();
boolean skipped = true;
String skipValue = null;
for ( PermutationIterator it = new PermutationIterator(ranges); it.hasNext(); ) {
Permutation p = (Permutation) it.next();
if ( skipped ) {
if ( skipIterator.hasNext() ) {
skipValue = (String) skipIterator.next();
skipped = false;
}
}
System.out.println(p);
String value = (String) p.getValues().get(p.getValues().size() - 1);
if ( value.equals(skipValue) ) {
it.skipCurrentRange();
skipped = true;
}
}
}
private boolean firstRun = true;
private List iterators;
private boolean maybeHaveMore = true;
private List ranges;
private boolean rangeSkipped = false;
private List values;
/**
* Constructs a new PermutationIterator that provides the values for each
* possible permutation of <code>ranges</code>.
*
* @param ranges non-null. Each {@link com.google.gwt.junit.client.Range}
* must have at least one element. ranges.size() must be > 1
*
* TODO(tobyr) Consider if empty Ranges ever make sense in the context of
* permutations.
*
*/
public PermutationIterator( List ranges ) {
this.ranges = ranges;
iterators = new ArrayList();
for ( int i = 0; i < ranges.size(); ++i ) {
Range r = ( Range ) ranges.get( i );
iterators.add( r.iterator() );
}
values = new ArrayList();
}
/**
* Returns a new <code>Permutation</code> containing the values of the next
* permutation.
*
* @return a non-null <code>Permutation</code>
*/
public boolean hasNext() {
if ( ! maybeHaveMore ) {
return false;
}
// Walk the iterators from bottom to top checking to see if any still have
// any available values
for ( int currentIterator = iterators.size() - 1; currentIterator >= 0; --currentIterator ) {
Iterator it = (Iterator) iterators.get( currentIterator );
if ( it.hasNext() ) {
return true;
}
}
return false;
}
public Object next() {
assert hasNext() : "No more available permutations in this iterator.";
if ( firstRun ) {
// Initialize all of our iterators and values on the first run
for ( int i = 0; i < iterators.size(); ++i ) {
Iterator it = ( Iterator ) iterators.get( i );
values.add( it.next() );
}
firstRun = false;
return new Permutation( values );
}
if ( rangeSkipped ) {
rangeSkipped = false;
return new Permutation( values );
}
// Walk through the iterators from bottom to top, finding the first one
// which has a value available. Increment it, reset all of the subsequent
// iterators, and then return the current permutation.
for ( int currentIteratorIndex = iterators.size() - 1; currentIteratorIndex >= 0; --currentIteratorIndex ) {
Iterator it = (Iterator) iterators.get( currentIteratorIndex );
if ( it.hasNext() ) {
values.set( currentIteratorIndex, it.next() );
for ( int i = currentIteratorIndex + 1; i < iterators.size(); ++i ) {
Range resetRange = (Range) ranges.get( i );
Iterator resetIterator = resetRange.iterator();
iterators.set(i, resetIterator);
values.set( i, resetIterator.next() );
}
return new Permutation( values );
}
}
throw new AssertionError( "Assertion failed - Couldn't find a non-empty iterator." );
}
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Skips the remaining set of values in the bottom
* {@link com.google.gwt.junit.client.Range}. This method affects the results
* of both hasNext() and next().
*
*/
public void skipCurrentRange() {
rangeSkipped = true;
for ( int currentIteratorIndex = iterators.size() - 2; currentIteratorIndex >= 0; --currentIteratorIndex ) {
Iterator it = (Iterator) iterators.get( currentIteratorIndex );
if ( it.hasNext() ) {
values.set( currentIteratorIndex, it.next() );
for ( int i = currentIteratorIndex + 1; i < iterators.size(); ++i ) {
Range resetRange = (Range) ranges.get( i );
Iterator resetIterator = resetRange.iterator();
iterators.set( i, resetIterator );
values.set( i, resetIterator.next() );
}
return;
}
}
maybeHaveMore = false;
}
}