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