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 }