View Javadoc

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