View Javadoc
1   package org.pojomatic.internal;
2   
3   import java.lang.invoke.CallSite;
4   import java.lang.invoke.ConstantCallSite;
5   import java.lang.invoke.MethodHandle;
6   import java.lang.invoke.MethodHandles;
7   import java.lang.invoke.MethodType;
8   import java.lang.reflect.AnnotatedElement;
9   import java.lang.reflect.Field;
10  import java.lang.reflect.Method;
11  import java.security.AccessController;
12  import java.security.PrivilegedActionException;
13  import java.security.PrivilegedExceptionAction;
14  import java.util.Arrays;
15  
16  import org.pojomatic.Pojomator;
17  import org.pojomatic.PropertyElement;
18  
19  public abstract class BasePojomator<T> implements Pojomator<T> {
20    protected final Class<?> pojoClass;
21    private final ClassProperties classProperties;
22  
23    protected BasePojomator(Class<?> pojoClass, ClassProperties classProperties) {
24      this.pojoClass = pojoClass;
25      this.classProperties = classProperties;
26    }
27  
28    @Override
29    public boolean isCompatibleForEquality(Class<?> otherClass) {
30      return classProperties.isCompatibleForEquals(otherClass);
31    }
32  
33    @Override
34    public String toString() {
35      StringBuilder builder = new StringBuilder();
36      builder.append("Pojomator for ").append(pojoClass.getName()).append(" with equals properties ");
37      propertiesList(builder, classProperties.getEqualsProperties());
38      builder.append(", hashCodeProperties ");
39      propertiesList(builder, classProperties.getHashCodeProperties());
40      builder.append(", and toStringProperties ");
41      propertiesList(builder, classProperties.getToStringProperties());
42      return builder.toString();
43    }
44  
45    private void propertiesList(StringBuilder builder, final Iterable<PropertyElement> properties) {
46      builder.append("{");
47      boolean firstElement = true;
48      for (PropertyElement prop: properties) {
49        if (!firstElement) {
50          builder.append(",");
51        }
52        else {
53          firstElement = false;
54        }
55        builder.append(prop.getName());
56      }
57      builder.append("}");
58    }
59  
60    /**
61     * Construct a call site for a property accessor. Because {@code pojoClass} might not be a public class, the
62     * parameter in {@code methodType} cannot be {@code pojoClass}, but instead must be just {@code Object.class}. The
63     * {@code pojoClass} parameter will be stored as static field in the Pojomator class, and passed in from it's
64     * bootstrap method.
65     * @param caller A Lookup from the original call site.
66     * @param name the name of the dynamic method. This should either be "field_&lt;fieldName&gt;" or "method_&lt;methodName&gt;".
67     * @param methodType the type of the dynamic method; the return type should be the type of the aforementioned field
68     *   or method
69     * @param pojomatorClass the type of the pojomator class
70     * @return a CallSite which invokes the method or gets the field value.
71     * @throws Throwable if there are reflection issues
72     */
73    protected static CallSite bootstrap(
74        MethodHandles.Lookup caller, String name, MethodType methodType, Class<?> pojomatorClass)
75        throws Throwable {
76      return new ConstantCallSite(
77        MethodHandles.explicitCastArguments(
78          getTypedMethod(caller, name, pojomatorClass),
79          MethodType.methodType(methodType.returnType(), Object.class)));
80    }
81  
82    /**
83     * Compare two values of static type Object for equality. If both values are arrays, then they will be considered
84     * equal iff they have the same class, and (recursively) an equal set of elements.
85     * @param instanceValue the first value to compare
86     * @param otherValue the second value to compare
87     * @return true if {@code instanceValue} and {@code otherValue} are equal to each other.
88     */
89    protected static boolean areObjectValuesEqual(Object instanceValue, Object otherValue) {
90      if (instanceValue == otherValue) {
91        return true;
92      }
93      if (instanceValue == null || otherValue == null) {
94        return false;
95      }
96      else {
97        if (!instanceValue.getClass().isArray()) {
98          if (!instanceValue.equals(otherValue)) {
99            return false;
100         }
101       }
102       else {
103         return compareArrays(instanceValue, otherValue);
104       }
105     }
106     return true;
107   }
108 
109   /**
110    * Compare two values of array type for equality. They will be considered
111    * equal iff they have the same class, and (recursively) an equal set of elements.
112    * @param instanceValue the first value to compare
113    * @param otherValue the second value to compare
114    * @return true if {@code instanceValue} and {@code otherValue} are equal to each other.
115    */
116   protected static boolean compareArrays(Object instanceValue, Object otherValue) {
117     if (instanceValue == otherValue) {
118       return true;
119     }
120     if (instanceValue == null || otherValue == null) {
121       return false;
122     }
123     if (!instanceValue.getClass().equals(otherValue.getClass())) {
124       return false;
125     }
126     final Class<?> instanceComponentClass = instanceValue.getClass().getComponentType();
127 
128     if (!instanceComponentClass.isPrimitive()) {
129       return Arrays.deepEquals((Object[]) instanceValue, (Object[]) otherValue);
130     }
131     else { // instanceComponentClass is primitive
132       if (Boolean.TYPE == instanceComponentClass) {
133         return Arrays.equals((boolean[]) instanceValue, (boolean[]) otherValue);
134       }
135       else if (Byte.TYPE == instanceComponentClass) {
136         return Arrays.equals((byte[]) instanceValue, (byte[]) otherValue);
137       }
138       else if (Character.TYPE == instanceComponentClass) {
139         return Arrays.equals((char[]) instanceValue, (char[]) otherValue);
140       }
141       else if (Short.TYPE == instanceComponentClass) {
142         return Arrays.equals((short[]) instanceValue, (short[]) otherValue);
143       }
144       else if (Integer.TYPE == instanceComponentClass) {
145         return Arrays.equals((int[]) instanceValue, (int[]) otherValue);
146       }
147       else if (Long.TYPE == instanceComponentClass) {
148         return Arrays.equals((long[]) instanceValue, (long[]) otherValue);
149       }
150       else if (Float.TYPE == instanceComponentClass) {
151         return Arrays.equals((float[]) instanceValue, (float[]) otherValue);
152       }
153       else if (Double.TYPE == instanceComponentClass) {
154         return Arrays.equals((double[]) instanceValue, (double[]) otherValue);
155       }
156       else {
157         // should NEVER happen
158         throw new IllegalStateException(
159           "unknown primitive type " + instanceComponentClass.getName());
160       }
161     }
162   }
163 
164 
165   /**
166    * Given an object which is of array type, compute it's hashCode by calling the appropriate signature of
167    * {@link Arrays}{@code .hashCode()}
168    * @param array the array
169    * @param deepArray whether to do a deep hashCode for Object arrays.
170    * @return the hashCode
171    */
172   protected static int arrayHashCode(Object array, boolean deepArray) {
173     Class<?> componentType = array.getClass().getComponentType();
174     if (! componentType.isPrimitive()) {
175       return deepArray ? Arrays.deepHashCode((Object[]) array) : Arrays.hashCode((Object[]) array);
176     }
177     if (componentType == boolean.class) {
178       return Arrays.hashCode((boolean[]) array);
179     }
180     if (componentType == byte.class) {
181       return Arrays.hashCode((byte[]) array);
182     }
183     if (componentType == char.class) {
184       return Arrays.hashCode((char[]) array);
185     }
186     if (componentType == short.class) {
187       return Arrays.hashCode((short[]) array);
188     }
189     if (componentType == int.class) {
190       return Arrays.hashCode((int[]) array);
191     }
192     if (componentType == long.class) {
193       return Arrays.hashCode((long[]) array);
194     }
195     if (componentType == float.class) {
196       return Arrays.hashCode((float[]) array);
197     }
198     if (componentType == double.class) {
199       return Arrays.hashCode((double[]) array);
200     }
201     throw new IllegalStateException("unknown primitive type " + componentType.getName());
202   }
203 
204   protected static <T> T checkNotNull(T reference) {
205     if (reference == null) {
206       throw new NullPointerException();
207     }
208     return reference;
209   }
210 
211   protected static <T> T checkNotNull(T reference, String message) {
212     if (reference == null) {
213       throw new NullPointerException(message);
214     }
215     return reference;
216   }
217 
218   protected static void checkNotNullPop(Object reference) {
219     if (reference == null) {
220       throw new NullPointerException();
221     }
222   }
223 
224   protected void checkCompatibleForEquality(T instance, String label) {
225     if (!isCompatibleForEquality(instance.getClass())) {
226       throw new IllegalArgumentException(
227         label + " has type " + instance.getClass().getName()
228         + " which is not compatible for equality with " + pojoClass.getName());
229     }
230   }
231 
232   /**
233    * Get a method handle to access a field or invoke a no-arg method.
234    * @param caller A Lookup from the original call site.
235    * @param name the name of the dynamic method. This should be of the form "get_xxx", where "element_xxx" will be a
236    * static field containing a {@link PropertyElement} instance referring to the property to be accessed.
237    * @param pojomatorClass the type of the pojomator class
238    * @return the MethodHandle
239    * @throws Throwable
240    */
241   private static MethodHandle getTypedMethod(
242     final MethodHandles.Lookup caller, final String name, final Class<?> pojomatorClass)
243     throws Throwable {
244     try {
245       return AccessController.doPrivileged(new PrivilegedExceptionAction<MethodHandle>() {
246         @Override
247         public MethodHandle run() throws Exception {
248           return getTypedMethodPrivileged(caller, name, pojomatorClass);
249         }
250       });
251     } catch (PrivilegedActionException e) {
252       throw e.getCause();
253     }
254   }
255 
256   /**
257    * Do the work for {@link #getTypedMethod(java.lang.invoke.MethodHandles.Lookup, String, Class)}. This method will be
258    * run inside of a {@link AccessController#doPrivileged(PrivilegedExceptionAction)} block, hence should make sure to
259    * not run untrusted code.
260    * @param pojomatorClass the type of the pojomator class
261    * @return the MethodHandle
262    * @throws NoSuchFieldException
263    * @throws IllegalAccessException
264    */
265   private static MethodHandle getTypedMethodPrivileged(
266     MethodHandles.Lookup caller, String name, Class<?> pojomatorClass)
267     throws NoSuchFieldException, IllegalAccessException {
268     String elementName = "element_" + name.substring(4);
269     Field elementField = pojomatorClass.getDeclaredField(elementName);
270     elementField.setAccessible(true);
271     PropertyElement property = (PropertyElement) elementField.get(null);
272     AnnotatedElement element = property.getElement();
273     // Note that while element is a reference to untrusted code, we do not actually invoke this code inside a
274     // doPrivileged block - we merely make it accessible to be invoked later, outside of a doPriviliged block
275     if (element instanceof Field) {
276       Field field = (Field) element;
277       field.setAccessible(true);
278       return caller.unreflectGetter(field);
279     }
280     else if (element instanceof Method) {
281       Method method = (Method) element;
282       method.setAccessible(true);
283       return caller.unreflect(method);
284     }
285     else {
286       throw new IllegalArgumentException("Cannot handle element of type " + element.getClass().getName());
287     }
288   }
289 
290 }