View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.common.util;
20  
21  import java.lang.reflect.Constructor;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.lang.reflect.ParameterizedType;
25  import java.lang.reflect.Type;
26  import java.lang.reflect.TypeVariable;
27  
28  public class ReflectUtils
29  {
30      private static class GenericRef
31      {
32          // The base class reference lookup started from
33          private final Class<?> baseClass;
34          // The interface that we are interested in
35          private final Class<?> ifaceClass;
36  
37          // The actual class generic interface was found on
38          Class<?> genericClass;
39  
40          // The found genericType
41          public Type genericType;
42          private int genericIndex;
43  
44          public GenericRef(final Class<?> baseClass, final Class<?> ifaceClass)
45          {
46              this.baseClass = baseClass;
47              this.ifaceClass = ifaceClass;
48          }
49  
50          public boolean needsUnwrap()
51          {
52              return (genericClass == null) && (genericType != null) && (genericType instanceof TypeVariable<?>);
53          }
54  
55          public void setGenericFromType(Type type, int index)
56          {
57              // debug("setGenericFromType(%s,%d)",toShortName(type),index);
58              this.genericType = type;
59              this.genericIndex = index;
60              if (type instanceof Class)
61              {
62                  this.genericClass = (Class<?>)type;
63              }
64          }
65  
66          @Override
67          public String toString()
68          {
69              StringBuilder builder = new StringBuilder();
70              builder.append("GenericRef [baseClass=");
71              builder.append(baseClass);
72              builder.append(", ifaceClass=");
73              builder.append(ifaceClass);
74              builder.append(", genericType=");
75              builder.append(genericType);
76              builder.append(", genericClass=");
77              builder.append(genericClass);
78              builder.append("]");
79              return builder.toString();
80          }
81      }
82  
83      private static StringBuilder appendTypeName(StringBuilder sb, Type type, boolean ellipses)
84      {
85          if (type instanceof Class<?>)
86          {
87              Class<?> ctype = (Class<?>)type;
88              if (ctype.isArray())
89              {
90                  try
91                  {
92                      int dimensions = 0;
93                      while (ctype.isArray())
94                      {
95                          dimensions++;
96                          ctype = ctype.getComponentType();
97                      }
98                      sb.append(ctype.getName());
99                      for (int i = 0; i < dimensions; i++)
100                     {
101                         if (ellipses)
102                         {
103                             sb.append("...");
104                         }
105                         else
106                         {
107                             sb.append("[]");
108                         }
109                     }
110                     return sb;
111                 }
112                 catch (Throwable ignore)
113                 {
114                     // ignore
115                 }
116             }
117 
118             sb.append(ctype.getName());
119         }
120         else
121         {
122             sb.append(type.toString());
123         }
124 
125         return sb;
126     }
127 
128     /**
129      * Given a Base (concrete) Class, find the interface specified, and return its concrete Generic class declaration.
130      * 
131      * @param baseClass
132      *            the base (concrete) class to look in
133      * @param ifaceClass
134      *            the interface of interest
135      * @return the (concrete) generic class that the interface exposes
136      */
137     public static Class<?> findGenericClassFor(Class<?> baseClass, Class<?> ifaceClass)
138     {
139         GenericRef ref = new GenericRef(baseClass,ifaceClass);
140         if (resolveGenericRef(ref,baseClass))
141         {
142             // debug("Generic Found: %s",ref.genericClass);
143             return ref.genericClass;
144         }
145 
146         // debug("Generic not found: %s",ref);
147         return null;
148     }
149 
150     private static int findTypeParameterIndex(Class<?> clazz, TypeVariable<?> needVar)
151     {
152         // debug("findTypeParameterIndex(%s, [%s])",toShortName(clazz),toShortName(needVar));
153         TypeVariable<?> params[] = clazz.getTypeParameters();
154         for (int i = 0; i < params.length; i++)
155         {
156             if (params[i].getName().equals(needVar.getName()))
157             {
158                 // debug("Type Parameter found at index: [%d]",i);
159                 return i;
160             }
161         }
162         // debug("Type Parameter NOT found");
163         return -1;
164     }
165 
166     public static boolean isDefaultConstructable(Class<?> clazz)
167     {
168         int mods = clazz.getModifiers();
169         if (Modifier.isAbstract(mods) || !Modifier.isPublic(mods))
170         {
171             // Needs to be public, non-abstract
172             return false;
173         }
174 
175         Class<?>[] noargs = new Class<?>[0];
176         try
177         {
178             // Needs to have a no-args constructor
179             Constructor<?> constructor = clazz.getConstructor(noargs);
180             // Constructor needs to be public
181             return Modifier.isPublic(constructor.getModifiers());
182         }
183         catch (NoSuchMethodException | SecurityException e)
184         {
185             return false;
186         }
187     }
188 
189     private static boolean resolveGenericRef(GenericRef ref, Class<?> clazz, Type type)
190     {
191         if (type instanceof Class)
192         {
193             if (type == ref.ifaceClass)
194             {
195                 // is this a straight ref or a TypeVariable?
196                 // debug("Found ref (as class): %s",toShortName(type));
197                 ref.setGenericFromType(type,0);
198                 return true;
199             }
200             else
201             {
202                 // Keep digging
203                 return resolveGenericRef(ref,type);
204             }
205         }
206 
207         if (type instanceof ParameterizedType)
208         {
209             ParameterizedType ptype = (ParameterizedType)type;
210             Type rawType = ptype.getRawType();
211             if (rawType == ref.ifaceClass)
212             {
213                 // debug("Found ref on [%s] as ParameterizedType [%s]",toShortName(clazz),toShortName(ptype));
214                 // Always get the raw type parameter, let unwrap() solve for what it is
215                 ref.setGenericFromType(ptype.getActualTypeArguments()[0],0);
216                 return true;
217             }
218             else
219             {
220                 // Keep digging
221                 return resolveGenericRef(ref,rawType);
222             }
223         }
224         return false;
225     }
226 
227     private static boolean resolveGenericRef(GenericRef ref, Type type)
228     {
229         if ((type == null) || (type == Object.class))
230         {
231             return false;
232         }
233 
234         if (type instanceof Class)
235         {
236             Class<?> clazz = (Class<?>)type;
237             // prevent spinning off into Serialization and other parts of the
238             // standard tree that we could care less about
239             if (clazz.getName().matches("^javax*\\..*"))
240             {
241                 return false;
242             }
243 
244             Type ifaces[] = clazz.getGenericInterfaces();
245             for (Type iface : ifaces)
246             {
247                 // debug("resolve %s interface[]: %s",toShortName(clazz),toShortName(iface));
248                 if (resolveGenericRef(ref,clazz,iface))
249                 {
250                     if (ref.needsUnwrap())
251                     {
252                         // debug("## Unwrap class %s::%s",toShortName(clazz),toShortName(iface));
253                         TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
254                         // debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
255 
256                         // attempt to find typeParameter on class itself
257                         int typeParamIdx = findTypeParameterIndex(clazz,needVar);
258                         // debug("type param index for %s[%s] is [%d]",toShortName(clazz),toShortName(needVar),typeParamIdx);
259 
260                         if (typeParamIdx >= 0)
261                         {
262                             // found a type parameter, use it
263                             // debug("unwrap from class [%s] - typeParameters[%d]",toShortName(clazz),typeParamIdx);
264                             TypeVariable<?> params[] = clazz.getTypeParameters();
265                             if (params.length >= typeParamIdx)
266                             {
267                                 ref.setGenericFromType(params[typeParamIdx],typeParamIdx);
268                             }
269                         }
270                         else if (iface instanceof ParameterizedType)
271                         {
272                             // use actual args on interface
273                             Type arg = ((ParameterizedType)iface).getActualTypeArguments()[ref.genericIndex];
274                             ref.setGenericFromType(arg,ref.genericIndex);
275                         }
276                     }
277                     return true;
278                 }
279             }
280 
281             type = clazz.getGenericSuperclass();
282             return resolveGenericRef(ref,type);
283         }
284 
285         if (type instanceof ParameterizedType)
286         {
287             ParameterizedType ptype = (ParameterizedType)type;
288             Class<?> rawClass = (Class<?>)ptype.getRawType();
289             if (resolveGenericRef(ref,rawClass))
290             {
291                 if (ref.needsUnwrap())
292                 {
293                     // debug("## Unwrap ParameterizedType %s::%s",toShortName(type),toShortName(rawClass));
294                     TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
295                     // debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
296                     int typeParamIdx = findTypeParameterIndex(rawClass,needVar);
297                     // debug("type paramIdx of %s::%s is index [%d]",toShortName(rawClass),toShortName(needVar),typeParamIdx);
298 
299                     Type arg = ptype.getActualTypeArguments()[typeParamIdx];
300                     ref.setGenericFromType(arg,typeParamIdx);
301                     return true;
302                 }
303             }
304         }
305 
306         return false;
307     }
308 
309     public static String toShortName(Type type)
310     {
311         if (type == null)
312         {
313             return "<null>";
314         }
315 
316         if (type instanceof Class)
317         {
318             String name = ((Class<?>)type).getName();
319             return trimClassName(name);
320         }
321 
322         if (type instanceof ParameterizedType)
323         {
324             ParameterizedType ptype = (ParameterizedType)type;
325             StringBuilder str = new StringBuilder();
326             str.append(trimClassName(((Class<?>)ptype.getRawType()).getName()));
327             str.append("<");
328             Type args[] = ptype.getActualTypeArguments();
329             for (int i = 0; i < args.length; i++)
330             {
331                 if (i > 0)
332                 {
333                     str.append(",");
334                 }
335                 str.append(args[i]);
336             }
337             str.append(">");
338             return str.toString();
339         }
340 
341         return type.toString();
342     }
343 
344     public static String toString(Class<?> pojo, Method method)
345     {
346         StringBuilder str = new StringBuilder();
347 
348         // method modifiers
349         int mod = method.getModifiers() & Modifier.methodModifiers();
350         if (mod != 0)
351         {
352             str.append(Modifier.toString(mod)).append(' ');
353         }
354 
355         // return type
356         Type retType = method.getGenericReturnType();
357         appendTypeName(str,retType,false).append(' ');
358 
359         // class name
360         str.append(pojo.getName());
361         str.append("#");
362 
363         // method name
364         str.append(method.getName());
365 
366         // method parameters
367         str.append('(');
368         Type[] params = method.getGenericParameterTypes();
369         for (int j = 0; j < params.length; j++)
370         {
371             boolean ellipses = method.isVarArgs() && (j == (params.length - 1));
372             appendTypeName(str,params[j],ellipses);
373             if (j < (params.length - 1))
374             {
375                 str.append(", ");
376             }
377         }
378         str.append(')');
379 
380         // TODO: show exceptions?
381         return str.toString();
382     }
383 
384     public static String trimClassName(String name)
385     {
386         int idx = name.lastIndexOf('.');
387         name = name.substring(idx + 1);
388         idx = name.lastIndexOf('$');
389         if (idx >= 0)
390         {
391             name = name.substring(idx + 1);
392         }
393         return name;
394     }
395 }