From 2d52494b90507ba676ef2f444500feb9349785eb Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 3 Aug 2011 17:06:54 -0700 Subject: [PATCH] HHH-6423 : Add JoinedIterable --- .../util/collections/JoinedIterable.java | 112 ++++++++++++++ .../test/util/JoinedIterableTest.java | 139 ++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/util/JoinedIterableTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java new file mode 100644 index 0000000000..e9456615fd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/JoinedIterable.java @@ -0,0 +1,112 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.internal.util.collections; + +import java.util.Iterator; +import java.util.List; + + +/** + * An JoinedIterable is an Iterable that wraps a number of Iterables. + * + * This class makes multiple iterables look like one to the caller. + * When any method from the Iterator interface is called on the + * Iterator object returned by {@link #iterator()}, the JoinedIterable + * will delegate to a single underlying Iterator. The JoinedIterable will + * invoke the iterator on each Iterable, in sequence, until all Iterators + * are exhausted. + * + * @author Gail Badner (adapted from JoinedIterator) + */ +public class JoinedIterable implements Iterable { + private final TypeSafeJoinedIterator iterator; + + public JoinedIterable(List> iterables) { + if ( iterables == null ) { + throw new NullPointerException( "Unexpected null iterables argument" ); + } + iterator = new TypeSafeJoinedIterator( iterables ); + } + + public Iterator iterator() { + return iterator; + } + + private class TypeSafeJoinedIterator implements Iterator { + + // wrapped iterators + private List> iterables; + + // index of current iterator in the wrapped iterators array + private int currentIterableIndex; + + // the current iterator + private Iterator currentIterator; + + // the last used iterator + private Iterator lastUsedIterator; + + public TypeSafeJoinedIterator(List> iterables) { + this.iterables = iterables; + } + + public boolean hasNext() { + updateCurrentIterator(); + return currentIterator.hasNext(); + } + + public T next() { + updateCurrentIterator(); + return currentIterator.next(); + } + + public void remove() { + updateCurrentIterator(); + lastUsedIterator.remove(); + } + + // call this before any Iterator method to make sure that the current Iterator + // is not exhausted + @SuppressWarnings( {"unchecked"}) + protected void updateCurrentIterator() { + + if ( currentIterator == null) { + if( iterables.size() == 0 ) { + currentIterator = EmptyIterator.INSTANCE; + } + else { + currentIterator = iterables.get( 0 ).iterator(); + } + // set last used iterator here, in case the user calls remove + // before calling hasNext() or next() (although they shouldn't) + lastUsedIterator = currentIterator; + } + + while (! currentIterator.hasNext() && currentIterableIndex < iterables.size() - 1) { + currentIterableIndex++; + currentIterator = iterables.get( currentIterableIndex ).iterator(); + } + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/JoinedIterableTest.java b/hibernate-core/src/test/java/org/hibernate/test/util/JoinedIterableTest.java new file mode 100644 index 0000000000..5248ddb689 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/util/JoinedIterableTest.java @@ -0,0 +1,139 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program 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 distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.junit.Test; + +import org.hibernate.internal.util.collections.JoinedIterable; +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author Steve Ebersole + */ +public class JoinedIterableTest extends BaseUnitTestCase { + @Test + public void testNullIterables() { + try { + new JoinedIterable( null ); + fail(); + } + catch (NullPointerException ex) { + // expected + } + } + + @Test + public void testSingleEmptyIterable() { + Set emptyList = new HashSet(); + List> iterableSets = new ArrayList>( ); + iterableSets.add( emptyList ); + Iterable iterable = new JoinedIterable( iterableSets ); + assertFalse( iterable.iterator().hasNext() ); + try { + iterable.iterator().next(); + fail( "Should have thrown NoSuchElementException because the underlying collection is empty."); + } + catch ( NoSuchElementException ex ) { + // expected + } + try { + iterable.iterator().remove(); + fail( "Should have thrown IllegalStateException because the underlying collection is empty." ); + } + catch ( IllegalStateException ex ) { + // expected + } + for ( String s : iterable ) { + fail( "Should not have entered loop because underlying collection is empty"); + } + } + + @Test + public void testSingleIterableOfSingletonCollection() { + final String str = "a string"; + Set singleTonSet = new HashSet( 1 ); + singleTonSet.add( str ); + List> iterableSets = new ArrayList>( ); + iterableSets.add( singleTonSet ); + Iterable iterable = new JoinedIterable( iterableSets ); + assertTrue( iterable.iterator().hasNext() ); + assertSame( str, iterable.iterator().next() ); + assertFalse( iterable.iterator().hasNext() ); + try { + iterable.iterator().next(); + fail( "Should have thrown NoSuchElementException because the underlying collection is empty."); + } + catch ( NoSuchElementException ex ) { + // expected + } + for ( String s : iterable ) { + fail( "should not have entered loop because underlying iterator should have been exhausted." ); + } + assertEquals( 1, singleTonSet.size() ); + iterable = new JoinedIterable( iterableSets ); + for ( String s : iterable ) { + assertSame( str, s ); + iterable.iterator().remove(); + } + assertTrue( singleTonSet.isEmpty() ); + } + + @Test + public void testJoinedIterables() { + List> listOfIterables = new ArrayList>( ); + + List twoElementList = Arrays.asList( 0, 1 ); + listOfIterables.add( twoElementList ); + + List emptyList = new ArrayList( ); + listOfIterables.add( emptyList ); + + List oneElementList = Arrays.asList( 2 ); + listOfIterables.add( oneElementList ); + + List threeElementList = Arrays.asList( 3, 4, 5 ); + listOfIterables.add( threeElementList ); + + JoinedIterable joinedIterable = new JoinedIterable( listOfIterables ); + + int i = 0; + for ( Integer val : joinedIterable ) { + assertEquals( Integer.valueOf( i ), val ); + i++; + } + } +}