View Javadoc

1   package com.atlassian.plugins.rest.doclet.generators.schema;
2   
3   import com.google.common.base.Function;
4   import com.google.common.base.Objects;
5   import com.google.common.base.Preconditions;
6   import com.google.common.collect.ImmutableList;
7   import com.google.common.collect.Iterables;
8   import com.google.common.collect.Maps;
9   
10  import java.lang.reflect.ParameterizedType;
11  import java.lang.reflect.Type;
12  import java.lang.reflect.TypeVariable;
13  import java.lang.reflect.WildcardType;
14  import java.util.Arrays;
15  import java.util.Collections;
16  import java.util.List;
17  import java.util.Map;
18  
19  /**
20   * This is a wrapper over class/type which provides advanced support for generics.
21   */
22  public class RichClass
23  {
24      private final Class<?> actualClass;
25      private final List<Type> genericTypes;
26      private final Map<String, Type> typeVariablesResolution;
27  
28      private RichClass(final Class<?> actualClass, final List<Type> genericTypes, final Map<String, Type> typeVariablesResolution)
29      {
30          this.actualClass = actualClass;
31          this.genericTypes = genericTypes;
32          this.typeVariablesResolution = Maps.newHashMap(typeVariablesResolution);
33  
34          this.typeVariablesResolution.putAll(typeVariablesResolutionsFromType(actualClass.getGenericSuperclass()));
35          for (Type type : actualClass.getGenericInterfaces())
36          {
37              this.typeVariablesResolution.putAll(typeVariablesResolutionsFromType(type));
38          }
39      }
40  
41      public static RichClass of(final Class<?> actualClass, final Class<?>... genericTypes)
42      {
43          Preconditions.checkArgument(actualClass.getTypeParameters().length == genericTypes.length, actualClass + " has " + actualClass.getTypeParameters().length + " type parameters but " + genericTypes.length + " were provided");
44  
45          Type type = genericTypes.length == 0 ? actualClass : new ParameterizedType() {
46  
47              @Override
48              public java.lang.reflect.Type[] getActualTypeArguments()
49              {
50                  return genericTypes;
51              }
52  
53              @Override
54              public java.lang.reflect.Type getRawType()
55              {
56                  return actualClass;
57              }
58  
59              @Override
60              public java.lang.reflect.Type getOwnerType()
61              {
62                  return null;
63              }
64          };
65  
66          return RichClass.of(type);
67      }
68  
69      public static RichClass of(Type type)
70      {
71          return create(type, Collections.<String, Type>emptyMap());
72      }
73  
74      private static RichClass create(Type type, Map<String, Type> genericTypesMapping)
75      {
76          if (type instanceof Class)
77          {
78              Class<?> clazz = (Class<?>) type;
79              if (clazz.isArray())
80              {
81                  return new RichClass(List.class, Collections.<Type>singletonList(clazz.getComponentType()), Collections.<String, Type>emptyMap());
82              }
83              else
84              {
85                  return new RichClass(clazz, Collections.<Type>emptyList(), Collections.<String, Type>emptyMap());
86              }
87          }
88          else if (type instanceof ParameterizedType)
89          {
90              Class<?> actualClass = (Class<?>) ((ParameterizedType) type).getRawType();
91              List<Type> actualTypeArguments = Arrays.asList(((ParameterizedType) type).getActualTypeArguments());
92              Map<String, Type> typeVariablesResolution = typeVariablesResolution(actualClass.getTypeParameters(), actualTypeArguments);
93              typeVariablesResolution.putAll(genericTypesMapping);
94              return new RichClass(actualClass, actualTypeArguments, typeVariablesResolution);
95          }
96          else if (type instanceof WildcardType)
97          {
98              return create(((WildcardType) type).getUpperBounds()[0], genericTypesMapping);
99          }
100         else if (type instanceof TypeVariable)
101         {
102             Type declaredType = genericTypesMapping.get(((TypeVariable) type).getName());
103             Preconditions.checkState(declaredType != null, "unresolved type variable: " + type);
104             return create(declaredType, genericTypesMapping);
105         }
106         else
107         {
108             throw new IllegalStateException("Unsupported type: " + type);
109         }
110     }
111 
112     public RichClass createContainedType(Type type)
113     {
114         return create(type, typeVariablesResolution);
115     }
116 
117     private static Map<String, Type> typeVariablesResolutionsFromType(Type type)
118     {
119         if (type instanceof ParameterizedType)
120         {
121             Class<?> actualClass = (Class<?>) ((ParameterizedType) type).getRawType();
122             List<Type> actualTypeArguments = Arrays.asList(((ParameterizedType) type).getActualTypeArguments());
123             return typeVariablesResolution(actualClass.getTypeParameters(), actualTypeArguments);
124         }
125         return Collections.emptyMap();
126     }
127 
128     private static <T> Map<String, Type> typeVariablesResolution(final TypeVariable<Class<T>>[] typeParameters, final List<Type> actualTypeArguments)
129     {
130         Map<String, Type> result = Maps.newHashMap();
131         for (int i = 0; i < typeParameters.length; i++)
132         {
133             Type actualType = actualTypeArguments.get(i);
134             if (!(actualType instanceof TypeVariable))
135             {
136                 result.put(typeParameters[i].getName(), actualType);
137             }
138         }
139         return result;
140     }
141 
142     public Class<?> getActualClass()
143     {
144         return actualClass;
145     }
146 
147     public boolean hasGenericType()
148     {
149         return genericTypes.size() > 0;
150     }
151 
152     public List<RichClass> getGenericTypes()
153     {
154         return ImmutableList.copyOf(Iterables.transform(genericTypes, new Function<Type, RichClass>()
155         {
156             @Override
157             public RichClass apply(final Type type)
158             {
159                 return createContainedType(type);
160             }
161         }));
162     }
163 
164     @Override
165     public boolean equals(Object o)
166     {
167         if (this == o) return true;
168         if (o == null || getClass() != o.getClass()) return false;
169 
170         RichClass that = (RichClass) o;
171 
172         return Objects.equal(this.actualClass, that.actualClass) &&
173                 Objects.equal(this.genericTypes, that.genericTypes) &&
174                 Objects.equal(this.typeVariablesResolution, that.typeVariablesResolution);
175     }
176 
177     @Override
178     public int hashCode()
179     {
180         return Objects.hashCode(actualClass, genericTypes, typeVariablesResolution);
181     }
182 
183     @Override
184     public String toString()
185     {
186         return actualClass.getSimpleName() + " " + genericTypes;
187     }
188 }