Completed
Branch master (dc3042)
by Zaahid
03:52
created

convert(Object,Class)   D

Complexity

Conditions 12

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 19
c 1
b 0
f 0
dl 0
loc 24
ccs 0
cts 15
cp 0
crap 156
rs 4.8

How to fix   Complexity   

Complexity

Complex classes like com.strider.datadefender.requirement.TypeConverter.convert(Object,Class) often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*
2
 * Copyright 2014, Armenak Grigoryan, and individual contributors as indicated
3
 * by the @authors tag. See the copyright.txt in the distribution for a
4
 * full listing of individual contributors.
5
 *
6
 * This is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation; either version 2.1 of
9
 * the License, or (at your option) any later version.
10
 *
11
 * This software is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
 * Lesser General Public License for more details.
15
 */
16
package com.strider.datadefender.requirement;
17
18
import java.lang.reflect.Constructor;
19
import java.lang.reflect.InvocationTargetException;
20
import java.util.ArrayList;
21
import java.util.Arrays;
22
import java.util.HashMap;
23
import java.util.List;
24
import java.util.Map;
25
import java.util.stream.Collectors;
26
import org.apache.commons.beanutils.ConvertUtils;
27
import org.apache.commons.lang3.ClassUtils;
28
29
import lombok.extern.log4j.Log4j2;
30
31
/**
32
 * Provides 'convert' and 'isConvertible' static methods for detection/
33
 * conversion of types for Function and Column elements.
34
 *
35
 * @author Zaahid Bateson
36
 */
37
@Log4j2
38
public class TypeConverter {
39
40
    private static Map<Integer, Integer> cachedConversionWeight = new HashMap<>();
41
    private static final List<Class<?>> primitiveOrder = List.of(
42
        double.class, Double.class, float.class, Float.class, long.class, Long.class,
43
        int.class, Integer.class, short.class, Short.class, byte.class, Byte.class,
44
        boolean.class, Boolean.class, char.class, Character.class
45
    );
46
47
    private TypeConverter() {
48
    }
49
50
    /**
51
     * Returns true if an object of type "from" can be converted to type "to" by
52
     * the 'convert' method.
53
     *
54
     * @param from
55
     * @param to
56
     * @return
57
     */
58
    public static boolean isConvertible(Class<?> from, Class<?> to) {
59
        if (ClassUtils.isAssignable(from, to) || String.class.equals(to)) {
60
            return true;
61
        } else if (ClassUtils.isPrimitiveOrWrapper(from) && String.class.isAssignableFrom(from)) {
62
            return true;
63
        }
64
        return (getConvertibleConstructor(from, to) != null);
65
    }
66
67
    private static int getConversionScore(Class<?> from, Class<?> to) {
68
        if (from.equals(to)) {
69
            return Integer.MAX_VALUE;
70
        } else if (ClassUtils.isAssignable(from, to)) {
71
            return Integer.MAX_VALUE - 1;
72
        } else if (String.class.equals(to)) {
73
            return Integer.MAX_VALUE - 2;
74
        } else if (ClassUtils.isPrimitiveOrWrapper(to) && String.class.isAssignableFrom(from)) {
75
            return Integer.MAX_VALUE - 3 - primitiveOrder.indexOf(to);
76
        }
77
        List<Class<?>> ordered = buildOrderedListForType(to);
78
        if (ordered.contains(from)) {
79
            return Integer.MAX_VALUE - 100 - ordered.indexOf(from);
80
        }
81
        int ind = 0;
82
        for (Class<?> conv : ordered) {
83
            ++ind;
84
            if (isConvertible(conv, to)) {
85
                return Integer.MAX_VALUE - 200 - ind;
86
            }
87
        }
88
        return Integer.MIN_VALUE;
89
    }
90
91
    public static int compareConversion(Class<?> from, Class<?> firstTo, Class<?> secondTo) {
92
        if (firstTo.equals(secondTo)) {
93
            return 0;
94
        }
95
        return getConversionScore(from, secondTo) - getConversionScore(from, firstTo);
96
    }
97
98
    /**
99
     * Converts the passed value to the passed type if possible.
100
     *
101
     * Conversion is performed in the following order:
102
     *  - Returned as-is if the value is of the same type or a sub-class of the
103
     *    type.
104
     *  - If type is java.lang.String, call toString on value and return it.
105
     *  - If value is a primitive, primitive wrapper, or String, and type
106
     *    represents one of those as well, an attempt is made to convert from/to
107
     *    as needed.
108
     *  - Otherwise, look for a constructor in the passed type that accepts a
109
     *    single argument of:
110
     *    o value itself (as its type or an interface/superclass)
111
     *    o A String argument, in which case toString is called on value
112
     *    o If value is primitive, the primitive type or it's wrapper
113
     *    o If value is a String, a primitive or wrapper argument
114
     *
115
     * @param value
116
     * @param type
117
     * @return
118
     */
119
    public static Object convert(Object value, Class<?> type)
120
        throws InstantiationException,
121
        IllegalAccessException,
122
        IllegalArgumentException,
123
        InvocationTargetException {
124
        if (ClassUtils.isAssignable(value.getClass(), type)) {
125
            return value;
126
        } else if (String.class.equals(type)) {
127
            return value.toString();
128
        } else if (ClassUtils.isPrimitiveOrWrapper(type) && value instanceof String) {
129
            return ConvertUtils.convert(value, type);
130
        }
131
        Constructor<?> constr = getConvertibleConstructor(value.getClass(), type);
132
        Class<?> pt = (constr != null && constr.getParameterCount() > 0) ? constr.getParameterTypes()[0] : null;
133
        if (!ClassUtils.isAssignable(value.getClass(), pt)) {
134
            if (pt != null && ClassUtils.isAssignable(String.class, pt)) {
135
                return constr.newInstance(value.toString());
136
            } else if (pt != null && ClassUtils.isPrimitiveOrWrapper(pt) && value instanceof String) {
137
                return constr.newInstance(ConvertUtils.convert(value, pt));
138
            }
139
            // try anyway...
140
        }
141
142
        return constr.newInstance(value);
143
    }
144
145
    private static Constructor<?> getConstructorInOrder(Class<?> in, List<Class<?>> cls) {
146
        List<Constructor<?>> constructors = Arrays.stream(in.getConstructors()).filter((c) -> c.getParameterCount() == 1).collect(Collectors.toList());
147
        for (Class<?> p : cls) {
148
            for (Constructor<?> c : constructors) {
149
                if (ClassUtils.isAssignable(p, c.getParameterTypes()[0])) {
150
                    return c;
151
                }
152
            }
153
        }
154
        return null;
155
    }
156
157
    private static List<Class<?>> buildOrderedListForType(Class<?> from) {
158
        List<Class<?>> list = new ArrayList<>();
159
        list.add(from);
160
        if (ClassUtils.isPrimitiveOrWrapper(from)) {
161
            list.add(from.isPrimitive() ? ClassUtils.primitiveToWrapper(from) : ClassUtils.wrapperToPrimitive(from));
162
        }
163
        if (String.class.equals(from)) {
164
            list.addAll(primitiveOrder);
165
        } else {
166
            list.add(String.class);
167
        }
168
        return list;
169
    }
170
171
    private static Constructor<?> getConvertibleConstructor(Class<?> from, Class<?> to) {
172
        return getConstructorInOrder(
173
            to,
174
            buildOrderedListForType(from)
175
        );
176
    }
177
}
178