| 
0001 /*0002  * Copyright 2008-2015 the original author or authors.
 0003  *
 0004  * Licensed under the Apache License, Version 2.0 (the "License");
 0005  * you may not use this file except in compliance with the License.
 0006  * You may obtain a copy of the License at
 0007  *
 0008  *     http://www.apache.org/licenses/LICENSE-2.0
 0009  *
 0010  * Unless required by applicable law or agreed to in writing, software
 0011  * distributed under the License is distributed on an "AS IS" BASIS,
 0012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 0013  * See the License for the specific language governing permissions and
 0014  * limitations under the License.
 0015  */
 0016 package griffon.util;
 0017
 0018 import griffon.core.Observable;
 0019 import griffon.core.Vetoable;
 0020 import griffon.core.artifact.GriffonArtifact;
 0021 import griffon.core.artifact.GriffonMvcArtifact;
 0022 import griffon.core.event.EventPublisher;
 0023 import griffon.core.i18n.MessageSource;
 0024 import griffon.core.mvc.MVCHandler;
 0025 import griffon.core.resources.ResourceHandler;
 0026 import griffon.core.resources.ResourceResolver;
 0027 import griffon.core.threading.ThreadingHandler;
 0028 import griffon.exceptions.BeanInstantiationException;
 0029 import griffon.exceptions.FieldException;
 0030 import griffon.exceptions.InstanceMethodInvocationException;
 0031 import griffon.exceptions.PropertyException;
 0032 import griffon.exceptions.StaticMethodInvocationException;
 0033
 0034 import javax.annotation.Nonnull;
 0035 import javax.annotation.Nullable;
 0036 import java.beans.BeanInfo;
 0037 import java.beans.IntrospectionException;
 0038 import java.beans.Introspector;
 0039 import java.beans.PropertyDescriptor;
 0040 import java.lang.reflect.Field;
 0041 import java.lang.reflect.InvocationTargetException;
 0042 import java.lang.reflect.Method;
 0043 import java.lang.reflect.Modifier;
 0044 import java.util.ArrayList;
 0045 import java.util.Collection;
 0046 import java.util.HashMap;
 0047 import java.util.HashSet;
 0048 import java.util.LinkedHashMap;
 0049 import java.util.List;
 0050 import java.util.Map;
 0051 import java.util.Set;
 0052 import java.util.SortedSet;
 0053 import java.util.TreeSet;
 0054 import java.util.regex.Pattern;
 0055
 0056 import static griffon.util.GriffonNameUtils.requireNonBlank;
 0057 import static griffon.util.MethodUtils.invokeExactMethod;
 0058 import static griffon.util.MethodUtils.invokeMethod;
 0059 import static java.util.Objects.requireNonNull;
 0060
 0061 /**
 0062  * Class containing utility methods for dealing with Griffon class artifacts.<p>
 0063  * Contains utility methods copied from commons-lang and commons-beanutils in order
 0064  * to reduce dependencies on external libraries.<p>
 0065  * <p/>
 0066  * <b>Contains code copied from commons-beanutils and commons-langs</b>
 0067  *
 0068  * @author Graeme Rocher (Grails 0.1)
 0069  */
 0070 public class GriffonClassUtils {
 0071     public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
 0072     public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
 0073     public static final Object[] EMPTY_ARGS = EMPTY_OBJECT_ARRAY;
 0074
 0075     private static final String PROPERTY_GET_PREFIX = "get";
 0076     private static final String PROPERTY_IS_PREFIX = "is";
 0077     private static final String PROPERTY_SET_PREFIX = "set";
 0078     public static final Map<Class<?>, Class<?>> PRIMITIVE_TYPE_COMPATIBLE_CLASSES = new LinkedHashMap<>();
 0079     public static final Map<String, String> PRIMITIVE_TYPE_COMPATIBLE_TYPES = new LinkedHashMap<>();
 0080
 0081     private static final Pattern EVENT_HANDLER_PATTERN = Pattern.compile("^on[A-Z][\\w]*$");
 0082     private static final Pattern CONTRIBUTION_PATTERN = Pattern.compile("^with[A-Z][a-z0-9_]*[\\w]*$");
 0083     private static final Pattern GETTER_PATTERN_1 = Pattern.compile("^get[A-Z][\\w]*$");
 0084     private static final Pattern GETTER_PATTERN_2 = Pattern.compile("^is[A-Z][\\w]*$");
 0085     private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z][\\w]*$");
 0086     private static final Set<MethodDescriptor> BASIC_METHODS = new TreeSet<>();
 0087     private static final Set<MethodDescriptor> ARTIFACT_METHODS = new TreeSet<>();
 0088     private static final Set<MethodDescriptor> MVC_METHODS = new TreeSet<>();
 0089     private static final Set<MethodDescriptor> THREADING_METHODS = new TreeSet<>();
 0090     private static final Set<MethodDescriptor> EVENT_PUBLISHER_METHODS = new TreeSet<>();
 0091     private static final Set<MethodDescriptor> OBSERVABLE_METHODS = new TreeSet<>();
 0092     private static final Set<MethodDescriptor> RESOURCE_HANDLER_METHODS = new TreeSet<>();
 0093     private static final Set<MethodDescriptor> MESSAGE_SOURCE_METHODS = new TreeSet<>();
 0094     private static final Set<MethodDescriptor> RESOURCE_RESOLVER_METHODS = new TreeSet<>();
 0095     private static final String ERROR_TYPE_NULL = "Argument 'type' must not be null";
 0096     private static final String ERROR_METHOD_NAME_BLANK = "Argument 'methodName' must not be blank";
 0097     private static final String ERROR_OBJECT_NULL = "Argument 'object' must not be null";
 0098     private static final String ERROR_CLAZZ_NULL = "Argument 'clazz' must not be null";
 0099     private static final String ERROR_DESCRIPTOR_NULL = "Argument 'descriptor' must not be null";
 0100     private static final String ERROR_BEAN_NULL = "Argument 'bean' must not be null";
 0101     private static final String ERROR_NAME_BLANK = "Argument 'name' must not be blank";
 0102     private static final String ERROR_PROPERTIES_NULL = "Argument 'properties' must not be null";
 0103     private static final String ERROR_FIELDS_NULL = "Argument 'fields' must not be null";
 0104     private static final String ERROR_PROPERTY_NAME_BLANK = "Argument 'propertyName' must not be blank";
 0105     private static final String ERROR_METHOD_NULL = "Argument 'method' must not be null";
 0106
 0107     /**
 0108      * Just add two entries to the class compatibility map
 0109      *
 0110      * @param left
 0111      * @param right
 0112      */
 0113     private static void registerPrimitiveClassPair(Class<?> left, Class<?> right) {
 0114         PRIMITIVE_TYPE_COMPATIBLE_CLASSES.put(left, right);
 0115         PRIMITIVE_TYPE_COMPATIBLE_CLASSES.put(right, left);
 0116         PRIMITIVE_TYPE_COMPATIBLE_TYPES.put(left.getName(), right.getName());
 0117         PRIMITIVE_TYPE_COMPATIBLE_TYPES.put(right.getName(), left.getName());
 0118     }
 0119
 0120     static {
 0121         registerPrimitiveClassPair(Boolean.class, boolean.class);
 0122         registerPrimitiveClassPair(Integer.class, int.class);
 0123         registerPrimitiveClassPair(Short.class, short.class);
 0124         registerPrimitiveClassPair(Byte.class, byte.class);
 0125         registerPrimitiveClassPair(Character.class, char.class);
 0126         registerPrimitiveClassPair(Long.class, long.class);
 0127         registerPrimitiveClassPair(Float.class, float.class);
 0128         registerPrimitiveClassPair(Double.class, double.class);
 0129
 0130         for (Method method : Object.class.getMethods()) {
 0131             MethodDescriptor md = MethodDescriptor.forMethod(method);
 0132             if (!BASIC_METHODS.contains(md)) {
 0133                 BASIC_METHODS.add(md);
 0134             }
 0135         }
 0136
 0137         try {
 0138             Class groovyObjectClass = GriffonClassUtils.class.getClassLoader().loadClass("groovy.lang.GroovyObject");
 0139             for (Method method : groovyObjectClass.getMethods()) {
 0140                 MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0141                 if (!BASIC_METHODS.contains(md)) {
 0142                     BASIC_METHODS.add(md);
 0143                 }
 0144             }
 0145         } catch (ClassNotFoundException cnfe) {
 0146             // ignore
 0147         }
 0148
 0149         try {
 0150             Class groovyObjectClass = GriffonClassUtils.class.getClassLoader().loadClass("groovy.lang.GroovyObjectSupport");
 0151             for (Method method : groovyObjectClass.getMethods()) {
 0152                 MethodDescriptor md = MethodDescriptor.forMethod(method);
 0153                 if (!BASIC_METHODS.contains(md)) {
 0154                     BASIC_METHODS.add(md);
 0155                 }
 0156             }
 0157         } catch (ClassNotFoundException cnfe) {
 0158             // ignore
 0159         }
 0160
 0161         for (Method method : GriffonArtifact.class.getMethods()) {
 0162             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0163             if (!ARTIFACT_METHODS.contains(md)) {
 0164                 ARTIFACT_METHODS.add(md);
 0165             }
 0166         }
 0167
 0168         // MVC_METHODS.add(new MethodDescriptor("getMvcGroup"));
 0169         for (Method method : MVCHandler.class.getMethods()) {
 0170             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0171             if (!MVC_METHODS.contains(md)) {
 0172                 MVC_METHODS.add(md);
 0173             }
 0174         }
 0175         for (Method method : GriffonMvcArtifact.class.getMethods()) {
 0176             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0177             if (!MVC_METHODS.contains(md)) {
 0178                 MVC_METHODS.add(md);
 0179             }
 0180         }
 0181
 0182         // GriffonView
 0183         MVC_METHODS.add(new MethodDescriptor("initUI"));
 0184         // GriffonController
 0185         MVC_METHODS.add(new MethodDescriptor("invokeAction", new Class<?>[]{String.class, Object[].class}));
 0186         MVC_METHODS.add(new MethodDescriptor("invokeAction", new Class<?>[]{String.class, Object[].class}, Modifier.PUBLIC | Modifier.TRANSIENT));
 0187
 0188         for (Method method : ThreadingHandler.class.getMethods()) {
 0189             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0190             if (!THREADING_METHODS.contains(md)) {
 0191                 THREADING_METHODS.add(md);
 0192             }
 0193         }
 0194         // Special case due to the usage of varargs
 0195         //THREADING_METHODS.add(new MethodDescriptor("runFuture", new Class<?>[]{Object[].class}));
 0196
 0197         for (Method method : EventPublisher.class.getMethods()) {
 0198             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0199             if (!EVENT_PUBLISHER_METHODS.contains(md)) {
 0200                 EVENT_PUBLISHER_METHODS.add(md);
 0201             }
 0202         }
 0203
 0204         for (Method method : Observable.class.getMethods()) {
 0205             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0206             if (!OBSERVABLE_METHODS.contains(md)) {
 0207                 OBSERVABLE_METHODS.add(md);
 0208             }
 0209         }
 0210         for (Method method : Vetoable.class.getMethods()) {
 0211             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0212             if (!OBSERVABLE_METHODS.contains(md)) {
 0213                 OBSERVABLE_METHODS.add(md);
 0214             }
 0215         }
 0216
 0217         for (Method method : ResourceHandler.class.getMethods()) {
 0218             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0219             if (!RESOURCE_HANDLER_METHODS.contains(md)) {
 0220                 RESOURCE_HANDLER_METHODS.add(md);
 0221             }
 0222         }
 0223
 0224         for (Method method : MessageSource.class.getMethods()) {
 0225             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0226             if (!MESSAGE_SOURCE_METHODS.contains(md)) {
 0227                 MESSAGE_SOURCE_METHODS.add(md);
 0228             }
 0229         }
 0230
 0231         for (Method method : ResourceResolver.class.getMethods()) {
 0232             MethodDescriptor md = MethodDescriptor.forMethod(method, true);
 0233             if (!RESOURCE_RESOLVER_METHODS.contains(md)) {
 0234                 RESOURCE_RESOLVER_METHODS.add(md);
 0235             }
 0236         }
 0237     }
 0238
 0239     /**
 0240      * Checks that the specified condition is met. This method is designed
 0241      * primarily for doing parameter validation in methods and constructors,
 0242      * as demonstrated below:
 0243      * <blockquote><pre>
 0244      * public Foo(int[] array) {
 0245      *     GriffonClassUtils.requireState(array.length > 0);
 0246      * }
 0247      * </pre></blockquote>
 0248      *
 0249      * @param condition the condition to check
 0250      * @throws IllegalStateException if {@code condition} evaluates to false
 0251      */
 0252     public static void requireState(boolean condition) {
 0253         if (!condition) {
 0254             throw new IllegalStateException();
 0255         }
 0256     }
 0257
 0258     /**
 0259      * Checks that the specified condition is met and throws a customized
 0260      * {@link IllegalStateException} if it is. This method is designed primarily
 0261      * for doing parameter validation in methods and constructors with multiple
 0262      * parameters, as demonstrated below:
 0263      * <blockquote><pre>
 0264      * public Foo(int[] array) {
 0265      *     GriffonClassUtils.requireState(array.length > 0, "array must not be empty");
 0266      * }
 0267      * </pre></blockquote>
 0268      *
 0269      * @param condition the condition to check
 0270      * @param message   detail message to be used in the event that a {@code
 0271      *                  IllegalStateException} is thrown
 0272      * @throws IllegalStateException if {@code condition} evaluates to false
 0273      */
 0274     public static void requireState(boolean condition, String message) {
 0275         if (!condition) {
 0276             throw new IllegalStateException(message);
 0277         }
 0278     }
 0279
 0280     /**
 0281      * Finds out if the given string represents the name of an
 0282      * event handler by matching against the following pattern:
 0283      * "^on[A-Z][\\w]*$"<p>
 0284      * <p/>
 0285      * <pre>
 0286      * isEventHandler("onBootstrapEnd") = true
 0287      * isEventHandler("mvcGroupInit")   = false
 0288      * isEventHandler("online")         = false
 0289      * </pre>
 0290      *
 0291      * @param name the name of a possible event handler
 0292      * @return true if the name matches the given event handler
 0293      * pattern, false otherwise.
 0294      */
 0295     public static boolean isEventHandler(@Nonnull String name) {
 0296         requireNonBlank(name, ERROR_NAME_BLANK);
 0297         return EVENT_HANDLER_PATTERN.matcher(name).matches();
 0298     }
 0299
 0300     /**
 0301      * Finds out if the given Method represents an event handler
 0302      * by matching its name against the following pattern:
 0303      * "^on[A-Z][\\w]*$"<p>
 0304      * <pre>
 0305      * // assuming getMethod() returns an appropriate Method reference
 0306      * isEventHandler(getMethod("onBootstrapEnd")) = true
 0307      * isEventHandler(getMethod("mvcGroupInit"))   = false
 0308      * isEventHandler(getMethod("online"))         = false
 0309      * </pre>
 0310      *
 0311      * @param method a Method reference
 0312      * @return true if the method name matches the given event handler
 0313      * pattern, false otherwise.
 0314      */
 0315     public static boolean isEventHandler(@Nonnull Method method) {
 0316         requireNonNull(method, ERROR_METHOD_NULL);
 0317         return isEventHandler(MethodDescriptor.forMethod(method));
 0318     }
 0319
 0320     /**
 0321      * Finds out if the given Method represents an event handler
 0322      * by matching its name against the following pattern:
 0323      * "^on[A-Z][\\w]*$"<p>
 0324      * <pre>
 0325      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0326      * isEventHandler(getMethod("onBootstrapEnd")) = true
 0327      * isEventHandler(getMethod("mvcGroupInit"))   = false
 0328      * isEventHandler(getMethod("online"))         = false
 0329      * </pre>
 0330      *
 0331      * @param method a MethodDescriptor reference
 0332      * @return true if the method name matches the given event handler
 0333      * pattern, false otherwise.
 0334      */
 0335     public static boolean isEventHandler(@Nonnull MethodDescriptor method) {
 0336         requireNonNull(method, ERROR_METHOD_NULL);
 0337         return isInstanceMethod(method) &&
 0338             EVENT_HANDLER_PATTERN.matcher(method.getName()).matches();
 0339     }
 0340
 0341     /**
 0342      * Finds out if the given {@code Method} belongs either to the
 0343      * {@code Object} class or the {@code GroovyObject} class.<p>
 0344      *
 0345      * @param method a Method reference
 0346      * @return true if the method belongs to {@code Object} or
 0347      * {@code GroovyObject}, false otherwise.
 0348      */
 0349     public static boolean isBasicMethod(@Nonnull Method method) {
 0350         requireNonNull(method, ERROR_METHOD_NULL);
 0351         return isBasicMethod(MethodDescriptor.forMethod(method));
 0352     }
 0353
 0354     /**
 0355      * Finds out if the given string represents the name of a
 0356      * contribution method by matching against the following pattern:
 0357      * "^with[A-Z][a-z0-9_]*[\w]*$"<p>
 0358      * <p/>
 0359      * <pre>
 0360      * isContributionMethod("withRest")     = true
 0361      * isContributionMethod("withMVCGroup") = false
 0362      * isContributionMethod("without")      = false
 0363      * </pre>
 0364      *
 0365      * @param name the name of a possible contribution method
 0366      * @return true if the name matches the given contribution method
 0367      * pattern, false otherwise.
 0368      */
 0369     public static boolean isContributionMethod(@Nonnull String name) {
 0370         requireNonBlank(name, ERROR_NAME_BLANK);
 0371         return CONTRIBUTION_PATTERN.matcher(name).matches();
 0372     }
 0373
 0374     /**
 0375      * Finds out if the given Method represents a contribution method
 0376      * by matching its name against the following pattern:
 0377      * "^with[A-Z][a-z0-9_]*[\w]*$"<p>
 0378      * <pre>
 0379      * // assuming getMethod() returns an appropriate Method reference
 0380      * isContributionMethod(getMethod("withRest"))     = true
 0381      * isContributionMethod(getMethod("withMVCGroup")) = false
 0382      * isContributionMethod(getMethod("without"))      = false
 0383      * </pre>
 0384      *
 0385      * @param method a Method reference
 0386      * @return true if the method name matches the given contribution method
 0387      * pattern, false otherwise.
 0388      */
 0389     public static boolean isContributionMethod(@Nonnull Method method) {
 0390         requireNonNull(method, ERROR_METHOD_NULL);
 0391         return isContributionMethod(MethodDescriptor.forMethod(method));
 0392     }
 0393
 0394     /**
 0395      * Finds out if the given Method represents a contribution method
 0396      * by matching its name against the following pattern:
 0397      * "^with[A-Z][a-z0-9_]*[\w]*$"<p>
 0398      * <pre>
 0399      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0400      * isContributionMethod(getMethod("withRest"))     = true
 0401      * isContributionMethod(getMethod("withMVCGroup")) = false
 0402      * isContributionMethod(getMethod("without"))      = false
 0403      * </pre>
 0404      *
 0405      * @param method a MethodDescriptor reference
 0406      * @return true if the method name matches the given contribution method
 0407      * pattern, false otherwise.
 0408      */
 0409     public static boolean isContributionMethod(@Nonnull MethodDescriptor method) {
 0410         requireNonNull(method, ERROR_METHOD_NULL);
 0411         return isInstanceMethod(method) &&
 0412             CONTRIBUTION_PATTERN.matcher(method.getName()).matches();
 0413     }
 0414
 0415     /**
 0416      * Finds out if the given {@code MethodDescriptor} belongs either to the
 0417      * {@code Object} class or the {@code GroovyObject} class.<p>
 0418      *
 0419      * @param method a MethodDescriptor reference
 0420      * @return true if the method belongs to {@code Object} or
 0421      * {@code GroovyObject}, false otherwise.
 0422      */
 0423     public static boolean isBasicMethod(@Nonnull MethodDescriptor method) {
 0424         requireNonNull(method, ERROR_METHOD_NULL);
 0425         return isInstanceMethod(method) && BASIC_METHODS.contains(method);
 0426     }
 0427
 0428     /**
 0429      * Finds out if the given {@code Method} was injected by the Groovy
 0430      * compiler.<p>
 0431      * Performs a basic checks against the method's name, returning true
 0432      * if the name starts with either "super$" or "this$".
 0433      *
 0434      * @param method a Method reference
 0435      * @return true if the method matches the given criteria, false otherwise.
 0436      */
 0437     public static boolean isGroovyInjectedMethod(@Nonnull Method method) {
 0438         requireNonNull(method, ERROR_METHOD_NULL);
 0439         return isGroovyInjectedMethod(MethodDescriptor.forMethod(method));
 0440     }
 0441
 0442     /**
 0443      * Finds out if the given {@code MethodDescriptor} was injected by the Groovy
 0444      * compiler.<p>
 0445      * Performs a basic checks against the method's name, returning true
 0446      * if the name starts with either "super$" or "this$".
 0447      *
 0448      * @param method a MethodDescriptor reference
 0449      * @return true if the method matches the given criteria, false otherwise.
 0450      */
 0451     public static boolean isGroovyInjectedMethod(@Nonnull MethodDescriptor method) {
 0452         requireNonNull(method, ERROR_METHOD_NULL);
 0453         return isInstanceMethod(method) &&
 0454             (method.getName().startsWith("super$") || method.getName().startsWith("this$"));
 0455     }
 0456
 0457     /**
 0458      * Finds out if the given {@code Method} is a getter method.
 0459      * <p/>
 0460      * <pre>
 0461      * // assuming getMethod() returns an appropriate Method reference
 0462      * isGetterMethod(getMethod("getFoo"))       = true
 0463      * isGetterMethod(getMethod("getfoo") )      = false
 0464      * isGetterMethod(getMethod("mvcGroupInit")) = false
 0465      * isGetterMethod(getMethod("isFoo"))        = true
 0466      * isGetterMethod(getMethod("island"))       = false
 0467      * </pre>
 0468      *
 0469      * @param method a Method reference
 0470      * @return true if the method is a getter, false otherwise.
 0471      */
 0472     public static boolean isGetterMethod(@Nonnull Method method) {
 0473         requireNonNull(method, ERROR_METHOD_NULL);
 0474         return isGetterMethod(MethodDescriptor.forMethod(method));
 0475     }
 0476
 0477     /**
 0478      * Finds out if the given {@code MetaMethod} is a getter method.
 0479      * <p/>
 0480      * <pre>
 0481      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0482      * isGetterMethod(getMethod("getFoo"))       = true
 0483      * isGetterMethod(getMethod("getfoo") )      = false
 0484      * isGetterMethod(getMethod("mvcGroupInit")) = false
 0485      * isGetterMethod(getMethod("isFoo"))        = true
 0486      * isGetterMethod(getMethod("island"))       = false
 0487      * </pre>
 0488      *
 0489      * @param method a MethodDescriptor reference
 0490      * @return true if the method is a getter, false otherwise.
 0491      */
 0492     public static boolean isGetterMethod(@Nonnull MethodDescriptor method) {
 0493         requireNonNull(method, ERROR_METHOD_NULL);
 0494         return isInstanceMethod(method) &&
 0495             (GETTER_PATTERN_1.matcher(method.getName()).matches() || GETTER_PATTERN_2.matcher(method.getName()).matches());
 0496     }
 0497
 0498     /**
 0499      * Finds out if the given {@code Method} is a setter method.
 0500      * <p/>
 0501      * <pre>
 0502      * // assuming getMethod() returns an appropriate Method reference
 0503      * isGetterMethod(getMethod("setFoo"))       = true
 0504      * isGetterMethod(getMethod("setfoo"))       = false
 0505      * isGetterMethod(getMethod("mvcGroupInit")) = false
 0506      * </pre>
 0507      *
 0508      * @param method a Method reference
 0509      * @return true if the method is a setter, false otherwise.
 0510      */
 0511     public static boolean isSetterMethod(@Nonnull Method method) {
 0512         requireNonNull(method, ERROR_METHOD_NULL);
 0513         return isSetterMethod(MethodDescriptor.forMethod(method));
 0514     }
 0515
 0516     /**
 0517      * Finds out if the given {@code MethodDescriptor} is a setter method.
 0518      * <p/>
 0519      * <pre>
 0520      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0521      * isGetterMethod(getMethod("setFoo"))       = true
 0522      * isGetterMethod(getMethod("setfoo"))       = false
 0523      * isGetterMethod(getMethod("mvcGroupInit")) = false
 0524      * </pre>
 0525      *
 0526      * @param method a MethodDescriptor reference
 0527      * @return true if the method is a setter, false otherwise.
 0528      */
 0529     public static boolean isSetterMethod(@Nonnull MethodDescriptor method) {
 0530         requireNonNull(method, ERROR_METHOD_NULL);
 0531         return isInstanceMethod(method) && SETTER_PATTERN.matcher(method.getName()).matches();
 0532     }
 0533
 0534     /**
 0535      * Finds out if the given {@code Method} belongs to the set of
 0536      * predefined Artifact methods by convention.
 0537      * <p/>
 0538      * <pre>
 0539      * // assuming getMethod() returns an appropriate Method reference
 0540      * isArtifactMethod(getMethod("newInstance"))    = true
 0541      * isArtifactMethod(getMethod("griffonDestroy")) = false
 0542      * isArtifactMethod(getMethod("foo"))            = false
 0543      * </pre>
 0544      *
 0545      * @param method a Method reference
 0546      * @return true if the method is an Artifact method, false otherwise.
 0547      */
 0548     public static boolean isArtifactMethod(@Nonnull Method method) {
 0549         requireNonNull(method, ERROR_METHOD_NULL);
 0550         return isArtifactMethod(MethodDescriptor.forMethod(method));
 0551     }
 0552
 0553     /**
 0554      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0555      * predefined Artifact methods by convention.
 0556      * <p/>
 0557      * <pre>
 0558      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0559      * isArtifactMethod(getMethod("newInstance"))    = true
 0560      * isArtifactMethod(getMethod("griffonDestroy")) = false
 0561      * isArtifactMethod(getMethod("foo"))            = false
 0562      * </pre>
 0563      *
 0564      * @param method a MethodDescriptor reference
 0565      * @return true if the method is an Artifact method, false otherwise.
 0566      */
 0567     public static boolean isArtifactMethod(@Nonnull MethodDescriptor method) {
 0568         requireNonNull(method, ERROR_METHOD_NULL);
 0569         return isInstanceMethod(method) &&
 0570             ARTIFACT_METHODS.contains(method);
 0571     }
 0572
 0573     /**
 0574      * Finds out if the given {@code Method} belongs to the set of
 0575      * predefined MVC methods by convention.
 0576      * <p/>
 0577      * <pre>
 0578      * // assuming getMethod() returns an appropriate Method reference
 0579      * isMvcMethod(getMethod("mvcGroupInit"))    = true
 0580      * isMvcMethod(getMethod("mvcGroupDestroy")) = true
 0581      * isMvcMethod(getMethod("foo"))             = false
 0582      * </pre>
 0583      *
 0584      * @param method a Method reference
 0585      * @return true if the method is an MVC method, false otherwise.
 0586      */
 0587     public static boolean isMvcMethod(@Nonnull Method method) {
 0588         requireNonNull(method, ERROR_METHOD_NULL);
 0589         return isMvcMethod(MethodDescriptor.forMethod(method));
 0590     }
 0591
 0592     /**
 0593      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0594      * predefined MVC methods by convention.
 0595      * <p/>
 0596      * <pre>
 0597      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0598      * isMvcMethod(getMethod("mvcGroupInit"))    = true
 0599      * isMvcMethod(getMethod("mvcGroupDestroy")) = true
 0600      * isMvcMethod(getMethod("foo"))             = false
 0601      * </pre>
 0602      *
 0603      * @param method a MethodDescriptor reference
 0604      * @return true if the method is an MVC method, false otherwise.
 0605      */
 0606     public static boolean isMvcMethod(@Nonnull MethodDescriptor method) {
 0607         requireNonNull(method, ERROR_METHOD_NULL);
 0608         return isInstanceMethod(method) &&
 0609             MVC_METHODS.contains(method);
 0610     }
 0611
 0612     /**
 0613      * Finds out if the given {@code Method} belongs to the set of
 0614      * predefined threading methods by convention.
 0615      * <p/>
 0616      * <pre>
 0617      * // assuming getMethod() returns an appropriate Method reference
 0618      * isThreadingMethod(getMethod("execOutsideUI"))    = true
 0619      * isThreadingMethod(getMethod("doLater"))          = true
 0620      * isThreadingMethod(getMethod("foo"))              = false
 0621      * </pre>
 0622      *
 0623      * @param method a Method reference
 0624      * @return true if the method is a threading method, false otherwise.
 0625      */
 0626     public static boolean isThreadingMethod(@Nonnull Method method) {
 0627         requireNonNull(method, ERROR_METHOD_NULL);
 0628         return isThreadingMethod(MethodDescriptor.forMethod(method));
 0629     }
 0630
 0631     /**
 0632      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0633      * predefined threading methods by convention.
 0634      * <p/>
 0635      * <pre>
 0636      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0637      * isThreadingMethod(getMethod("execOutsideUI"))    = true
 0638      * isThreadingMethod(getMethod("doLater"))          = true
 0639      * isThreadingMethod(getMethod("foo"))              = false
 0640      * </pre>
 0641      *
 0642      * @param method a MethodDescriptor reference
 0643      * @return true if the method is a threading method, false otherwise.
 0644      */
 0645     public static boolean isThreadingMethod(@Nonnull MethodDescriptor method) {
 0646         requireNonNull(method, ERROR_METHOD_NULL);
 0647         return isInstanceMethod(method) &&
 0648             THREADING_METHODS.contains(method);
 0649     }
 0650
 0651     /**
 0652      * Finds out if the given {@code Method} belongs to the set of
 0653      * predefined event publisher methods by convention.
 0654      * <p/>
 0655      * <pre>
 0656      * // assuming getMethod() returns an appropriate Method reference
 0657      * isEventPublisherMethod(getMethod("addEventPublisher"))  = true
 0658      * isEventPublisherMethod(getMethod("publishEvent"))       = true
 0659      * isEventPublisherMethod(getMethod("foo"))                = false
 0660      * </pre>
 0661      *
 0662      * @param method a Method reference
 0663      * @return true if the method is an @EventPublisher method, false otherwise.
 0664      */
 0665     public static boolean isEventPublisherMethod(@Nonnull Method method) {
 0666         requireNonNull(method, ERROR_METHOD_NULL);
 0667         return isEventPublisherMethod(MethodDescriptor.forMethod(method));
 0668     }
 0669
 0670     /**
 0671      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0672      * predefined event publisher methods by convention.
 0673      * <p/>
 0674      * <pre>
 0675      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0676      * isEventPublisherMethod(getMethod("addEventPublisher"))  = true
 0677      * isEventPublisherMethod(getMethod("publishEvent"))       = true
 0678      * isEventPublisherMethod(getMethod("foo"))                = false
 0679      * </pre>
 0680      *
 0681      * @param method a MethodDescriptor reference
 0682      * @return true if the method is an @EventPublisher method, false otherwise.
 0683      */
 0684     public static boolean isEventPublisherMethod(@Nonnull MethodDescriptor method) {
 0685         requireNonNull(method, ERROR_METHOD_NULL);
 0686         return isInstanceMethod(method) &&
 0687             EVENT_PUBLISHER_METHODS.contains(method);
 0688     }
 0689
 0690     /**
 0691      * Finds out if the given {@code Method} belongs to the set of
 0692      * predefined observable methods by convention.
 0693      * <p/>
 0694      * <pre>
 0695      * // assuming getMethod() returns an appropriate Method reference
 0696      * isObservableMethod(getMethod("addPropertyChangeListener"))  = true
 0697      * isObservableMethod(getMethod("getPropertyChangeListeners")) = true
 0698      * isObservableMethod(getMethod("foo"))                        = false
 0699      * </pre>
 0700      *
 0701      * @param method a Method reference
 0702      * @return true if the method is an Observable method, false otherwise.
 0703      */
 0704     public static boolean isObservableMethod(@Nonnull Method method) {
 0705         requireNonNull(method, ERROR_METHOD_NULL);
 0706         return isObservableMethod(MethodDescriptor.forMethod(method));
 0707     }
 0708
 0709     /**
 0710      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0711      * predefined observable methods by convention.
 0712      * <p/>
 0713      * <pre>
 0714      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0715      * isObservableMethod(getMethod("addPropertyChangeListener"))  = true
 0716      * isObservableMethod(getMethod("getPropertyChangeListeners")) = true
 0717      * isObservableMethod(getMethod("foo"))                        = false
 0718      * </pre>
 0719      *
 0720      * @param method a MethodDescriptor reference
 0721      * @return true if the method is an Observable method, false otherwise.
 0722      */
 0723     public static boolean isObservableMethod(@Nonnull MethodDescriptor method) {
 0724         requireNonNull(method, ERROR_METHOD_NULL);
 0725         return isInstanceMethod(method) &&
 0726             OBSERVABLE_METHODS.contains(method);
 0727     }
 0728
 0729     /**
 0730      * Finds out if the given {@code Method} belongs to the set of
 0731      * predefined resources methods by convention.
 0732      * <p/>
 0733      * <pre>
 0734      * // assuming getMethod() returns an appropriate Method reference
 0735      * isResourceHandlerMethod(getMethod("getResourceAsURL"))    = true
 0736      * isResourceHandlerMethod(getMethod("getResourceAsStream")) = true
 0737      * isResourceHandlerMethod(getMethod("foo"))                 = false
 0738      * </pre>
 0739      *
 0740      * @param method a Method reference
 0741      * @return true if the method is an Observable method, false otherwise.
 0742      */
 0743     public static boolean isResourceHandlerMethod(@Nonnull Method method) {
 0744         requireNonNull(method, ERROR_METHOD_NULL);
 0745         return isResourceHandlerMethod(MethodDescriptor.forMethod(method, true));
 0746     }
 0747
 0748     /**
 0749      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0750      * predefined resources methods by convention.
 0751      * <p/>
 0752      * <pre>
 0753      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0754      * isResourceHandlerMethod(getMethod("getResourceAsURL"))    = true
 0755      * isResourceHandlerMethod(getMethod("getResourceAsStream")) = true
 0756      * isResourceHandlerMethod(getMethod("foo"))                 = false
 0757      * </pre>
 0758      *
 0759      * @param method a MethodDescriptor reference
 0760      * @return true if the method is an Observable method, false otherwise.
 0761      */
 0762     public static boolean isResourceHandlerMethod(@Nonnull MethodDescriptor method) {
 0763         requireNonNull(method, ERROR_METHOD_NULL);
 0764         return isInstanceMethod(method) &&
 0765             RESOURCE_HANDLER_METHODS.contains(method);
 0766     }
 0767
 0768     /**
 0769      * Finds out if the given {@code Method} belongs to the set of
 0770      * predefined message source methods by convention.
 0771      * <p/>
 0772      * <pre>
 0773      * // assuming getMethod() returns an appropriate Method reference
 0774      * isMessageSourceMethod(getMethod("getMessage"))    = true
 0775      * isMessageSourceMethod(getMethod("foo"))           = false
 0776      * </pre>
 0777      *
 0778      * @param method a Method reference
 0779      * @return true if the method is an Observable method, false otherwise.
 0780      */
 0781     public static boolean isMessageSourceMethod(@Nonnull Method method) {
 0782         requireNonNull(method, ERROR_METHOD_NULL);
 0783         return isMessageSourceMethod(MethodDescriptor.forMethod(method));
 0784     }
 0785
 0786     /**
 0787      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0788      * predefined message source methods by convention.
 0789      * <p/>
 0790      * <pre>
 0791      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0792      * isMessageSourceMethod(getMethod("getMessage"))    = true
 0793      * isMessageSourceMethod(getMethod("foo"))           = false
 0794      * </pre>
 0795      *
 0796      * @param method a MethodDescriptor reference
 0797      * @return true if the method is an Observable method, false otherwise.
 0798      */
 0799     public static boolean isMessageSourceMethod(@Nonnull MethodDescriptor method) {
 0800         requireNonNull(method, ERROR_METHOD_NULL);
 0801         return isInstanceMethod(method) &&
 0802             MESSAGE_SOURCE_METHODS.contains(method);
 0803     }
 0804
 0805     /**
 0806      * Finds out if the given {@code Method} belongs to the set of
 0807      * predefined resource resolver methods by convention.
 0808      * <p/>
 0809      * <pre>
 0810      * // assuming getMethod() returns an appropriate Method reference
 0811      * isResourceResolverMethod(getMethod("resolveResource")) = true
 0812      * isResourceResolverMethod(getMethod("foo"))             = false
 0813      * </pre>
 0814      *
 0815      * @param method a Method reference
 0816      * @return true if the method is an Observable method, false otherwise.
 0817      */
 0818     public static boolean isResourceResolverMethod(@Nonnull Method method) {
 0819         requireNonNull(method, ERROR_METHOD_NULL);
 0820         return isResourceResolverMethod(MethodDescriptor.forMethod(method));
 0821     }
 0822
 0823     /**
 0824      * Finds out if the given {@code MethodDescriptor} belongs to the set of
 0825      * predefined resource resolver methods by convention.
 0826      * <p/>
 0827      * <pre>
 0828      * // assuming getMethod() returns an appropriate MethodDescriptor reference
 0829      * isResourceResolverMethod(getMethod("resolveResource")) = true
 0830      * isResourceResolverMethod(getMethod("foo"))             = false
 0831      * </pre>
 0832      *
 0833      * @param method a MethodDescriptor reference
 0834      * @return true if the method is an Observable method, false otherwise.
 0835      */
 0836     public static boolean isResourceResolverMethod(@Nonnull MethodDescriptor method) {
 0837         requireNonNull(method, ERROR_METHOD_NULL);
 0838         return isInstanceMethod(method) &&
 0839             RESOURCE_RESOLVER_METHODS.contains(method);
 0840     }
 0841
 0842     /**
 0843      * Finds out if the given {@code Method} is an instance method, i.e,
 0844      * it is public and non-static.
 0845      *
 0846      * @param method a Method reference
 0847      * @return true if the method is an instance method, false otherwise.
 0848      */
 0849     public static boolean isInstanceMethod(@Nonnull Method method) {
 0850         requireNonNull(method, ERROR_METHOD_NULL);
 0851         return isInstanceMethod(MethodDescriptor.forMethod(method));
 0852     }
 0853
 0854     /**
 0855      * Finds out if the given {@code MethodDescriptor} is an instance method, i.e,
 0856      * it is public and non-static.
 0857      *
 0858      * @param method a MethodDescriptor reference
 0859      * @return true if the method is an instance method, false otherwise.
 0860      */
 0861     public static boolean isInstanceMethod(@Nonnull MethodDescriptor method) {
 0862         requireNonNull(method, ERROR_METHOD_NULL);
 0863         int modifiers = method.getModifiers();
 0864         return Modifier.isPublic(modifiers) &&
 0865             !Modifier.isAbstract(modifiers) &&
 0866             !Modifier.isStatic(modifiers);
 0867     }
 0868
 0869     /**
 0870      * Finds out if the given {@code Method} matches the following criteria:<ul>
 0871      * <li>isInstanceMethod(method)</li>
 0872      * <li>! isBasicMethod(method)</li>
 0873      * <li>! isGroovyInjectedMethod(method)</li>
 0874      * <li>! isThreadingMethod(method)</li>
 0875      * <li>! isArtifactMethod(method)</li>
 0876      * <li>! isMvcMethod(method)</li>
 0877      * <li>! isServiceMethod(method)</li>
 0878      * <li>! isEventPublisherMethod(method)</li>
 0879      * <li>! isObservableMethod(method)</li>
 0880      * <li>! isResourceHandlerMethod(method)</li>
 0881      * <li>! isGetterMethod(method)</li>
 0882      * <li>! isSetterMethod(method)</li>
 0883      * <li>! isContributionMethod(method)</li>
 0884      * </ul>
 0885      *
 0886      * @param method a Method reference
 0887      * @return true if the method matches the given criteria, false otherwise.
 0888      */
 0889     public static boolean isPlainMethod(@Nonnull Method method) {
 0890         requireNonNull(method, ERROR_METHOD_NULL);
 0891         return isPlainMethod(MethodDescriptor.forMethod(method));
 0892     }
 0893
 0894     /**
 0895      * Finds out if the given {@code MethodDescriptor} matches the following criteria:<ul>
 0896      * <li>isInstanceMethod(method)</li>
 0897      * <li>! isBasicMethod(method)</li>
 0898      * <li>! isGroovyInjectedMethod(method)</li>
 0899      * <li>! isThreadingMethod(method)</li>
 0900      * <li>! isArtifactMethod(method)</li>
 0901      * <li>! isMvcMethod(method)</li>
 0902      * <li>! isServiceMethod(method)</li>
 0903      * <li>! isEventPublisherMethod(method)</li>
 0904      * <li>! isObservableMethod(method)</li>
 0905      * <li>! isResourceHandlerMethod(method)</li>
 0906      * <li>! isGetterMethod(method)</li>
 0907      * <li>! isSetterMethod(method)</li>
 0908      * <li>! isContributionMethod(method)</li>
 0909      * </ul>
 0910      *
 0911      * @param method a MethodDescriptor reference
 0912      * @return true if the method matches the given criteria, false otherwise.
 0913      */
 0914     public static boolean isPlainMethod(@Nonnull MethodDescriptor method) {
 0915         requireNonNull(method, ERROR_METHOD_NULL);
 0916         return isInstanceMethod(method) &&
 0917             !isBasicMethod(method) &&
 0918             !isGroovyInjectedMethod(method) &&
 0919             !isThreadingMethod(method) &&
 0920             !isArtifactMethod(method) &&
 0921             !isMvcMethod(method) &&
 0922             !isEventPublisherMethod(method) &&
 0923             !isObservableMethod(method) &&
 0924             !isResourceHandlerMethod(method) &&
 0925             !isGetterMethod(method) &&
 0926             !isSetterMethod(method) &&
 0927             !isContributionMethod(method);
 0928     }
 0929
 0930     /**
 0931      * Returns true if the specified property in the specified class is of the specified type
 0932      *
 0933      * @param clazz        The class which contains the property
 0934      * @param propertyName The property name
 0935      * @param type         The type to check
 0936      * @return A boolean value
 0937      */
 0938     public static boolean isPropertyOfType(Class<?> clazz, String propertyName, Class<?> type) {
 0939         try {
 0940             Class<?> propType = getPropertyType(clazz, propertyName);
 0941             return propType != null && propType.equals(type);
 0942         } catch (Exception e) {
 0943             return false;
 0944         }
 0945     }
 0946
 0947     /**
 0948      * Instantiates a Class, wrapping any exceptions in a RuntimeException.
 0949      *
 0950      * @param clazz target Class for which an object will be instantiated
 0951      * @return the newly instantiated object.
 0952      * @throws BeanInstantiationException if an error occurs when creating the object
 0953      */
 0954     @Nonnull
 0955     public static Object instantiateClass(@Nonnull Class<?> clazz) {
 0956         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 0957         try {
 0958             return clazz.newInstance();
 0959         } catch (Exception e) {
 0960             throw new BeanInstantiationException("Could not create an instance of " + clazz, e);
 0961         }
 0962     }
 0963
 0964     @Nonnull
 0965     public static Object instantiate(@Nonnull Class<?> clazz, @Nullable Object[] args) {
 0966         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 0967         try {
 0968             if (args == null) {
 0969                 args = EMPTY_OBJECT_ARRAY;
 0970             }
 0971             int arguments = args.length;
 0972             Class<?>[] parameterTypes = new Class<?>[arguments];
 0973             for (int i = 0; i < arguments; i++) {
 0974                 parameterTypes[i] = args[i].getClass();
 0975             }
 0976             return clazz.getDeclaredConstructor(parameterTypes).newInstance(args);
 0977         } catch (Exception e) {
 0978             throw new BeanInstantiationException("Could not create an instance of " + clazz, e);
 0979         }
 0980     }
 0981
 0982     /**
 0983      * Returns the value of the specified property and type from an instance of the specified Griffon class
 0984      *
 0985      * @param clazz        The name of the class which contains the property
 0986      * @param propertyName The property name
 0987      * @param propertyType The property type
 0988      * @return The value of the property or null if none exists
 0989      */
 0990     @Nullable
 0991     public static Object getPropertyValueOfNewInstance(@Nullable Class<?> clazz, @Nullable String propertyName, Class<?> propertyType) {
 0992         // validate
 0993         if (clazz == null || GriffonNameUtils.isBlank(propertyName)) {
 0994             return null;
 0995         }
 0996
 0997         Object instance;
 0998         try {
 0999             instance = instantiateClass(clazz);
 1000         } catch (BeanInstantiationException e) {
 1001             return null;
 1002         }
 1003
 1004         return getPropertyOrStaticPropertyOrFieldValue(instance, propertyName);
 1005     }
 1006
 1007     /**
 1008      * Returns the value of the specified property and type from an instance of the specified Griffon class
 1009      *
 1010      * @param clazz        The name of the class which contains the property
 1011      * @param propertyName The property name
 1012      * @return The value of the property or null if none exists
 1013      */
 1014     public static Object getPropertyValueOfNewInstance(Class<?> clazz, String propertyName) {
 1015         // validate
 1016         if (clazz == null || GriffonNameUtils.isBlank(propertyName)) {
 1017             return null;
 1018         }
 1019
 1020         Object instance;
 1021         try {
 1022             instance = instantiateClass(clazz);
 1023         } catch (BeanInstantiationException e) {
 1024             return null;
 1025         }
 1026
 1027         return getPropertyOrStaticPropertyOrFieldValue(instance, propertyName);
 1028     }
 1029
 1030     /**
 1031      * Retrieves a PropertyDescriptor for the specified instance and property value
 1032      *
 1033      * @param instance      The instance
 1034      * @param propertyValue The value of the property
 1035      * @return The PropertyDescriptor
 1036      */
 1037     public static PropertyDescriptor getPropertyDescriptorForValue(Object instance, Object propertyValue) {
 1038         if (instance == null || propertyValue == null)
 1039             return null;
 1040
 1041         PropertyDescriptor[] descriptors = getPropertyDescriptors(instance.getClass());
 1042
 1043         for (PropertyDescriptor pd : descriptors) {
 1044             if (isAssignableOrConvertibleFrom(pd.getPropertyType(), propertyValue.getClass())) {
 1045                 Object value;
 1046                 try {
 1047                     value = getReadMethod(pd).invoke(instance, (Object[]) null);
 1048                 } catch (Exception e) {
 1049                     throw new RuntimeException("Problem calling readMethod of " + pd, e);
 1050                 }
 1051                 if (propertyValue.equals(value))
 1052                     return pd;
 1053             }
 1054         }
 1055         return null;
 1056     }
 1057
 1058     /**
 1059      * Returns the type of the given property contained within the specified class
 1060      *
 1061      * @param clazz        The class which contains the property
 1062      * @param propertyName The name of the property
 1063      * @return The property type or null if none exists
 1064      */
 1065     @Nullable
 1066     public static Class<?> getPropertyType(@Nullable Class<?> clazz, @Nullable String propertyName) {
 1067         if (clazz == null || GriffonNameUtils.isBlank(propertyName)) {
 1068             return null;
 1069         }
 1070
 1071         try {
 1072             PropertyDescriptor desc = getPropertyDescriptor(clazz, propertyName);
 1073             if (desc != null) {
 1074                 return desc.getPropertyType();
 1075             } else {
 1076                 return null;
 1077             }
 1078         } catch (Exception e) {
 1079             // if there are any errors in instantiating just return null for the moment
 1080             return null;
 1081         }
 1082     }
 1083
 1084     /**
 1085      * Retrieves all the properties of the given class for the given type
 1086      *
 1087      * @param clazz        The class to retrieve the properties from
 1088      * @param propertyType The type of the properties you wish to retrieve
 1089      * @return An array of PropertyDescriptor instances
 1090      */
 1091     @Nonnull
 1092     public static PropertyDescriptor[] getPropertiesOfType(@Nullable Class<?> clazz, @Nullable Class<?> propertyType) {
 1093         if (clazz == null || propertyType == null) {
 1094             return new PropertyDescriptor[0];
 1095         }
 1096
 1097         Set<PropertyDescriptor> properties = new HashSet<>();
 1098         try {
 1099             PropertyDescriptor[] descriptors = getPropertyDescriptors(clazz);
 1100
 1101             for (PropertyDescriptor descriptor : descriptors) {
 1102                 Class<?> currentPropertyType = descriptor.getPropertyType();
 1103                 if (isTypeInstanceOfPropertyType(propertyType, currentPropertyType)) {
 1104                     properties.add(descriptor);
 1105                 }
 1106             }
 1107         } catch (Exception e) {
 1108             // if there are any errors in instantiating just return null for the moment
 1109             return new PropertyDescriptor[0];
 1110         }
 1111         return properties.toArray(new PropertyDescriptor[properties.size()]);
 1112     }
 1113
 1114     private static boolean isTypeInstanceOfPropertyType(Class<?> type, Class<?> propertyType) {
 1115         return propertyType.isAssignableFrom(type) && !propertyType.equals(Object.class);
 1116     }
 1117
 1118     /**
 1119      * Retrieves all the properties of the given class which are assignable to the given type
 1120      *
 1121      * @param clazz             The class to retrieve the properties from
 1122      * @param propertySuperType The type of the properties you wish to retrieve
 1123      * @return An array of PropertyDescriptor instances
 1124      */
 1125     public static PropertyDescriptor[] getPropertiesAssignableToType(Class<?> clazz, Class<?> propertySuperType) {
 1126         if (clazz == null || propertySuperType == null)
 1127             return new PropertyDescriptor[0];
 1128
 1129         Set<PropertyDescriptor> properties = new HashSet<>();
 1130         try {
 1131             PropertyDescriptor[] descriptors = getPropertyDescriptors(clazz);
 1132
 1133             for (PropertyDescriptor descriptor : descriptors) {
 1134                 if (propertySuperType.isAssignableFrom(descriptor.getPropertyType())) {
 1135                     properties.add(descriptor);
 1136                 }
 1137             }
 1138         } catch (Exception e) {
 1139             return new PropertyDescriptor[0];
 1140         }
 1141         return properties.toArray(new PropertyDescriptor[properties.size()]);
 1142     }
 1143
 1144     /**
 1145      * Retrieves a property of the given class of the specified name and type
 1146      *
 1147      * @param clazz        The class to retrieve the property from
 1148      * @param propertyName The name of the property
 1149      * @param propertyType The type of the property
 1150      * @return A PropertyDescriptor instance or null if none exists
 1151      */
 1152     public static PropertyDescriptor getProperty(Class<?> clazz, String propertyName, Class<?> propertyType) {
 1153         if (clazz == null || propertyName == null || propertyType == null)
 1154             return null;
 1155
 1156         try {
 1157             PropertyDescriptor pd = getPropertyDescriptor(clazz, propertyName);
 1158             if (pd.getPropertyType().equals(propertyType)) {
 1159                 return pd;
 1160             } else {
 1161                 return null;
 1162             }
 1163         } catch (Exception e) {
 1164             // if there are any errors in instantiating just return null for the moment
 1165             return null;
 1166         }
 1167     }
 1168
 1169     /**
 1170      * Convenience method for converting a collection to an Object[]
 1171      *
 1172      * @param c The collection
 1173      * @return An object array
 1174      */
 1175     public static Object[] collectionToObjectArray(Collection<?> c) {
 1176         if (c == null) return EMPTY_OBJECT_ARRAY;
 1177         return c.toArray(new Object[c.size()]);
 1178     }
 1179
 1180     /**
 1181      * Detect if left and right types are matching types. In particular,
 1182      * test if one is a primitive type and the other is the corresponding
 1183      * Java wrapper type. Primitive and wrapper classes may be passed to
 1184      * either arguments.
 1185      *
 1186      * @param leftType
 1187      * @param rightType
 1188      * @return true if one of the classes is a native type and the other the object representation
 1189      * of the same native type
 1190      */
 1191     public static boolean isMatchBetweenPrimitiveAndWrapperTypes(@Nonnull Class<?> leftType, @Nonnull Class<?> rightType) {
 1192         requireNonNull(leftType, "Left type is null!");
 1193         requireNonNull(rightType, "Right type is null!");
 1194         return isMatchBetweenPrimitiveAndWrapperTypes(leftType.getName(), rightType.getName());
 1195     }
 1196
 1197     /**
 1198      * Detect if left and right types are matching types. In particular,
 1199      * test if one is a primitive type and the other is the corresponding
 1200      * Java wrapper type. Primitive and wrapper classes may be passed to
 1201      * either arguments.
 1202      *
 1203      * @param leftType
 1204      * @param rightType
 1205      * @return true if one of the classes is a native type and the other the object representation
 1206      * of the same native type
 1207      */
 1208     public static boolean isMatchBetweenPrimitiveAndWrapperTypes(@Nonnull String leftType, @Nonnull String rightType) {
 1209         requireNonBlank(leftType, "Left type is null!");
 1210         requireNonBlank(rightType, "Right type is null!");
 1211         String r = PRIMITIVE_TYPE_COMPATIBLE_TYPES.get(leftType);
 1212         return r != null && r.equals(rightType);
 1213     }
 1214
 1215     @Nullable
 1216     @SuppressWarnings("ConstantConditions")
 1217     private static Method findDeclaredMethod(@Nonnull Class<?> clazz, @Nonnull String methodName, Class[] parameterTypes) {
 1218         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 1219         requireNonBlank(methodName, ERROR_METHOD_NAME_BLANK);
 1220         while (clazz != null) {
 1221             try {
 1222                 Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
 1223                 if (method != null) return method;
 1224             } catch (NoSuchMethodException | SecurityException e) {
 1225                 // skip
 1226             }
 1227             clazz = clazz.getSuperclass();
 1228         }
 1229
 1230         return null;
 1231     }
 1232
 1233     /**
 1234      * <p>Work out if the specified property is readable and static. Java introspection does not
 1235      * recognize this concept of static properties but Groovy does. We also consider public static fields
 1236      * as static properties with no getters/setters</p>
 1237      *
 1238      * @param clazz        The class to check for static property
 1239      * @param propertyName The property name
 1240      * @return true if the property with name propertyName has a static getter method
 1241      */
 1242     public static boolean isStaticProperty(@Nonnull Class<?> clazz, @Nonnull String propertyName) {
 1243         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 1244         requireNonBlank(propertyName, ERROR_PROPERTY_NAME_BLANK);
 1245         Method getter = findDeclaredMethod(clazz, getGetterName(propertyName), null);
 1246         if (getter != null) {
 1247             return isPublicStatic(getter);
 1248         } else {
 1249             try {
 1250                 Field f = clazz.getDeclaredField(propertyName);
 1251                 if (f != null) {
 1252                     return isPublicStatic(f);
 1253                 }
 1254             } catch (NoSuchFieldException ignore) {
 1255                 //ignore
 1256             }
 1257         }
 1258
 1259         return false;
 1260     }
 1261
 1262     /**
 1263      * Determine whether the method is declared public static
 1264      *
 1265      * @param m the method to be tested
 1266      * @return True if the method is declared public static
 1267      */
 1268     public static boolean isPublicStatic(@Nonnull Method m) {
 1269         requireNonNull(m, "Argument 'method' must not be null");
 1270         final int modifiers = m.getModifiers();
 1271         return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
 1272     }
 1273
 1274     /**
 1275      * Determine whether the field is declared public static
 1276      *
 1277      * @param f the field to be tested
 1278      * @return True if the field is declared public static
 1279      */
 1280     public static boolean isPublicStatic(@Nonnull Field f) {
 1281         requireNonNull(f, "Argument 'field' must not be null");
 1282         final int modifiers = f.getModifiers();
 1283         return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
 1284     }
 1285
 1286     /**
 1287      * Calculate the name for a getter method to retrieve the specified property
 1288      *
 1289      * @param propertyName the name of the property
 1290      * @return The name for the getter method for this property, if it were to exist, i.e. getConstraints
 1291      */
 1292     @Nonnull
 1293     public static String getGetterName(@Nonnull String propertyName) {
 1294         requireNonBlank(propertyName, ERROR_PROPERTY_NAME_BLANK);
 1295         return PROPERTY_GET_PREFIX + Character.toUpperCase(propertyName.charAt(0))
 1296             + propertyName.substring(1);
 1297     }
 1298
 1299     /**
 1300      * <p>Get a static property value, which has a public static getter or is just a public static field.</p>
 1301      *
 1302      * @param clazz The class to check for static property
 1303      * @param name  The property name
 1304      * @return The value if there is one, or null if unset OR there is no such property
 1305      */
 1306     @Nullable
 1307     public static Object getStaticPropertyValue(@Nonnull Class<?> clazz, @Nonnull String name) {
 1308         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 1309         requireNonBlank(name, ERROR_NAME_BLANK);
 1310         Method getter = findDeclaredMethod(clazz, getGetterName(name), null);
 1311         try {
 1312             if (getter != null) {
 1313                 return getter.invoke(null, (Object[]) null);
 1314             } else {
 1315                 Field f = clazz.getDeclaredField(name);
 1316                 if (f != null) {
 1317                     return f.get(null);
 1318                 }
 1319             }
 1320         } catch (Exception ignore) {
 1321             //ignore
 1322         }
 1323         return null;
 1324     }
 1325
 1326     /**
 1327      * <p>Looks for a property of the reference instance with a given name.</p>
 1328      * <p>If found its value is returned. We follow the Java bean conventions with augmentation for groovy support
 1329      * and static fields/properties. We will therefore match, in this order:
 1330      * </p>
 1331      * <ol>
 1332      * <li>Standard public bean property (with getter or just public field, using normal introspection)
 1333      * <li>Public static property with getter method
 1334      * <li>Public static field
 1335      * </ol>
 1336      *
 1337      * @return property value or null if no property found
 1338      */
 1339     @Nullable
 1340     public static Object getPropertyOrStaticPropertyOrFieldValue(@Nonnull Object obj, @Nonnull String name) {
 1341         requireNonNull(obj, ERROR_OBJECT_NULL);
 1342         requireNonBlank(name, ERROR_NAME_BLANK);
 1343         if (isReadable(obj, name)) {
 1344             try {
 1345                 return getProperty(obj, name);
 1346             } catch (Exception e) {
 1347                 throw new PropertyException(obj, name);
 1348             }
 1349         } else {
 1350             // Look for public fields
 1351             if (isPublicField(obj, name)) {
 1352                 return getFieldValue(obj, name);
 1353             }
 1354
 1355             // Look for statics
 1356             Class<?> clazz = obj.getClass();
 1357             if (isStaticProperty(clazz, name)) {
 1358                 return getStaticPropertyValue(clazz, name);
 1359             } else {
 1360                 return null;
 1361             }
 1362         }
 1363     }
 1364
 1365     /**
 1366      * Get the value of a declared field on an object
 1367      *
 1368      * @param obj  the instance that owns the field
 1369      * @param name the name of the file to lookup
 1370      * @return The object value or null if there is no such field or access problems
 1371      */
 1372     @Nullable
 1373     public static Object getFieldValue(@Nonnull Object obj, @Nonnull String name) {
 1374         requireNonNull(obj, ERROR_OBJECT_NULL);
 1375         requireNonBlank(name, ERROR_NAME_BLANK);
 1376         Class<?> clazz = obj.getClass();
 1377         Field f;
 1378         try {
 1379             f = clazz.getDeclaredField(name);
 1380             return f.get(obj);
 1381         } catch (Exception e) {
 1382             return null;
 1383         }
 1384     }
 1385
 1386     /**
 1387      * Get the a declared field on an object
 1388      *
 1389      * @param obj  the instance that owns the field
 1390      * @param name the name of the file to lookup
 1391      * @return The field or null if there is no such field or access problems
 1392      */
 1393     @Nullable
 1394     public static Field getField(@Nonnull Object obj, @Nonnull String name) {
 1395         requireNonNull(obj, ERROR_OBJECT_NULL);
 1396         requireNonBlank(name, ERROR_NAME_BLANK);
 1397         return getField(obj.getClass(), name);
 1398     }
 1399
 1400     /**
 1401      * Get the a declared field on a class
 1402      *
 1403      * @param clazz the clazz that owns the field
 1404      * @param name  the name of the file to lookup
 1405      * @return The field or null if there is no such field or access problems
 1406      */
 1407     @Nullable
 1408     public static Field getField(@Nonnull Class<?> clazz, @Nonnull String name) {
 1409         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 1410         requireNonBlank(name, ERROR_NAME_BLANK);
 1411         Field f;
 1412         try {
 1413             f = clazz.getDeclaredField(name);
 1414             return f;
 1415         } catch (Exception e) {
 1416             return null;
 1417         }
 1418     }
 1419
 1420     /**
 1421      * Work out if the specified object has a public field with the name supplied.
 1422      *
 1423      * @param obj  the instance that owns the field
 1424      * @param name the name of the file to lookup
 1425      * @return True if a public field with the name exists
 1426      */
 1427     public static boolean isPublicField(@Nonnull Object obj, @Nonnull String name) {
 1428         requireNonNull(obj, ERROR_OBJECT_NULL);
 1429         requireNonBlank(name, ERROR_NAME_BLANK);
 1430         Class<?> clazz = obj.getClass();
 1431         Field f;
 1432         try {
 1433             f = clazz.getDeclaredField(name);
 1434             return Modifier.isPublic(f.getModifiers());
 1435         } catch (NoSuchFieldException e) {
 1436             return false;
 1437         }
 1438     }
 1439
 1440     /**
 1441      * Checks whether the specified property is inherited from a super class
 1442      *
 1443      * @param clz          The class to check
 1444      * @param propertyName The property name
 1445      * @return True if the property is inherited
 1446      */
 1447     public static boolean isPropertyInherited(@Nullable Class<?> clz, @Nonnull String propertyName) {
 1448         if (clz == null) return false;
 1449         requireNonBlank(propertyName, ERROR_PROPERTY_NAME_BLANK);
 1450         Class<?> superClass = clz.getSuperclass();
 1451
 1452         PropertyDescriptor pd;
 1453         try {
 1454             pd = getPropertyDescriptor(superClass, propertyName);
 1455         } catch (Exception e) {
 1456             throw new PropertyException(superClass, propertyName, e);
 1457         }
 1458         return pd != null && pd.getReadMethod() != null;
 1459     }
 1460
 1461     /**
 1462      * Creates a concrete collection for the supplied interface
 1463      *
 1464      * @param interfaceType The interface
 1465      * @return ArrayList for List, TreeSet for SortedSet, HashSet for Set etc.
 1466      */
 1467     @Nonnull
 1468     public static Collection<?> createConcreteCollection(@Nonnull Class<?> interfaceType) {
 1469         requireNonNull(interfaceType, ERROR_TYPE_NULL);
 1470         Collection<?> elements;
 1471         if (interfaceType.equals(List.class)) {
 1472             elements = new ArrayList<>();
 1473         } else if (interfaceType.equals(SortedSet.class)) {
 1474             elements = new TreeSet<>();
 1475         } else {
 1476             elements = new HashSet<>();
 1477         }
 1478         return elements;
 1479     }
 1480
 1481     /**
 1482      * Retrieves the name of a setter for the specified property name
 1483      *
 1484      * @param propertyName The property name
 1485      * @return The setter equivalent
 1486      */
 1487     @Nonnull
 1488     public static String getSetterName(@Nonnull String propertyName) {
 1489         requireNonBlank(propertyName, ERROR_PROPERTY_NAME_BLANK);
 1490         return PROPERTY_SET_PREFIX + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
 1491     }
 1492
 1493     /**
 1494      * Returns true if the name of the method specified and the number of arguments make it a javabean property
 1495      *
 1496      * @param name True if its a Javabean property
 1497      * @param args The arguments
 1498      * @return True if it is a javabean property method
 1499      */
 1500     @SuppressWarnings("ConstantConditions")
 1501     public static boolean isGetter(@Nullable String name, @Nullable Class[] args) {
 1502         if (GriffonNameUtils.isBlank(name) || args == null) return false;
 1503         if (args.length != 0) return false;
 1504
 1505         if (name.startsWith(PROPERTY_GET_PREFIX)) {
 1506             name = name.substring(3);
 1507             if (name.length() > 0 && Character.isUpperCase(name.charAt(0)))
 1508                 return true;
 1509         } else if (name.startsWith(PROPERTY_IS_PREFIX)) {
 1510             name = name.substring(2);
 1511             if (name.length() > 0 && Character.isUpperCase(name.charAt(0)))
 1512                 return true;
 1513         }
 1514         return false;
 1515     }
 1516
 1517     /**
 1518      * Returns a property name equivalent for the given getter name or null if it is not a getter
 1519      *
 1520      * @param getterName The getter name
 1521      * @return The property name equivalent
 1522      */
 1523     @Nullable
 1524     @SuppressWarnings("ConstantConditions")
 1525     public static String getPropertyForGetter(@Nullable String getterName) {
 1526         if (GriffonNameUtils.isBlank(getterName)) return null;
 1527
 1528         if (getterName.startsWith(PROPERTY_GET_PREFIX)) {
 1529             String prop = getterName.substring(3);
 1530             return convertPropertyName(prop);
 1531         } else if (getterName.startsWith(PROPERTY_IS_PREFIX)) {
 1532             String prop = getterName.substring(2);
 1533             return convertPropertyName(prop);
 1534         }
 1535         return null;
 1536     }
 1537
 1538     @Nonnull
 1539     private static String convertPropertyName(@Nonnull String prop) {
 1540         if (Character.isUpperCase(prop.charAt(0)) && Character.isUpperCase(prop.charAt(1))) {
 1541             return prop;
 1542         } else if (Character.isDigit(prop.charAt(0))) {
 1543             return prop;
 1544         } else {
 1545             return Character.toLowerCase(prop.charAt(0)) + prop.substring(1);
 1546         }
 1547     }
 1548
 1549     /**
 1550      * Returns a property name equivalent for the given setter name or null if it is not a getter
 1551      *
 1552      * @param setterName The setter name
 1553      * @return The property name equivalent
 1554      */
 1555     @Nullable
 1556     @SuppressWarnings("ConstantConditions")
 1557     public static String getPropertyForSetter(@Nullable String setterName) {
 1558         if (GriffonNameUtils.isBlank(setterName)) return null;
 1559
 1560         if (setterName.startsWith(PROPERTY_SET_PREFIX)) {
 1561             String prop = setterName.substring(3);
 1562             return convertPropertyName(prop);
 1563         }
 1564         return null;
 1565     }
 1566
 1567     @SuppressWarnings("ConstantConditions")
 1568     public static boolean isSetter(@Nullable String name, @Nullable Class[] args) {
 1569         if (GriffonNameUtils.isBlank(name) || args == null) return false;
 1570
 1571         if (name.startsWith(PROPERTY_SET_PREFIX)) {
 1572             if (args.length != 1) return false;
 1573             name = name.substring(3);
 1574             if (name.length() > 0 && Character.isUpperCase(name.charAt(0)))
 1575                 return true;
 1576         }
 1577
 1578         return false;
 1579     }
 1580
 1581     /**
 1582      * Returns true if the specified clazz parameter is either the same as, or is a superclass or super interface
 1583      * of, the specified type parameter. Converts primitive types to compatible class automatically.
 1584      *
 1585      * @param clazz
 1586      * @param type
 1587      * @return True if the class is a taglib
 1588      * @see java.lang.Class#isAssignableFrom(Class)
 1589      */
 1590     public static boolean isAssignableOrConvertibleFrom(@Nullable Class<?> clazz, @Nullable Class<?> type) {
 1591         if (type == null || clazz == null) {
 1592             return false;
 1593         } else if (type.isPrimitive()) {
 1594             // convert primitive type to compatible class
 1595             Class<?> primitiveClass = PRIMITIVE_TYPE_COMPATIBLE_CLASSES.get(type);
 1596             return primitiveClass != null && clazz.isAssignableFrom(primitiveClass);
 1597         } else {
 1598             return clazz.isAssignableFrom(type);
 1599         }
 1600     }
 1601
 1602     /**
 1603      * Retrieves a boolean value from a Map for the given key
 1604      *
 1605      * @param key The key that references the boolean value
 1606      * @param map The map to look in
 1607      * @return A boolean value which will be false if the map is null, the map doesn't contain the key or the value is false
 1608      */
 1609     public static boolean getBooleanFromMap(@Nullable String key, @Nullable Map<String, Object> map) {
 1610         if (map == null) return false;
 1611         if (map.containsKey(key)) {
 1612             Object o = map.get(key);
 1613             if (o == null) return false;
 1614             else if (o instanceof Boolean) {
 1615                 return (Boolean) o;
 1616             } else {
 1617                 return Boolean.valueOf(o.toString());
 1618             }
 1619         }
 1620         return false;
 1621     }
 1622
 1623     /**
 1624      * Returns whether the specified class is either within one of the specified packages or
 1625      * within a subpackage of one of the packages
 1626      *
 1627      * @param clazz       The class
 1628      * @param packageList The list of packages
 1629      * @return True if it is within the list of specified packages
 1630      */
 1631     public static boolean isClassBelowPackage(@Nonnull Class<?> clazz, @Nonnull List<?> packageList) {
 1632         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 1633         requireNonNull(packageList, "Argument 'packageList' must not be null");
 1634         String classPackage = clazz.getPackage().getName();
 1635         for (Object packageName : packageList) {
 1636             if (packageName != null) {
 1637                 if (classPackage.startsWith(packageName.toString())) {
 1638                     return true;
 1639                 }
 1640             }
 1641         }
 1642         return false;
 1643     }
 1644
 1645     /**
 1646      * Sets or updates an object's properties.
 1647      * <p/>
 1648      * This method will attempt setting a property using a matching
 1649      * {@code PropertyDescriptor}; next it will try direct field
 1650      * access if the property was not found.
 1651      *
 1652      * @param bean       the target object on which properties will be set
 1653      * @param properties names and values for properties to be set
 1654      * @throws PropertyException if a property could be found
 1655      * @since 2.1.0
 1656      */
 1657     public static void setPropertiesOrFields(@Nonnull Object bean, @Nonnull Map<String, Object> properties) throws PropertyException {
 1658         requireNonNull(bean, ERROR_BEAN_NULL);
 1659         requireNonNull(properties, ERROR_PROPERTIES_NULL);
 1660         for (Map.Entry<String, Object> entry : properties.entrySet()) {
 1661             setPropertyOrFieldValue(bean, entry.getKey(), entry.getValue());
 1662         }
 1663     }
 1664
 1665     /**
 1666      * Sets or updates an object's properties.
 1667      * <p/>
 1668      * This method will attempt setting a property using a matching
 1669      * {@code PropertyDescriptor}; next it will try direct field
 1670      * access if the property was not found.
 1671      *
 1672      * @param bean       the target object on which properties will be set
 1673      * @param properties names and values for properties to be set
 1674      * @since 2.1.0
 1675      */
 1676     public static void setPropertiesOrFieldsNoException(@Nonnull Object bean, @Nonnull Map<String, Object> properties) {
 1677         requireNonNull(bean, ERROR_BEAN_NULL);
 1678         requireNonNull(properties, ERROR_PROPERTIES_NULL);
 1679         for (Map.Entry<String, Object> entry : properties.entrySet()) {
 1680             try {
 1681                 setPropertyOrFieldValue(bean, entry.getKey(), entry.getValue());
 1682             } catch (PropertyException pe) {
 1683                 // ignore
 1684             }
 1685         }
 1686     }
 1687
 1688     /**
 1689      * Sets or updates an object's property.
 1690      * <p/>
 1691      * This method will attempt setting a property using a matching
 1692      * {@code PropertyDescriptor}; next it will try direct field
 1693      * access if the property was not found.
 1694      *
 1695      * @param bean  the target object on which the property will be set
 1696      * @param name  the name of the property to set
 1697      * @param value the value to be set
 1698      * @throws PropertyException if the property could not be found
 1699      * @since 2.1.0
 1700      */
 1701     public static void setPropertyOrFieldValue(@Nonnull Object bean, @Nonnull String name, @Nullable Object value) throws PropertyException {
 1702         try {
 1703             setPropertyValue(bean, name, value);
 1704         } catch (PropertyException pe) {
 1705             try {
 1706                 setFieldValue(bean, name, value);
 1707             } catch (FieldException fe) {
 1708                 throw pe;
 1709             }
 1710         }
 1711     }
 1712
 1713     /**
 1714      * Sets or updates an object's properties.
 1715      * <p/>
 1716      * This method will attempt setting a property using direct field
 1717      * access; next it will try a {@code PropertyDescriptor} if a
 1718      * matching field name was not found.
 1719      *
 1720      * @param bean       the target object on which properties will be set
 1721      * @param properties names and values for properties to be set
 1722      * @throws FieldException if the field could not be found
 1723      * @since 2.1.0
 1724      */
 1725     public static void setFieldsOrProperties(@Nonnull Object bean, @Nonnull Map<String, Object> properties) throws FieldException {
 1726         requireNonNull(bean, ERROR_BEAN_NULL);
 1727         requireNonNull(properties, ERROR_PROPERTIES_NULL);
 1728         for (Map.Entry<String, Object> entry : properties.entrySet()) {
 1729             setFieldOrPropertyValue(bean, entry.getKey(), entry.getValue());
 1730         }
 1731     }
 1732
 1733     /**
 1734      * Sets or updates an object's properties.
 1735      * <p/>
 1736      * This method will attempt setting a property using direct field
 1737      * access; next it will try a {@code PropertyDescriptor} if a
 1738      * matching field name was not found.
 1739      *
 1740      * @param bean       the target object on which properties will be set
 1741      * @param properties names and values for properties to be set
 1742      * @since 2.1.0
 1743      */
 1744     public static void setFieldsOrPropertiesNoException(@Nonnull Object bean, @Nonnull Map<String, Object> properties) {
 1745         requireNonNull(bean, ERROR_BEAN_NULL);
 1746         requireNonNull(properties, ERROR_PROPERTIES_NULL);
 1747         for (Map.Entry<String, Object> entry : properties.entrySet()) {
 1748             try {
 1749                 setFieldOrPropertyValue(bean, entry.getKey(), entry.getValue());
 1750             } catch (FieldException pe) {
 1751                 // ignore
 1752             }
 1753         }
 1754     }
 1755
 1756     /**
 1757      * Sets or updates an object's property.
 1758      * <p/>
 1759      * This method will attempt setting a property using direct field
 1760      * access; next it will try a {@code PropertyDescriptor} if a
 1761      * matching field name was not found.
 1762      *
 1763      * @param bean  the target object on which the property will be set
 1764      * @param name  the name of the property to set
 1765      * @param value the value to be set
 1766      * @throws FieldException if the property could not be found
 1767      * @since 2.1.0
 1768      */
 1769     public static void setFieldOrPropertyValue(@Nonnull Object bean, @Nonnull String name, @Nullable Object value) throws FieldException {
 1770         try {
 1771             setFieldValue(bean, name, value);
 1772         } catch (FieldException fe) {
 1773             try {
 1774                 setPropertyValue(bean, name, value);
 1775             } catch (PropertyException pe) {
 1776                 throw fe;
 1777             }
 1778         }
 1779     }
 1780
 1781     /**
 1782      * Sets or updates field values on an object.
 1783      *
 1784      * @param bean   the target object on which field values will be set
 1785      * @param fields names and values of fields to be set
 1786      * @throws FieldException if a field could not be found
 1787      * @since 2.1.0
 1788      */
 1789     public static void setFields(@Nonnull Object bean, @Nonnull Map<String, Object> fields) throws FieldException {
 1790         requireNonNull(bean, ERROR_BEAN_NULL);
 1791         requireNonNull(fields, ERROR_FIELDS_NULL);
 1792         for (Map.Entry<String, Object> entry : fields.entrySet()) {
 1793             setFieldValue(bean, entry.getKey(), entry.getValue());
 1794         }
 1795     }
 1796
 1797     /**
 1798      * Sets or updates field values on an object.
 1799      *
 1800      * @param bean   the target object on which field values will be set
 1801      * @param fields names and values of fields to be set
 1802      * @since 2.1.0
 1803      */
 1804     public static void setFieldsNoException(@Nonnull Object bean, @Nonnull Map<String, Object> fields) {
 1805         requireNonNull(bean, ERROR_BEAN_NULL);
 1806         requireNonNull(fields, ERROR_FIELDS_NULL);
 1807         for (Map.Entry<String, Object> entry : fields.entrySet()) {
 1808             try {
 1809                 setFieldValue(bean, entry.getKey(), entry.getValue());
 1810             } catch (FieldException e) {
 1811                 // ignore
 1812             }
 1813         }
 1814     }
 1815
 1816     /**
 1817      * Sets or updates an object's field.
 1818      *
 1819      * @param bean  the target object on which the field will be set
 1820      * @param name  the name of the field to set
 1821      * @param value the value to be set
 1822      * @throws FieldException if the field could not be found
 1823      * @since 2.1.0
 1824      */
 1825     public static void setFieldValue(@Nonnull Object bean, @Nonnull String name, @Nullable Object value) throws FieldException {
 1826         requireNonNull(bean, ERROR_BEAN_NULL);
 1827         requireNonBlank(name, ERROR_NAME_BLANK);
 1828         try {
 1829             setField(bean, name, value);
 1830         } catch (IllegalAccessException | NoSuchFieldException e) {
 1831             throw new FieldException(bean, name, value, e);
 1832         }
 1833     }
 1834
 1835     /**
 1836      * Sets or updates properties on an object.
 1837      *
 1838      * @param bean       the target object on which properties will be set
 1839      * @param properties names and values of properties to be set
 1840      * @throws PropertyException if a property could not be found
 1841      */
 1842     public static void setProperties(@Nonnull Object bean, @Nonnull Map<String, Object> properties) throws PropertyException {
 1843         requireNonNull(bean, ERROR_BEAN_NULL);
 1844         requireNonNull(properties, ERROR_PROPERTIES_NULL);
 1845         for (Map.Entry<String, Object> entry : properties.entrySet()) {
 1846             setPropertyValue(bean, entry.getKey(), entry.getValue());
 1847         }
 1848     }
 1849
 1850     /**
 1851      * Sets or updates properties on an object.
 1852      *
 1853      * @param bean       the target object on which properties will be set
 1854      * @param properties names and values of properties to be set
 1855      */
 1856     public static void setPropertiesNoException(@Nonnull Object bean, @Nonnull Map<String, Object> properties) {
 1857         requireNonNull(bean, ERROR_BEAN_NULL);
 1858         requireNonNull(properties, ERROR_PROPERTIES_NULL);
 1859         for (Map.Entry<String, Object> entry : properties.entrySet()) {
 1860             try {
 1861                 setPropertyValue(bean, entry.getKey(), entry.getValue());
 1862             } catch (PropertyException e) {
 1863                 // ignore
 1864             }
 1865         }
 1866     }
 1867
 1868     /**
 1869      * /**
 1870      * Sets or updates a property on an object.
 1871      *
 1872      * @param bean  the target object on which the property will be set
 1873      * @param name  the name of the property to set
 1874      * @param value the value to be set
 1875      * @throws PropertyException if the property could not be found
 1876      */
 1877     public static void setPropertyValue(@Nonnull Object bean, @Nonnull String name, @Nullable Object value) throws PropertyException {
 1878         requireNonNull(bean, ERROR_BEAN_NULL);
 1879         requireNonBlank(name, ERROR_NAME_BLANK);
 1880         try {
 1881             setProperty(bean, name, value);
 1882         } catch (IllegalAccessException | NoSuchMethodException e) {
 1883             throw new PropertyException(bean, name, value, e);
 1884         } catch (InvocationTargetException e) {
 1885             throw new PropertyException(bean, name, value, e.getTargetException());
 1886         }
 1887     }
 1888
 1889     /**
 1890      * Returns the value of a property.
 1891      *
 1892      * @param bean the owner of the property
 1893      * @param name the name of the property to retrieve
 1894      * @return the value read from the matching property
 1895      * @throws PropertyException if the property could not be found
 1896      */
 1897     @Nullable
 1898     public static Object getPropertyValue(@Nonnull Object bean, @Nonnull String name) throws PropertyException {
 1899         requireNonNull(bean, ERROR_BEAN_NULL);
 1900         requireNonBlank(name, ERROR_NAME_BLANK);
 1901         try {
 1902             return getProperty(bean, name);
 1903         } catch (IllegalAccessException | NoSuchMethodException e) {
 1904             throw new PropertyException(bean, name, e);
 1905         } catch (InvocationTargetException e) {
 1906             throw new PropertyException(bean, name, e.getTargetException());
 1907         }
 1908     }
 1909
 1910     // -- The following methods and properties were copied from commons-beanutils
 1911
 1912     private static final Map<String, PropertyDescriptor[]> descriptorsCache = new LinkedHashMap<>();
 1913
 1914     /**
 1915      * <p>Retrieve the property descriptor for the specified property of the
 1916      * specified bean, or return <code>null</code> if there is no such
 1917      * descriptor.</p>
 1918      * This method does not resolve index, nested nor mapped properties.<p>
 1919      *
 1920      * @param bean Bean for which a property descriptor is requested
 1921      * @param name name of the property for which a property descriptor
 1922      *             is requested
 1923      * @return the property descriptor or null if the bean does not have
 1924      * a property that matches the specified name.
 1925      * @throws IllegalAccessException    if the caller does not have
 1926      *                                   access to the property accessor method
 1927      * @throws IllegalArgumentException  if <code>bean</code> or
 1928      *                                   <code>name</code> is null
 1929      * @throws InvocationTargetException if the property accessor method
 1930      *                                   throws an exception
 1931      * @throws NoSuchMethodException     if an accessor method for this
 1932      *                                   property cannot be found
 1933      */
 1934     @Nullable
 1935     public static PropertyDescriptor getPropertyDescriptor(@Nonnull Object bean,
 1936                                                            @Nonnull String name)
 1937         throws IllegalAccessException, InvocationTargetException,
 1938         NoSuchMethodException {
 1939         requireNonNull(bean, ERROR_BEAN_NULL);
 1940         requireNonBlank(name, ERROR_NAME_BLANK);
 1941
 1942         return getPropertyDescriptor(bean instanceof Class ? (Class<?>) bean : bean.getClass(), name);
 1943     }
 1944
 1945     /**
 1946      * <p>Retrieve the property descriptor for the specified property of the
 1947      * specified class, or return <code>null</code> if there is no such
 1948      * descriptor.</p>
 1949      * This method does not resolve index, nested nor mapped properties.<p>
 1950      *
 1951      * @param clazz class for which a property descriptor is requested
 1952      * @param name  name of the property for which a property descriptor
 1953      *              is requested
 1954      * @return the property descriptor or null if the bean does not have
 1955      * a property that matches the specified name.
 1956      * @throws IllegalAccessException    if the caller does not have
 1957      *                                   access to the property accessor method
 1958      * @throws IllegalArgumentException  if <code>bean</code> or
 1959      *                                   <code>name</code> is null
 1960      * @throws InvocationTargetException if the property accessor method
 1961      *                                   throws an exception
 1962      * @throws NoSuchMethodException     if an accessor method for this
 1963      *                                   property cannot be found
 1964      */
 1965     @Nullable
 1966     public static PropertyDescriptor getPropertyDescriptor(@Nonnull Class<?> clazz,
 1967                                                            @Nonnull String name)
 1968         throws IllegalAccessException, InvocationTargetException,
 1969         NoSuchMethodException {
 1970         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 1971         requireNonBlank(name, ERROR_NAME_BLANK);
 1972
 1973         PropertyDescriptor[] descriptors = getPropertyDescriptors(clazz);
 1974         for (PropertyDescriptor descriptor : descriptors) {
 1975             if (name.equals(descriptor.getName())) {
 1976                 return (descriptor);
 1977             }
 1978         }
 1979
 1980         return null;
 1981     }
 1982
 1983     /**
 1984      * <p>Retrieve the property descriptors for the specified class,
 1985      * introspecting and caching them the first time a particular bean class
 1986      * is encountered.</p>
 1987      *
 1988      * @param beanClass Bean class for which property descriptors are requested
 1989      * @return the property descriptors
 1990      * @throws IllegalArgumentException if <code>beanClass</code> is null
 1991      */
 1992     @Nonnull
 1993     public static PropertyDescriptor[] getPropertyDescriptors(@Nonnull Class<?> beanClass) {
 1994         requireNonNull(beanClass, ERROR_CLAZZ_NULL);
 1995
 1996         // Look up any cached descriptors for this bean class
 1997         PropertyDescriptor[] descriptors;
 1998         descriptors = descriptorsCache.get(beanClass.getName());
 1999         if (descriptors != null) {
 2000             return descriptors;
 2001         }
 2002
 2003         // Introspect the bean and cache the generated descriptors
 2004         BeanInfo beanInfo;
 2005         try {
 2006             beanInfo = Introspector.getBeanInfo(beanClass);
 2007         } catch (IntrospectionException e) {
 2008             return (new PropertyDescriptor[0]);
 2009         }
 2010         descriptors = beanInfo.getPropertyDescriptors();
 2011         if (descriptors == null) {
 2012             descriptors = new PropertyDescriptor[0];
 2013         }
 2014
 2015         descriptorsCache.put(beanClass.getName(), descriptors);
 2016         return descriptors;
 2017     }
 2018
 2019     /**
 2020      * <p>Return an accessible property getter method for this property,
 2021      * if there is one; otherwise return <code>null</code>.</p>
 2022      *
 2023      * @param descriptor Property descriptor to return a getter for
 2024      * @return The read method
 2025      */
 2026     @Nullable
 2027     public static Method getReadMethod(@Nonnull PropertyDescriptor descriptor) {
 2028         requireNonNull(descriptor, ERROR_DESCRIPTOR_NULL);
 2029         return (MethodUtils.getAccessibleMethod(descriptor.getReadMethod()));
 2030     }
 2031
 2032     /**
 2033      * <p>Return <code>true</code> if the specified property name identifies
 2034      * a readable property on the specified bean; otherwise, return
 2035      * <code>false</code>.
 2036      *
 2037      * @param bean Bean to be examined
 2038      * @param name Property name to be evaluated
 2039      * @return <code>true</code> if the property is readable,
 2040      * otherwise <code>false</code>
 2041      * @throws IllegalArgumentException if <code>bean</code>
 2042      *                                  or <code>name</code> is <code>null</code>
 2043      * @since BeanUtils 1.6
 2044      */
 2045     public static boolean isReadable(@Nonnull Object bean, @Nonnull String name) {
 2046         // Validate method parameters
 2047         requireNonNull(bean, ERROR_BEAN_NULL);
 2048         requireNonBlank(name, ERROR_NAME_BLANK);
 2049
 2050         try {
 2051             PropertyDescriptor desc = getPropertyDescriptor(bean, name);
 2052             if (desc != null) {
 2053                 Method readMethod = getReadMethod(bean.getClass(), desc);
 2054                 if (readMethod != null) {
 2055                     readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
 2056                 }
 2057                 return (readMethod != null);
 2058             } else {
 2059                 return false;
 2060             }
 2061         } catch (IllegalAccessException e) {
 2062             return false;
 2063         } catch (InvocationTargetException e) {
 2064             return false;
 2065         } catch (NoSuchMethodException e) {
 2066             return false;
 2067         }
 2068     }
 2069
 2070     /**
 2071      * <p>Return an accessible property setter method for this property,
 2072      * if there is one; otherwise return <code>null</code>.</p>
 2073      *
 2074      * @param descriptor Property descriptor to return a setter for
 2075      * @return The write method
 2076      */
 2077     @Nullable
 2078     public static Method getWriteMethod(@Nonnull PropertyDescriptor descriptor) {
 2079         requireNonNull(descriptor, ERROR_DESCRIPTOR_NULL);
 2080         return (MethodUtils.getAccessibleMethod(descriptor.getWriteMethod()));
 2081     }
 2082
 2083     /**
 2084      * <p>Return <code>true</code> if the specified property name identifies
 2085      * a writable property on the specified bean; otherwise, return
 2086      * <code>false</code>.
 2087      *
 2088      * @param bean Bean to be examined
 2089      * @param name Property name to be evaluated
 2090      * @return <code>true</code> if the property is writable,
 2091      * otherwise <code>false</code>
 2092      * @throws IllegalArgumentException if <code>bean</code>
 2093      *                                  or <code>name</code> is <code>null</code>
 2094      */
 2095     public static boolean isWritable(@Nonnull Object bean, @Nonnull String name) {
 2096         // Validate method parameters
 2097         requireNonNull(bean, ERROR_BEAN_NULL);
 2098         requireNonBlank(name, ERROR_NAME_BLANK);
 2099
 2100         try {
 2101             PropertyDescriptor desc = getPropertyDescriptor(bean, name);
 2102             if (desc != null) {
 2103                 Method writeMethod = getWriteMethod(bean.getClass(), desc);
 2104                 if (writeMethod != null) {
 2105                     writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
 2106                 }
 2107                 return (writeMethod != null);
 2108             } else {
 2109                 return false;
 2110             }
 2111         } catch (IllegalAccessException e) {
 2112             return false;
 2113         } catch (InvocationTargetException e) {
 2114             return false;
 2115         } catch (NoSuchMethodException e) {
 2116             return false;
 2117         }
 2118     }
 2119
 2120     /**
 2121      * Sets the value of the specified field of the specified bean.
 2122      *
 2123      * @param bean  Bean whose field is to be mutated
 2124      * @param name  Name of the field to be mutated
 2125      * @param value The value to be set on the property
 2126      * @throws IllegalAccessException   if the caller does not have
 2127      *                                  access to the field
 2128      * @throws IllegalArgumentException if <code>bean</code> or
 2129      *                                  <code>name</code> is null
 2130      * @throws NoSuchFieldException     if the named field cannot be found
 2131      * @throws FieldException           if the field cannot be set
 2132      * @since 2.1.0
 2133      */
 2134     public static void setField(@Nonnull Object bean, @Nonnull String name, @Nullable Object value)
 2135         throws NoSuchFieldException, IllegalAccessException, FieldException {
 2136         requireNonNull(bean, ERROR_BEAN_NULL);
 2137         requireNonBlank(name, ERROR_NAME_BLANK);
 2138
 2139         Class<?> declaringClass = bean.getClass();
 2140         while (declaringClass != null) {
 2141             try {
 2142                 Field field = bean.getClass().getDeclaredField(name);
 2143
 2144                 // type conversion needed?
 2145                 Class<?> propertyType = field.getType();
 2146                 if (value != null && !propertyType.isAssignableFrom(value.getClass())) {
 2147                     value = TypeUtils.convertValue(propertyType, value);
 2148                 }
 2149
 2150                 field.setAccessible(true);
 2151                 try {
 2152                     field.set(bean, value);
 2153                     return;
 2154                 } catch (IllegalArgumentException iae) {
 2155                     throw new FieldException(bean, name, value, iae);
 2156                 }
 2157             } catch (NoSuchFieldException nsfe) {
 2158                 declaringClass = declaringClass.getSuperclass();
 2159             }
 2160         }
 2161         throw new NoSuchFieldException(name);
 2162     }
 2163
 2164     /**
 2165      * Sets the value of the specified property of the specified bean.
 2166      *
 2167      * @param bean  Bean whose property is to be mutated
 2168      * @param name  Name of the property to be mutated
 2169      * @param value The value to be set on the property
 2170      * @throws IllegalAccessException    if the caller does not have
 2171      *                                   access to the property accessor method
 2172      * @throws IllegalArgumentException  if <code>bean</code> or
 2173      *                                   <code>name</code> is null
 2174      * @throws InvocationTargetException if the property accessor method
 2175      *                                   throws an exception
 2176      * @throws NoSuchMethodException     if an accessor method for this
 2177      *                                   property cannot be found
 2178      * @throws PropertyException         if the property cannot be set
 2179      */
 2180     public static void setProperty(@Nonnull Object bean, @Nonnull String name, @Nullable Object value)
 2181         throws IllegalAccessException, InvocationTargetException,
 2182         NoSuchMethodException, PropertyException {
 2183         requireNonNull(bean, ERROR_BEAN_NULL);
 2184         requireNonBlank(name, ERROR_NAME_BLANK);
 2185
 2186         // Retrieve the property setter method for the specified property
 2187         PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
 2188         if (descriptor == null) {
 2189             throw new NoSuchMethodException("Unknown property '" +
 2190                 name + "' on class '" + bean.getClass() + "'");
 2191         }
 2192         Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
 2193         if (writeMethod == null) {
 2194             throw new NoSuchMethodException("Property '" + name +
 2195                 "' has no setter method in class '" + bean.getClass() + "'");
 2196         }
 2197
 2198         // type conversion needed?
 2199         Class<?> propertyType = descriptor.getPropertyType();
 2200         if (value != null && !propertyType.isAssignableFrom(value.getClass())) {
 2201             value = TypeUtils.convertValue(propertyType, value);
 2202         }
 2203
 2204         // Call the property setter
 2205         try {
 2206             writeMethod.invoke(bean, value);
 2207         } catch (IllegalArgumentException iae) {
 2208             throw new PropertyException(bean, name, value, iae);
 2209         }
 2210     }
 2211
 2212     /**
 2213      * Return the value of the specified property of the specified bean,
 2214      * no matter which property reference format is used, with no
 2215      * type conversions.
 2216      *
 2217      * @param bean Bean whose property is to be extracted
 2218      * @param name Possibly indexed and/or nested name of the property
 2219      *             to be extracted
 2220      * @return the property value
 2221      * @throws IllegalAccessException    if the caller does not have
 2222      *                                   access to the property accessor method
 2223      * @throws IllegalArgumentException  if <code>bean</code> or
 2224      *                                   <code>name</code> is null
 2225      * @throws InvocationTargetException if the property accessor method
 2226      *                                   throws an exception
 2227      * @throws NoSuchMethodException     if an accessor method for this
 2228      *                                   property cannot be found
 2229      */
 2230     @Nullable
 2231     public static Object getProperty(@Nonnull Object bean, @Nonnull String name)
 2232         throws IllegalAccessException, InvocationTargetException,
 2233         NoSuchMethodException {
 2234         requireNonNull(bean, ERROR_BEAN_NULL);
 2235         requireNonBlank(name, ERROR_NAME_BLANK);
 2236
 2237         // Retrieve the property getter method for the specified property
 2238         PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
 2239         if (descriptor == null) {
 2240             throw new NoSuchMethodException("Unknown property '" +
 2241                 name + "' on class '" + bean.getClass() + "'");
 2242         }
 2243         Method readMethod = getReadMethod(bean.getClass(), descriptor);
 2244         if (readMethod == null) {
 2245             throw new NoSuchMethodException("Property '" + name +
 2246                 "' has no getter method in class '" + bean.getClass() + "'");
 2247         }
 2248
 2249         // Call the property getter and return the value
 2250         return readMethod.invoke(bean, EMPTY_OBJECT_ARRAY);
 2251     }
 2252
 2253     /**
 2254      * <p>Return an accessible property getter method for this property,
 2255      * if there is one; otherwise return <code>null</code>.</p>
 2256      *
 2257      * @param clazz      The class of the read method will be invoked on
 2258      * @param descriptor Property descriptor to return a getter for
 2259      * @return The read method
 2260      */
 2261     @Nullable
 2262     public static Method getReadMethod(@Nonnull Class<?> clazz, @Nonnull PropertyDescriptor descriptor) {
 2263         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 2264         requireNonNull(descriptor, ERROR_DESCRIPTOR_NULL);
 2265         return (MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod()));
 2266     }
 2267
 2268     /**
 2269      * <p>Return an accessible property setter method for this property,
 2270      * if there is one; otherwise return <code>null</code>.</p>
 2271      *
 2272      * @param clazz      The class of the write method will be invoked on
 2273      * @param descriptor Property descriptor to return a setter for
 2274      * @return The write method
 2275      */
 2276     @Nullable
 2277     public static Method getWriteMethod(@Nonnull Class<?> clazz, @Nonnull PropertyDescriptor descriptor) {
 2278         requireNonNull(clazz, ERROR_CLAZZ_NULL);
 2279         requireNonNull(descriptor, ERROR_DESCRIPTOR_NULL);
 2280         return (MethodUtils.getAccessibleMethod(clazz, descriptor.getWriteMethod()));
 2281     }
 2282
 2283     // -- The following methods and properties were copied from commons-lang
 2284
 2285     /**
 2286      * <p>Validate that the argument condition is <code>true</code>; otherwise
 2287      * throwing an exception with the specified message. This method is useful when
 2288      * validating according to an arbitrary boolean expression, such as validating a
 2289      * primitive number or using your own custom validation expression.</p>
 2290      * <p/>
 2291      * <pre>
 2292      * isTrue( (i > 0), "The value must be greater than zero");
 2293      * isTrue( myObject.isOk(), "The object is not OK");
 2294      * </pre>
 2295      *
 2296      * @param expression the boolean expression to check
 2297      * @param message    the exception message if invalid
 2298      * @throws IllegalArgumentException if expression is <code>false</code>
 2299      */
 2300     public static void isTrue(boolean expression, String message) {
 2301         if (expression) {
 2302             throw new IllegalArgumentException(message);
 2303         }
 2304     }
 2305
 2306     @Nullable
 2307     public static Object invokeInstanceMethod(@Nonnull Object object, @Nonnull String methodName) {
 2308         return invokeInstanceMethod(object, methodName, EMPTY_ARGS);
 2309     }
 2310
 2311     @Nullable
 2312     public static Object invokeInstanceMethod(@Nonnull Object object, @Nonnull String methodName, Object arg) {
 2313         return invokeInstanceMethod(object, methodName, new Object[]{arg});
 2314     }
 2315
 2316     @Nullable
 2317     public static Object invokeInstanceMethod(@Nonnull Object object, @Nonnull String methodName, Object... args) {
 2318         requireNonNull(object, ERROR_OBJECT_NULL);
 2319         requireNonBlank(methodName, ERROR_METHOD_NAME_BLANK);
 2320         try {
 2321             return invokeMethod(object, methodName, args);
 2322         } catch (NoSuchMethodException | IllegalAccessException e) {
 2323             throw new InstanceMethodInvocationException(object, methodName, args, e);
 2324         } catch (InvocationTargetException e) {
 2325             throw new InstanceMethodInvocationException(object, methodName, args, e.getTargetException());
 2326         }
 2327     }
 2328
 2329     @Nullable
 2330     public static Object invokeExactInstanceMethod(@Nonnull Object object, @Nonnull String methodName) {
 2331         return invokeExactInstanceMethod(object, methodName, EMPTY_ARGS);
 2332     }
 2333
 2334     @Nullable
 2335     public static Object invokeExactInstanceMethod(@Nonnull Object object, @Nonnull String methodName, Object arg) {
 2336         return invokeExactInstanceMethod(object, methodName, new Object[]{arg});
 2337     }
 2338
 2339     @Nullable
 2340     public static Object invokeExactInstanceMethod(@Nonnull Object object, @Nonnull String methodName, Object... args) {
 2341         requireNonNull(object, ERROR_OBJECT_NULL);
 2342         requireNonBlank(methodName, ERROR_METHOD_NAME_BLANK);
 2343         try {
 2344             return invokeExactMethod(object, methodName, args);
 2345         } catch (NoSuchMethodException | IllegalAccessException e) {
 2346             throw new InstanceMethodInvocationException(object, methodName, args, e);
 2347         } catch (InvocationTargetException e) {
 2348             throw new InstanceMethodInvocationException(object, methodName, args, e.getTargetException());
 2349         }
 2350     }
 2351
 2352     @Nullable
 2353     public static Object invokeStaticMethod(@Nonnull Class<?> type, @Nonnull String methodName) {
 2354         return invokeStaticMethod(type, methodName, EMPTY_ARGS);
 2355     }
 2356
 2357     @Nullable
 2358     public static Object invokeStaticMethod(@Nonnull Class<?> type, @Nonnull String methodName, Object arg) {
 2359         return invokeStaticMethod(type, methodName, new Object[]{arg});
 2360     }
 2361
 2362     @Nullable
 2363     public static Object invokeStaticMethod(@Nonnull Class<?> type, @Nonnull String methodName, Object... args) {
 2364         requireNonNull(type, ERROR_TYPE_NULL);
 2365         requireNonBlank(methodName, ERROR_METHOD_NAME_BLANK);
 2366         try {
 2367             return MethodUtils.invokeStaticMethod(type, methodName, args);
 2368         } catch (NoSuchMethodException | IllegalAccessException e) {
 2369             throw new StaticMethodInvocationException(type, methodName, args, e);
 2370         } catch (InvocationTargetException e) {
 2371             throw new StaticMethodInvocationException(type, methodName, args, e.getTargetException());
 2372         }
 2373     }
 2374
 2375     @Nullable
 2376     public static Object invokeExactStaticMethod(@Nonnull Class<?> type, @Nonnull String methodName) {
 2377         return invokeExactStaticMethod(type, methodName, EMPTY_ARGS);
 2378     }
 2379
 2380     @Nullable
 2381     public static Object invokeExactStaticMethod(@Nonnull Class<?> type, @Nonnull String methodName, Object arg) {
 2382         return invokeExactStaticMethod(type, methodName, new Object[]{arg});
 2383     }
 2384
 2385     @Nullable
 2386     public static Object invokeExactStaticMethod(@Nonnull Class<?> type, @Nonnull String methodName, Object... args) {
 2387         requireNonNull(type, ERROR_TYPE_NULL);
 2388         requireNonBlank(methodName, ERROR_METHOD_NAME_BLANK);
 2389         try {
 2390             return MethodUtils.invokeExactStaticMethod(type, methodName, args);
 2391         } catch (NoSuchMethodException | IllegalAccessException e) {
 2392             throw new StaticMethodInvocationException(type, methodName, args, e);
 2393         } catch (InvocationTargetException e) {
 2394             throw new StaticMethodInvocationException(type, methodName, args, e.getTargetException());
 2395         }
 2396     }
 2397
 2398     private static final String EMPTY_STRING = "";
 2399
 2400     /**
 2401      * <p>The package separator character: <code>'.' == {@value}</code>.</p>
 2402      */
 2403     public static final char PACKAGE_SEPARATOR_CHAR = '.';
 2404
 2405     /**
 2406      * <p>The package separator String: <code>"."</code>.</p>
 2407      */
 2408     public static final String PACKAGE_SEPARATOR = String.valueOf(PACKAGE_SEPARATOR_CHAR);
 2409
 2410     /**
 2411      * <p>The inner class separator character: <code>'$' == {@value}</code>.</p>
 2412      */
 2413     public static final char INNER_CLASS_SEPARATOR_CHAR = '$';
 2414
 2415     /**
 2416      * <p>The inner class separator String: <code>"$"</code>.</p>
 2417      */
 2418     public static final String INNER_CLASS_SEPARATOR = String.valueOf(INNER_CLASS_SEPARATOR_CHAR);
 2419
 2420     /**
 2421      * Maps a primitive class name to its corresponding abbreviation used in array class names.
 2422      */
 2423     private static final Map<String, String> abbreviationMap = new HashMap<>();
 2424
 2425     /**
 2426      * Maps an abbreviation used in array class names to corresponding primitive class name.
 2427      */
 2428     private static final Map<String, String> reverseAbbreviationMap = new HashMap<>();
 2429
 2430     /**
 2431      * Add primitive type abbreviation to maps of abbreviations.
 2432      *
 2433      * @param primitive    Canonical name of primitive type
 2434      * @param abbreviation Corresponding abbreviation of primitive type
 2435      */
 2436     private static void addAbbreviation(String primitive, String abbreviation) {
 2437         abbreviationMap.put(primitive, abbreviation);
 2438         reverseAbbreviationMap.put(abbreviation, primitive);
 2439     }
 2440
 2441     /**
 2442      * Feed abbreviation maps
 2443      */
 2444     static {
 2445         addAbbreviation("int", "I");
 2446         addAbbreviation("boolean", "Z");
 2447         addAbbreviation("float", "F");
 2448         addAbbreviation("long", "J");
 2449         addAbbreviation("short", "S");
 2450         addAbbreviation("byte", "B");
 2451         addAbbreviation("double", "D");
 2452         addAbbreviation("char", "C");
 2453     }
 2454
 2455     // ----------------------------------------------------------------------
 2456
 2457     /**
 2458      * <p>Gets the class name minus the package name for an <code>Object</code>.</p>
 2459      *
 2460      * @param object      the class to get the short name for, may be null
 2461      * @param valueIfNull the value to return if null
 2462      * @return the class name of the object without the package name, or the null value
 2463      */
 2464     @Nonnull
 2465     public static String getShortClassName(@Nullable Object object, @Nonnull String valueIfNull) {
 2466         if (object == null) {
 2467             return valueIfNull;
 2468         }
 2469         return getShortClassName(object.getClass());
 2470     }
 2471
 2472     /**
 2473      * <p>Gets the class name minus the package name from a <code>Class</code>.</p>
 2474      *
 2475      * @param cls the class to get the short name for.
 2476      * @return the class name without the package name or an empty string
 2477      */
 2478     @Nonnull
 2479     public static String getShortClassName(@Nullable Class<?> cls) {
 2480         if (cls == null) {
 2481             return EMPTY_STRING;
 2482         }
 2483         return getShortClassName(cls.getName());
 2484     }
 2485
 2486     /**
 2487      * <p>Gets the class name minus the package name from a String.</p>
 2488      * <p/>
 2489      * <p>The string passed in is assumed to be a class name - it is not checked.</p>
 2490      *
 2491      * @param className the className to get the short name for
 2492      * @return the class name of the class without the package name or an empty string
 2493      */
 2494     @Nonnull
 2495     public static String getShortClassName(@Nullable String className) {
 2496         if (className == null) {
 2497             return EMPTY_STRING;
 2498         }
 2499         if (className.length() == 0) {
 2500             return EMPTY_STRING;
 2501         }
 2502
 2503         StringBuilder arrayPrefix = new StringBuilder();
 2504
 2505         // Handle array encoding
 2506         if (className.startsWith("[")) {
 2507             while (className.charAt(0) == '[') {
 2508                 className = className.substring(1);
 2509                 arrayPrefix.append("[]");
 2510             }
 2511             // Strip Object type encoding
 2512             if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
 2513                 className = className.substring(1, className.length() - 1);
 2514             }
 2515         }
 2516
 2517         if (reverseAbbreviationMap.containsKey(className)) {
 2518             className = reverseAbbreviationMap.get(className);
 2519         }
 2520
 2521         int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
 2522         int innerIdx = className.indexOf(
 2523             INNER_CLASS_SEPARATOR_CHAR, lastDotIdx == -1 ? 0 : lastDotIdx + 1);
 2524         String out = className.substring(lastDotIdx + 1);
 2525         if (innerIdx != -1) {
 2526             out = out.replace(INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR);
 2527         }
 2528         return out + arrayPrefix;
 2529     }
 2530
 2531     // Package name
 2532     // ----------------------------------------------------------------------
 2533
 2534     /**
 2535      * <p>Gets the package name of an <code>Object</code>.</p>
 2536      *
 2537      * @param object      the class to get the package name for, may be null
 2538      * @param valueIfNull the value to return if null
 2539      * @return the package name of the object, or the null value
 2540      */
 2541     @Nonnull
 2542     public static String getPackageName(@Nullable Object object, @Nonnull String valueIfNull) {
 2543         if (object == null) {
 2544             return valueIfNull;
 2545         }
 2546         return getPackageName(object.getClass());
 2547     }
 2548
 2549     /**
 2550      * <p>Gets the package name of a <code>Class</code>.</p>
 2551      *
 2552      * @param cls the class to get the package name for, may be <code>null</code>.
 2553      * @return the package name or an empty string
 2554      */
 2555     @Nonnull
 2556     public static String getPackageName(@Nullable Class<?> cls) {
 2557         if (cls == null) {
 2558             return EMPTY_STRING;
 2559         }
 2560         return getPackageName(cls.getName());
 2561     }
 2562
 2563     /**
 2564      * <p>Gets the package name from a <code>String</code>.</p>
 2565      * <p/>
 2566      * <p>The string passed in is assumed to be a class name - it is not checked.</p>
 2567      * <p>If the class is unpackaged, return an empty string.</p>
 2568      *
 2569      * @param className the className to get the package name for, may be <code>null</code>
 2570      * @return the package name or an empty string
 2571      */
 2572     @Nonnull
 2573     public static String getPackageName(@Nullable String className) {
 2574         if (className == null || className.length() == 0) {
 2575             return EMPTY_STRING;
 2576         }
 2577
 2578         // Strip array encoding
 2579         while (className.charAt(0) == '[') {
 2580             className = className.substring(1);
 2581         }
 2582         // Strip Object type encoding
 2583         if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
 2584             className = className.substring(1);
 2585         }
 2586
 2587         int i = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
 2588         if (i == -1) {
 2589             return EMPTY_STRING;
 2590         }
 2591         return className.substring(0, i);
 2592     }
 2593
 2594     /**
 2595      * param instance array to the type array
 2596      *
 2597      * @param args the arguments
 2598      * @return the types of the arguments
 2599      */
 2600     @Nullable
 2601     public static Class<?>[] convertToTypeArray(@Nullable Object[] args) {
 2602         if (args == null) {
 2603             return null;
 2604         }
 2605         int s = args.length;
 2606         Class<?>[] ans = new Class<?>[s];
 2607         for (int i = 0; i < s; i++) {
 2608             Object o = args[i];
 2609             ans[i] = o != null ? o.getClass() : null;
 2610         }
 2611         return ans;
 2612     }
 2613 }
 |