package io.realm.internal; import java.lang.ref.WeakReference; import java.util.Date; import io.realm.RealmChangeListener; import io.realm.RealmFieldType; /** * A PendingRow is a row relies on a pending async query. * Before the query returns, calling any accessors will immediately throw. In this case run {@link #executeQuery()} to * get the queried row immediately. If the query results is empty, an {@link InvalidRow} will be returned. * After the query returns, {@link FrontEnd#onQueryFinished(Row)} will be called to give the front end a chance to reset * the row. If the async query returns an empty result, the query will be executed again later until a valid row is * contained by the query results. */ public class PendingRow implements Row { // Implement this interface to reset the PendingRow to a Row backed by real data when query returned. public interface FrontEnd { // When asyncQuery is true, the pending query is executed asynchronously. void onQueryFinished(Row row); } private static final String QUERY_NOT_RETURNED_MESSAGE = "The pending query has not been executed."; private static final String PROXY_NOT_SET_MESSAGE = "The 'frontEnd' has not been set."; private static final String QUERY_EXECUTED_MESSAGE = "The query has been executed. This 'PendingRow' is not valid anymore."; private SharedRealm sharedRealm; private Collection pendingCollection; private RealmChangeListener<PendingRow> listener; private WeakReference<FrontEnd> frontEndRef; private boolean returnCheckedRow; public PendingRow(SharedRealm sharedRealm, TableQuery query, SortDescriptor sortDescriptor, final boolean returnCheckedRow) { this.sharedRealm = sharedRealm; pendingCollection = new Collection(sharedRealm, query, sortDescriptor, null); listener = new RealmChangeListener<PendingRow>() { @Override public void onChange(PendingRow pendingRow) { notifyFrontEnd(); } }; pendingCollection.addListener(this, listener); this.returnCheckedRow = returnCheckedRow; sharedRealm.addPendingRow(this); } // To set the front end of this PendingRow. public void setFrontEnd(FrontEnd frontEnd) { this.frontEndRef = new WeakReference<FrontEnd>(frontEnd); } @Override public long getColumnCount() { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public String getColumnName(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getColumnIndex(String columnName) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public RealmFieldType getColumnType(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public Table getTable() { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getIndex() { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getLong(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean getBoolean(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public float getFloat(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public double getDouble(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public Date getDate(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public String getString(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public byte[] getBinaryByteArray(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public long getLink(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean isNullLink(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public LinkView getLinkList(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setLong(long columnIndex, long value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setBoolean(long columnIndex, boolean value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setFloat(long columnIndex, float value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setDouble(long columnIndex, double value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setDate(long columnIndex, Date date) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setString(long columnIndex, String value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setBinaryByteArray(long columnIndex, byte[] data) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setLink(long columnIndex, long value) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void nullifyLink(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean isNull(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public void setNull(long columnIndex) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean isAttached() { return false; } @Override public void checkIfAttached() { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } @Override public boolean hasColumn(String fieldName) { throw new IllegalStateException(QUERY_NOT_RETURNED_MESSAGE); } private void clearPendingCollection() { pendingCollection.removeListener(this, listener); pendingCollection = null; listener = null; sharedRealm.removePendingRow(this); } private void notifyFrontEnd() { if (frontEndRef == null) { throw new IllegalStateException(PROXY_NOT_SET_MESSAGE); } FrontEnd frontEnd = frontEndRef.get(); if (frontEnd == null) { // The front end is GCed. clearPendingCollection(); return; } if (pendingCollection.isValid()) { // PendingRow will always get the first Row of the query since we only support findFirst. UncheckedRow uncheckedRow = pendingCollection.firstUncheckedRow(); // Clear the pending collection immediately in case beginTransaction is called in the listener which will // execute the query again. clearPendingCollection(); // If no rows returned by the query, notify the frontend with an invalid row. if (uncheckedRow != null) { Row row = returnCheckedRow ? CheckedRow.getFromRow(uncheckedRow) : uncheckedRow; // Ask the front end to reset the row and stop async query. frontEnd.onQueryFinished(row); } else { // No row matches the query, return a invalid row. frontEnd.onQueryFinished(InvalidRow.INSTANCE); } } else { clearPendingCollection(); } } // Execute the query immediately and call frontend's onQueryFinished(). public void executeQuery() { if (pendingCollection == null) { throw new IllegalStateException(QUERY_EXECUTED_MESSAGE); } notifyFrontEnd(); } }