BasePojomator.java
package org.pojomatic.internal;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import org.pojomatic.Pojomator;
import org.pojomatic.PropertyElement;
public abstract class BasePojomator<T> implements Pojomator<T> {
protected final Class<?> pojoClass;
private final ClassProperties classProperties;
protected BasePojomator(Class<?> pojoClass, ClassProperties classProperties) {
this.pojoClass = pojoClass;
this.classProperties = classProperties;
}
@Override
public boolean isCompatibleForEquality(Class<?> otherClass) {
return classProperties.isCompatibleForEquals(otherClass);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Pojomator for ").append(pojoClass.getName()).append(" with equals properties ");
propertiesList(builder, classProperties.getEqualsProperties());
builder.append(", hashCodeProperties ");
propertiesList(builder, classProperties.getHashCodeProperties());
builder.append(", and toStringProperties ");
propertiesList(builder, classProperties.getToStringProperties());
return builder.toString();
}
private void propertiesList(StringBuilder builder, final Iterable<PropertyElement> properties) {
builder.append("{");
boolean firstElement = true;
for (PropertyElement prop: properties) {
if (!firstElement) {
builder.append(",");
}
else {
firstElement = false;
}
builder.append(prop.getName());
}
builder.append("}");
}
/**
* Construct a call site for a property accessor. Because {@code pojoClass} might not be a public class, the
* parameter in {@code methodType} cannot be {@code pojoClass}, but instead must be just {@code Object.class}. The
* {@code pojoClass} parameter will be stored as static field in the Pojomator class, and passed in from it's
* bootstrap method.
* @param caller A Lookup from the original call site.
* @param name the name of the dynamic method. This should either be "field_<fieldName>" or "method_<methodName>".
* @param methodType the type of the dynamic method; the return type should be the type of the aforementioned field
* or method
* @param pojomatorClass the type of the pojomator class
* @return a CallSite which invokes the method or gets the field value.
* @throws Throwable if there are reflection issues
*/
protected static CallSite bootstrap(
MethodHandles.Lookup caller, String name, MethodType methodType, Class<?> pojomatorClass)
throws Throwable {
return new ConstantCallSite(
MethodHandles.explicitCastArguments(
getTypedMethod(caller, name, pojomatorClass),
MethodType.methodType(methodType.returnType(), Object.class)));
}
/**
* Compare two values of static type Object for equality. If both values are arrays, then they will be considered
* equal iff they have the same class, and (recursively) an equal set of elements.
* @param instanceValue the first value to compare
* @param otherValue the second value to compare
* @return true if {@code instanceValue} and {@code otherValue} are equal to each other.
*/
protected static boolean areObjectValuesEqual(Object instanceValue, Object otherValue) {
if (instanceValue == otherValue) {
return true;
}
if (instanceValue == null || otherValue == null) {
return false;
}
else {
if (!instanceValue.getClass().isArray()) {
if (!instanceValue.equals(otherValue)) {
return false;
}
}
else {
return compareArrays(instanceValue, otherValue);
}
}
return true;
}
/**
* Compare two values of array type for equality. They will be considered
* equal iff they have the same class, and (recursively) an equal set of elements.
* @param instanceValue the first value to compare
* @param otherValue the second value to compare
* @return true if {@code instanceValue} and {@code otherValue} are equal to each other.
*/
protected static boolean compareArrays(Object instanceValue, Object otherValue) {
if (instanceValue == otherValue) {
return true;
}
if (instanceValue == null || otherValue == null) {
return false;
}
if (!instanceValue.getClass().equals(otherValue.getClass())) {
return false;
}
final Class<?> instanceComponentClass = instanceValue.getClass().getComponentType();
if (!instanceComponentClass.isPrimitive()) {
return Arrays.deepEquals((Object[]) instanceValue, (Object[]) otherValue);
}
else { // instanceComponentClass is primitive
if (Boolean.TYPE == instanceComponentClass) {
return Arrays.equals((boolean[]) instanceValue, (boolean[]) otherValue);
}
else if (Byte.TYPE == instanceComponentClass) {
return Arrays.equals((byte[]) instanceValue, (byte[]) otherValue);
}
else if (Character.TYPE == instanceComponentClass) {
return Arrays.equals((char[]) instanceValue, (char[]) otherValue);
}
else if (Short.TYPE == instanceComponentClass) {
return Arrays.equals((short[]) instanceValue, (short[]) otherValue);
}
else if (Integer.TYPE == instanceComponentClass) {
return Arrays.equals((int[]) instanceValue, (int[]) otherValue);
}
else if (Long.TYPE == instanceComponentClass) {
return Arrays.equals((long[]) instanceValue, (long[]) otherValue);
}
else if (Float.TYPE == instanceComponentClass) {
return Arrays.equals((float[]) instanceValue, (float[]) otherValue);
}
else if (Double.TYPE == instanceComponentClass) {
return Arrays.equals((double[]) instanceValue, (double[]) otherValue);
}
else {
// should NEVER happen
throw new IllegalStateException(
"unknown primitive type " + instanceComponentClass.getName());
}
}
}
/**
* Given an object which is of array type, compute it's hashCode by calling the appropriate signature of
* {@link Arrays}{@code .hashCode()}
* @param array the array
* @param deepArray whether to do a deep hashCode for Object arrays.
* @return the hashCode
*/
protected static int arrayHashCode(Object array, boolean deepArray) {
Class<?> componentType = array.getClass().getComponentType();
if (! componentType.isPrimitive()) {
return deepArray ? Arrays.deepHashCode((Object[]) array) : Arrays.hashCode((Object[]) array);
}
if (componentType == boolean.class) {
return Arrays.hashCode((boolean[]) array);
}
if (componentType == byte.class) {
return Arrays.hashCode((byte[]) array);
}
if (componentType == char.class) {
return Arrays.hashCode((char[]) array);
}
if (componentType == short.class) {
return Arrays.hashCode((short[]) array);
}
if (componentType == int.class) {
return Arrays.hashCode((int[]) array);
}
if (componentType == long.class) {
return Arrays.hashCode((long[]) array);
}
if (componentType == float.class) {
return Arrays.hashCode((float[]) array);
}
if (componentType == double.class) {
return Arrays.hashCode((double[]) array);
}
throw new IllegalStateException("unknown primitive type " + componentType.getName());
}
protected static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
protected static <T> T checkNotNull(T reference, String message) {
if (reference == null) {
throw new NullPointerException(message);
}
return reference;
}
protected static void checkNotNullPop(Object reference) {
if (reference == null) {
throw new NullPointerException();
}
}
protected void checkCompatibleForEquality(T instance, String label) {
if (!isCompatibleForEquality(instance.getClass())) {
throw new IllegalArgumentException(
label + " has type " + instance.getClass().getName()
+ " which is not compatible for equality with " + pojoClass.getName());
}
}
/**
* Get a method handle to access a field or invoke a no-arg method.
* @param caller A Lookup from the original call site.
* @param name the name of the dynamic method. This should be of the form "get_xxx", where "element_xxx" will be a
* static field containing a {@link PropertyElement} instance referring to the property to be accessed.
* @param pojomatorClass the type of the pojomator class
* @return the MethodHandle
* @throws Throwable
*/
private static MethodHandle getTypedMethod(
final MethodHandles.Lookup caller, final String name, final Class<?> pojomatorClass)
throws Throwable {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<MethodHandle>() {
@Override
public MethodHandle run() throws Exception {
return getTypedMethodPrivileged(caller, name, pojomatorClass);
}
});
} catch (PrivilegedActionException e) {
throw e.getCause();
}
}
/**
* Do the work for {@link #getTypedMethod(java.lang.invoke.MethodHandles.Lookup, String, Class)}. This method will be
* run inside of a {@link AccessController#doPrivileged(PrivilegedExceptionAction)} block, hence should make sure to
* not run untrusted code.
* @param pojomatorClass the type of the pojomator class
* @return the MethodHandle
* @throws NoSuchFieldException
* @throws IllegalAccessException
*/
private static MethodHandle getTypedMethodPrivileged(
MethodHandles.Lookup caller, String name, Class<?> pojomatorClass)
throws NoSuchFieldException, IllegalAccessException {
String elementName = "element_" + name.substring(4);
Field elementField = pojomatorClass.getDeclaredField(elementName);
elementField.setAccessible(true);
PropertyElement property = (PropertyElement) elementField.get(null);
AnnotatedElement element = property.getElement();
// Note that while element is a reference to untrusted code, we do not actually invoke this code inside a
// doPrivileged block - we merely make it accessible to be invoked later, outside of a doPriviliged block
if (element instanceof Field) {
Field field = (Field) element;
field.setAccessible(true);
return caller.unreflectGetter(field);
}
else if (element instanceof Method) {
Method method = (Method) element;
method.setAccessible(true);
return caller.unreflect(method);
}
else {
throw new IllegalArgumentException("Cannot handle element of type " + element.getClass().getName());
}
}
}