BasePojomator.java

  1. package org.pojomatic.internal;

  2. import java.lang.invoke.CallSite;
  3. import java.lang.invoke.ConstantCallSite;
  4. import java.lang.invoke.MethodHandle;
  5. import java.lang.invoke.MethodHandles;
  6. import java.lang.invoke.MethodType;
  7. import java.lang.reflect.AnnotatedElement;
  8. import java.lang.reflect.Field;
  9. import java.lang.reflect.Method;
  10. import java.security.AccessController;
  11. import java.security.PrivilegedActionException;
  12. import java.security.PrivilegedExceptionAction;
  13. import java.util.Arrays;

  14. import org.pojomatic.Pojomator;
  15. import org.pojomatic.PropertyElement;

  16. public abstract class BasePojomator<T> implements Pojomator<T> {
  17.   protected final Class<?> pojoClass;
  18.   private final ClassProperties classProperties;

  19.   protected BasePojomator(Class<?> pojoClass, ClassProperties classProperties) {
  20.     this.pojoClass = pojoClass;
  21.     this.classProperties = classProperties;
  22.   }

  23.   @Override
  24.   public boolean isCompatibleForEquality(Class<?> otherClass) {
  25.     return classProperties.isCompatibleForEquals(otherClass);
  26.   }

  27.   @Override
  28.   public String toString() {
  29.     StringBuilder builder = new StringBuilder();
  30.     builder.append("Pojomator for ").append(pojoClass.getName()).append(" with equals properties ");
  31.     propertiesList(builder, classProperties.getEqualsProperties());
  32.     builder.append(", hashCodeProperties ");
  33.     propertiesList(builder, classProperties.getHashCodeProperties());
  34.     builder.append(", and toStringProperties ");
  35.     propertiesList(builder, classProperties.getToStringProperties());
  36.     return builder.toString();
  37.   }

  38.   private void propertiesList(StringBuilder builder, final Iterable<PropertyElement> properties) {
  39.     builder.append("{");
  40.     boolean firstElement = true;
  41.     for (PropertyElement prop: properties) {
  42.       if (!firstElement) {
  43.         builder.append(",");
  44.       }
  45.       else {
  46.         firstElement = false;
  47.       }
  48.       builder.append(prop.getName());
  49.     }
  50.     builder.append("}");
  51.   }

  52.   /**
  53.    * Construct a call site for a property accessor. Because {@code pojoClass} might not be a public class, the
  54.    * parameter in {@code methodType} cannot be {@code pojoClass}, but instead must be just {@code Object.class}. The
  55.    * {@code pojoClass} parameter will be stored as static field in the Pojomator class, and passed in from it's
  56.    * bootstrap method.
  57.    * @param caller A Lookup from the original call site.
  58.    * @param name the name of the dynamic method. This should either be "field_&lt;fieldName&gt;" or "method_&lt;methodName&gt;".
  59.    * @param methodType the type of the dynamic method; the return type should be the type of the aforementioned field
  60.    *   or method
  61.    * @param pojomatorClass the type of the pojomator class
  62.    * @return a CallSite which invokes the method or gets the field value.
  63.    * @throws Throwable if there are reflection issues
  64.    */
  65.   protected static CallSite bootstrap(
  66.       MethodHandles.Lookup caller, String name, MethodType methodType, Class<?> pojomatorClass)
  67.       throws Throwable {
  68.     return new ConstantCallSite(
  69.       MethodHandles.explicitCastArguments(
  70.         getTypedMethod(caller, name, pojomatorClass),
  71.         MethodType.methodType(methodType.returnType(), Object.class)));
  72.   }

  73.   /**
  74.    * Compare two values of static type Object for equality. If both values are arrays, then they will be considered
  75.    * equal iff they have the same class, and (recursively) an equal set of elements.
  76.    * @param instanceValue the first value to compare
  77.    * @param otherValue the second value to compare
  78.    * @return true if {@code instanceValue} and {@code otherValue} are equal to each other.
  79.    */
  80.   protected static boolean areObjectValuesEqual(Object instanceValue, Object otherValue) {
  81.     if (instanceValue == otherValue) {
  82.       return true;
  83.     }
  84.     if (instanceValue == null || otherValue == null) {
  85.       return false;
  86.     }
  87.     else {
  88.       if (!instanceValue.getClass().isArray()) {
  89.         if (!instanceValue.equals(otherValue)) {
  90.           return false;
  91.         }
  92.       }
  93.       else {
  94.         return compareArrays(instanceValue, otherValue);
  95.       }
  96.     }
  97.     return true;
  98.   }

  99.   /**
  100.    * Compare two values of array type for equality. They will be considered
  101.    * equal iff they have the same class, and (recursively) an equal set of elements.
  102.    * @param instanceValue the first value to compare
  103.    * @param otherValue the second value to compare
  104.    * @return true if {@code instanceValue} and {@code otherValue} are equal to each other.
  105.    */
  106.   protected static boolean compareArrays(Object instanceValue, Object otherValue) {
  107.     if (instanceValue == otherValue) {
  108.       return true;
  109.     }
  110.     if (instanceValue == null || otherValue == null) {
  111.       return false;
  112.     }
  113.     if (!instanceValue.getClass().equals(otherValue.getClass())) {
  114.       return false;
  115.     }
  116.     final Class<?> instanceComponentClass = instanceValue.getClass().getComponentType();

  117.     if (!instanceComponentClass.isPrimitive()) {
  118.       return Arrays.deepEquals((Object[]) instanceValue, (Object[]) otherValue);
  119.     }
  120.     else { // instanceComponentClass is primitive
  121.       if (Boolean.TYPE == instanceComponentClass) {
  122.         return Arrays.equals((boolean[]) instanceValue, (boolean[]) otherValue);
  123.       }
  124.       else if (Byte.TYPE == instanceComponentClass) {
  125.         return Arrays.equals((byte[]) instanceValue, (byte[]) otherValue);
  126.       }
  127.       else if (Character.TYPE == instanceComponentClass) {
  128.         return Arrays.equals((char[]) instanceValue, (char[]) otherValue);
  129.       }
  130.       else if (Short.TYPE == instanceComponentClass) {
  131.         return Arrays.equals((short[]) instanceValue, (short[]) otherValue);
  132.       }
  133.       else if (Integer.TYPE == instanceComponentClass) {
  134.         return Arrays.equals((int[]) instanceValue, (int[]) otherValue);
  135.       }
  136.       else if (Long.TYPE == instanceComponentClass) {
  137.         return Arrays.equals((long[]) instanceValue, (long[]) otherValue);
  138.       }
  139.       else if (Float.TYPE == instanceComponentClass) {
  140.         return Arrays.equals((float[]) instanceValue, (float[]) otherValue);
  141.       }
  142.       else if (Double.TYPE == instanceComponentClass) {
  143.         return Arrays.equals((double[]) instanceValue, (double[]) otherValue);
  144.       }
  145.       else {
  146.         // should NEVER happen
  147.         throw new IllegalStateException(
  148.           "unknown primitive type " + instanceComponentClass.getName());
  149.       }
  150.     }
  151.   }


  152.   /**
  153.    * Given an object which is of array type, compute it's hashCode by calling the appropriate signature of
  154.    * {@link Arrays}{@code .hashCode()}
  155.    * @param array the array
  156.    * @param deepArray whether to do a deep hashCode for Object arrays.
  157.    * @return the hashCode
  158.    */
  159.   protected static int arrayHashCode(Object array, boolean deepArray) {
  160.     Class<?> componentType = array.getClass().getComponentType();
  161.     if (! componentType.isPrimitive()) {
  162.       return deepArray ? Arrays.deepHashCode((Object[]) array) : Arrays.hashCode((Object[]) array);
  163.     }
  164.     if (componentType == boolean.class) {
  165.       return Arrays.hashCode((boolean[]) array);
  166.     }
  167.     if (componentType == byte.class) {
  168.       return Arrays.hashCode((byte[]) array);
  169.     }
  170.     if (componentType == char.class) {
  171.       return Arrays.hashCode((char[]) array);
  172.     }
  173.     if (componentType == short.class) {
  174.       return Arrays.hashCode((short[]) array);
  175.     }
  176.     if (componentType == int.class) {
  177.       return Arrays.hashCode((int[]) array);
  178.     }
  179.     if (componentType == long.class) {
  180.       return Arrays.hashCode((long[]) array);
  181.     }
  182.     if (componentType == float.class) {
  183.       return Arrays.hashCode((float[]) array);
  184.     }
  185.     if (componentType == double.class) {
  186.       return Arrays.hashCode((double[]) array);
  187.     }
  188.     throw new IllegalStateException("unknown primitive type " + componentType.getName());
  189.   }

  190.   protected static <T> T checkNotNull(T reference) {
  191.     if (reference == null) {
  192.       throw new NullPointerException();
  193.     }
  194.     return reference;
  195.   }

  196.   protected static <T> T checkNotNull(T reference, String message) {
  197.     if (reference == null) {
  198.       throw new NullPointerException(message);
  199.     }
  200.     return reference;
  201.   }

  202.   protected static void checkNotNullPop(Object reference) {
  203.     if (reference == null) {
  204.       throw new NullPointerException();
  205.     }
  206.   }

  207.   protected void checkCompatibleForEquality(T instance, String label) {
  208.     if (!isCompatibleForEquality(instance.getClass())) {
  209.       throw new IllegalArgumentException(
  210.         label + " has type " + instance.getClass().getName()
  211.         + " which is not compatible for equality with " + pojoClass.getName());
  212.     }
  213.   }

  214.   /**
  215.    * Get a method handle to access a field or invoke a no-arg method.
  216.    * @param caller A Lookup from the original call site.
  217.    * @param name the name of the dynamic method. This should be of the form "get_xxx", where "element_xxx" will be a
  218.    * static field containing a {@link PropertyElement} instance referring to the property to be accessed.
  219.    * @param pojomatorClass the type of the pojomator class
  220.    * @return the MethodHandle
  221.    * @throws Throwable
  222.    */
  223.   private static MethodHandle getTypedMethod(
  224.     final MethodHandles.Lookup caller, final String name, final Class<?> pojomatorClass)
  225.     throws Throwable {
  226.     try {
  227.       return AccessController.doPrivileged(new PrivilegedExceptionAction<MethodHandle>() {
  228.         @Override
  229.         public MethodHandle run() throws Exception {
  230.           return getTypedMethodPrivileged(caller, name, pojomatorClass);
  231.         }
  232.       });
  233.     } catch (PrivilegedActionException e) {
  234.       throw e.getCause();
  235.     }
  236.   }

  237.   /**
  238.    * Do the work for {@link #getTypedMethod(java.lang.invoke.MethodHandles.Lookup, String, Class)}. This method will be
  239.    * run inside of a {@link AccessController#doPrivileged(PrivilegedExceptionAction)} block, hence should make sure to
  240.    * not run untrusted code.
  241.    * @param pojomatorClass the type of the pojomator class
  242.    * @return the MethodHandle
  243.    * @throws NoSuchFieldException
  244.    * @throws IllegalAccessException
  245.    */
  246.   private static MethodHandle getTypedMethodPrivileged(
  247.     MethodHandles.Lookup caller, String name, Class<?> pojomatorClass)
  248.     throws NoSuchFieldException, IllegalAccessException {
  249.     String elementName = "element_" + name.substring(4);
  250.     Field elementField = pojomatorClass.getDeclaredField(elementName);
  251.     elementField.setAccessible(true);
  252.     PropertyElement property = (PropertyElement) elementField.get(null);
  253.     AnnotatedElement element = property.getElement();
  254.     // Note that while element is a reference to untrusted code, we do not actually invoke this code inside a
  255.     // doPrivileged block - we merely make it accessible to be invoked later, outside of a doPriviliged block
  256.     if (element instanceof Field) {
  257.       Field field = (Field) element;
  258.       field.setAccessible(true);
  259.       return caller.unreflectGetter(field);
  260.     }
  261.     else if (element instanceof Method) {
  262.       Method method = (Method) element;
  263.       method.setAccessible(true);
  264.       return caller.unreflect(method);
  265.     }
  266.     else {
  267.       throw new IllegalArgumentException("Cannot handle element of type " + element.getClass().getName());
  268.     }
  269.   }

  270. }