Created
September 22, 2017 23:56
-
-
Save MisterRager/0aa0fbfa5a6e3cd3d95a01219921f54a to your computer and use it in GitHub Desktop.
OrderedData: an abstraction over Data!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2017 PlanGrid, Inc. All rights reserved. | |
*/ | |
package com.plangrid.android.dmodel.mapper; | |
import android.database.Cursor; | |
import rx.functions.Func1; | |
public class OrderedCursorData<T> implements OrderedData<T> { | |
private final Cursor cursor; | |
private final Func1<Cursor, T> factory; | |
public OrderedCursorData(final Cursor cursor, final Func1<Cursor, T> factory) { | |
this.cursor = cursor; | |
this.factory = factory; | |
} | |
@Override public T getRecord(final int index) { | |
return ((cursor != null) && (cursor.getCount() < index) && cursor.moveToPosition(index)) ? factory.call(cursor) : null; | |
} | |
@Override public int getCount() { | |
return (cursor != null) ? cursor.getCount() : 0; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2017 PlanGrid, Inc. All rights reserved. | |
*/ | |
package com.plangrid.android.dmodel.mapper; | |
public interface OrderedData<T> { | |
T getRecord(final int index); | |
int getCount(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2017 PlanGrid, Inc. All rights reserved. | |
*/ | |
package com.plangrid.android.dmodel.mapper; | |
import android.util.Pair; | |
import com.plangrid.android.adapters.SectionHeader; | |
import com.plangrid.android.helpers.Utils; | |
import java.util.Map; | |
import java.util.TreeMap; | |
import rx.functions.Func1; | |
public class SectionedOrderedData<T> implements OrderedData<SectionedOrderedData.RecordHolder<T>> { | |
private final OrderedData<T> underlyingData; | |
private final Func1<T, SectionHeader> headerFactory; | |
// A map of every index that marks the beginning of a group of items to the header at that index | |
private final Map<Integer, SectionHeader> sectionHeaders = new TreeMap<>(); | |
public SectionedOrderedData(final OrderedData<T> underlyingData, final Func1<T, SectionHeader> headerFactory) { | |
this.underlyingData = underlyingData; | |
this.headerFactory = headerFactory; | |
} | |
@Override public RecordHolder<T> getRecord(final int index) { | |
final Pair<Integer, Integer> indexInfo = searchActualIndexInfo(index); | |
if (indexInfo == null) { | |
return null; | |
} | |
return sectionHeaders.containsKey(indexInfo.first) ? new RecordHolder<>(sectionHeaders.get(indexInfo.first)) | |
: new RecordHolder<>(underlyingData.getRecord(indexInfo.first)); | |
} | |
// Find where in the underlying index an index actually refers to, counting all sections up until the given index | |
// First: the index in the underlying data source, Second: which section we're in, zero-indexed | |
private Pair<Integer, Integer> searchActualIndexInfo(final int index) { | |
int previousHeaderIndex = 0; | |
int walk = 0; | |
int sectionsBefore = 0; | |
// First, find the nearest already-found header index, and count the number of sections before the requested index | |
for (final int sectionIndex : sectionHeaders.keySet()) { | |
if ((sectionIndex + sectionsBefore) <= index) { | |
previousHeaderIndex = sectionIndex; | |
walk = sectionIndex; | |
++sectionsBefore; | |
} else { | |
break; | |
} | |
} | |
final int underlyingLimit = underlyingData.getCount(); | |
while ((index > (walk + sectionsBefore)) && (walk < underlyingLimit)) { | |
final SectionHeader previousHeader = sectionHeaders.get(previousHeaderIndex); | |
final SectionHeader currentHeader = headerFactory.call(underlyingData.getRecord(walk)); | |
if (!Utils.equals(currentHeader, previousHeader)) { | |
sectionHeaders.put(walk, currentHeader); | |
previousHeaderIndex = walk; | |
++sectionsBefore; | |
} else { | |
++walk; | |
} | |
} | |
return ((walk + sectionsBefore) == index) ? new Pair<>(walk, sectionsBefore) : null; | |
} | |
// WARNING: this eagerly searches for all headers that exist, inflating all objects along the way | |
@Override public int getCount() { | |
if (underlyingData == null) { | |
return 0; | |
} | |
final int underlyingCount = underlyingData.getCount(); | |
int startAt = underlyingCount; | |
do { | |
final Pair<Integer, Integer> end = searchActualIndexInfo(startAt); | |
startAt = (end != null) ? (end.first + end.second) : -1; | |
} | |
while (startAt > 0); | |
return underlyingCount + sectionHeaders.size(); | |
} | |
public static class RecordHolder<T> { | |
private final boolean isHeader; | |
private final T value; | |
private final SectionHeader header; | |
public RecordHolder(final T value) { | |
this(false, value, null); | |
} | |
public RecordHolder(final SectionHeader header) { | |
this(true, null, header); | |
} | |
private RecordHolder(final boolean isHeader, final T value, final SectionHeader header) { | |
this.isHeader = isHeader; | |
this.value = value; | |
this.header = header; | |
} | |
public void visit(final RecordVisitor<T> visitor) { | |
if (isHeader) { | |
visitor.header(header); | |
} else { | |
visitor.record(value); | |
} | |
} | |
} | |
public interface RecordVisitor<T> { | |
void header(SectionHeader header); | |
void record(T value); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment