/* * Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * 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.amazon.carbonado.cursor; import java.util.Comparator; import java.util.NoSuchElementException; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; /** * Abstract cursor for aggregation and finding distinct data. The source cursor * must be ordered in some fashion by the grouping properties. The arrangement of * properties must match, but it does not matter if they are ascending or * descending. * * @author Brian S O'Neill * @see SortedCursor * @param source type, can be anything * @param aggregate type, can be anything */ public abstract class GroupedCursor extends AbstractCursor { private final Cursor mCursor; private final Comparator mGroupComparator; private S mGroupLeader; private G mNextAggregate; /** * Create a GroupedCursor with an existing group comparator. The comparator * defines the ordering of the source cursor, and it should be a partial * odering. If group comparator defines a total ordering, then all groups * have one member. * * @param cursor source of elements which must be ordered properly * @param groupComparator comparator which defines ordering of source cursor */ protected GroupedCursor(Cursor cursor, Comparator groupComparator) { if (cursor == null || groupComparator == null) { throw new IllegalArgumentException(); } mCursor = cursor; mGroupComparator = groupComparator; } /** * Create a GroupedCursor using properties to define the group * comparator. The set of properties defines the ordering of the source * cursor, and it should be a partial ordering. If properties define a * total ordering, then all groups have one member. * * @param cursor source of elements which must be ordered properly * @param type type of storable to create cursor for * @param groupProperties list of properties to group by * @throws IllegalArgumentException if any property is null or not a member * of storable type */ protected GroupedCursor(Cursor cursor, Class type, String... groupProperties) { if (cursor == null) { throw new IllegalArgumentException(); } mCursor = cursor; mGroupComparator = SortedCursor.createComparator(type, groupProperties); } /** * Returns the comparator used to identify group boundaries. */ public Comparator comparator() { return mGroupComparator; } /** * This method is called for the first entry in a group. This method is not * called again until after finishGroup is called. * * @param groupLeader first entry in group */ protected abstract void beginGroup(S groupLeader) throws FetchException; /** * This method is called when more entries are found for the current * group. This method is not called until after beginGroup has been * called. It may called multiple times until finishGroup is called. * * @param groupMember additional entry in group */ protected abstract void addToGroup(S groupMember) throws FetchException; /** * This method is called when a group is finished, and it can return an * aggregate. Simply return null if aggregate should be filtered out. * * @return aggregate, or null to filter it out */ protected abstract G finishGroup() throws FetchException; public void close() throws FetchException { mCursor.close(); mGroupLeader = null; mNextAggregate = null; } public boolean hasNext() throws FetchException { if (mNextAggregate != null) { return true; } try { if (mCursor.hasNext()) { if (mGroupLeader == null) { beginGroup(mGroupLeader = mCursor.next()); } while (mCursor.hasNext()) { S groupMember = mCursor.next(); if (mGroupComparator.compare(mGroupLeader, groupMember) == 0) { addToGroup(groupMember); } else { G aggregate = finishGroup(); beginGroup(mGroupLeader = groupMember); if (aggregate != null) { mNextAggregate = aggregate; return true; } } } G aggregate = finishGroup(); mGroupLeader = null; if (aggregate != null) { mNextAggregate = aggregate; return true; } } } catch (NoSuchElementException e) { } catch (FetchException e) { try { close(); } catch (Exception e2) { // Don't care. } throw e; } if (mGroupLeader != null) { G aggregate = finishGroup(); mGroupLeader = null; if (aggregate != null) { mNextAggregate = aggregate; return true; } } return false; } public G next() throws FetchException { try { if (hasNext()) { G next = mNextAggregate; mNextAggregate = null; return next; } } catch (FetchException e) { try { close(); } catch (Exception e2) { // Don't care. } throw e; } throw new NoSuchElementException(); } @Override public int skipNext(int amount) throws FetchException { if (amount <= 0) { if (amount < 0) { throw new IllegalArgumentException("Cannot skip negative amount: " + amount); } return 0; } try { int count = 0; while (--amount >= 0 && hasNext()) { ++count; mNextAggregate = null; } return count; } catch (FetchException e) { try { close(); } catch (Exception e2) { // Don't care. } throw e; } } }