Skip to content
This repository has been archived by the owner on Feb 26, 2023. It is now read-only.

Fix for Parcelable[] ClassCastException #1294

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -9,6 +9,7 @@ Export-Package: org.androidannotations.annotations,
org.androidannotations.annotations.sharedpreferences,
org.androidannotations.api,
org.androidannotations.api.builder,
org.androidannotations.api.bundle,
org.androidannotations.api.rest,
org.androidannotations.api.roboguice,
org.androidannotations.api.sharedpreferences,
Expand Down
@@ -0,0 +1,65 @@
/**
* Copyright (C) 2010-2015 eBusiness Information, Excilys Group
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed To in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.androidannotations.api.bundle;

import java.lang.reflect.Array;

import android.os.Bundle;
import android.os.Parcelable;

/**
* Utility class for working with {@link Bundle} objects.
*/
public class BundleHelper {

private BundleHelper() {

}

/**
* This method extracts a {@link Parcelable} array from the {@link Bundle},
* and returns it in an array whose type is the exact {@link Parcelable}
* subclass. This is needed because {@link Bundle#getParcelable(String)}
* returns an array of {@link Parcelable}, and we would get
* {@link ClassCastException} when we assign it to {@link Parcelable}
* subclass arrays.
*
* For more info, see <a
* href="https://github.com/excilys/androidannotations/issues/1208">this</a>
* url.
*
* @param bundle
* the bundle holding the array which is extracted
* @param key
* the array is associated with this key
* @param type
* the desired type of the returned array
* @return a {@link Parcelable} subclass typed array which holds the objects
* from {@link Bundle#getParcelableArray(String)} or
* <code>null</code> if {@link Bundle#getParcelableArray(String)}
* returned <code>null</code> for the key
*/
@SuppressWarnings("unchecked")
public static <T extends Parcelable> T[] getParcelableArray(Bundle bundle, String key, Class<T[]> type) {
Parcelable[] value = bundle.getParcelableArray(key);
if (value == null) {
return null;
}
Object copy = Array.newInstance(type.getComponentType(), value.length);
System.arraycopy(value, 0, copy, 0, value.length);
return (T[]) copy;
}
}
Expand Up @@ -105,7 +105,7 @@ private void injectExtraInComponent(Element element, HasExtras hasExtras, JField
JExpression intent = invoke("getIntent");
JBlock ifContainsKey = injectExtrasBlock._if(JExpr.invoke(extras, "containsKey").arg(extraKeyStaticField))._then();

JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromIntentOrBundle(elementClass, intent, extras, extraKeyStaticField, injectExtrasMethod);
JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromIntentOrBundle(elementClass, intent, extras, extraKeyStaticField, injectExtrasMethod, hasExtras);
ifContainsKey.assign(extraField, restoreMethodCall);
}

Expand Down
Expand Up @@ -80,7 +80,7 @@ public JExpression getExtraValue(VariableElement parameter, JVar intent, JVar ex
}

BundleHelper bundleHelper = new BundleHelper(annotationHelper, parameter.asType());
JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromIntentOrBundle(parameterClass, intent, extras, getStaticExtraField(generatedClass, extraKey, holder), annotatedMethod);
JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromIntentOrBundle(parameterClass, intent, extras, getStaticExtraField(generatedClass, extraKey, holder), annotatedMethod, holder);

return block.decl(parameterClass, parameterName, restoreMethodCall);
}
Expand Down
Expand Up @@ -99,7 +99,7 @@ private void injectArgInComponent(Element element, EFragmentHolder holder, Bundl
JFieldRef extraField = JExpr.ref(fieldName);

JBlock ifContainsKey = injectExtrasBlock._if(JExpr.invoke(bundle, "containsKey").arg(extraKeyStaticField))._then();
JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromBundle(elementClass, bundle, extraKeyStaticField, injectExtrasMethod);
JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromBundle(elementClass, bundle, extraKeyStaticField, injectExtrasMethod, holder);
ifContainsKey.assign(extraField, restoreMethodCall);
}

