001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.beans.PropertyEditor;
020import java.beans.PropertyEditorManager;
021import java.lang.reflect.InvocationTargetException;
022import java.lang.reflect.Method;
023import java.lang.reflect.Proxy;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.LinkedHashMap;
032import java.util.LinkedHashSet;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Locale;
036import java.util.Map;
037import java.util.Set;
038import java.util.regex.Pattern;
039
040import org.apache.camel.CamelContext;
041import org.apache.camel.Component;
042import org.apache.camel.NoTypeConversionAvailableException;
043import org.apache.camel.TypeConversionException;
044import org.apache.camel.TypeConverter;
045import org.apache.camel.component.properties.PropertiesComponent;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import static org.apache.camel.util.ObjectHelper.isAssignableFrom;
050
051/**
052 * Helper for introspections of beans.
053 * <p/>
054 * <b>Important: </b> Its recommended to call the {@link #stop()} method when
055 * {@link org.apache.camel.CamelContext} is being stopped. This allows to clear the introspection cache.
056 * <p/>
057 * This implementation will skip methods from <tt>java.lang.Object</tt> and <tt>java.lang.reflect.Proxy</tt>.
058 * <p/>
059 * This implementation will use a cache when the {@link #getProperties(Object, java.util.Map, String)}
060 * method is being used. Also the {@link #cacheClass(Class)} method gives access to the introspect cache.
061 */
062public final class IntrospectionSupport {
063
064    private static final Logger LOG = LoggerFactory.getLogger(IntrospectionSupport.class);
065    private static final Pattern GETTER_PATTERN = Pattern.compile("(get|is)[A-Z].*");
066    private static final Pattern SETTER_PATTERN = Pattern.compile("set[A-Z].*");
067    private static final List<Method> EXCLUDED_METHODS = new ArrayList<Method>();
068    // use a cache to speedup introspecting for known classes during startup
069    // use a weak cache as we dont want the cache to keep around as it reference classes
070    // which could prevent classloader to unload classes if being referenced from this cache
071    private static final LRUCache<Class<?>, ClassInfo> CACHE = new LRUWeakCache<Class<?>, ClassInfo>(1000);
072    private static final Object LOCK = new Object();
073
074    static {
075        // exclude all java.lang.Object methods as we dont want to invoke them
076        EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods()));
077        // exclude all java.lang.reflect.Proxy methods as we dont want to invoke them
078        EXCLUDED_METHODS.addAll(Arrays.asList(Proxy.class.getMethods()));
079    }
080
081    private static final Set<Class<?>> PRIMITIVE_CLASSES = new HashSet<Class<?>>();
082
083    static {
084        PRIMITIVE_CLASSES.add(String.class);
085        PRIMITIVE_CLASSES.add(Character.class);
086        PRIMITIVE_CLASSES.add(Boolean.class);
087        PRIMITIVE_CLASSES.add(Byte.class);
088        PRIMITIVE_CLASSES.add(Short.class);
089        PRIMITIVE_CLASSES.add(Integer.class);
090        PRIMITIVE_CLASSES.add(Long.class);
091        PRIMITIVE_CLASSES.add(Float.class);
092        PRIMITIVE_CLASSES.add(Double.class);
093        PRIMITIVE_CLASSES.add(char.class);
094        PRIMITIVE_CLASSES.add(boolean.class);
095        PRIMITIVE_CLASSES.add(byte.class);
096        PRIMITIVE_CLASSES.add(short.class);
097        PRIMITIVE_CLASSES.add(int.class);
098        PRIMITIVE_CLASSES.add(long.class);
099        PRIMITIVE_CLASSES.add(float.class);
100        PRIMITIVE_CLASSES.add(double.class);
101    }
102
103    /**
104     * Structure of an introspected class.
105     */
106    public static final class ClassInfo {
107        public Class<?> clazz;
108        public MethodInfo[] methods;
109    }
110
111    /**
112     * Structure of an introspected method.
113     */
114    public static final class MethodInfo {
115        public Method method;
116        public Boolean isGetter;
117        public Boolean isSetter;
118        public String getterOrSetterShorthandName;
119        public Boolean hasGetterAndSetter;
120    }
121
122    /**
123     * Utility classes should not have a public constructor.
124     */
125    private IntrospectionSupport() {
126    }
127
128    /**
129     * {@link org.apache.camel.CamelContext} should call this stop method when its stopping.
130     * <p/>
131     * This implementation will clear its introspection cache.
132     */
133    public static void stop() {
134        if (LOG.isDebugEnabled()) {
135            LOG.debug("Clearing cache[size={}, hits={}, misses={}, evicted={}]", new Object[]{CACHE.size(), CACHE.getHits(), CACHE.getMisses(), CACHE.getEvicted()});
136        }
137        CACHE.clear();
138
139        // flush java beans introspector as it may be in use by the PropertyEditor
140        java.beans.Introspector.flushCaches();
141    }
142
143    public static boolean isGetter(Method method) {
144        String name = method.getName();
145        Class<?> type = method.getReturnType();
146        Class<?> params[] = method.getParameterTypes();
147
148        if (!GETTER_PATTERN.matcher(name).matches()) {
149            return false;
150        }
151
152        // special for isXXX boolean
153        if (name.startsWith("is")) {
154            return params.length == 0 && type.getSimpleName().equalsIgnoreCase("boolean");
155        }
156
157        return params.length == 0 && !type.equals(Void.TYPE);
158    }
159
160    public static String getGetterShorthandName(Method method) {
161        if (!isGetter(method)) {
162            return method.getName();
163        }
164
165        String name = method.getName();
166        if (name.startsWith("get")) {
167            name = name.substring(3);
168            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
169        } else if (name.startsWith("is")) {
170            name = name.substring(2);
171            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
172        }
173
174        return name;
175    }
176
177    public static String getSetterShorthandName(Method method) {
178        if (!isSetter(method)) {
179            return method.getName();
180        }
181
182        String name = method.getName();
183        if (name.startsWith("set")) {
184            name = name.substring(3);
185            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
186        }
187
188        return name;
189    }
190
191    public static boolean isSetter(Method method, boolean allowBuilderPattern) {
192        String name = method.getName();
193        Class<?> type = method.getReturnType();
194        Class<?> params[] = method.getParameterTypes();
195
196        if (!SETTER_PATTERN.matcher(name).matches()) {
197            return false;
198        }
199
200        return params.length == 1 && (type.equals(Void.TYPE) || (allowBuilderPattern && method.getDeclaringClass().isAssignableFrom(type)));
201    }
202    
203    public static boolean isSetter(Method method) {
204        return isSetter(method, false);
205    }
206
207
208    /**
209     * Will inspect the target for properties.
210     * <p/>
211     * Notice a property must have both a getter/setter method to be included.
212     * Notice all <tt>null</tt> values will be included.
213     *
214     * @param target         the target bean
215     * @param properties     the map to fill in found properties
216     * @param optionPrefix   an optional prefix to append the property key
217     * @return <tt>true</tt> if any properties was found, <tt>false</tt> otherwise.
218     */
219    public static boolean getProperties(Object target, Map<String, Object> properties, String optionPrefix) {
220        return getProperties(target, properties, optionPrefix, true);
221    }
222
223    /**
224     * Will inspect the target for properties.
225     * <p/>
226     * Notice a property must have both a getter/setter method to be included.
227     *
228     * @param target         the target bean
229     * @param properties     the map to fill in found properties
230     * @param optionPrefix   an optional prefix to append the property key
231     * @param includeNull    whether to include <tt>null</tt> values
232     * @return <tt>true</tt> if any properties was found, <tt>false</tt> otherwise.
233     */
234    public static boolean getProperties(Object target, Map<String, Object> properties, String optionPrefix, boolean includeNull) {
235        ObjectHelper.notNull(target, "target");
236        ObjectHelper.notNull(properties, "properties");
237        boolean rc = false;
238        if (optionPrefix == null) {
239            optionPrefix = "";
240        }
241
242        ClassInfo cache = cacheClass(target.getClass());
243
244        for (MethodInfo info : cache.methods) {
245            Method method = info.method;
246            // we can only get properties if we have both a getter and a setter
247            if (info.isGetter && info.hasGetterAndSetter) {
248                String name = info.getterOrSetterShorthandName;
249                try {
250                    // we may want to set options on classes that has package view visibility, so override the accessible
251                    method.setAccessible(true);
252                    Object value = method.invoke(target);
253                    if (value != null || includeNull) {
254                        properties.put(optionPrefix + name, value);
255                        rc = true;
256                    }
257                } catch (Exception e) {
258                    if (LOG.isTraceEnabled()) {
259                        LOG.trace("Error invoking getter method " + method + ". This exception is ignored.", e);
260                    }
261                }
262            }
263        }
264
265        return rc;
266    }
267
268    /**
269     * Introspects the given class.
270     *
271     * @param clazz the class
272     * @return the introspection result as a {@link ClassInfo} structure.
273     */
274    public static ClassInfo cacheClass(Class<?> clazz) {
275        ClassInfo cache = CACHE.get(clazz);
276        if (cache == null) {
277            cache = doIntrospectClass(clazz);
278            CACHE.put(clazz, cache);
279        }
280        return cache;
281    }
282
283    private static ClassInfo doIntrospectClass(Class<?> clazz) {
284        ClassInfo answer = new ClassInfo();
285        answer.clazz = clazz;
286
287        // loop each method on the class and gather details about the method
288        // especially about getter/setters
289        List<MethodInfo> found = new ArrayList<MethodInfo>();
290        Method[] methods = clazz.getMethods();
291        for (Method method : methods) {
292            if (EXCLUDED_METHODS.contains(method)) {
293                continue;
294            }
295
296            MethodInfo cache = new MethodInfo();
297            cache.method = method;
298            if (isGetter(method)) {
299                cache.isGetter = true;
300                cache.isSetter = false;
301                cache.getterOrSetterShorthandName = getGetterShorthandName(method);
302            } else if (isSetter(method)) {
303                cache.isGetter = false;
304                cache.isSetter = true;
305                cache.getterOrSetterShorthandName = getSetterShorthandName(method);
306            } else {
307                cache.isGetter = false;
308                cache.isSetter = false;
309                cache.hasGetterAndSetter = false;
310            }
311            found.add(cache);
312        }
313
314        // for all getter/setter, find out if there is a corresponding getter/setter,
315        // so we have a read/write bean property.
316        for (MethodInfo info : found) {
317            info.hasGetterAndSetter = false;
318            if (info.isGetter) {
319                // loop and find the matching setter
320                for (MethodInfo info2 : found) {
321                    if (info2.isSetter && info.getterOrSetterShorthandName.equals(info2.getterOrSetterShorthandName)) {
322                        info.hasGetterAndSetter = true;
323                        break;
324                    }
325                }
326            } else if (info.isSetter) {
327                // loop and find the matching getter
328                for (MethodInfo info2 : found) {
329                    if (info2.isGetter && info.getterOrSetterShorthandName.equals(info2.getterOrSetterShorthandName)) {
330                        info.hasGetterAndSetter = true;
331                        break;
332                    }
333                }
334            }
335        }
336
337        answer.methods = found.toArray(new MethodInfo[found.size()]);
338        return answer;
339    }
340
341    public static boolean hasProperties(Map<String, Object> properties, String optionPrefix) {
342        ObjectHelper.notNull(properties, "properties");
343
344        if (ObjectHelper.isNotEmpty(optionPrefix)) {
345            for (Object o : properties.keySet()) {
346                String name = (String) o;
347                if (name.startsWith(optionPrefix)) {
348                    return true;
349                }
350            }
351            // no parameters with this prefix
352            return false;
353        } else {
354            return !properties.isEmpty();
355        }
356    }
357
358    public static Object getProperty(Object target, String property) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
359        ObjectHelper.notNull(target, "target");
360        ObjectHelper.notNull(property, "property");
361
362        property = property.substring(0, 1).toUpperCase(Locale.ENGLISH) + property.substring(1);
363
364        Class<?> clazz = target.getClass();
365        Method method = getPropertyGetter(clazz, property);
366        return method.invoke(target);
367    }
368
369    public static Object getOrElseProperty(Object target, String property, Object defaultValue) {
370        try {
371            return getProperty(target, property);
372        } catch (Exception e) {
373            return defaultValue;
374        }
375    }
376
377    public static Method getPropertyGetter(Class<?> type, String propertyName) throws NoSuchMethodException {
378        if (isPropertyIsGetter(type, propertyName)) {
379            return type.getMethod("is" + ObjectHelper.capitalize(propertyName));
380        } else {
381            return type.getMethod("get" + ObjectHelper.capitalize(propertyName));
382        }
383    }
384
385    public static Method getPropertySetter(Class<?> type, String propertyName) throws NoSuchMethodException {
386        String name = "set" + ObjectHelper.capitalize(propertyName);
387        for (Method method : type.getMethods()) {
388            if (isSetter(method) && method.getName().equals(name)) {
389                return method;
390            }
391        }
392        throw new NoSuchMethodException(type.getCanonicalName() + "." + name);
393    }
394
395    public static boolean isPropertyIsGetter(Class<?> type, String propertyName) {
396        try {
397            Method method = type.getMethod("is" + ObjectHelper.capitalize(propertyName));
398            if (method != null) {
399                return method.getReturnType().isAssignableFrom(boolean.class) || method.getReturnType().isAssignableFrom(Boolean.class);
400            }
401        } catch (NoSuchMethodException e) {
402            // ignore
403        }
404        return false;
405    }
406    
407    public static boolean setProperties(Object target, Map<String, Object> properties, String optionPrefix, boolean allowBuilderPattern) throws Exception {
408        ObjectHelper.notNull(target, "target");
409        ObjectHelper.notNull(properties, "properties");
410        boolean rc = false;
411
412        for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext();) {
413            Map.Entry<String, Object> entry = it.next();
414            String name = entry.getKey().toString();
415            if (name.startsWith(optionPrefix)) {
416                Object value = properties.get(name);
417                name = name.substring(optionPrefix.length());
418                if (setProperty(target, name, value, allowBuilderPattern)) {
419                    it.remove();
420                    rc = true;
421                }
422            }
423        }
424        
425        return rc;
426    }
427
428    public static boolean setProperties(Object target, Map<String, Object> properties, String optionPrefix) throws Exception {
429        ObjectHelper.notEmpty(optionPrefix, "optionPrefix");
430        return setProperties(target, properties, optionPrefix, false);
431    }
432
433    public static Map<String, Object> extractProperties(Map<String, Object> properties, String optionPrefix) {
434        ObjectHelper.notNull(properties, "properties");
435
436        Map<String, Object> rc = new LinkedHashMap<String, Object>(properties.size());
437
438        for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext();) {
439            Map.Entry<String, Object> entry = it.next();
440            String name = entry.getKey();
441            if (name.startsWith(optionPrefix)) {
442                Object value = properties.get(name);
443                name = name.substring(optionPrefix.length());
444                rc.put(name, value);
445                it.remove();
446            }
447        }
448
449        return rc;
450    }
451
452    public static boolean setProperties(CamelContext context, TypeConverter typeConverter, Object target, Map<String, Object> properties) throws Exception {
453        ObjectHelper.notNull(target, "target");
454        ObjectHelper.notNull(properties, "properties");
455        boolean rc = false;
456
457        for (Iterator<Map.Entry<String, Object>> iter = properties.entrySet().iterator(); iter.hasNext();) {
458            Map.Entry<String, Object> entry = iter.next();
459            if (setProperty(context, typeConverter, target, entry.getKey(), entry.getValue())) {
460                iter.remove();
461                rc = true;
462            }
463        }
464
465        return rc;
466    }
467    
468    public static boolean setProperties(TypeConverter typeConverter, Object target, Map<String, Object> properties) throws Exception {
469        return setProperties(null, typeConverter, target, properties);
470    }
471
472    public static boolean setProperties(Object target, Map<String, Object> properties) throws Exception {
473        return setProperties(null, target, properties);
474    }
475
476    /**
477     * This method supports two modes to set a property:
478     *
479     * 1. Setting a property that has already been resolved, this is the case when {@code context} and {@code refName} are
480     * NULL and {@code value} is non-NULL.
481     *
482     * 2. Setting a property that has not yet been resolved, the property will be resolved based on the suitable methods
483     * found matching the property name on the {@code target} bean. For this mode to be triggered the parameters
484     * {@code context} and {@code refName} must NOT be NULL, and {@code value} MUST be NULL.
485     *
486     */
487    public static boolean setProperty(CamelContext context, TypeConverter typeConverter, Object target, String name, Object value, String refName, boolean allowBuilderPattern) throws Exception {
488        Class<?> clazz = target.getClass();
489        Collection<Method> setters;
490
491        // we need to lookup the value from the registry
492        if (context != null && refName != null && value == null) {
493            setters = findSetterMethodsOrderedByParameterType(clazz, name, allowBuilderPattern);
494        } else {
495            // find candidates of setter methods as there can be overloaded setters
496            setters = findSetterMethods(clazz, name, value, allowBuilderPattern);
497        }
498        if (setters.isEmpty()) {
499            return false;
500        }
501
502        // loop and execute the best setter method
503        Exception typeConversionFailed = null;
504        for (Method setter : setters) {
505            Class<?> parameterType = setter.getParameterTypes()[0];
506            Object ref = value;
507            // try and lookup the reference based on the method
508            if (context != null && refName != null && ref == null) {
509                String s = StringHelper.replaceAll(refName, "#", "");
510                ref = CamelContextHelper.lookup(context, s);
511                if (ref == null) {
512                    // try the next method if nothing was found
513                    continue;
514                } else {
515                    // setter method has not the correct type
516                    // (must use ObjectHelper.isAssignableFrom which takes primitive types into account)
517                    boolean assignable = isAssignableFrom(parameterType, ref.getClass());
518                    if (!assignable) {
519                        continue;
520                    }
521                }
522            }
523
524            try {
525                try {
526                    // If the type is null or it matches the needed type, just use the value directly
527                    if (value == null || isAssignableFrom(parameterType, ref.getClass())) {
528                        // we may want to set options on classes that has package view visibility, so override the accessible
529                        setter.setAccessible(true);
530                        setter.invoke(target, ref);
531                        if (LOG.isDebugEnabled()) {
532                            LOG.debug("Configured property: {} on bean: {} with value: {}", new Object[]{name, target, ref});
533                        }
534                        return true;
535                    } else {
536                        // We need to convert it
537                        Object convertedValue = convert(typeConverter, parameterType, ref);
538                        // we may want to set options on classes that has package view visibility, so override the accessible
539                        setter.setAccessible(true);
540                        setter.invoke(target, convertedValue);
541                        if (LOG.isDebugEnabled()) {
542                            LOG.debug("Configured property: {} on bean: {} with value: {}", new Object[]{name, target, ref});
543                        }
544                        return true;
545                    }
546                } catch (InvocationTargetException e) {
547                    // lets unwrap the exception
548                    Throwable throwable = e.getCause();
549                    if (throwable instanceof Exception) {
550                        Exception exception = (Exception)throwable;
551                        throw exception;
552                    } else {
553                        Error error = (Error)throwable;
554                        throw error;
555                    }
556                }
557            // ignore exceptions as there could be another setter method where we could type convert successfully
558            } catch (SecurityException e) {
559                typeConversionFailed = e;
560            } catch (NoTypeConversionAvailableException e) {
561                typeConversionFailed = e;
562            } catch (IllegalArgumentException e) {
563                typeConversionFailed = e;
564            }
565            if (LOG.isTraceEnabled()) {
566                LOG.trace("Setter \"{}\" with parameter type \"{}\" could not be used for type conversions of {}",
567                        new Object[]{setter, parameterType, ref});
568            }
569        }
570
571        if (typeConversionFailed != null && !isPropertyPlaceholder(context, value)) {
572            // we did not find a setter method to use, and if we did try to use a type converter then throw
573            // this kind of exception as the caused by will hint this error
574            throw new IllegalArgumentException("Could not find a suitable setter for property: " + name
575                    + " as there isn't a setter method with same type: " + (value != null ? value.getClass().getCanonicalName() : "[null]")
576                    + " nor type conversion possible: " + typeConversionFailed.getMessage());
577        } else {
578            return false;
579        }
580    }
581
582    private static boolean isPropertyPlaceholder(CamelContext context, Object value) {
583        if (context != null) {
584            Component component = context.hasComponent("properties");
585            if (component != null) {
586                PropertiesComponent pc;
587                try {
588                    pc = context.getTypeConverter().mandatoryConvertTo(PropertiesComponent.class, component);
589                } catch (Exception e) {
590                    return false;
591                }
592                if (value.toString().contains(pc.getPrefixToken()) && value.toString().contains(pc.getSuffixToken())) {
593                    return true;
594                }
595            }
596        }
597        return false;
598    }
599
600    public static boolean setProperty(CamelContext context, TypeConverter typeConverter, Object target, String name, Object value) throws Exception {
601        // allow build pattern as a setter as well
602        return setProperty(context, typeConverter, target, name, value, null, true);
603    }
604    
605    public static boolean setProperty(TypeConverter typeConverter, Object target, String name, Object value) throws Exception {
606        // allow build pattern as a setter as well
607        return setProperty(null, typeConverter, target, name, value, null, true);
608    }
609    
610    public static boolean setProperty(Object target, String name, Object value, boolean allowBuilderPattern) throws Exception {
611        return setProperty(null, null, target, name, value, null, allowBuilderPattern);
612    }
613
614    public static boolean setProperty(Object target, String name, Object value) throws Exception {
615        // allow build pattern as a setter as well
616        return setProperty(target, name, value, true);
617    }
618
619    private static Object convert(TypeConverter typeConverter, Class<?> type, Object value)
620        throws URISyntaxException, NoTypeConversionAvailableException {
621        if (typeConverter != null) {
622            return typeConverter.mandatoryConvertTo(type, value);
623        }
624        if (type == URI.class) {
625            return new URI(value.toString());
626        }
627        PropertyEditor editor = PropertyEditorManager.findEditor(type);
628        if (editor != null) {
629            // property editor is not thread safe, so we need to lock
630            Object answer;
631            synchronized (LOCK) {
632                editor.setAsText(value.toString());
633                answer = editor.getValue();
634            }
635            return answer;
636        }
637        return null;
638    }
639
640    public static Set<Method> findSetterMethods(Class<?> clazz, String name, boolean allowBuilderPattern) {
641        Set<Method> candidates = new LinkedHashSet<Method>();
642
643        // Build the method name.
644        name = "set" + ObjectHelper.capitalize(name);
645        while (clazz != Object.class) {
646            // Since Object.class.isInstance all the objects,
647            // here we just make sure it will be add to the bottom of the set.
648            Method objectSetMethod = null;
649            Method[] methods = clazz.getMethods();
650            for (Method method : methods) {
651                if (method.getName().equals(name) && isSetter(method, allowBuilderPattern)) {
652                    Class<?> params[] = method.getParameterTypes();
653                    if (params[0].equals(Object.class)) {
654                        objectSetMethod = method;
655                    } else {
656                        candidates.add(method);
657                    }
658                }
659            }
660            if (objectSetMethod != null) {
661                candidates.add(objectSetMethod);
662            }
663            clazz = clazz.getSuperclass();
664        }
665        return candidates;
666    }
667
668    private static Set<Method> findSetterMethods(Class<?> clazz, String name, Object value, boolean allowBuilderPattern) {
669        Set<Method> candidates = findSetterMethods(clazz, name, allowBuilderPattern);
670
671        if (candidates.isEmpty()) {
672            return candidates;
673        } else if (candidates.size() == 1) {
674            // only one
675            return candidates;
676        } else {
677            // find the best match if possible
678            LOG.trace("Found {} suitable setter methods for setting {}", candidates.size(), name);
679            // prefer to use the one with the same instance if any exists
680            for (Method method : candidates) {                               
681                if (method.getParameterTypes()[0].isInstance(value)) {
682                    LOG.trace("Method {} is the best candidate as it has parameter with same instance type", method);
683                    // retain only this method in the answer
684                    candidates.clear();
685                    candidates.add(method);
686                    return candidates;
687                }
688            }
689            // fallback to return what we have found as candidates so far
690            return candidates;
691        }
692    }
693
694    protected static List<Method> findSetterMethodsOrderedByParameterType(Class<?> target, String propertyName, boolean allowBuilderPattern) {
695        List<Method> answer = new LinkedList<Method>();
696        List<Method> primitives = new LinkedList<Method>();
697        Set<Method> setters = findSetterMethods(target, propertyName, allowBuilderPattern);
698        for (Method setter : setters) {
699            Class<?> parameterType = setter.getParameterTypes()[0];
700            if (PRIMITIVE_CLASSES.contains(parameterType)) {
701                primitives.add(setter);
702            } else {
703                answer.add(setter);
704            }
705        }
706        // primitives get added last
707        answer.addAll(primitives);
708        return answer;
709    }
710
711}