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