Bug 13141 - java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Set
Summary: java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util...
Status: RESOLVED DUPLICATE of bug 15891
Alias: None
Product: Android
Classification: Xamarin
Component: BCL Class Libraries ()
Version: 4.8.x
Hardware: PC Mac OS
: Highest normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2013-07-09 23:50 UTC by Jérémie Laval
Modified: 2014-01-10 16:06 UTC (History)
2 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED DUPLICATE of bug 15891

Description Jérémie Laval 2013-07-09 23:50:00 UTC
A very bizarre bug. I'm using the following code snippet:

preferences = context.GetSharedPreferences ("preferences", FileCreationMode.Private)
preferences.GetStringSet (FavoriteKey, new string[0]);

Which works fine on the devices (Android Jellybean) I have. One of the user of my app (on Android 4.0.4, Samsung GS2) though is seeing the following stack trace when the code is called which seems to trigger in native:

07-09 20:50:51.042 I/MonoDroid( 7818):  at dalvik.system.NativeStart.main(Native Method)
07-09 20:50:51.052 E/mono    ( 7818):
07-09 20:50:51.052 E/mono    ( 7818): Unhandled Exception:
07-09 20:50:51.052 E/mono    ( 7818): Java.Lang.RuntimeException: Exception of type 'Java.Lang.RuntimeException' was thrown.
07-09 20:50:51.052 E/mono    ( 7818): at Android.Runtime.JNIEnv.CallObjectMethod (intptr,intptr,Android.Runtime.JValue[]) <0x00080>
07-09 20:50:51.052 E/mono    ( 7818): at Android.Content.ISharedPreferencesInvoker.GetStringSet (string,System.Collections.Generic.ICollection`1<string>) <0x00177>
07-09 20:50:51.052 E/mono    ( 7818): at Moyeu.FavoriteManager.GetFavoritesStationIds () <0x0004b>
07-09 20:50:51.052 E/mono    ( 7818): at Moyeu.InfoWindowAdapter.GetInfoContents (Android.Gms.Maps.Model.Marker) <0x002df>
07-09 20:50:51.052 E/mono    ( 7818): at Android.Gms.Maps.GoogleMap/IInfoWindowAdapterInvoker.n_GetInfoContents_Lcom_google_android_gms_maps_model_Marker_(intptr,intptr,intptr) <0x0005b>
07-09 20:50:51.052 E/mono    ( 7818): at (wrapper dynamic-method) object.2b5263c8-0bca-4f98-84e3-6f1f5aeb4787 (intptr,intptr,intptr) <0x00043>
07-09 20:50:51.052 E/mono    ( 7818):
07-09 20:50:51.052 E/mono    ( 7818):   --- End of managed exception stack trace ---
07-09 20:50:51.052 E/mono    ( 7818): java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Set
07-09 20:50:51.052 E/mono    ( 7818):   at android.app.SharedPreferencesImpl.getStringSet(SharedPreferencesImpl.java:219)
07-09 20:50:51.052 E/mono    ( 7818):   at moyeu.InfoWindowAdapter.n_getInfoContents(Native Method)
07-09 20:50:51.052 E/mono    ( 7818):   at moyeu.InfoWindo

It's not clear to me if the exception is internal to Android (there is a cast in the method body) or if, for some reason on that Android version, we don't correctly cast the array parameter to a set.
Comment 1 Jonathan Pryor 2013-07-10 10:08:49 UTC
The line number is different, but the cast within SharedPreferencesImpl.getStringSet() is presumably this:

https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/SharedPreferencesImpl.java#L232

            Set<String> v = (Set<String>) mMap.get(key);

Thus, the issue is not related to the `defValues` parameter, or your passing a `new string[0]`. The issue is that `mMap.get(FavoriteKey)` is returning something that isn't a Set<T>, raising the question: where all do you use `FavoriteKey`, in particular with which `ISharedPreferencesEditor.Put*()` methods?

A quick perusal of the SharedPreferencesImpl.java code shows no type consistency checks, e.g. it's entirely possible that you could do (untested):

    preferences = context.GetSharedPreferences (
            "preferences", FileCreationMode.Private);
    using (var editor = preferences.Edit()) {
        editor.PutInt("key", 42);
        editor.Commit();
    }
    string value = preferences.GetString("key", null);  // boom!

i.e. if the method+type that you add the value with doesn't match the method+type that you retrieve with, things can blow up with a ClassCastException.

Furthermore, since preferences can be backed by a file, this is also a versioning issue. If v1 of your app uses an int, and in v2 you use a string value for the same key, v2 won't be able to load v1-generated preferences with the same codepath; you'd need to use ISharedPreferences.All.
Comment 2 Jérémie Laval 2013-07-10 11:28:52 UTC
The Get call is done before any Put and without any prior file.

I think my initial point is still valid though, the parameter of that method shouldn't be a ICollection<T> because none of the casting code in JavaObjectExtensions converts them to a Java Set. AFAICT, the new string[0] call is converted to an ArrayList internally which makes me wonder how can this work at all.

Either we should had support there for converting a ISet<T> (and put that as the parameter type) or directly accept JavaSet<T>.
Comment 4 Karl Waclawek 2013-08-06 11:52:25 UTC
I have had this problem for a while, even before release 4.8, but so far it was not an issue for me as I had other things to work on.
I can confirm the above. Futher:

I also get an error when calling PutStringSet:
Java.Lang.NoClassDefFoundError: android/runtime/JavaSet_1

This may be related.
Comment 5 Karl Waclawek 2013-08-07 10:50:03 UTC
Additional info:

In my call to PutStringSet() I am always passing a non-empty string array (but which never gets saved due to the exception).

However, when calling GetStringSet() later, I am first calling Contains(), which returns true (not sure why, as the PutStringSet() call failed).

Then, when actually calling GetStringSet(), I am getting two exceptions:
Java.Lang.NoClassDefFoundError: android/runtime/JavaSet_1,
   followed by
Java.Lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Set.

I am using the latest stable release (4.8) with API 15 on Android 4.1.2 (Galaxy S3).
Comment 6 Karl Waclawek 2013-08-07 12:57:10 UTC
Even more info:

The exception in PutStringSet is a handled exception. I just did not notice that because the debugger failed to stop at the next statement even though I single-stepped through the code. So PutStringSet seems to work.

Also, in GetStringSet(), the "Java.Lang.NoClassDefFoundError: android/runtime/JavaSet_1" exception is handled inside the method, only the class cast exception escapes the call.
Comment 7 Karl Waclawek 2013-08-07 13:04:43 UTC
I suggest looking at the analysis here:
http://forums.xamarin.com/discussion/6531/preferencemanager-getstringset-keeps-crashing
Comment 8 Jonathan Pryor 2014-01-10 16:06:21 UTC
This should be fixed with the fix for Bug #15891. If it isn't, please re-open.

*** This bug has been marked as a duplicate of bug 15891 ***