Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import android.content.ContentProvider;
     20 import android.database.Cursor;
     21 import android.net.Uri;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.HashMap;
     29 import java.util.Map;
     30 
     31 public class ContentProviderOperation implements Parcelable {
     32     /** @hide exposed for unit tests */
     33     public final static int TYPE_INSERT = 1;
     34     /** @hide exposed for unit tests */
     35     public final static int TYPE_UPDATE = 2;
     36     /** @hide exposed for unit tests */
     37     public final static int TYPE_DELETE = 3;
     38     /** @hide exposed for unit tests */
     39     public final static int TYPE_ASSERT = 4;
     40 
     41     private final int mType;
     42     private final Uri mUri;
     43     private final String mSelection;
     44     private final String[] mSelectionArgs;
     45     private final ContentValues mValues;
     46     private final Integer mExpectedCount;
     47     private final ContentValues mValuesBackReferences;
     48     private final Map<Integer, Integer> mSelectionArgsBackReferences;
     49     private final boolean mYieldAllowed;
     50 
     51     private final static String TAG = "ContentProviderOperation";
     52 
     53     /**
     54      * Creates a {@link ContentProviderOperation} by copying the contents of a
     55      * {@link Builder}.
     56      */
     57     private ContentProviderOperation(Builder builder) {
     58         mType = builder.mType;
     59         mUri = builder.mUri;
     60         mValues = builder.mValues;
     61         mSelection = builder.mSelection;
     62         mSelectionArgs = builder.mSelectionArgs;
     63         mExpectedCount = builder.mExpectedCount;
     64         mSelectionArgsBackReferences = builder.mSelectionArgsBackReferences;
     65         mValuesBackReferences = builder.mValuesBackReferences;
     66         mYieldAllowed = builder.mYieldAllowed;
     67     }
     68 
     69     private ContentProviderOperation(Parcel source) {
     70         mType = source.readInt();
     71         mUri = Uri.CREATOR.createFromParcel(source);
     72         mValues = source.readInt() != 0 ? ContentValues.CREATOR.createFromParcel(source) : null;
     73         mSelection = source.readInt() != 0 ? source.readString() : null;
     74         mSelectionArgs = source.readInt() != 0 ? source.readStringArray() : null;
     75         mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
     76         mValuesBackReferences = source.readInt() != 0
     77                 ? ContentValues.CREATOR.createFromParcel(source)
     78                 : null;
     79         mSelectionArgsBackReferences = source.readInt() != 0
     80                 ? new HashMap<Integer, Integer>()
     81                 : null;
     82         if (mSelectionArgsBackReferences != null) {
     83             final int count = source.readInt();
     84             for (int i = 0; i < count; i++) {
     85                 mSelectionArgsBackReferences.put(source.readInt(), source.readInt());
     86             }
     87         }
     88         mYieldAllowed = source.readInt() != 0;
     89     }
     90 
     91     /** @hide */
     92     public ContentProviderOperation(ContentProviderOperation cpo, boolean removeUserIdFromUri) {
     93         mType = cpo.mType;
     94         if (removeUserIdFromUri) {
     95             mUri = ContentProvider.getUriWithoutUserId(cpo.mUri);
     96         } else {
     97             mUri = cpo.mUri;
     98         }
     99         mValues = cpo.mValues;
    100         mSelection = cpo.mSelection;
    101         mSelectionArgs = cpo.mSelectionArgs;
    102         mExpectedCount = cpo.mExpectedCount;
    103         mSelectionArgsBackReferences = cpo.mSelectionArgsBackReferences;
    104         mValuesBackReferences = cpo.mValuesBackReferences;
    105         mYieldAllowed = cpo.mYieldAllowed;
    106     }
    107 
    108     /** @hide */
    109     public ContentProviderOperation getWithoutUserIdInUri() {
    110         if (ContentProvider.uriHasUserId(mUri)) {
    111             return new ContentProviderOperation(this, true);
    112         }
    113         return this;
    114     }
    115 
    116     public void writeToParcel(Parcel dest, int flags) {
    117         dest.writeInt(mType);
    118         Uri.writeToParcel(dest, mUri);
    119         if (mValues != null) {
    120             dest.writeInt(1);
    121             mValues.writeToParcel(dest, 0);
    122         } else {
    123             dest.writeInt(0);
    124         }
    125         if (mSelection != null) {
    126             dest.writeInt(1);
    127             dest.writeString(mSelection);
    128         } else {
    129             dest.writeInt(0);
    130         }
    131         if (mSelectionArgs != null) {
    132             dest.writeInt(1);
    133             dest.writeStringArray(mSelectionArgs);
    134         } else {
    135             dest.writeInt(0);
    136         }
    137         if (mExpectedCount != null) {
    138             dest.writeInt(1);
    139             dest.writeInt(mExpectedCount);
    140         } else {
    141             dest.writeInt(0);
    142         }
    143         if (mValuesBackReferences != null) {
    144             dest.writeInt(1);
    145             mValuesBackReferences.writeToParcel(dest, 0);
    146         } else {
    147             dest.writeInt(0);
    148         }
    149         if (mSelectionArgsBackReferences != null) {
    150             dest.writeInt(1);
    151             dest.writeInt(mSelectionArgsBackReferences.size());
    152             for (Map.Entry<Integer, Integer> entry : mSelectionArgsBackReferences.entrySet()) {
    153                 dest.writeInt(entry.getKey());
    154                 dest.writeInt(entry.getValue());
    155             }
    156         } else {
    157             dest.writeInt(0);
    158         }
    159         dest.writeInt(mYieldAllowed ? 1 : 0);
    160     }
    161 
    162     /**
    163      * Create a {@link Builder} suitable for building an insert {@link ContentProviderOperation}.
    164      * @param uri The {@link Uri} that is the target of the insert.
    165      * @return a {@link Builder}
    166      */
    167     public static Builder newInsert(Uri uri) {
    168         return new Builder(TYPE_INSERT, uri);
    169     }
    170 
    171     /**
    172      * Create a {@link Builder} suitable for building an update {@link ContentProviderOperation}.
    173      * @param uri The {@link Uri} that is the target of the update.
    174      * @return a {@link Builder}
    175      */
    176     public static Builder newUpdate(Uri uri) {
    177         return new Builder(TYPE_UPDATE, uri);
    178     }
    179 
    180     /**
    181      * Create a {@link Builder} suitable for building a delete {@link ContentProviderOperation}.
    182      * @param uri The {@link Uri} that is the target of the delete.
    183      * @return a {@link Builder}
    184      */
    185     public static Builder newDelete(Uri uri) {
    186         return new Builder(TYPE_DELETE, uri);
    187     }
    188 
    189     /**
    190      * Create a {@link Builder} suitable for building a
    191      * {@link ContentProviderOperation} to assert a set of values as provided
    192      * through {@link Builder#withValues(ContentValues)}.
    193      */
    194     public static Builder newAssertQuery(Uri uri) {
    195         return new Builder(TYPE_ASSERT, uri);
    196     }
    197 
    198     public Uri getUri() {
    199         return mUri;
    200     }
    201 
    202     public boolean isYieldAllowed() {
    203         return mYieldAllowed;
    204     }
    205 
    206     /** @hide exposed for unit tests */
    207     public int getType() {
    208         return mType;
    209     }
    210 
    211     public boolean isWriteOperation() {
    212         return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE;
    213     }
    214 
    215     public boolean isReadOperation() {
    216         return mType == TYPE_ASSERT;
    217     }
    218 
    219     /**
    220      * Applies this operation using the given provider. The backRefs array is used to resolve any
    221      * back references that were requested using
    222      * {@link Builder#withValueBackReferences(ContentValues)} and
    223      * {@link Builder#withSelectionBackReference}.
    224      * @param provider the {@link ContentProvider} on which this batch is applied
    225      * @param backRefs a {@link ContentProviderResult} array that will be consulted
    226      * to resolve any requested back references.
    227      * @param numBackRefs the number of valid results on the backRefs array.
    228      * @return a {@link ContentProviderResult} that contains either the {@link Uri} of the inserted
    229      * row if this was an insert otherwise the number of rows affected.
    230      * @throws OperationApplicationException thrown if either the insert fails or
    231      * if the number of rows affected didn't match the expected count
    232      */
    233     public ContentProviderResult apply(ContentProvider provider, ContentProviderResult[] backRefs,
    234             int numBackRefs) throws OperationApplicationException {
    235         ContentValues values = resolveValueBackReferences(backRefs, numBackRefs);
    236         String[] selectionArgs =
    237                 resolveSelectionArgsBackReferences(backRefs, numBackRefs);
    238 
    239         if (mType == TYPE_INSERT) {
    240             Uri newUri = provider.insert(mUri, values);
    241             if (newUri == null) {
    242                 throw new OperationApplicationException("insert failed");
    243             }
    244             return new ContentProviderResult(newUri);
    245         }
    246 
    247         int numRows;
    248         if (mType == TYPE_DELETE) {
    249             numRows = provider.delete(mUri, mSelection, selectionArgs);
    250         } else if (mType == TYPE_UPDATE) {
    251             numRows = provider.update(mUri, values, mSelection, selectionArgs);
    252         } else if (mType == TYPE_ASSERT) {
    253             // Assert that all rows match expected values
    254             String[] projection =  null;
    255             if (values != null) {
    256                 // Build projection map from expected values
    257                 final ArrayList<String> projectionList = new ArrayList<String>();
    258                 for (Map.Entry<String, Object> entry : values.valueSet()) {
    259                     projectionList.add(entry.getKey());
    260                 }
    261                 projection = projectionList.toArray(new String[projectionList.size()]);
    262             }
    263             final Cursor cursor = provider.query(mUri, projection, mSelection, selectionArgs, null);
    264             try {
    265                 numRows = cursor.getCount();
    266                 if (projection != null) {
    267                     while (cursor.moveToNext()) {
    268                         for (int i = 0; i < projection.length; i++) {
    269                             final String cursorValue = cursor.getString(i);
    270                             final String expectedValue = values.getAsString(projection[i]);
    271                             if (!TextUtils.equals(cursorValue, expectedValue)) {
    272                                 // Throw exception when expected values don't match
    273                                 Log.e(TAG, this.toString());
    274                                 throw new OperationApplicationException("Found value " + cursorValue
    275                                         + " when expected " + expectedValue + " for column "
    276                                         + projection[i]);
    277                             }
    278                         }
    279                     }
    280                 }
    281             } finally {
    282                 cursor.close();
    283             }
    284         } else {
    285             Log.e(TAG, this.toString());
    286             throw new IllegalStateException("bad type, " + mType);
    287         }
    288 
    289         if (mExpectedCount != null && mExpectedCount != numRows) {
    290             Log.e(TAG, this.toString());
    291             throw new OperationApplicationException("wrong number of rows: " + numRows);
    292         }
    293 
    294         return new ContentProviderResult(numRows);
    295     }
    296 
    297     /**
    298      * The ContentValues back references are represented as a ContentValues object where the
    299      * key refers to a column and the value is an index of the back reference whose
    300      * valued should be associated with the column.
    301      * <p>
    302      * This is intended to be a private method but it is exposed for
    303      * unit testing purposes
    304      * @param backRefs an array of previous results
    305      * @param numBackRefs the number of valid previous results in backRefs
    306      * @return the ContentValues that should be used in this operation application after
    307      * expansion of back references. This can be called if either mValues or mValuesBackReferences
    308      * is null
    309      */
    310     public ContentValues resolveValueBackReferences(
    311             ContentProviderResult[] backRefs, int numBackRefs) {
    312         if (mValuesBackReferences == null) {
    313             return mValues;
    314         }
    315         final ContentValues values;
    316         if (mValues == null) {
    317             values = new ContentValues();
    318         } else {
    319             values = new ContentValues(mValues);
    320         }
    321         for (Map.Entry<String, Object> entry : mValuesBackReferences.valueSet()) {
    322             String key = entry.getKey();
    323             Integer backRefIndex = mValuesBackReferences.getAsInteger(key);
    324             if (backRefIndex == null) {
    325                 Log.e(TAG, this.toString());
    326                 throw new IllegalArgumentException("values backref " + key + " is not an integer");
    327             }
    328             values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex));
    329         }
    330         return values;
    331     }
    332 
    333     /**
    334      * The Selection Arguments back references are represented as a Map of Integer->Integer where
    335      * the key is an index into the selection argument array (see {@link Builder#withSelection})
    336      * and the value is the index of the previous result that should be used for that selection
    337      * argument array slot.
    338      * <p>
    339      * This is intended to be a private method but it is exposed for
    340      * unit testing purposes
    341      * @param backRefs an array of previous results
    342      * @param numBackRefs the number of valid previous results in backRefs
    343      * @return the ContentValues that should be used in this operation application after
    344      * expansion of back references. This can be called if either mValues or mValuesBackReferences
    345      * is null
    346      */
    347     public String[] resolveSelectionArgsBackReferences(
    348             ContentProviderResult[] backRefs, int numBackRefs) {
    349         if (mSelectionArgsBackReferences == null) {
    350             return mSelectionArgs;
    351         }
    352         String[] newArgs = new String[mSelectionArgs.length];
    353         System.arraycopy(mSelectionArgs, 0, newArgs, 0, mSelectionArgs.length);
    354         for (Map.Entry<Integer, Integer> selectionArgBackRef
    355                 : mSelectionArgsBackReferences.entrySet()) {
    356             final Integer selectionArgIndex = selectionArgBackRef.getKey();
    357             final int backRefIndex = selectionArgBackRef.getValue();
    358             newArgs[selectionArgIndex] =
    359                     String.valueOf(backRefToValue(backRefs, numBackRefs, backRefIndex));
    360         }
    361         return newArgs;
    362     }
    363 
    364     @Override
    365     public String toString() {
    366         return "mType: " + mType + ", mUri: " + mUri +
    367                 ", mSelection: " + mSelection +
    368                 ", mExpectedCount: " + mExpectedCount +
    369                 ", mYieldAllowed: " + mYieldAllowed +
    370                 ", mValues: " + mValues +
    371                 ", mValuesBackReferences: " + mValuesBackReferences +
    372                 ", mSelectionArgsBackReferences: " + mSelectionArgsBackReferences;
    373     }
    374 
    375     /**
    376      * Return the string representation of the requested back reference.
    377      * @param backRefs an array of results
    378      * @param numBackRefs the number of items in the backRefs array that are valid
    379      * @param backRefIndex which backRef to be used
    380      * @throws ArrayIndexOutOfBoundsException thrown if the backRefIndex is larger than
    381      * the numBackRefs
    382      * @return the string representation of the requested back reference.
    383      */
    384     private long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
    385             Integer backRefIndex) {
    386         if (backRefIndex >= numBackRefs) {
    387             Log.e(TAG, this.toString());
    388             throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
    389                     + " but there are only " + numBackRefs + " back refs");
    390         }
    391         ContentProviderResult backRef = backRefs[backRefIndex];
    392         long backRefValue;
    393         if (backRef.uri != null) {
    394             backRefValue = ContentUris.parseId(backRef.uri);
    395         } else {
    396             backRefValue = backRef.count;
    397         }
    398         return backRefValue;
    399     }
    400 
    401     public int describeContents() {
    402         return 0;
    403     }
    404 
    405     public static final Creator<ContentProviderOperation> CREATOR =
    406             new Creator<ContentProviderOperation>() {
    407         public ContentProviderOperation createFromParcel(Parcel source) {
    408             return new ContentProviderOperation(source);
    409         }
    410 
    411         public ContentProviderOperation[] newArray(int size) {
    412             return new ContentProviderOperation[size];
    413         }
    414     };
    415 
    416     /**
    417      * Used to add parameters to a {@link ContentProviderOperation}. The {@link Builder} is
    418      * first created by calling {@link ContentProviderOperation#newInsert(android.net.Uri)},
    419      * {@link ContentProviderOperation#newUpdate(android.net.Uri)},
    420      * {@link ContentProviderOperation#newDelete(android.net.Uri)} or
    421      * {@link ContentProviderOperation#newAssertQuery(Uri)}. The withXXX methods
    422      * can then be used to add parameters to the builder. See the specific methods to find for
    423      * which {@link Builder} type each is allowed. Call {@link #build} to create the
    424      * {@link ContentProviderOperation} once all the parameters have been supplied.
    425      */
    426     public static class Builder {
    427         private final int mType;
    428         private final Uri mUri;
    429         private String mSelection;
    430         private String[] mSelectionArgs;
    431         private ContentValues mValues;
    432         private Integer mExpectedCount;
    433         private ContentValues mValuesBackReferences;
    434         private Map<Integer, Integer> mSelectionArgsBackReferences;
    435         private boolean mYieldAllowed;
    436 
    437         /** Create a {@link Builder} of a given type. The uri must not be null. */
    438         private Builder(int type, Uri uri) {
    439             if (uri == null) {
    440                 throw new IllegalArgumentException("uri must not be null");
    441             }
    442             mType = type;
    443             mUri = uri;
    444         }
    445 
    446         /** Create a ContentProviderOperation from this {@link Builder}. */
    447         public ContentProviderOperation build() {
    448             if (mType == TYPE_UPDATE) {
    449                 if ((mValues == null || mValues.size() == 0)
    450                         && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)) {
    451                     throw new IllegalArgumentException("Empty values");
    452                 }
    453             }
    454             if (mType == TYPE_ASSERT) {
    455                 if ((mValues == null || mValues.size() == 0)
    456                         && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)
    457                         && (mExpectedCount == null)) {
    458                     throw new IllegalArgumentException("Empty values");
    459                 }
    460             }
    461             return new ContentProviderOperation(this);
    462         }
    463 
    464         /**
    465          * Add a {@link ContentValues} of back references. The key is the name of the column
    466          * and the value is an integer that is the index of the previous result whose
    467          * value should be used for the column. The value is added as a {@link String}.
    468          * A column value from the back references takes precedence over a value specified in
    469          * {@link #withValues}.
    470          * This can only be used with builders of type insert, update, or assert.
    471          * @return this builder, to allow for chaining.
    472          */
    473         public Builder withValueBackReferences(ContentValues backReferences) {
    474             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    475                 throw new IllegalArgumentException(
    476                         "only inserts, updates, and asserts can have value back-references");
    477             }
    478             mValuesBackReferences = backReferences;
    479             return this;
    480         }
    481 
    482         /**
    483          * Add a ContentValues back reference.
    484          * A column value from the back references takes precedence over a value specified in
    485          * {@link #withValues}.
    486          * This can only be used with builders of type insert, update, or assert.
    487          * @return this builder, to allow for chaining.
    488          */
    489         public Builder withValueBackReference(String key, int previousResult) {
    490             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    491                 throw new IllegalArgumentException(
    492                         "only inserts, updates, and asserts can have value back-references");
    493             }
    494             if (mValuesBackReferences == null) {
    495                 mValuesBackReferences = new ContentValues();
    496             }
    497             mValuesBackReferences.put(key, previousResult);
    498             return this;
    499         }
    500 
    501         /**
    502          * Add a back references as a selection arg. Any value at that index of the selection arg
    503          * that was specified by {@link #withSelection} will be overwritten.
    504          * This can only be used with builders of type update, delete, or assert.
    505          * @return this builder, to allow for chaining.
    506          */
    507         public Builder withSelectionBackReference(int selectionArgIndex, int previousResult) {
    508             if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
    509                 throw new IllegalArgumentException("only updates, deletes, and asserts "
    510                         + "can have selection back-references");
    511             }
    512             if (mSelectionArgsBackReferences == null) {
    513                 mSelectionArgsBackReferences = new HashMap<Integer, Integer>();
    514             }
    515             mSelectionArgsBackReferences.put(selectionArgIndex, previousResult);
    516             return this;
    517         }
    518 
    519         /**
    520          * The ContentValues to use. This may be null. These values may be overwritten by
    521          * the corresponding value specified by {@link #withValueBackReference} or by
    522          * future calls to {@link #withValues} or {@link #withValue}.
    523          * This can only be used with builders of type insert, update, or assert.
    524          * @return this builder, to allow for chaining.
    525          */
    526         public Builder withValues(ContentValues values) {
    527             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    528                 throw new IllegalArgumentException(
    529                         "only inserts, updates, and asserts can have values");
    530             }
    531             if (mValues == null) {
    532                 mValues = new ContentValues();
    533             }
    534             mValues.putAll(values);
    535             return this;
    536         }
    537 
    538         /**
    539          * A value to insert or update. This value may be overwritten by
    540          * the corresponding value specified by {@link #withValueBackReference}.
    541          * This can only be used with builders of type insert, update, or assert.
    542          * @param key the name of this value
    543          * @param value the value itself. the type must be acceptable for insertion by
    544          * {@link ContentValues#put}
    545          * @return this builder, to allow for chaining.
    546          */
    547         public Builder withValue(String key, Object value) {
    548             if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
    549                 throw new IllegalArgumentException("only inserts and updates can have values");
    550             }
    551             if (mValues == null) {
    552                 mValues = new ContentValues();
    553             }
    554             if (value == null) {
    555                 mValues.putNull(key);
    556             } else if (value instanceof String) {
    557                 mValues.put(key, (String) value);
    558             } else if (value instanceof Byte) {
    559                 mValues.put(key, (Byte) value);
    560             } else if (value instanceof Short) {
    561                 mValues.put(key, (Short) value);
    562             } else if (value instanceof Integer) {
    563                 mValues.put(key, (Integer) value);
    564             } else if (value instanceof Long) {
    565                 mValues.put(key, (Long) value);
    566             } else if (value instanceof Float) {
    567                 mValues.put(key, (Float) value);
    568             } else if (value instanceof Double) {
    569                 mValues.put(key, (Double) value);
    570             } else if (value instanceof Boolean) {
    571                 mValues.put(key, (Boolean) value);
    572             } else if (value instanceof byte[]) {
    573                 mValues.put(key, (byte[]) value);
    574             } else {
    575                 throw new IllegalArgumentException("bad value type: " + value.getClass().getName());
    576             }
    577             return this;
    578         }
    579 
    580         /**
    581          * The selection and arguments to use. An occurrence of '?' in the selection will be
    582          * replaced with the corresponding occurence of the selection argument. Any of the
    583          * selection arguments may be overwritten by a selection argument back reference as
    584          * specified by {@link #withSelectionBackReference}.
    585          * This can only be used with builders of type update, delete, or assert.
    586          * @return this builder, to allow for chaining.
    587          */
    588         public Builder withSelection(String selection, String[] selectionArgs) {
    589             if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
    590                 throw new IllegalArgumentException(
    591                         "only updates, deletes, and asserts can have selections");
    592             }
    593             mSelection = selection;
    594             if (selectionArgs == null) {
    595                 mSelectionArgs = null;
    596             } else {
    597                 mSelectionArgs = new String[selectionArgs.length];
    598                 System.arraycopy(selectionArgs, 0, mSelectionArgs, 0, selectionArgs.length);
    599             }
    600             return this;
    601         }
    602 
    603         /**
    604          * If set then if the number of rows affected by this operation do not match
    605          * this count {@link OperationApplicationException} will be throw.
    606          * This can only be used with builders of type update, delete, or assert.
    607          * @return this builder, to allow for chaining.
    608          */
    609         public Builder withExpectedCount(int count) {
    610             if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
    611                 throw new IllegalArgumentException(
    612                         "only updates, deletes, and asserts can have expected counts");
    613             }
    614             mExpectedCount = count;
    615             return this;
    616         }
    617 
    618         public Builder withYieldAllowed(boolean yieldAllowed) {
    619             mYieldAllowed = yieldAllowed;
    620             return this;
    621         }
    622     }
    623 }
    624