Expand Down
Expand Up @@ -74,7 +74,7 @@ public void process(Element element, HasInstanceState holder) {
JFieldRef ref = ref(fieldName);
saveStateBody.invoke(saveStateBundleParam, bundleHelper.getMethodNameToSave()).arg(fieldName).arg(ref);

JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromBundle(elementClass, restoreStateBundleParam, JExpr.lit(fieldName), restoreStateMethod);
JExpression restoreMethodCall = bundleHelper.getExpressionToRestoreFromBundle(elementClass, restoreStateBundleParam, JExpr.lit(fieldName), restoreStateMethod, holder);
restoreStateBody.assign(ref, restoreMethodCall);
}
}
Expand Up @@ -114,7 +114,7 @@ private void addActionInOnHandleIntent(EIntentServiceHolder holder, ExecutableEl
JClass extraParamClass = codeModelHelper.typeMirrorToJClass(param.asType(), holder);

BundleHelper bundleHelper = new BundleHelper(annotationHelper, param.asType());
JExpression getExtraExpression = bundleHelper.getExpressionToRestoreFromIntentOrBundle(extraParamClass, intent, extras, paramVar, onHandleIntentMethod);
JExpression getExtraExpression = bundleHelper.getExpressionToRestoreFromIntentOrBundle(extraParamClass, intent, extras, paramVar, onHandleIntentMethod, holder);

JVar extraField = callActionBlock.decl(extraParamClass, extraParamName, getExtraExpression);
callActionInvocation.arg(extraField);
Expand Down
Expand Up @@ -29,6 +29,8 @@
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import org.androidannotations.holder.GeneratedClassHolder;

import com.sun.codemodel.JClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
Expand Down Expand Up @@ -114,7 +116,6 @@ public BundleHelper(AnnotationHelper helper, TypeMirror element) {
if (isTypeParcelable(elementType)) {
methodNameToSave = "put" + "ParcelableArray";
methodNameToRestore = "get" + "ParcelableArray";
restoreCallNeedCastStatement = true;

if (hasTypeArguments) {
restoreCallNeedsSuppressWarning = true;
Expand Down Expand Up @@ -207,16 +208,23 @@ private boolean isTypeParcelable(TypeElement elementType) {
return elementType != null && annotationHelper.isSubtype(elementType, parcelableType);
}

public JExpression getExpressionToRestoreFromIntentOrBundle(JClass variableClass, JExpression intent, JExpression extras, JExpression extraKey, JMethod method) {
public JExpression getExpressionToRestoreFromIntentOrBundle(JClass variableClass, JExpression intent, JExpression extras, JExpression extraKey, JMethod method, GeneratedClassHolder holder) {
if ("byte[]".equals(element.toString())) {
return intent.invoke("getByteArrayExtra").arg(extraKey);
} else {
return getExpressionToRestoreFromBundle(variableClass, extras, extraKey, method);
return getExpressionToRestoreFromBundle(variableClass, extras, extraKey, method, holder);
}
}

public JExpression getExpressionToRestoreFromBundle(JClass variableClass, JExpression bundle, JExpression extraKey, JMethod method) {
JExpression expressionToRestore = JExpr.invoke(bundle, methodNameToRestore).arg(extraKey);
public JExpression getExpressionToRestoreFromBundle(JClass variableClass, JExpression bundle, JExpression extraKey, JMethod method, GeneratedClassHolder holder) {
JExpression expressionToRestore;
if (methodNameToRestore.equals("getParcelableArray")) {
JClass erasure = variableClass.elementType().erasure().array();
expressionToRestore = holder.refClass(org.androidannotations.api.bundle.BundleHelper.class).staticInvoke("getParcelableArray").arg(bundle).arg(extraKey).arg(erasure.dotclass());
} else {
expressionToRestore = JExpr.invoke(bundle, methodNameToRestore).arg(extraKey);
}

if (restoreCallNeedCastStatement()) {
expressionToRestore = JExpr.cast(variableClass, expressionToRestore);

Expand Down
@@ -0,0 +1,50 @@
/**
* Copyright (C) 2010-2014 eBusiness Information, Excilys Group
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed To in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.androidannotations.test15;

import static org.robolectric.Robolectric.directlyOn;

import java.util.Arrays;

import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.internal.ReflectionHelpers;
import org.robolectric.shadows.ShadowBundle;

import android.os.Bundle;
import android.os.Parcelable;

/***
* Workaround for https://github.com/robolectric/robolectric/issues/1440
*/
@Implements(Bundle.class)
public class CustomShadowBundle extends ShadowBundle {

@RealObject
private Bundle realObject;

@Implementation
public Parcelable[] getParcelableArray(String key) {
Parcelable[] array = directlyOn(realObject, Bundle.class, "getParcelableArray", new ReflectionHelpers.ClassParameter<String>(String.class, key));

if (array == null) {
return null;
}

return Arrays.copyOf(array, array.length, Parcelable[].class);
}
}
Expand Up @@ -20,9 +20,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import android.os.Bundle;

@Config(shadows = CustomShadowBundle.class)
@RunWith(RobolectricTestRunner.class)
public class FragmentArgsTest {

Expand Down
Expand Up @@ -24,11 +24,13 @@
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;

import android.app.Activity;
import android.content.Context;

@Config(shadows = CustomShadowBundle.class)
@RunWith(RobolectricTestRunner.class)
public class InjectExtraTest {

Expand Down
Expand Up @@ -22,15 +22,18 @@
import java.util.Arrays;
import java.util.Collection;

import org.androidannotations.test15.CustomShadowBundle;
import org.fest.util.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
import org.robolectric.ParameterizedRobolectricTestRunnerWorkaround;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;

import android.os.Bundle;

@Config(shadows = CustomShadowBundle.class)
@RunWith(ParameterizedRobolectricTestRunnerWorkaround.class)
public class SaveInstanceStateActivityParameterizedTest {

Expand Down