Skip to content

Commit 9dba3db

Browse files
committed
GH-962 - Fix Javadoc extraction for methods referring to types nested in interfaces.
A bug [0] in APTK causes our Javadoc extraction to fail for methods whose parameters are classes nested in interfaces. We fix that by temporarily including a fixed copy of TypeElementWrapper, that looks up outer types on interfaces as well. [0] toolisticon/aptk#163
1 parent 3b608e9 commit 9dba3db

File tree

3 files changed

+426
-0
lines changed

3 files changed

+426
-0
lines changed
Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
package io.toolisticon.aptk.tools.wrapper;
2+
3+
import io.toolisticon.aptk.tools.ElementUtils;
4+
import io.toolisticon.aptk.tools.TypeMirrorWrapper;
5+
import io.toolisticon.aptk.tools.corematcher.AptkCoreMatchers;
6+
import io.toolisticon.aptk.tools.fluentfilter.FluentElementFilter;
7+
8+
import java.util.Collections;
9+
import java.util.HashSet;
10+
import java.util.List;
11+
import java.util.Optional;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
14+
15+
import javax.lang.model.element.Element;
16+
import javax.lang.model.element.ElementKind;
17+
import javax.lang.model.element.ExecutableElement;
18+
import javax.lang.model.element.Modifier;
19+
import javax.lang.model.element.NestingKind;
20+
import javax.lang.model.element.TypeElement;
21+
import javax.lang.model.element.VariableElement;
22+
import javax.lang.model.type.TypeMirror;
23+
24+
/**
25+
* <em>Copy of the original {@link TypeElementWrapper} of the Toolisticon APTK project located
26+
* <a href="https://github.com/toolisticon/aptk">here</a>. Tweaks {@link #getOuterType()} to include
27+
* {@link ElementKind#INTERFACE} in the outer class lookup to fix the issue reported
28+
* <a href="https://github.com/toolisticon/aptk/issues/163">here</a>.</em> <br />
29+
* Wraps a TypeElement to provide some convenience * functionality
30+
*/
31+
public class TypeElementWrapper extends ElementWrapper<TypeElement> {
32+
33+
private final static String TYPE_ELEMENT_CLASS_NAME = "javax.lang.model.element.TypeElement";
34+
35+
/**
36+
* Hidden constructor.
37+
*
38+
* @param typeElement The TypeElement to wrap
39+
*/
40+
private TypeElementWrapper(TypeElement typeElement) {
41+
super(typeElement);
42+
}
43+
44+
// methods inherited from TypeElement
45+
46+
/**
47+
* Returns the NestingKind.
48+
*
49+
* @return the NestingKind.
50+
*/
51+
public NestingKind getNestingKind() {
52+
return this.element.getNestingKind();
53+
}
54+
55+
public boolean hasNestingKind(NestingKind nestingKind) {
56+
return nestingKind != null && getNestingKind().equals(nestingKind);
57+
}
58+
59+
public boolean isNested() {
60+
return getNestingKind().isNested();
61+
}
62+
63+
/**
64+
* Gets the nested name of the element. If the TypeElement isn't a nested type the simple name will be returned.
65+
*
66+
* @return the nested name of the element
67+
*/
68+
public String getNestedName() {
69+
70+
String name = getSimpleName();
71+
72+
TypeElementWrapper current = this;
73+
74+
while (current.isNested()) {
75+
76+
current = current.getOuterType().get();
77+
name = current.getSimpleName() + "." + name;
78+
79+
}
80+
81+
return name;
82+
}
83+
84+
/**
85+
* Returns the qualified name.
86+
*
87+
* @return the qualified name.
88+
*/
89+
public String getQualifiedName() {
90+
return this.element.getQualifiedName().toString();
91+
}
92+
93+
/**
94+
* Check for qualified name.
95+
*
96+
* @param name the name to check for
97+
* @return true if qualified name matches passed name, otherwise false
98+
*/
99+
public boolean hasQualifiedName(String name) {
100+
return name != null && getQualifiedName().equals(name);
101+
}
102+
103+
/**
104+
* Gets the binary name for the type element.
105+
*/
106+
public String getBinaryName() {
107+
108+
// According to https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html the binary name looks like:
109+
// -- The binary name of a top level type (§7.6) is its canonical name (§6.7).
110+
// -- The binary name of a member type (§8.5, §9.5) consists of the binary name of its immediately enclosing type,
111+
// followed by $, followed by the simple name of the member.
112+
113+
if (this.isNested()) {
114+
// Safe to call since its of nested kind
115+
TypeElementWrapper enclosingTypeElement = this.getOuterType().get();
116+
return enclosingTypeElement.getBinaryName() + "$" + this.getSimpleName();
117+
} else {
118+
return this.getQualifiedName();
119+
}
120+
121+
}
122+
123+
/**
124+
* Gets the direct superclass
125+
*
126+
* @return a TypeMirrorWrapper of the direct superclass
127+
*/
128+
public TypeMirrorWrapper getSuperclass() {
129+
return TypeMirrorWrapper.wrap(this.element.getSuperclass());
130+
}
131+
132+
/**
133+
* Returns list of TypeMirrorWrapper containing all directly implemented interfaces. Doesn't return interfaces
134+
* implemented by superclasses.
135+
*
136+
* @return list of TypeMirrorWrapper containing all directly implemented interfaces
137+
*/
138+
public List<TypeMirrorWrapper> getInterfaces() {
139+
return this.element.getInterfaces().stream().map(TypeMirrorWrapper::wrap).collect(Collectors.toList());
140+
}
141+
142+
/**
143+
* Returns Set of TypeMirrorWrapper containing all implemented interfaces (including those of superclasses).
144+
*
145+
* @return Set of TypeMirrorWrapper containing all implemented interfaces
146+
*/
147+
public Set<TypeMirrorWrapper> getAllInterfaces() {
148+
149+
// Add own interfaces
150+
Set<TypeMirrorWrapper> result = new HashSet<>(this.getInterfaces());
151+
152+
// recursively add those of superclasses.
153+
Optional<TypeElementWrapper> superClass = getSuperclass().getTypeElement();
154+
superClass.ifPresent(typeElementWrapper -> result.addAll(typeElementWrapper.getAllInterfaces()));
155+
156+
return result;
157+
}
158+
159+
/**
160+
* Returns list of TypeParameterElementWrapper instances describing all TypeParameters.
161+
*
162+
* @return list of TypeParameterElementWrapper instances describing all TypeParameters, or an empty list
163+
*/
164+
public List<TypeParameterElementWrapper> getTypeParameters() {
165+
return this.element.getTypeParameters().stream().map(TypeParameterElementWrapper::wrap)
166+
.collect(Collectors.toList());
167+
}
168+
169+
/**
170+
* Checks if wrapped TypeElement has type parameters.
171+
*
172+
* @return true, if TypeElement has type parameters, otherwise false.
173+
*/
174+
public boolean hasTypeParameters() {
175+
return getTypeParameters().size() > 0;
176+
}
177+
178+
/**
179+
* Returns an enclosed method. Works only if all parameter types are already compiled.
180+
*
181+
* @param name the method name
182+
* @param parameterTypes The parameter types
183+
* @return the wrapped ExecutableElement instance if method is found, otherwise null
184+
*/
185+
public Optional<ExecutableElementWrapper> getMethod(String name, Class<?>... parameterTypes) {
186+
List<ExecutableElement> filterResult = FluentElementFilter
187+
.createFluentElementFilter(this.element.getEnclosedElements())
188+
.applyFilter(AptkCoreMatchers.IS_METHOD)
189+
.applyFilter(AptkCoreMatchers.BY_NAME).filterByOneOf(name)
190+
.applyFilter(AptkCoreMatchers.BY_PARAMETER_TYPE).filterByOneOf(parameterTypes)
191+
.getResult();
192+
return Optional.ofNullable(filterResult.size() > 0 ? ExecutableElementWrapper.wrap(filterResult.get(0)) : null);
193+
}
194+
195+
/**
196+
* Returns list of ExecutableElementWrapper for all methods in wrapped TypeElement, filtered by modifiers.
197+
*
198+
* @param modifier modifiers used for filtering
199+
* @return list of ExecutableElementWrapper for all methods in wrapped TypeElement, or empty list
200+
*/
201+
public List<ExecutableElementWrapper> getMethods(Modifier... modifier) {
202+
return FluentElementFilter.createFluentElementFilter(
203+
this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_METHOD)
204+
.applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
205+
.getResult()
206+
.stream().map(ExecutableElementWrapper::wrap).collect(Collectors.toList());
207+
}
208+
209+
/**
210+
* Returns list of VariableElementWrapper for all fields in wrapped TypeElement, filtered by modifiers.
211+
*
212+
* @param modifier modifiers used for filtering
213+
* @return list of VariableElementWrapper for all fields in wrapped TypeElement, or empty list
214+
*/
215+
public List<VariableElementWrapper> getFields(Modifier... modifier) {
216+
return FluentElementFilter.createFluentElementFilter(
217+
this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_FIELD)
218+
.applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
219+
.getResult()
220+
.stream().map(VariableElementWrapper::wrap).collect(Collectors.toList());
221+
}
222+
223+
/**
224+
* Returns Optional of VariableElementWrapper of field with passed name in wrapped TypeElement.
225+
*
226+
* @param name Name of the field
227+
* @return Optional of ExecutableElementWrapper of field with passed name in wrapped TypeElement, or empty list
228+
*/
229+
public Optional<VariableElementWrapper> getFieldByName(String name) {
230+
List<VariableElementWrapper> fields = FluentElementFilter.createFluentElementFilter(
231+
this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_FIELD)
232+
.applyFilter(AptkCoreMatchers.BY_NAME).filterByOneOf(name)
233+
.getResult()
234+
.stream().map(VariableElementWrapper::wrap).collect(Collectors.toList());
235+
236+
return Optional.ofNullable(fields.size() > 0 ? fields.get(0) : null);
237+
}
238+
239+
/**
240+
* Returns list of VariableElementWrapper for all constructors in wrapped TypeElement, filtered by modifiers.
241+
*
242+
* @param modifier modifiers used for filtering
243+
* @return list of ExecutableElementWrapper for all constructors in wrapped TypeElement, or empty list
244+
*/
245+
public List<ExecutableElementWrapper> getConstructors(Modifier... modifier) {
246+
return FluentElementFilter.createFluentElementFilter(
247+
this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_CONSTRUCTOR)
248+
.applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
249+
.getResult()
250+
.stream().map(ExecutableElementWrapper::wrap).collect(Collectors.toList());
251+
}
252+
253+
/**
254+
* Returns list of VariableElementWrapper for all inner types in wrapped TypeElement, filtered by modifiers.
255+
*
256+
* @param modifier modifiers used for filtering
257+
* @return list of ExecutableElementWrapper for all inner types in wrapped TypeElement, or empty list
258+
*/
259+
public List<TypeElementWrapper> getInnerTypes(Modifier... modifier) {
260+
return FluentElementFilter.createFluentElementFilter(
261+
this.element.getEnclosedElements()).applyFilter(AptkCoreMatchers.IS_CLASS)
262+
.applyFilter(AptkCoreMatchers.BY_MODIFIER).filterByAllOf(modifier)
263+
.getResult()
264+
.stream().map(TypeElementWrapper::wrap).collect(Collectors.toList());
265+
}
266+
267+
/**
268+
* Returns the direct outer type for nested classes. Returned type may not be a top level type, since java allows
269+
* nested classes in nested classes.
270+
*
271+
* @return an Optional containing the outer type of nested classes, if present.
272+
*/
273+
public Optional<TypeElementWrapper> getOuterType() {
274+
if (this.element.getNestingKind() != NestingKind.MEMBER) {
275+
return Optional.empty();
276+
}
277+
278+
return Optional.of(TypeElementWrapper.wrap(ElementUtils.AccessEnclosingElements
279+
.<TypeElement> getFirstEnclosingElementOfKind(this.element, ElementKind.CLASS, ElementKind.INTERFACE)));
280+
}
281+
282+
/**
283+
* Returns the direct outer type for nested classes.
284+
*
285+
* @return an Optional containing the outer type of nested classes, if present.
286+
*/
287+
public Optional<TypeElementWrapper> getOuterTopLevelType() {
288+
if (this.element.getNestingKind() != NestingKind.MEMBER) {
289+
return Optional.empty();
290+
}
291+
292+
return Optional.of(this.getAllEnclosingElements().stream()
293+
.filter(ElementWrapper::isTypeElement)
294+
.map(ElementWrapper::toTypeElement)
295+
.filter(e -> e.hasNestingKind(NestingKind.TOP_LEVEL)).collect(Collectors.toList()).get(0));
296+
297+
}
298+
299+
/**
300+
* Gets all enum constant names of enum as VariableElements.
301+
*
302+
* @return A list containing all enum constant names or null if wrapped TypeElement is no enum.
303+
*/
304+
public List<VariableElementWrapper> getEnumValues() {
305+
return isEnum() ? this.filterEnclosedElements()
306+
.applyFilter(AptkCoreMatchers.BY_ELEMENT_KIND)
307+
.filterByOneOf(ElementKind.ENUM_CONSTANT)
308+
.getResult()
309+
.stream().map(e -> VariableElementWrapper.wrap((VariableElement) e))
310+
.collect(Collectors.toList())
311+
: null;
312+
}
313+
314+
/**
315+
* Returns the record components of this class or interface element in declaration order.
316+
*
317+
* @return the record components, or an empty list if there are none
318+
*/
319+
public List<RecordComponentElementWrapper> getRecordComponents() {
320+
321+
// This method is available from Java 16 - the introduction of records, so this check is sufficient to prevent
322+
// reflective calling of method
323+
if (!isRecord()) {
324+
return Collections.EMPTY_LIST;
325+
}
326+
List<? extends Element> recordComponentElements = this
327+
.<List<? extends Element>> invokeParameterlessMethodOfElement(TYPE_ELEMENT_CLASS_NAME, "getRecordComponents");
328+
329+
return recordComponentElements.stream().map(RecordComponentElementWrapper::wrap).collect(Collectors.toList());
330+
331+
}
332+
333+
/**
334+
* Returns the permitted classes of this class or interface element in declaration order.
335+
*
336+
* @return the permitted classes, or an empty list if there are none
337+
*/
338+
public List<TypeMirrorWrapper> getPermittedSubclasses() {
339+
340+
// must make sure that method exists, otherwise return the default value
341+
if (!hasMethod(TYPE_ELEMENT_CLASS_NAME, "getPermittedSubclasses")) {
342+
// TODO MUST CHECK WHAT SHOULD BE RETURNED FOR NON SEALED CLASSES!
343+
return Collections.EMPTY_LIST;
344+
}
345+
346+
List<TypeMirror> typeMirrors = this.<List<TypeMirror>> invokeParameterlessMethodOfElement(TYPE_ELEMENT_CLASS_NAME,
347+
"getPermittedSubclasses");
348+
return typeMirrors.stream().map(TypeMirrorWrapper::wrap).collect(Collectors.toList());
349+
}
350+
351+
/**
352+
* Wraps a TypeElement.
353+
*
354+
* @param element the TypeElement to wrap
355+
* @return a wrapper instance
356+
*/
357+
public static TypeElementWrapper wrap(TypeElement element) {
358+
return new TypeElementWrapper(element);
359+
}
360+
361+
/**
362+
* Retrieve TypeElementWrapper by fully qualified name.
363+
*
364+
* @param fqn the fully qualified name of the element
365+
* @return an Optional that contains the wrapper instance, if TypeElement for fqn exists
366+
*/
367+
public static Optional<TypeElementWrapper> getByFqn(String fqn) {
368+
return getByTypeMirror(TypeMirrorWrapper.wrap(fqn));
369+
}
370+
371+
/**
372+
* Retrieve TypeElementWrapper by fully qualified name.
373+
*
374+
* @param clazz the class of the element
375+
* @return an Optional that contains the wrapper instance, if TypeElement for class exists (only for DeclaredTypes)
376+
*/
377+
public static Optional<TypeElementWrapper> getByClass(Class<?> clazz) {
378+
return getByTypeMirror(TypeMirrorWrapper.wrap(clazz));
379+
}
380+
381+
/**
382+
* Retrieve TypeElementWrapper by fully qualified name.
383+
*
384+
* @param typeMirror the TypeMirror of the element
385+
* @return an Optional that contains the wrapper instance, if TypeElement for TypeMirror exists (only for
386+
* DeclaredTypes)
387+
*/
388+
public static Optional<TypeElementWrapper> getByTypeMirror(TypeMirror typeMirror) {
389+
return getByTypeMirror(TypeMirrorWrapper.wrap(typeMirror));
390+
}
391+
392+
public static Optional<TypeElementWrapper> getByTypeMirror(TypeMirrorWrapper typeMirror) {
393+
return typeMirror.getTypeElement();
394+
}
395+
}

0 commit comments

Comments
 (0)