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