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 == null) return 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 != null) return 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 == null) return 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(0, 1).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 == null) return false;
1497 if (args.length != 0) return false;
1498
1499 if (name.startsWith(PROPERTY_GET_PREFIX)) {
1500 name = name.substring(3);
1501 if (name.length() > 0 && 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() > 0 && 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 == null) return false;
1564
1565 if (name.startsWith(PROPERTY_SET_PREFIX)) {
1566 if (args.length != 1) return false;
1567 name = name.substring(3);
1568 if (name.length() > 0 && 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 == null) return false;
1605 if (map.containsKey(key)) {
1606 Object o = map.get(key);
1607 if (o == null) return false;
1608 else if (o instanceof Boolean) {
1609 return (Boolean) o;
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>'.' == {@value}</code>.</p>
2130 */
2131 public static final char PACKAGE_SEPARATOR_CHAR = '.';
2132
2133 /**
2134 * <p>The package separator String: <code>"."</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 == -1 ? 0 : 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 }
|