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     */
017    package org.apache.xbean.recipe;
018    
019    import org.apache.xbean.propertyeditor.PropertyEditors;
020    
021    import java.lang.reflect.Constructor;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    import java.lang.reflect.Modifier;
025    import java.lang.reflect.Field;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.LinkedHashMap;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Iterator;
032    import java.security.AccessController;
033    import java.security.PrivilegedAction;
034    
035    /**
036     * @version $Rev: 6688 $ $Date: 2005-12-29T02:08:29.200064Z $
037     */
038    public class ObjectRecipe extends AbstractRecipe {
039        private final String type;
040        private final String factoryMethod;
041        private final String[] constructorArgNames;
042        private final Class[] constructorArgTypes;
043        private final LinkedHashMap<Property,Object> properties;
044        private final List<Option> options = new ArrayList<Option>();
045        private final Map<String,Object> unsetProperties = new LinkedHashMap<String,Object>();
046    
047        public ObjectRecipe(Class type) {
048            this(type.getName());
049        }
050    
051        public ObjectRecipe(Class type, String factoryMethod) {
052            this(type.getName(), factoryMethod);
053        }
054    
055        public ObjectRecipe(Class type, Map<String,Object> properties) {
056            this(type.getName(), properties);
057        }
058    
059        public ObjectRecipe(Class type, String[] constructorArgNames, Class[] constructorArgTypes) {
060            this(type.getName(), constructorArgNames, constructorArgTypes);
061        }
062    
063        public ObjectRecipe(Class type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
064            this(type.getName(), factoryMethod, constructorArgNames, constructorArgTypes);
065        }
066    
067        public ObjectRecipe(String typeName) {
068            this(typeName, null, null, null, null);
069        }
070    
071        public ObjectRecipe(String typeName, String factoryMethod) {
072            this(typeName, factoryMethod, null, null, null);
073        }
074    
075        public ObjectRecipe(String typeName, Map<String,Object> properties) {
076            this(typeName, null, null, null, properties);
077        }
078    
079        public ObjectRecipe(String typeName, String[] constructorArgNames, Class[] constructorArgTypes) {
080            this(typeName, null, constructorArgNames, constructorArgTypes, null);
081        }
082    
083        public ObjectRecipe(String typeName, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes) {
084            this(typeName, factoryMethod, constructorArgNames, constructorArgTypes, null);
085        }
086    
087        public ObjectRecipe(String type, String factoryMethod, String[] constructorArgNames, Class[] constructorArgTypes, Map<String,Object> properties) {
088            options.add(Option.FIELD_INJECTION);
089            
090            this.type = type;
091            this.factoryMethod = factoryMethod;
092            if (constructorArgNames != null) {
093                this.constructorArgNames = constructorArgNames;
094            } else {
095                this.constructorArgNames = new String[0];
096            }
097            if (constructorArgTypes != null) {
098                this.constructorArgTypes = constructorArgTypes;
099            } else {
100                this.constructorArgTypes = new Class[0];
101            }
102            if (properties != null) {
103                this.properties = new LinkedHashMap<Property,Object>();
104                setAllProperties(properties);
105            } else {
106                this.properties = new LinkedHashMap<Property,Object>();
107            }
108        }
109    
110        public void allow(Option option){
111            options.add(option);
112        }
113    
114        public void disallow(Option option){
115            options.remove(option);
116        }
117    
118        public Object getProperty(String name) {
119            Object value = properties.get(new Property(name));
120            return value;
121        }
122    
123        public void setProperty(String name, Object value) {
124            setProperty(new Property(name), value);
125        }
126    
127        public void setFieldProperty(String name, Object value){
128            setProperty(new FieldProperty(name), value);
129            options.add(Option.FIELD_INJECTION);
130        }
131    
132        public void setMethodProperty(String name, Object value){
133            setProperty(new SetterProperty(name), value);
134        }
135    
136        private void setProperty(Property key, Object value) {
137            if (value instanceof UnsetPropertiesRecipe) {
138                allow(Option.IGNORE_MISSING_PROPERTIES);
139            }
140            properties.put(key, value);
141        }
142    
143    
144        public void setAllProperties(Map map) {
145            if (map == null) throw new NullPointerException("map is null");
146            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
147                Map.Entry entry = (Map.Entry) iterator.next();
148                String name = (String) entry.getKey();
149                Object value = entry.getValue();
150                setProperty(name, value);
151            }
152        }
153    
154        public Map<String,Object> getUnsetProperties() {
155            return unsetProperties;
156        }
157    
158        public boolean canCreate(Class type, ClassLoader classLoader) {
159            Class myType = getType(classLoader);
160            return type.isAssignableFrom(myType);
161        }
162    
163        public Object create(ClassLoader classLoader) throws ConstructionException {
164            unsetProperties.clear();
165            // load the type class
166            Class typeClass = getType(classLoader);
167    
168            // verify that it is a class we can construct
169            if (!Modifier.isPublic(typeClass.getModifiers())) {
170                throw new ConstructionException("Class is not public: " + typeClass.getName());
171            }
172            if (Modifier.isInterface(typeClass.getModifiers())) {
173                throw new ConstructionException("Class is an interface: " + typeClass.getName());
174            }
175            if (Modifier.isAbstract(typeClass.getModifiers())) {
176                throw new ConstructionException("Class is abstract: " + typeClass.getName());
177            }
178    
179            // clone the properties so they can be used again
180            Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
181    
182            // find the factory method is one is declared
183            Method factoryMethod = null;
184            if (this.factoryMethod != null) {
185                factoryMethod = findFactoryMethod(typeClass, this.factoryMethod);
186            }
187    
188            // create the instance
189            Object result;
190            if (factoryMethod != null && Modifier.isStatic(factoryMethod.getModifiers())) {
191                result = createInstance(factoryMethod, propertyValues, classLoader);
192            } else {
193                Constructor constructor = selectConstructor(typeClass);
194                result = createInstance(constructor, propertyValues, classLoader);
195            }
196            Object instance = result;
197    
198            setProperties(propertyValues, instance, instance.getClass(), classLoader);
199            // call instance factory method
200            if (factoryMethod != null && !Modifier.isStatic(factoryMethod.getModifiers())) {
201                try {
202                    instance = factoryMethod.invoke(instance);
203                } catch (Exception e) {
204                    Throwable t = e;
205                    if (e instanceof InvocationTargetException) {
206                        InvocationTargetException invocationTargetException = (InvocationTargetException) e;
207                        if (invocationTargetException.getCause() != null) {
208                            t = invocationTargetException.getCause();
209                        }
210                    }
211                    throw new ConstructionException("Error calling factory method: " + factoryMethod, t);
212                }
213            }
214    
215            return instance;
216        }
217        
218        public Class setStaticProperties(ClassLoader classLoader) throws ConstructionException {
219            unsetProperties.clear();
220            // load the type class
221            Class typeClass = getType(classLoader);
222    
223            // verify that it is a class we can construct
224            if (!Modifier.isPublic(typeClass.getModifiers())) {
225                throw new ConstructionException("Class is not public: " + typeClass.getName());
226            }
227            if (Modifier.isInterface(typeClass.getModifiers())) {
228                throw new ConstructionException("Class is an interface: " + typeClass.getName());
229            }
230            if (Modifier.isAbstract(typeClass.getModifiers())) {
231                throw new ConstructionException("Class is abstract: " + typeClass.getName());
232            }
233    
234            // clone the properties so they can be used again
235            Map<Property,Object> propertyValues = new LinkedHashMap<Property,Object>(properties);
236    
237            setProperties(propertyValues, null, typeClass, classLoader);
238    
239            return typeClass;
240        }
241    
242        private void setProperties(Map<Property, Object> propertyValues, Object instance, Class clazz, ClassLoader classLoader) {
243            boolean allowPrivate = options.contains(Option.PRIVATE_PROPERTIES);
244            boolean allowStatic = options.contains(Option.STATIC_PROPERTIES);
245            boolean ignoreMissingProperties = options.contains(Option.IGNORE_MISSING_PROPERTIES);
246            boolean caseInsesnitive = options.contains(Option.CASE_INSENSITIVE_PROPERTIES);
247            // set remaining properties
248            for (Map.Entry<Property, Object> entry : RecipeHelper.prioritizeProperties(propertyValues)) {
249                Property propertyName = entry.getKey();
250                Object propertyValue = entry.getValue();
251    
252                setProperty(instance, clazz, propertyName, propertyValue, allowPrivate, allowStatic, ignoreMissingProperties, caseInsesnitive, classLoader);
253            }
254    
255        }
256    
257        private Class getType(ClassLoader classLoader) {
258            Class typeClass = null;
259            try {
260                typeClass = Class.forName(type, true, classLoader);
261            } catch (ClassNotFoundException e) {
262                throw new ConstructionException("Type class could not be found: " + type);
263            }
264            return typeClass;
265        }
266    
267        private void setProperty(Object instance, Class clazz, Property propertyName, Object propertyValue, boolean allowPrivate, boolean allowStatic, boolean ignoreMissingProperties, boolean caseInsesnitive, ClassLoader classLoader) {
268            Member member;
269            try {
270                if (propertyName instanceof SetterProperty){
271                    member = new MethodMember(findSetter(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader));
272                } else if (propertyName instanceof FieldProperty){
273                    member = new FieldMember(findField(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader));
274                } else {
275                    try {
276                        member = new MethodMember(findSetter(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader));
277                    } catch (MissingAccessorException noSetter) {
278                        if (!options.contains(Option.FIELD_INJECTION)) {
279                            throw noSetter;
280                        }
281    
282                        try {
283                            member = new FieldMember(findField(clazz, propertyName.name, propertyValue, allowPrivate, allowStatic, caseInsesnitive, classLoader));
284                        } catch (MissingAccessorException noField) {
285                            throw (noField.getMatchLevel() > noSetter.getMatchLevel())? noField: noSetter;
286                        }
287                    }
288                }
289            } catch (MissingAccessorException e) {
290                if (ignoreMissingProperties){
291                    unsetProperties.put(propertyName.name, propertyValue);
292                    return;
293                } else {
294                    throw e;
295                }
296            }
297    
298            try {
299                propertyValue = convert(member.getType(), propertyValue, classLoader);
300                member.setValue(instance, propertyValue);
301            } catch (Exception e) {
302                Throwable t = e;
303                if (e instanceof InvocationTargetException) {
304                    InvocationTargetException invocationTargetException = (InvocationTargetException) e;
305                    if (invocationTargetException.getCause() != null) {
306                        t = invocationTargetException.getCause();
307                    }
308                }
309                throw new ConstructionException("Error setting property: " + member, t);
310            }
311        }
312    
313        private Object[] extractConstructorArgs(Map propertyValues, Class[] constructorArgTypes, ClassLoader classLoader) {
314            Object[] parameters = new Object[constructorArgNames.length];
315            for (int i = 0; i < constructorArgNames.length; i++) {
316                Property name = new Property(constructorArgNames[i]);
317                Class type = constructorArgTypes[i];
318    
319                Object value;
320                if (propertyValues.containsKey(name)) {
321                    value = propertyValues.remove(name);
322                    if (!RecipeHelper.isInstance(type, value) && !RecipeHelper.isConvertable(type, value, classLoader)) {
323                        throw new ConstructionException("Invalid and non-convertable constructor parameter type: " +
324                                "name=" + name + ", " +
325                                "index=" + i + ", " +
326                                "expected=" + type.getName() + ", " +
327                                "actual=" + getClassName(value));
328                    }
329                    value = convert(type, value, classLoader);
330                } else {
331                    value = getDefaultValue(type);
332                }
333    
334    
335                parameters[i] = value;
336            }
337            return parameters;
338        }
339    
340        private static String getClassName(Object value) {
341            if (value == null) return "null";
342    
343            return value.getClass().getName();
344        }
345    
346        private Object convert(Class type, Object value, ClassLoader classLoader) {
347            if (value instanceof Recipe) {
348                if (value instanceof SecretRecipe) {
349                    SecretRecipe recipe = (SecretRecipe) value;
350                    value = recipe.create(this, classLoader);
351                } else {
352                    Recipe recipe = (Recipe) value;
353                    value = recipe.create(classLoader);
354                }
355            }
356    
357            if (value instanceof String && (type != Object.class)) {
358                String stringValue = (String) value;
359                value = PropertyEditors.getValue(type, stringValue);
360            }
361            return value;
362        }
363    
364        private static Object getDefaultValue(Class type) {
365            if (type.equals(Boolean.TYPE)) {
366                return Boolean.FALSE;
367            } else if (type.equals(Character.TYPE)) {
368                return new Character((char) 0);
369            } else if (type.equals(Byte.TYPE)) {
370                return new Byte((byte) 0);
371            } else if (type.equals(Short.TYPE)) {
372                return new Short((short) 0);
373            } else if (type.equals(Integer.TYPE)) {
374                return new Integer(0);
375            } else if (type.equals(Long.TYPE)) {
376                return new Long(0);
377            } else if (type.equals(Float.TYPE)) {
378                return new Float(0);
379            } else if (type.equals(Double.TYPE)) {
380                return new Double(0);
381            }
382            return null;
383        }
384    
385        private Object createInstance(Constructor constructor, Map propertyValues, ClassLoader classLoader) {
386            // get the constructor parameters
387            Object[] parameters = extractConstructorArgs(propertyValues, constructor.getParameterTypes(), classLoader);
388    
389            try {
390                Object object = constructor.newInstance(parameters);
391                return object;
392            } catch (Exception e) {
393                Throwable t = e;
394                if (e instanceof InvocationTargetException) {
395                    InvocationTargetException invocationTargetException = (InvocationTargetException) e;
396                    if (invocationTargetException.getCause() != null) {
397                        t = invocationTargetException.getCause();
398                    }
399                }
400                throw new ConstructionException("Error invoking constructor: " + constructor, t);
401            }
402        }
403    
404        private Object createInstance(Method method, Map propertyValues, ClassLoader classLoader) {
405            // get the constructor parameters
406            Object[] parameters = extractConstructorArgs(propertyValues, method.getParameterTypes(), classLoader);
407    
408            try {
409                Object object = method.invoke(null, parameters);
410                return object;
411            } catch (Exception e) {
412                Throwable t = e;
413                if (e instanceof InvocationTargetException) {
414                    InvocationTargetException invocationTargetException = (InvocationTargetException) e;
415                    if (invocationTargetException.getCause() != null) {
416                        t = invocationTargetException.getCause();
417                    }
418                }
419                throw new ConstructionException("Error invoking factory method: " + method, t);
420            }
421        }
422    
423        private Constructor selectConstructor(Class typeClass) {
424            if (constructorArgNames.length > 0 && constructorArgTypes.length == 0) {
425                ArrayList<Constructor> matches = new ArrayList<Constructor>();
426    
427                Constructor[] constructors = typeClass.getConstructors();
428                for (Constructor constructor : constructors) {
429                    if (constructor.getParameterTypes().length == constructorArgNames.length) {
430                        matches.add(constructor);
431                    }
432                }
433    
434                if (matches.size() < 1) {
435                    StringBuffer buffer = new StringBuffer("No parameter types supplied; unable to find a potentially valid constructor: ");
436                    buffer.append("constructor= public ").append(typeClass.getName());
437                    buffer.append(toArgumentList(constructorArgNames));
438                    throw new ConstructionException(buffer.toString());
439                } else if (matches.size() > 1) {
440                    StringBuffer buffer = new StringBuffer("No parameter types supplied; found too many potentially valid constructors: ");
441                    buffer.append("constructor= public ").append(typeClass.getName());
442                    buffer.append(toArgumentList(constructorArgNames));
443                    throw new ConstructionException(buffer.toString());
444                }
445    
446                return matches.get(0);
447            }
448    
449            try {
450                Constructor constructor = typeClass.getConstructor(constructorArgTypes);
451    
452                if (!Modifier.isPublic(constructor.getModifiers())) {
453                    // this will never occur since private constructors are not returned from
454                    // getConstructor, but leave this here anyway, just to be safe
455                    throw new ConstructionException("Constructor is not public: " + constructor);
456                }
457    
458                return constructor;
459            } catch (NoSuchMethodException e) {
460                // try to find a matching private method
461                Constructor[] constructors = typeClass.getDeclaredConstructors();
462                for (Constructor constructor : constructors) {
463                    if (isAssignableFrom(constructorArgTypes, constructor.getParameterTypes())) {
464                        if (!Modifier.isPublic(constructor.getModifiers())) {
465                            throw new ConstructionException("Constructor is not public: " + constructor);
466                        }
467                    }
468                }
469    
470                StringBuffer buffer = new StringBuffer("Unable to find a valid constructor: ");
471                buffer.append("constructor= public ").append(typeClass.getName());
472                buffer.append(toParameterList(constructorArgTypes));
473                throw new ConstructionException(buffer.toString());
474            }
475        }
476    
477        private String toParameterList(Class[] parameterTypes) {
478            StringBuffer buffer = new StringBuffer();
479            buffer.append("(");
480            for (int i = 0; i < parameterTypes.length; i++) {
481                Class type = parameterTypes[i];
482                if (i > 0) buffer.append(", ");
483                buffer.append(type.getName());
484            }
485            buffer.append(")");
486            return buffer.toString();
487        }
488    
489        private String toArgumentList(String[] parameterNames) {
490            StringBuffer buffer = new StringBuffer();
491            buffer.append("(");
492            for (int i = 0; i < parameterNames.length; i++) {
493                String parameterName = parameterNames[i];
494                if (i > 0) buffer.append(", ");
495                buffer.append('<').append(parameterName).append('>');
496            }
497            buffer.append(")");
498            return buffer.toString();
499        }
500    
501        public Method findFactoryMethod(Class typeClass, String factoryMethod) {
502            if (factoryMethod == null) throw new NullPointerException("name is null");
503            if (factoryMethod.length() == 0) throw new IllegalArgumentException("name is an empty string");
504    
505            int matchLevel = 0;
506            MissingFactoryMethodException missException = null;
507    
508            List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
509            methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
510            for (Method method : methods) {
511                if (method.getName().equals(factoryMethod)) {
512                    if (Modifier.isStatic(method.getModifiers())) {
513                        if (method.getParameterTypes().length != constructorArgNames.length) {
514                            if (matchLevel < 1) {
515                                matchLevel = 1;
516                                missException = new MissingFactoryMethodException("Static factory method has " + method.getParameterTypes().length + " arugments " +
517                                        "but expected " + constructorArgNames.length + " arguments: " + method);
518                            }
519                            continue;
520                        }
521    
522                        if (constructorArgTypes.length > 0 && !isAssignableFrom(constructorArgTypes, method.getParameterTypes())) {
523                            if (matchLevel < 2) {
524                                matchLevel = 2;
525                                missException = new MissingFactoryMethodException("Static factory method has signature " +
526                                        "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(method.getParameterTypes()) +
527                                        " but expected signature " +
528                                        "public static " + typeClass.getName() + "." + factoryMethod + toParameterList(constructorArgTypes));
529                            }
530                            continue;
531                        }
532                    } else {
533                        if (method.getParameterTypes().length != 0) {
534                            if (matchLevel < 1) {
535                                matchLevel = 1;
536                                missException = new MissingFactoryMethodException("Instance factory method has parameters: " + method);
537                            }
538                            continue;
539                        }
540                    }
541    
542                    if (method.getReturnType() == Void.TYPE) {
543                        if (matchLevel < 3) {
544                            matchLevel = 3;
545                            missException = new MissingFactoryMethodException("Factory method does not return a value: " + method);
546                        }
547                        continue;
548                    }
549    
550                    if (Modifier.isAbstract(method.getModifiers())) {
551                        if (matchLevel < 4) {
552                            matchLevel = 4;
553                            missException = new MissingFactoryMethodException("Factory method is abstract: " + method);
554                        }
555                        continue;
556                    }
557    
558                    if (!Modifier.isPublic(method.getModifiers())) {
559                        if (matchLevel < 5) {
560                            matchLevel = 5;
561                            missException = new MissingFactoryMethodException("Factory method is not public: " + method);
562                        }
563                        continue;
564                    }
565    
566                    return method;
567                }
568            }
569    
570            if (missException != null) {
571                throw missException;
572            } else {
573                StringBuffer buffer = new StringBuffer("Unable to find a valid factory method: ");
574                buffer.append("public void ").append(typeClass.getName()).append(".");
575                buffer.append(factoryMethod).append("()");
576                throw new MissingFactoryMethodException(buffer.toString());
577            }
578        }
579    
580        /**
581         * @deprecated use the method with allowStatic
582         * @param typeClass
583         * @param propertyName
584         * @param propertyValue
585         * @param allowPrivate
586         * @param classLoader
587         * @return field
588         */
589        public static Method findSetter(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, ClassLoader classLoader) {
590            return findSetter(typeClass, propertyName,  propertyValue, allowPrivate, false, false, classLoader);
591        }
592    
593        public static Method findSetter(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, boolean allowStatic, boolean caseInsesnitive, ClassLoader classLoader) {
594            if (propertyName == null) throw new NullPointerException("name is null");
595            if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string");
596    
597            if (propertyName.contains("/")){
598                String[] strings = propertyName.split("/");
599                if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName);
600    
601                String className = strings[0];
602                propertyName = strings[1];
603    
604                boolean found = false;
605                while(!typeClass.equals(Object.class) && !found){
606                    if (typeClass.getName().equals(className)){
607                        found = true;
608                        break;
609                    } else {
610                        typeClass = typeClass.getSuperclass();
611                    }
612                }
613    
614                if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1);
615            }
616    
617            String setterName = "set" + Character.toUpperCase(propertyName.charAt(0));
618            if (propertyName.length() > 0) {
619                setterName += propertyName.substring(1);
620            }
621    
622    
623            int matchLevel = 0;
624            MissingAccessorException missException = null;
625    
626            List<Method> methods = new ArrayList<Method>(Arrays.asList(typeClass.getMethods()));
627            methods.addAll(Arrays.asList(typeClass.getDeclaredMethods()));
628            for (Method method : methods) {
629                if (method.getName().equals(setterName) || (caseInsesnitive && method.getName().equalsIgnoreCase(setterName))) {
630                    if (method.getParameterTypes().length == 0) {
631                        if (matchLevel < 1) {
632                            matchLevel = 1;
633                            missException = new MissingAccessorException("Setter takes no parameters: " + method, matchLevel);
634                        }
635                        continue;
636                    }
637    
638                    if (method.getParameterTypes().length > 1) {
639                        if (matchLevel < 1) {
640                            matchLevel = 1;
641                            missException = new MissingAccessorException("Setter takes more then one parameter: " + method, matchLevel);
642                        }
643                        continue;
644                    }
645    
646                    if (method.getReturnType() != Void.TYPE) {
647                        if (matchLevel < 2) {
648                            matchLevel = 2;
649                            missException = new MissingAccessorException("Setter returns a value: " + method, matchLevel);
650                        }
651                        continue;
652                    }
653    
654                    if (Modifier.isAbstract(method.getModifiers())) {
655                        if (matchLevel < 3) {
656                            matchLevel = 3;
657                            missException = new MissingAccessorException("Setter is abstract: " + method, matchLevel);
658                        }
659                        continue;
660                    }
661    
662                    if (!allowPrivate && !Modifier.isPublic(method.getModifiers())) {
663                        if (matchLevel < 4) {
664                            matchLevel = 4;
665                            missException = new MissingAccessorException("Setter is not public: " + method, matchLevel);
666                        }
667                        continue;
668                    }
669    
670                    if (!allowStatic && Modifier.isStatic(method.getModifiers())) {
671                        if (matchLevel < 4) {
672                            matchLevel = 4;
673                            missException = new MissingAccessorException("Setter is static: " + method, matchLevel);
674                        }
675                        continue;
676                    }
677    
678                    Class methodParameterType = method.getParameterTypes()[0];
679                    if (methodParameterType.isPrimitive() && propertyValue == null) {
680                        if (matchLevel < 6) {
681                            matchLevel = 6;
682                            missException = new MissingAccessorException("Null can not be assigned to " +
683                                    methodParameterType.getName() + ": " + method, matchLevel);
684                        }
685                        continue;
686                    }
687    
688    
689                    if (!RecipeHelper.isInstance(methodParameterType, propertyValue) && !RecipeHelper.isConvertable(methodParameterType, propertyValue, classLoader)) {
690                        if (matchLevel < 5) {
691                            matchLevel = 5;
692                            missException = new MissingAccessorException(getClassName(propertyValue) + " can not be assigned or converted to " +
693                                    methodParameterType.getName() + ": " + method, matchLevel);
694                        }
695                        continue;
696                    }
697    
698                    if (allowPrivate && !Modifier.isPublic(method.getModifiers())) {
699                        setAccessible(method);
700                    }
701    
702                    return method;
703                }
704    
705            }
706    
707            if (missException != null) {
708                throw missException;
709            } else {
710                StringBuffer buffer = new StringBuffer("Unable to find a valid setter method: ");
711                buffer.append("public void ").append(typeClass.getName()).append(".");
712                buffer.append(setterName).append("(").append(getClassName(propertyValue)).append(")");
713                throw new MissingAccessorException(buffer.toString(), -1);
714            }
715        }
716    
717        /**
718         * @deprecated use the method with allowStatic
719         * @param typeClass
720         * @param propertyName
721         * @param propertyValue
722         * @param allowPrivate
723         * @param classLoader
724         * @return field
725         */
726        public static Field findField(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, ClassLoader classLoader) {
727            return findField(typeClass, propertyName,  propertyValue, allowPrivate, false, false, classLoader);
728        }
729    
730        public static Field findField(Class typeClass, String propertyName, Object propertyValue, boolean allowPrivate, boolean allowStatic, boolean caseInsesnitive, ClassLoader classLoader) {
731            if (propertyName == null) throw new NullPointerException("name is null");
732            if (propertyName.length() == 0) throw new IllegalArgumentException("name is an empty string");
733    
734            int matchLevel = 0;
735            MissingAccessorException missException = null;
736    
737            if (propertyName.contains("/")){
738                String[] strings = propertyName.split("/");
739                if (strings == null || strings.length != 2) throw new IllegalArgumentException("badly formed <class>/<attribute> property name: " + propertyName);
740    
741                String className = strings[0];
742                propertyName = strings[1];
743    
744                boolean found = false;
745                while(!typeClass.equals(Object.class) && !found){
746                    if (typeClass.getName().equals(className)){
747                        found = true;
748                        break;
749                    } else {
750                        typeClass = typeClass.getSuperclass();
751                    }
752                }
753    
754                if (!found) throw new MissingAccessorException("Type not assignable to class: " + className, -1);
755            }
756            
757            List<Field> fields = new ArrayList<Field>(Arrays.asList(typeClass.getDeclaredFields()));
758            Class parent = typeClass.getSuperclass();
759            while (parent != null){
760                fields.addAll(Arrays.asList(parent.getDeclaredFields()));
761                parent = parent.getSuperclass();
762            }
763    
764            for (Field field : fields) {
765                if (field.getName().equals(propertyName) || (caseInsesnitive && field.getName().equalsIgnoreCase(propertyName))) {
766    
767                    if (!allowPrivate && !Modifier.isPublic(field.getModifiers())) {
768                        if (matchLevel < 4) {
769                            matchLevel = 4;
770                            missException = new MissingAccessorException("Field is not public: " + field, matchLevel);
771                        }
772                        continue;
773                    }
774    
775                    if (!allowStatic && Modifier.isStatic(field.getModifiers())) {
776                        if (matchLevel < 4) {
777                            matchLevel = 4;
778                            missException = new MissingAccessorException("Field is static: " + field, matchLevel);
779                        }
780                        continue;
781                    }
782    
783                    Class fieldType = field.getType();
784                    if (fieldType.isPrimitive() && propertyValue == null) {
785                        if (matchLevel < 6) {
786                            matchLevel = 6;
787                            missException = new MissingAccessorException("Null can not be assigned to " +
788                                    fieldType.getName() + ": " + field, matchLevel);
789                        }
790                        continue;
791                    }
792    
793    
794                    if (!RecipeHelper.isInstance(fieldType, propertyValue) && !RecipeHelper.isConvertable(fieldType, propertyValue, classLoader)) {
795                        if (matchLevel < 5) {
796                            matchLevel = 5;
797                            missException = new MissingAccessorException(getClassName(propertyValue) + " can not be assigned or converted to " +
798                                    fieldType.getName() + ": " + field, matchLevel);
799                        }
800                        continue;
801                    }
802    
803                    if (allowPrivate && !Modifier.isPublic(field.getModifiers())) {
804                        setAccessible(field);
805                    }
806    
807                    return field;
808                }
809    
810            }
811    
812            if (missException != null) {
813                throw missException;
814            } else {
815                StringBuffer buffer = new StringBuffer("Unable to find a valid field: ");
816                buffer.append("public ").append(" ").append(getClassName(propertyValue));
817                buffer.append(" ").append(propertyName).append(";");
818                throw new MissingAccessorException(buffer.toString(), -1);
819            }
820        }
821    
822        public static boolean isAssignableFrom(Class[] expectedTypes, Class[] actualTypes) {
823            if (expectedTypes.length != actualTypes.length) {
824                return false;
825            }
826            for (int i = 0; i < expectedTypes.length; i++) {
827                Class expectedType = expectedTypes[i];
828                Class actualType = actualTypes[i];
829                if (expectedType != actualType && !RecipeHelper.isAssignableFrom(expectedType, actualType)) {
830                    return false;
831                }
832            }
833            return true;
834        }
835    
836        private static void setAccessible(final Method method) {
837            AccessController.doPrivileged(new PrivilegedAction<Object>() {
838                public Object run() {
839                    method.setAccessible(true);
840                    return null;
841                }
842            });
843        }
844    
845        private static void setAccessible(final Field field) {
846            AccessController.doPrivileged(new PrivilegedAction<Object>() {
847                public Object run() {
848                    field.setAccessible(true);
849                    return null;
850                }
851            });
852        }
853    
854        public static interface Member {
855            Class getType();
856            void setValue(Object instance, Object value) throws Exception;
857        }
858    
859        public static class MethodMember implements Member {
860            private final Method setter;
861    
862            public MethodMember(Method method) {
863                this.setter = method;
864            }
865    
866            public Class getType() {
867                return setter.getParameterTypes()[0];
868            }
869    
870            public void setValue(Object instance, Object value) throws Exception {
871                setter.invoke(instance, value);
872            }
873    
874            public String toString() {
875                return setter.toString();
876            }
877        }
878    
879        public static class FieldMember implements Member {
880            private final Field field;
881    
882            public FieldMember(Field field) {
883                this.field = field;
884            }
885    
886            public Class getType() {
887                return field.getType();
888            }
889    
890            public void setValue(Object instance, Object value) throws Exception {
891                field.set(instance, value);
892            }
893    
894            public String toString() {
895                return field.toString();
896            }
897        }
898    
899        private static class Property {
900            private final String name;
901    
902            public Property(String name) {
903                if (name == null) throw new NullPointerException("name is null");
904                this.name = name;
905            }
906    
907            public boolean equals(Object o) {
908                if (this == o) return true;
909                if (o == null) return false;
910                if (o instanceof String){
911                    return this.name.equals(o);
912                }
913                if (o instanceof Property) {
914                    Property property = (Property) o;
915                    return this.name.equals(property.name);
916                }
917                return false;
918            }
919    
920            public int hashCode() {
921                return name.hashCode();
922            }
923    
924            public String toString() {
925                return name;
926            }
927        }
928    
929        private static class SetterProperty extends Property {
930            public SetterProperty(String name) {
931                super(name);
932            }
933            public int hashCode() {
934                return super.hashCode()+2;
935            }
936            public String toString() {
937                return "[setter] "+toString();
938            }
939    
940        }
941        private static class FieldProperty extends Property {
942            public FieldProperty(String name) {
943                super(name);
944            }
945    
946            public int hashCode() {
947                return super.hashCode()+1;
948            }
949            public String toString() {
950                return "[field] "+toString();
951            }
952        }
953    }