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
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 }