Search code examples
javacastingrefactoringpmdstatic-code-analysis

Dynamically cast to each array type


I am using the following method to print all bundle contents in any possible case. The problem is that PMD is reporting both NcssCount and CyclomaticComplexity. I would like to improve the code so as to get rid of the warnings and not just supress them.

private static String bundleToString(Bundle bundle) {
    StringBuilder out = new StringBuilder("Bundle[");

    if (bundle == null) {
        out.append("null");
    } else {
        boolean first = true;
        for (String key : bundle.keySet()) {
            if (!first) {
                out.append(", ");
            }

            out.append(key).append('=');

            Object value = bundle.get(key);

            if (value instanceof int[]) {
                out.append(Arrays.toString((int[]) value));
            } else if (value instanceof byte[]) {
                out.append(Arrays.toString((byte[]) value));
            } else if (value instanceof boolean[]) {
                out.append(Arrays.toString((boolean[]) value));
            } else if (value instanceof short[]) {
                out.append(Arrays.toString((short[]) value));
            } else if (value instanceof long[]) {
                out.append(Arrays.toString((long[]) value));
            } else if (value instanceof float[]) {
                out.append(Arrays.toString((float[]) value));
            } else if (value instanceof double[]) {
                out.append(Arrays.toString((double[]) value));
            } else if (value instanceof String[]) {
                out.append(Arrays.toString((String[]) value));
            } else if (value instanceof CharSequence[]) {
                out.append(Arrays.toString((CharSequence[]) value));
            } else if (value instanceof Parcelable[]) {
                out.append(Arrays.toString((Parcelable[]) value));
            } else if (value instanceof Bundle) {
                out.append(bundleToString((Bundle) value));
            } else {
                out.append(value);
            }

            first = false;
        }
    }

    out.append(']');
    return out.toString();
}

I've tried to split the code in two parts with the following logic but the NcssCount and CyclomaticComplexity just moved to the new method that does all the array casting.

if (value != null && value.getClass().isArray()) {
   // the new method to call all the array casts
   newMethodThatSuffersFromTheSameProblem(out, value);
} else if (value instanceof Bundle) {
    out.append(bundleToString((Bundle) value));
} else {
    out.append(value);
}

Can I somehow do a dynamic cast to each array type?

if (value != null && value.getClass().isArray()) {
   out.append(Arrays.toString((<cast-to-class-array-[]>) value));
}

I've also tried out.append(Arrays.toString(value.getClass().cast(value))); but gets a compilation error Cannot resolve method 'toString(capture<? extends java.lang.Object>)'


Solution

  • } else if (value instanceof String[]) {
      out.append(Arrays.toString((String[]) value));
    } else if (value instanceof CharSequence[]) {
      out.append(Arrays.toString((CharSequence[]) value));
    } else if (value instanceof Parcelable[]) {
      out.append(Arrays.toString((Parcelable[]) value));
    

    These 3 ifs could be replaced with one

    } else if (value != null && value.getClass().isArray())
      out.append(Arrays.toString((Object[]) value));
    

    For the rest you can have static Map<Class, Function<Object, String>> convertors and pre-fill it as

    convertors.put(int[].class, value -> Arrays.toString((int [])value));
    ...
    

    and then use

    Function<Object, String> convertor = convertors.get(value.getClass());
    if (convertor != null) {
      out.append(convertor.apply(value));
    } else ...
    

    FOR OLDER ANDROID WHERE LAMBDAS ARE NOT AVAILABLE

    interface ToString { String convert(Object object); }
    ...
    static Map<Class, ToString> convertors = new HashMap();
    convertors.put(int[].class, new ToString() { public String convert(Object value) { return Arrays.toString((int [])value); }}); 
    ...
    

    and then use

    ToString convertor = convertors.get(value.getClass());
    if (convertor != null) {
      out.append(convertor.convert(value));
    } else ...