001/** 
002 * Copyright (c) 2007-2008, Regents of the University of Colorado 
003 * All rights reserved.
004 * 
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions are met:
007 * 
008 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
009 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
010 * Neither the name of the University of Colorado at Boulder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 
011 * 
012 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
013 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
014 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
015 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
016 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
017 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
018 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
019 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
020 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
021 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
022 * POSSIBILITY OF SUCH DAMAGE. 
023 */
024package org.cleartk.util;
025
026import java.lang.reflect.InvocationTargetException;
027import java.lang.reflect.ParameterizedType;
028import java.lang.reflect.Type;
029import java.lang.reflect.TypeVariable;
030import java.util.Map;
031import java.util.TreeMap;
032
033import org.apache.uima.resource.ResourceInitializationException;
034
035/**
036 * <br>
037 * Copyright (c) 2007-2008, Regents of the University of Colorado <br>
038 * All rights reserved.
039 */
040public class ReflectionUtil {
041
042  /**
043   * Perform an unchecked cast based on a type parameter.
044   * 
045   * @param <T>
046   *          The type to which the object should be cast.
047   * @param o
048   *          The object.
049   * @return The object, cast to the given type.
050   */
051  @SuppressWarnings("unchecked")
052  public static <T> T uncheckedCast(Object o) {
053    return (T) o;
054  }
055
056  public static interface TypeArgumentDelegator {
057    public Map<String, Type> getTypeArguments(Class<?> genericType);
058  }
059
060  public static <T> Type getTypeArgument(Class<T> genericType, String typeParameterName, T obj) {
061    Map<String, Type> typeArguments = getTypeArguments(genericType, obj);
062    return typeArguments == null ? null : typeArguments.get(typeParameterName);
063  }
064
065  /**
066   * Try to find the instantiation of all of genericTypes type parameters in objs class.
067   * 
068   * @param genericType
069   *          the generic supertype of objs class
070   * @param obj
071   *          an instantiation of a subclass of genericType. All of genericTypes type parameters
072   *          must have been instantiated in the inheritance hierarchy.
073   * @return a map of genericTypes type parameters (their name in the source code) to the type they
074   *         are instantiated as in obj
075   */
076  public static Map<String, Type> getTypeArguments(Class<?> genericType, Object obj) {
077    if (obj instanceof TypeArgumentDelegator) {
078      return ((TypeArgumentDelegator) obj).getTypeArguments(genericType);
079    }
080    Map<String, Type> typeMap = new TreeMap<String, Type>();
081    return getTypeArguments(genericType, obj.getClass(), typeMap);
082  }
083
084  public static boolean isAssignableFrom(Type type1, Type type2) {
085    if (type1 instanceof Class<?> && type2 instanceof Class<?>) {
086      return ((Class<?>) type1).isAssignableFrom((Class<?>) type2);
087    } else {
088      return type1.equals(type2);
089    }
090  }
091
092  private static Map<String, Type> getTypeArguments(
093      Class<?> genericType,
094      Type type,
095      Map<String, Type> typeMap) {
096    if (type instanceof ParameterizedType) {
097      return getTypeArguments(genericType, (ParameterizedType) type, typeMap);
098    } else if (type instanceof Class<?>) {
099      return getTypeArguments(genericType, (Class<?>) type, typeMap);
100    } else {
101      throw new IllegalArgumentException("type must be a ParameterizedType or Class");
102    }
103  }
104
105  private static Map<String, Type> getTypeArguments(
106      Class<?> genericType,
107      Class<?> classType,
108      Map<String, Type> typeMap) {
109    if (genericType.isInterface()) {
110      for (Type interfaceType : classType.getGenericInterfaces()) {
111        Map<String, Type> result = getTypeArguments(genericType, interfaceType, typeMap);
112        if (result != null)
113          return result;
114      }
115    }
116
117    Type superType = classType.getGenericSuperclass();
118    if (superType != null) {
119      return getTypeArguments(genericType, superType, typeMap);
120    }
121
122    return null;
123  }
124
125  private static Map<String, Type> getTypeArguments(
126      Class<?> genericType,
127      ParameterizedType paramType,
128      Map<String, Type> typeMap) {
129    Class<?> rawType = (Class<?>) paramType.getRawType();
130    if (rawType == genericType) {
131      // found it!
132      TypeVariable<?> typeVars[] = rawType.getTypeParameters();
133      Type actualTypes[] = paramType.getActualTypeArguments();
134      Map<String, Type> result = new TreeMap<String, Type>();
135      for (int i = 0; i < actualTypes.length; i++) {
136        while (actualTypes[i] != null && actualTypes[i] instanceof TypeVariable<?>) {
137          String key = typevarString((TypeVariable<?>) actualTypes[i]);
138          if (typeMap.containsKey(key))
139            actualTypes[i] = typeMap.get(key);
140          else
141            actualTypes[i] = null;
142        }
143        result.put(typeVars[i].getName(), actualTypes[i]);
144      }
145      return result;
146    } else {
147      TypeVariable<?> typeVars[] = rawType.getTypeParameters();
148      Type actualTypes[] = paramType.getActualTypeArguments();
149      for (int i = 0; i < typeVars.length; i++)
150        typeMap.put(typevarString(typeVars[i]), actualTypes[i]);
151      return getTypeArguments(genericType, paramType.getRawType(), typeMap);
152    }
153  }
154
155  private static String typevarString(TypeVariable<?> tv) {
156    return tv.getGenericDeclaration().toString() + " " + tv.getName();
157  }
158
159  /**
160   * Checks that the given type parameters of the given objects are compatible.
161   * 
162   * Type parameters are identified by providing the class in which the type parameter is defined,
163   * and the declared name of the type parameter.
164   * 
165   * Throws a ResourceInitializationException if the type parameters are not compatible.
166   * 
167   * @param <T>
168   *          Type of the class declaring the first type parameter
169   * @param <U>
170   *          Type of the class declaring the second type parameter
171   * @param paramDefiningClass1
172   *          The class declaring the first type parameter
173   * @param paramName1
174   *          The declared name of the first type parameter
175   * @param object1
176   *          The target object
177   * @param paramDefiningClass2
178   *          The class declaring the second type parameter
179   * @param paramName2
180   *          The declared name of the second type parameter
181   * @param object2
182   *          The source object
183   */
184  public static <T, U> void checkTypeParameterIsAssignable(
185      Class<T> paramDefiningClass1,
186      String paramName1,
187      T object1,
188      Class<U> paramDefiningClass2,
189      String paramName2,
190      U object2) throws ResourceInitializationException {
191
192    // get the type arguments from the objects
193    java.lang.reflect.Type type1 = ReflectionUtil.getTypeArgument(
194        paramDefiningClass1,
195        paramName1,
196        object1);
197    java.lang.reflect.Type type2 = ReflectionUtil.getTypeArgument(
198        paramDefiningClass2,
199        paramName2,
200        object2);
201
202    // both arguments missing is compatible
203    if (type1 == null && type2 == null) {
204      return;
205    }
206
207    // if the second type is not assignable to the first, raise an exception
208    if (type1 == null || type2 == null || !ReflectionUtil.isAssignableFrom(type1, type2)) {
209      throw CleartkInitializationException.incompatibleTypeParameters(
210          object1,
211          paramName1,
212          type1,
213          object2,
214          paramName2,
215          type2);
216    }
217  }
218
219  /**
220   * Checks that the given type parameters of the given objects are exactly equal.
221   * 
222   * Type parameters are identified by providing the class in which the type parameter is defined,
223   * and the declared name of the type parameter.
224   * 
225   * Throws an instance of the given exception class if type parameters are not exactly equal.
226   * 
227   * @param <T>
228   *          Type of the class declaring the first type parameter
229   * @param <U>
230   *          Type of the class declaring the second type parameter
231   * @param paramDefiningClass1
232   *          The class declaring the first type parameter
233   * @param paramName1
234   *          The declared name of the first type parameter
235   * @param object1
236   *          The target object
237   * @param paramDefiningClass2
238   *          The class declaring the second type parameter
239   * @param paramName2
240   *          The declared name of the second type parameter
241   * @param object2
242   *          The source object
243   */
244  public static <T, U, E extends Exception> void checkTypeParametersAreEqual(
245      Class<T> paramDefiningClass1,
246      String paramName1,
247      T object1,
248      Class<U> paramDefiningClass2,
249      String paramName2,
250      U object2,
251      Class<E> exceptionClass) throws E {
252
253    // get the type arguments from the objects
254    java.lang.reflect.Type type1 = ReflectionUtil.getTypeArgument(
255        paramDefiningClass1,
256        paramName1,
257        object1);
258    java.lang.reflect.Type type2 = ReflectionUtil.getTypeArgument(
259        paramDefiningClass2,
260        paramName2,
261        object2);
262
263    // both arguments missing is equal
264    if (type1 == null && type2 == null) {
265      return;
266    }
267
268    // if the second type is not equal to the first, raise an exception
269    if (type1 == null || type2 == null || !type1.equals(type2)) {
270      try {
271        throw exceptionClass.getConstructor(String.class).newInstance(
272            String.format(
273                "%s with %s %s is not equal to %s with %s %s",
274                object1.getClass().getSimpleName(),
275                paramName1,
276                type1,
277                object2.getClass().getSimpleName(),
278                paramName2,
279                type2));
280      } catch (InstantiationException e) {
281        throw new RuntimeException(e);
282      } catch (IllegalAccessException e) {
283        throw new RuntimeException(e);
284      } catch (InvocationTargetException e) {
285        throw new RuntimeException(e);
286      } catch (NoSuchMethodException e) {
287        throw new RuntimeException(e);
288      }
289    }
290  }
291
292}