-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Types.java
292 lines (251 loc) · 10.6 KB
/
Types.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/**
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.utils;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.sundr.adapter.api.AdapterContext;
import io.sundr.adapter.api.Adapters;
import io.sundr.adapter.apt.AptAdapter;
import io.sundr.adapter.apt.AptContext;
import io.sundr.builder.TypedVisitor;
import io.sundr.model.*;
import io.sundr.model.functions.GetDefinition;
import io.sundr.model.repo.DefinitionRepository;
import io.sundr.model.visitors.ApplyTypeParamMappingToMethod;
import io.sundr.model.visitors.ApplyTypeParamMappingToProperty;
import io.sundr.model.visitors.ApplyTypeParamMappingToTypeArguments;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Types {
private Types() {
throw new IllegalStateException("Utility class");
}
private static final Logger LOGGER = LoggerFactory.getLogger(Types.class);
private static final String NAMESPACED = Namespaced.class.getName();
public static final String JAVA_LANG_VOID = "java.lang.Void";
public static final AdapterContext REFLECTION_CONTEXT = AdapterContext.create(DefinitionRepository.getRepository());
/**
* Make sure the generation context is reset so that types can be properly introspected if classes have changed since the last generation round.
*/
public static void resetGenerationContext() {
DefinitionRepository.getRepository().clear();
}
public static TypeDef typeDefFrom(Class<?> clazz) {
return unshallow(Adapters.adaptType(clazz, REFLECTION_CONTEXT));
}
public static TypeDef unshallow(TypeDef definition) {
// resolve hierarchy
final List<ClassRef> classRefs = new ArrayList<>(Types.projectSuperClasses(definition));
// resolve properties
final List<Property> properties = Types.projectProperties(definition);
// re-create TypeDef with all the needed information
return new TypeDef(definition.getKind(), definition.getPackageName(),
definition.getName(), definition.getComments(), definition.getAnnotations(), classRefs,
definition.getImplementsList(), definition.getParameters(), properties,
definition.getConstructors(), definition.getMethods(), definition.getOuterTypeName(),
definition.getInnerTypes(), definition.getModifiers(), definition.getAttributes());
}
public static TypeDef typeDefFrom(ClassRef classRef) {
return unshallow(GetDefinition.of(classRef));
}
private static TypeDef projectDefinition(ClassRef ref) {
List<TypeRef> arguments = ref.getArguments();
TypeDef definition = GetDefinition.of(ref);
if (arguments.isEmpty()) {
return definition;
}
List<TypeParamDef> parameters = definition.getParameters();
if (parameters.size() != arguments.size()) {
throw new IllegalStateException("Incompatible reference " + ref + " to " + definition);
}
Map<String, TypeRef> mappings = new HashMap<>();
for (int i = 0; i < arguments.size(); i++) {
String name = parameters.get(i).getName();
TypeRef typeRef = arguments.get(i);
mappings.put(name, typeRef);
}
return new TypeDefBuilder(definition)
.accept(new ApplyTypeParamMappingToTypeArguments(mappings)) // existing type arguments must be handled before methods and properties
.accept(new ApplyTypeParamMappingToProperty(mappings),
new ApplyTypeParamMappingToMethod(mappings))
.build();
}
private static Set<ClassRef> projectSuperClasses(TypeDef definition) {
List<ClassRef> extendsList = definition.getExtendsList();
return extendsList.stream()
.flatMap(s -> Stream.concat(Stream.of(s), projectDefinition(s).getExtendsList().stream()))
.collect(Collectors.toSet());
}
/**
* All non-static properties (including inherited).
* @param typeDef The type.
* @return A list with all properties.
*/
private static List<Property> projectProperties(TypeDef typeDef) {
final String fqn = typeDef.getFullyQualifiedName();
return Stream.concat(
typeDef.getProperties().stream().filter(p -> {
// enums have self-referential static properties for each enum case so we cannot ignore them
if (typeDef.isEnum()) {
final TypeRef typeRef = p.getTypeRef();
if (typeRef instanceof ClassRef && fqn.equals(((ClassRef) typeRef).getFullyQualifiedName())) {
// we're dealing with an enum case so keep it
return true;
}
}
// otherwise exclude all static properties
return !p.isStatic();
}),
typeDef.getExtendsList().stream()
.filter(e -> !e.getFullyQualifiedName().startsWith("java."))
.flatMap(e -> projectProperties(projectDefinition(e))
.stream()
.filter(p -> filterCustomResourceProperties(e).test(p)))
)
.collect(Collectors.toList());
}
private static Predicate<Property> filterCustomResourceProperties(ClassRef ref) {
return p -> !p.isStatic() &&
(!ref.getFullyQualifiedName().equals(CUSTOM_RESOURCE_NAME) ||
(p.getName().equals("spec") || p.getName().equals("status")));
}
public static void output(TypeDef def) {
final StringBuilder builder = new StringBuilder(300);
Types.describeType(def, "", new HashSet<>(), builder);
LOGGER.debug("\n{}", builder);
}
public static void describeType(TypeDef def, String indent, Set<String> visited, StringBuilder builder) {
if (visited.isEmpty()) {
builder.append(indent).append(def.getFullyQualifiedName()).append("\n");
}
visited.add(def.getFullyQualifiedName());
for (Property property : def.getProperties()) {
TypeRef typeRef = property.getTypeRef();
if (typeRef instanceof ClassRef) {
final TypeDef typeDef = typeDefFrom((ClassRef) typeRef);
builder.append(indent).append("\t").append(property).append(" - ClassRef [").append(typeDef.getKind()).append("]\n");
if (!visited.contains(typeDef.getFullyQualifiedName())) {
describeType(typeDef, indent + "\t", visited, builder);
}
} else {
final String type;
if (typeRef instanceof PrimitiveRef) {
type = "PrimitiveRef";
} else if (typeRef instanceof TypeParamRef) {
type = "TypeParamRef";
} else if (typeRef instanceof VoidRef) {
type = "VoidRef";
} else if (typeRef instanceof WildcardRef) {
type = "WildcardRef";
} else {
type = "Unknown";
}
builder.append(indent).append("\t").append(property).append(" - ").append(type).append("\n");
}
}
visited.remove(def.getFullyQualifiedName());
}
public static class SpecAndStatus {
private static final SpecAndStatus UNRESOLVED = new SpecAndStatus(null, null);
private final String specClassName;
private final String statusClassName;
private final boolean unreliable;
public SpecAndStatus(String specClassName, String statusClassName) {
this.specClassName = specClassName;
this.statusClassName = statusClassName;
this.unreliable = specClassName == null || statusClassName == null;
}
public String getSpecClassName() {
return specClassName;
}
public String getStatusClassName() {
return statusClassName;
}
public boolean isUnreliable() {
return unreliable;
}
}
private static final String CUSTOM_RESOURCE_NAME = CustomResource.class.getCanonicalName();
public static SpecAndStatus resolveSpecAndStatusTypes(TypeDef definition) {
Optional<ClassRef> optionalCustomResourceRef = definition.getExtendsList().stream()
.filter(s -> s.getFullyQualifiedName().equals(CUSTOM_RESOURCE_NAME)).findFirst();
if (optionalCustomResourceRef.isPresent()) {
ClassRef customResourceRef = optionalCustomResourceRef.get();
List<TypeRef> arguments = customResourceRef.getArguments();
if (arguments.size() == 2) {
String specClassName = getClassFQNIfNotVoid(arguments.get(0));
String statusClassName = getClassFQNIfNotVoid(arguments.get(1));
return new SpecAndStatus(specClassName, statusClassName);
}
}
return SpecAndStatus.UNRESOLVED;
}
public static boolean isNamespaced(TypeDef definition) {
return isNamespaced(definition, new HashSet<>());
}
public static boolean isNamespaced(TypeDef definition, Set<TypeDef> visited) {
if (definition.getFullyQualifiedName().equals(NAMESPACED)) {
return true;
}
if (visited.contains(definition) || definition.getPackageName().startsWith("java.")) {
return false;
}
Set<TypeDef> newVisited = new HashSet<>(visited);
newVisited.add(definition);
for (ClassRef i : definition.getImplementsList()) {
if (isNamespaced(GetDefinition.of(i), newVisited)) {
return true;
}
}
for (ClassRef e : definition.getExtendsList()) {
if (isNamespaced(GetDefinition.of(e), newVisited)) {
return true;
}
}
return false;
}
/**
* Finds the status property. The method look up for a status property.
*
* @param typeDef the type whose status property we want to find
* @return the an optional property.
*/
public static Optional<Property> findStatusProperty(TypeDef typeDef) {
return typeDef.getProperties().stream()
.filter(Types::isStatusProperty)
.findFirst();
}
/**
* Returns true if the specified property corresponds to status.
* A property qualifies as `status` if it is called `status`.
* @param property the property we want to check
* @return {@code true} if named {@code status}, {@code false} otherwise
*/
public static boolean isStatusProperty(Property property) {
return "status".equals(property.getName()) && getClassFQNIfNotVoid(property.getTypeRef()) != null;
}
private static String getClassFQNIfNotVoid(TypeRef typeRef) {
if (!(typeRef instanceof ClassRef)) {
return null;
}
String fullyQualifiedName = ((ClassRef) typeRef).getFullyQualifiedName();
return JAVA_LANG_VOID.equals(fullyQualifiedName) ? null : fullyQualifiedName;
}
}