Skip to content

Instantly share code, notes, and snippets.

@MisterRager
Created September 22, 2017 23:56
Show Gist options
  • Save MisterRager/0aa0fbfa5a6e3cd3d95a01219921f54a to your computer and use it in GitHub Desktop.
Save MisterRager/0aa0fbfa5a6e3cd3d95a01219921f54a to your computer and use it in GitHub Desktop.
OrderedData: an abstraction over Data!
/*
* 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;
}
}
/*
* 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();
}
/*
* 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