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