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