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