
org.jsimpledb.Util Maven / Gradle / Ivy
Show all versions of jsimpledb-main Show documentation
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package org.jsimpledb;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.Constraint;
import javax.validation.groups.Default;
import org.jsimpledb.annotation.OnValidate;
/**
* Various utility routines.
*/
public final class Util {
private static final WildcardType QUESTION_MARK = new WildcardType() {
@Override
public Type[] getUpperBounds() {
return new Type[] { Object.class };
}
@Override
public Type[] getLowerBounds() {
return new Type[0];
}
@Override
public String toString() {
return "?";
}
};
private static Method newParameterizedTypeMethod;
private Util() {
}
/**
* Determine if any JSR 303 validation annotations are present on the given type itself
* or any of its methods (public methods only).
*
* @param type object type
* @return a non-null object with JSR 303 validation requirements, or null if none found
* @throws IllegalArgumentException if {@code type} is null
*/
public static AnnotatedElement hasValidation(Class> type) {
// Sanity check
Preconditions.checkArgument(type != null, "null type");
// Check for annotations on the class itself
if (Util.hasValidationAnnotation(type))
return type;
// Check methods
for (Method method : type.getDeclaredMethods()) {
// Check for JSR 303 annotation
if ((method.getModifiers() & Modifier.PUBLIC) != 0 && Util.hasValidationAnnotation(method))
return method;
}
// Recurse on supertypes
for (TypeToken> typeToken : TypeToken.of(type).getTypes()) {
final Class> superType = typeToken.getRawType();
if (superType == type)
continue;
final AnnotatedElement annotatedElement = Util.hasValidation(superType);
if (annotatedElement != null)
return annotatedElement;
}
// None found
return null;
}
/**
* Determine if instances of the given type require any validation under the default validation group.
*
*
* This will be true if {@code type} or any of its declared methods has a JSR 303 (public methods only)
* or {@link OnValidate @OnValidate} annotation, or if any of its super-types requires validation.
*
* @param type object type
* @return true if {@code type} has any validation requirements
* @throws IllegalArgumentException if {@code type} is null
* @see ValidationMode
*/
public static boolean requiresDefaultValidation(Class> type) {
// Sanity check
Preconditions.checkArgument(type != null, "null type");
// Check for annotations on the class itself
if (Util.hasDefaultValidationAnnotation(type))
return true;
// Check methods
for (Method method : type.getDeclaredMethods()) {
// Check for @OnValidate annotation
if (method.isAnnotationPresent(OnValidate.class))
return true;
// Check for JSR 303 annotation
if ((method.getModifiers() & Modifier.PUBLIC) != 0 && Util.requiresDefaultValidation(method))
return true;
}
// Recurse on superclasses
for (TypeToken> typeToken : TypeToken.of(type).getTypes()) {
final Class> superType = typeToken.getRawType();
if (superType != type && Util.requiresDefaultValidation(superType))
return true;
}
// Done
return false;
}
/**
* Determine if the given getter method, or any method it overrides, has a JSR 303 validation constraint
* applicable under the default validation group.
*
* @param method annotated method
* @return true if {@code obj} has one or more JSR 303 annotations
* @throws IllegalArgumentException if {@code method} is null
*/
public static boolean requiresDefaultValidation(Method method) {
Preconditions.checkArgument(method != null, "null method");
final String methodName = method.getName();
final Class>[] paramTypes = method.getParameterTypes();
for (TypeToken> typeToken : TypeToken.of(method.getDeclaringClass()).getTypes()) {
final Class> superType = typeToken.getRawType();
try {
method = superType.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
continue;
}
if (Util.hasDefaultValidationAnnotation(method))
return true;
}
return false;
}
/**
* Determine whether the given object has any JSR 303 annotation(s) defining validation constraints in the default group.
*
* @param obj annotated element
* @return true if {@code obj} has one or more JSR 303 default validation constraint annotations
* @throws IllegalArgumentException if {@code obj} is null
*/
public static boolean hasDefaultValidationAnnotation(AnnotatedElement obj) {
return Util.hasValidationAnnotation(obj, new Class>[] { Default.class });
}
/**
* Determine whether the given object has any JSR 303 annotation(s).
*
* @param obj annotated element
* @return true if {@code obj} has one or more JSR 303 validation constraint annotations
* @throws IllegalArgumentException if {@code obj} is null
*/
public static boolean hasValidationAnnotation(AnnotatedElement obj) {
return Util.hasValidationAnnotation(obj, null);
}
private static boolean hasValidationAnnotation(AnnotatedElement obj, Class[] validationGroups) {
Preconditions.checkArgument(obj != null, "null obj");
for (Annotation annotation : obj.getAnnotations()) {
final Class> annotationType = annotation.annotationType();
if (!annotationType.isAnnotationPresent(Constraint.class))
continue;
final Class>[] groups;
try {
groups = (Class>[])annotation.getClass().getMethod("groups").invoke(annotation);
} catch (NoSuchMethodException e) {
return true;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
if (groups == null || groups.length == 0)
return true;
return validationGroups == null || Util.isAnyGroupBeingValidated(groups, validationGroups);
}
return false;
}
/**
* Determine if a constraint whose {@code groups()} contain the given constraint group should be applied
* when validating with the given validation groups.
*
* @param constraintGroup validation group associated with a validation constraint
* @param validationGroups groups for which validation is being performed
* @return whether to apply the validation constraint
* @throws IllegalArgumentException if any null values are encountered
*/
public static boolean isGroupBeingValidated(Class> constraintGroup, Class>[] validationGroups) {
Preconditions.checkArgument(constraintGroup != null, "null constraintGroup");
Preconditions.checkArgument(validationGroups != null, "null validationGroups");
for (Class> validationGroup : validationGroups) {
Preconditions.checkArgument(validationGroup != null, "null validationGroup");
if (constraintGroup.isAssignableFrom(validationGroup))
return true;
}
return false;
}
/**
* Determine if a constraint whose {@code groups()} contain the given constraint groups should be applied
* when validating with the given validation groups.
*
* @param constraintGroups validation groups associated with a validation constraint
* @param validationGroups groups for which validation is being performed
* @return whether to apply the validation constraint
* @throws IllegalArgumentException if any null values are encountered
*/
public static boolean isAnyGroupBeingValidated(Class>[] constraintGroups, Class>[] validationGroups) {
Preconditions.checkArgument(constraintGroups != null, "null constraintGroups");
for (Class> constraintGroup : constraintGroups) {
if (Util.isGroupBeingValidated(constraintGroup, validationGroups))
return true;
}
return false;
}
/**
* Find the setter method corresponding to a getter method. It must be either public or protected.
*
* @param type Java type (possibly a sub-type of the type in which {@code getter} is declared)
* @param getter Java bean property getter method
* @return Java bean property setter method
* @throws IllegalArgumentException if no corresponding setter method exists
*/
static Method findJFieldSetterMethod(Class> type, Method getter) {
final Matcher matcher = Pattern.compile("(is|get)(.+)").matcher(getter.getName());
if (!matcher.matches()) {
throw new IllegalArgumentException("can't infer setter method name from getter method "
+ getter.getName() + "() because name does not follow Java bean naming conventions");
}
final String setterName = "set" + matcher.group(2);
for (TypeToken> superType : TypeToken.of(type).getTypes()) {
try {
final Method setter = superType.getRawType().getDeclaredMethod(setterName, getter.getReturnType());
if (setter.getReturnType() != Void.TYPE)
continue;
if ((setter.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0
|| (setter.getModifiers() & Modifier.PRIVATE) != 0) {
throw new IllegalArgumentException("invalid setter method " + setterName
+ "() corresponding to getter method " + getter.getName() + "(): method must be public or protected");
}
return setter;
} catch (NoSuchMethodException e) {
continue;
}
}
throw new IllegalArgumentException("can't find any setter method " + setterName
+ "() corresponding to getter method " + getter.getName() + "() taking " + getter.getReturnType()
+ " and returning void");
}
/**
* Find unimplemented abstract methods in the given class.
*/
static Map findAbstractMethods(Class> type) {
final HashMap map = new HashMap<>();
// First find all methods, but don't include overridden supertype methods
for (TypeToken> superType : TypeToken.of(type).getTypes()) {
for (Method method : superType.getRawType().getDeclaredMethods()) {
final MethodKey key = new MethodKey(method);
if (!map.containsKey(key))
map.put(key, method);
}
}
// Now discard all the non-abstract methods
for (Iterator> i = map.entrySet().iterator(); i.hasNext(); ) {
final Map.Entry entry = i.next();
if ((entry.getValue().getModifiers() & Modifier.ABSTRACT) == 0)
i.remove();
}
// Done
return map;
}
/**
* Find the narrowest type that is a supertype of all of the given types.
*
*
* This method delegates to {@link #findLowestCommonAncestor findLowestCommonAncestor()}
* after converting the {@link Class} instances to {@link TypeToken}s.
*
* @param types sub-types
* @return narrowest common super-type
* @throws IllegalArgumentException if any type in {@code types} is null
* @see #findLowestCommonAncestor findLowestCommonAncestor()
*/
public static TypeToken> findLowestCommonAncestorOfClasses(Iterable> types) {
return Util.findLowestCommonAncestor(Iterables.transform(types, new Function, TypeToken>>() {
@Override
public TypeToken> apply(Class> type) {
if (type == null)
throw new IllegalArgumentException("null type");
return TypeToken.of(type);
}
}));
}
/**
* Find the narrowest type that is a supertype of all of the given types.
*
*
* When there is more than one choice, heuristics are used. For example, we prefer
* non-interface types, and {@link JObject} over other interface types.
*
* @param types sub-types
* @return narrowest common super-type
*/
public static TypeToken> findLowestCommonAncestor(Iterable> types) {
// Gather all supertypes of types recursively
final HashSet> supertypes = new HashSet<>();
for (TypeToken> type : types)
Util.addSupertypes(supertypes, type);
// Throw out all supertypes that are not supertypes of every type
for (Iterator> i = supertypes.iterator(); i.hasNext(); ) {
final TypeToken> supertype = i.next();
for (TypeToken> type : types) {
if (!supertype.isSupertypeOf(type)) {
i.remove();
break;
}
}
}
// Throw out all supertypes that are supertypes of some other supertype
for (Iterator> i = supertypes.iterator(); i.hasNext(); ) {
final TypeToken> supertype = i.next();
for (TypeToken> supertype2 : supertypes) {
if (supertype2 != supertype && supertype.isSupertypeOf(supertype2)) {
i.remove();
break;
}
}
}
// Pick the best candidate that's not Object, if possible
final TypeToken