1 /* |
|
2 * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package jdk.javadoc.internal.doclets.toolkit.util; |
|
27 |
|
28 import java.util.*; |
|
29 import java.util.regex.Pattern; |
|
30 |
|
31 import javax.lang.model.element.Element; |
|
32 import javax.lang.model.element.ElementKind; |
|
33 import javax.lang.model.element.ExecutableElement; |
|
34 import javax.lang.model.element.TypeElement; |
|
35 import javax.lang.model.element.VariableElement; |
|
36 import javax.lang.model.type.TypeKind; |
|
37 import javax.lang.model.type.TypeMirror; |
|
38 import javax.lang.model.util.ElementFilter; |
|
39 |
|
40 import com.sun.source.doctree.DocCommentTree; |
|
41 import com.sun.source.doctree.DocTree; |
|
42 |
|
43 import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; |
|
44 import jdk.javadoc.internal.doclets.toolkit.Messages; |
|
45 |
|
46 /** |
|
47 * A data structure that encapsulates the visible members of a particular |
|
48 * type for a given class tree. To use this data structure, you must specify |
|
49 * the type of member you are interested in (nested class, field, constructor |
|
50 * or method) and the leaf of the class tree. The data structure will map |
|
51 * all visible members in the leaf and classes above the leaf in the tree. |
|
52 * |
|
53 * <p><b>This is NOT part of any supported API. |
|
54 * If you write code that depends on this, you do so at your own risk. |
|
55 * This code and its internal interfaces are subject to change or |
|
56 * deletion without notice.</b> |
|
57 * |
|
58 * @author Atul M Dambalkar |
|
59 * @author Jamie Ho (rewrite) |
|
60 */ |
|
61 public class VisibleMemberMap { |
|
62 |
|
63 private boolean noVisibleMembers = true; |
|
64 |
|
65 public static enum Kind { |
|
66 INNER_CLASSES, |
|
67 ENUM_CONSTANTS, |
|
68 FIELDS, |
|
69 CONSTRUCTORS, |
|
70 METHODS, |
|
71 ANNOTATION_TYPE_FIELDS, |
|
72 ANNOTATION_TYPE_MEMBER_OPTIONAL, |
|
73 ANNOTATION_TYPE_MEMBER_REQUIRED, |
|
74 PROPERTIES; |
|
75 |
|
76 public static final EnumSet<Kind> summarySet = EnumSet.range(INNER_CLASSES, METHODS); |
|
77 public static final EnumSet<Kind> detailSet = EnumSet.range(ENUM_CONSTANTS, METHODS); |
|
78 public static String getNavLinkLabels(Kind kind) { |
|
79 switch (kind) { |
|
80 case INNER_CLASSES: |
|
81 return "doclet.navNested"; |
|
82 case ENUM_CONSTANTS: |
|
83 return "doclet.navEnum"; |
|
84 case FIELDS: |
|
85 return "doclet.navField"; |
|
86 case CONSTRUCTORS: |
|
87 return "doclet.navConstructor"; |
|
88 case METHODS: |
|
89 return "doclet.navMethod"; |
|
90 default: |
|
91 throw new AssertionError("unknown kind:" + kind); |
|
92 } |
|
93 } |
|
94 } |
|
95 |
|
96 public static final String STARTLEVEL = "start"; |
|
97 |
|
98 // properties aren't named setA* or getA* |
|
99 private static final Pattern GETTERSETTERPATTERN = Pattern.compile("[sg]et\\p{Upper}.*"); |
|
100 /** |
|
101 * List of TypeElement objects for which ClassMembers objects are built. |
|
102 */ |
|
103 private final Set<TypeElement> visibleClasses; |
|
104 |
|
105 /** |
|
106 * Map for each member name on to a map which contains members with same |
|
107 * name-signature. The mapped map will contain mapping for each MemberDoc |
|
108 * onto it's respecive level string. |
|
109 */ |
|
110 private final Map<Object, Map<Element, String>> memberNameMap = new HashMap<>(); |
|
111 |
|
112 /** |
|
113 * Map of class and it's ClassMembers object. |
|
114 */ |
|
115 private final Map<TypeElement, ClassMembers> classMap = new HashMap<>(); |
|
116 |
|
117 /** |
|
118 * Type whose visible members are requested. This is the leaf of |
|
119 * the class tree being mapped. |
|
120 */ |
|
121 private final TypeElement typeElement; |
|
122 |
|
123 /** |
|
124 * Member kind: InnerClasses/Fields/Methods? |
|
125 */ |
|
126 public final Kind kind; |
|
127 |
|
128 /** |
|
129 * The configuration this VisibleMemberMap was created with. |
|
130 */ |
|
131 private final BaseConfiguration configuration; |
|
132 private final Messages messages; |
|
133 private final Utils utils; |
|
134 private final Comparator<Element> comparator; |
|
135 |
|
136 private final Map<TypeElement, List<Element>> propertiesCache; |
|
137 private final Map<Element, Element> classPropertiesMap; |
|
138 private final Map<Element, GetterSetter> getterSetterMap; |
|
139 |
|
140 /** |
|
141 * Construct a VisibleMemberMap of the given type for the given class. |
|
142 * |
|
143 * @param typeElement whose members are being mapped. |
|
144 * @param kind the kind of member that is being mapped. |
|
145 * @param configuration the configuration to use to construct this |
|
146 * VisibleMemberMap. If the field configuration.nodeprecated is true the |
|
147 * deprecated members are excluded from the map. If the field |
|
148 * configuration.javafx is true the JavaFX features are used. |
|
149 */ |
|
150 public VisibleMemberMap(TypeElement typeElement, |
|
151 Kind kind, |
|
152 BaseConfiguration configuration) { |
|
153 this.typeElement = typeElement; |
|
154 this.kind = kind; |
|
155 this.configuration = configuration; |
|
156 this.messages = configuration.getMessages(); |
|
157 this.utils = configuration.utils; |
|
158 propertiesCache = configuration.propertiesCache; |
|
159 classPropertiesMap = configuration.classPropertiesMap; |
|
160 getterSetterMap = configuration.getterSetterMap; |
|
161 comparator = utils.makeGeneralPurposeComparator(); |
|
162 visibleClasses = new LinkedHashSet<>(); |
|
163 new ClassMembers(typeElement, STARTLEVEL).build(); |
|
164 } |
|
165 |
|
166 /** |
|
167 * Return the list of visible classes in this map. |
|
168 * |
|
169 * @return the list of visible classes in this map. |
|
170 */ |
|
171 public SortedSet<TypeElement> getVisibleClasses() { |
|
172 SortedSet<TypeElement> vClasses = new TreeSet<>(comparator); |
|
173 vClasses.addAll(visibleClasses); |
|
174 return vClasses; |
|
175 } |
|
176 |
|
177 /** |
|
178 * Returns the first method where the given method is visible. |
|
179 * @param e the method whose visible enclosing type is to be found. |
|
180 * @return the method found or null |
|
181 */ |
|
182 public ExecutableElement getVisibleMethod(ExecutableElement e) { |
|
183 if (kind != Kind.METHODS || e.getKind() != ElementKind.METHOD) { |
|
184 throw new AssertionError("incompatible member type or visible member map" + e); |
|
185 } |
|
186 // start with the current class |
|
187 for (Element m : getMembers(typeElement)) { |
|
188 ExecutableElement mthd = (ExecutableElement)m; |
|
189 if (utils.executableMembersEqual(mthd, e)) { |
|
190 return mthd; |
|
191 } |
|
192 } |
|
193 |
|
194 for (TypeElement te : visibleClasses) { |
|
195 if (te == typeElement) |
|
196 continue; |
|
197 for (Element m : getMembers(te)) { |
|
198 ExecutableElement mthd = (ExecutableElement)m; |
|
199 if (utils.executableMembersEqual(mthd, e)) { |
|
200 return mthd; |
|
201 } |
|
202 } |
|
203 } |
|
204 return null; |
|
205 } |
|
206 |
|
207 /** |
|
208 * Returns the property field documentation belonging to the given member. |
|
209 * @param element the member for which the property documentation is needed. |
|
210 * @return the property field documentation, null if there is none. |
|
211 */ |
|
212 public Element getPropertyElement(Element element) { |
|
213 return classPropertiesMap.get(element); |
|
214 } |
|
215 |
|
216 /** |
|
217 * Returns the getter documentation belonging to the given property method. |
|
218 * @param propertyMethod the method for which the getter is needed. |
|
219 * @return the getter documentation, null if there is none. |
|
220 */ |
|
221 public Element getGetterForProperty(Element propertyMethod) { |
|
222 return getterSetterMap.get(propertyMethod).getGetter(); |
|
223 } |
|
224 |
|
225 /** |
|
226 * Returns the setter documentation belonging to the given property method. |
|
227 * @param propertyMethod the method for which the setter is needed. |
|
228 * @return the setter documentation, null if there is none. |
|
229 */ |
|
230 public Element getSetterForProperty(Element propertyMethod) { |
|
231 return getterSetterMap.get(propertyMethod).getSetter(); |
|
232 } |
|
233 |
|
234 /** |
|
235 * Return the package private members inherited by the class. Only return |
|
236 * if parent is package private and not documented. |
|
237 * |
|
238 * @return the package private members inherited by the class. |
|
239 */ |
|
240 private List<Element> getInheritedPackagePrivateMethods() { |
|
241 List<Element> results = new ArrayList<>(); |
|
242 for (TypeElement currentClass : visibleClasses) { |
|
243 if (currentClass != typeElement && |
|
244 utils.isPackagePrivate(currentClass) && |
|
245 !utils.isLinkable(currentClass)) { |
|
246 // Document these members in the child class because |
|
247 // the parent is inaccessible. |
|
248 results.addAll(classMap.get(currentClass).members); |
|
249 } |
|
250 } |
|
251 return results; |
|
252 } |
|
253 |
|
254 /** |
|
255 * Returns a list of visible enclosed members of the mapped type element. |
|
256 * |
|
257 * In the case of methods, the list may contain those methods that are |
|
258 * extended with no specification changes as indicated by the existence |
|
259 * of a sole @inheritDoc or devoid of any API commments. |
|
260 * |
|
261 * This list may also contain appended members, inherited by inaccessible |
|
262 * super types. These members are documented in the subtype when the |
|
263 * super type is not documented. |
|
264 * |
|
265 * @return a list of visible enclosed members |
|
266 */ |
|
267 |
|
268 public List<Element> getLeafMembers() { |
|
269 List<Element> result = new ArrayList<>(); |
|
270 result.addAll(getMembers(typeElement)); |
|
271 result.addAll(getInheritedPackagePrivateMethods()); |
|
272 return result; |
|
273 } |
|
274 |
|
275 // Cache to improve performance |
|
276 private HashMap<ExecutableElement, Boolean> overridenMethodCache = new HashMap<>(); |
|
277 |
|
278 private boolean hasOverridden(ExecutableElement method) { |
|
279 return overridenMethodCache.computeIfAbsent(method, m -> hasOverriddenCompute(m)); |
|
280 } |
|
281 |
|
282 private boolean hasOverriddenCompute(ExecutableElement method) { |
|
283 if (kind != Kind.METHODS) { |
|
284 throw new AssertionError("Unexpected kind: " + kind); |
|
285 } |
|
286 for (TypeElement t : visibleClasses) { |
|
287 for (Element member : classMap.get(t).members) { |
|
288 ExecutableElement inheritedMethod = (ExecutableElement)member; |
|
289 if (utils.elementUtils.overrides(method, inheritedMethod, t)) { |
|
290 return true; |
|
291 } |
|
292 } |
|
293 } |
|
294 return false; |
|
295 } |
|
296 |
|
297 /** |
|
298 * Returns a list of enclosed members for the given type. |
|
299 * |
|
300 * @param typeElement the given type |
|
301 * |
|
302 * @return a list of enclosed members |
|
303 */ |
|
304 public List<Element> getMembers(TypeElement typeElement) { |
|
305 List<Element> result = new ArrayList<>(); |
|
306 if (this.kind == Kind.METHODS) { |
|
307 for (Element member : classMap.get(typeElement).members) { |
|
308 ExecutableElement method = (ExecutableElement)member; |
|
309 if (hasOverridden(method)) { |
|
310 if (!utils.isSimpleOverride(method)) { |
|
311 result.add(method); |
|
312 } |
|
313 } else { |
|
314 result.add(method); |
|
315 } |
|
316 } |
|
317 } else { |
|
318 result.addAll(classMap.get(typeElement).members); |
|
319 } |
|
320 return result; |
|
321 } |
|
322 |
|
323 public boolean hasMembers(TypeElement typeElement) { |
|
324 return !classMap.get(typeElement).members.isEmpty(); |
|
325 } |
|
326 |
|
327 private void fillMemberLevelMap(List<? extends Element> list, String level) { |
|
328 for (Element element : list) { |
|
329 Object key = getMemberKey(element); |
|
330 Map<Element, String> memberLevelMap = memberNameMap.get(key); |
|
331 if (memberLevelMap == null) { |
|
332 memberLevelMap = new HashMap<>(); |
|
333 memberNameMap.put(key, memberLevelMap); |
|
334 } |
|
335 memberLevelMap.put(element, level); |
|
336 } |
|
337 } |
|
338 |
|
339 private void purgeMemberLevelMap(Iterable<? extends Element> list, String level) { |
|
340 for (Element element : list) { |
|
341 Object key = getMemberKey(element); |
|
342 Map<Element, String> memberLevelMap = memberNameMap.get(key); |
|
343 if (memberLevelMap != null && level.equals(memberLevelMap.get(element))) |
|
344 memberLevelMap.remove(element); |
|
345 } |
|
346 } |
|
347 |
|
348 /** |
|
349 * Represents a class member. |
|
350 */ |
|
351 private class ClassMember { |
|
352 private Set<Element> members; |
|
353 |
|
354 public ClassMember(Element element) { |
|
355 members = new HashSet<>(); |
|
356 members.add(element); |
|
357 } |
|
358 |
|
359 public boolean isEqual(ExecutableElement method) { |
|
360 for (Element member : members) { |
|
361 if (member.getKind() != ElementKind.METHOD) |
|
362 continue; |
|
363 ExecutableElement thatMethod = (ExecutableElement) member; |
|
364 if (utils.executableMembersEqual(method, thatMethod) && |
|
365 !utils.isSimpleOverride(thatMethod)) { |
|
366 members.add(method); |
|
367 return true; |
|
368 } |
|
369 } |
|
370 return false; |
|
371 } |
|
372 } |
|
373 |
|
374 /** |
|
375 * A data structure that represents the class members for |
|
376 * a visible class. |
|
377 */ |
|
378 private class ClassMembers { |
|
379 |
|
380 /** |
|
381 * The mapping class, whose inherited members are put in the |
|
382 * {@link #members} list. |
|
383 */ |
|
384 private final TypeElement typeElement; |
|
385 |
|
386 /** |
|
387 * List of members from the mapping class. |
|
388 */ |
|
389 private List<Element> members = null; |
|
390 |
|
391 /** |
|
392 * Level/Depth of inheritance. |
|
393 */ |
|
394 private final String level; |
|
395 |
|
396 private ClassMembers(TypeElement mappingClass, String level) { |
|
397 this.typeElement = mappingClass; |
|
398 this.level = level; |
|
399 if (classMap.containsKey(mappingClass) && |
|
400 level.startsWith(classMap.get(mappingClass).level)) { |
|
401 //Remove lower level class so that it can be replaced with |
|
402 //same class found at higher level. |
|
403 purgeMemberLevelMap(getClassMembers(mappingClass, false), |
|
404 classMap.get(mappingClass).level); |
|
405 classMap.remove(mappingClass); |
|
406 visibleClasses.remove(mappingClass); |
|
407 } |
|
408 if (!classMap.containsKey(mappingClass)) { |
|
409 classMap.put(mappingClass, this); |
|
410 visibleClasses.add(mappingClass); |
|
411 } |
|
412 } |
|
413 |
|
414 private void build() { |
|
415 if (kind == Kind.CONSTRUCTORS) { |
|
416 addMembers(typeElement); |
|
417 } else { |
|
418 mapClass(); |
|
419 } |
|
420 } |
|
421 |
|
422 private void mapClass() { |
|
423 addMembers(typeElement); |
|
424 List<? extends TypeMirror> interfaces = typeElement.getInterfaces(); |
|
425 for (TypeMirror anInterface : interfaces) { |
|
426 String locallevel = level + 1; |
|
427 ClassMembers cm = new ClassMembers(utils.asTypeElement(anInterface), locallevel); |
|
428 cm.mapClass(); |
|
429 } |
|
430 if (utils.isClass(typeElement)) { |
|
431 TypeElement superclass = utils.getSuperClass(typeElement); |
|
432 if (!(superclass == null || typeElement.equals(superclass))) { |
|
433 ClassMembers cm = new ClassMembers(superclass, level + "c"); |
|
434 cm.mapClass(); |
|
435 } |
|
436 } |
|
437 } |
|
438 |
|
439 /** |
|
440 * Get all the valid members from the mapping class. Get the list of |
|
441 * members for the class to be included into(ctii), also get the level |
|
442 * string for ctii. If mapping class member is not already in the |
|
443 * inherited member list and if it is visible in the ctii and not |
|
444 * overridden, put such a member in the inherited member list. |
|
445 * Adjust member-level-map, class-map. |
|
446 */ |
|
447 private void addMembers(TypeElement fromClass) { |
|
448 List<Element> result = new ArrayList<>(); |
|
449 for (Element element : getClassMembers(fromClass, true)) { |
|
450 if (memberIsVisible(element)) { |
|
451 if (!isOverridden(element, level)) { |
|
452 if (!utils.isHidden(element)) { |
|
453 result.add(element); |
|
454 } |
|
455 } |
|
456 } |
|
457 } |
|
458 if (members != null) { |
|
459 throw new AssertionError("members should not be null"); |
|
460 } |
|
461 members = Collections.unmodifiableList(result); |
|
462 if (!members.isEmpty()) { |
|
463 noVisibleMembers = false; |
|
464 } |
|
465 fillMemberLevelMap(getClassMembers(fromClass, false), level); |
|
466 } |
|
467 |
|
468 /** |
|
469 * Is given element visible in given typeElement in terms of inheritance? The given element |
|
470 * is visible in the given typeElement if it is public or protected and if it is |
|
471 * package-private if it's containing class is in the same package as the given typeElement. |
|
472 */ |
|
473 private boolean memberIsVisible(Element element) { |
|
474 if (utils.getEnclosingTypeElement(element).equals(VisibleMemberMap.this.typeElement)) { |
|
475 //Member is in class that we are finding visible members for. |
|
476 //Of course it is visible. |
|
477 return true; |
|
478 } else if (utils.isPrivate(element)) { |
|
479 //Member is in super class or implemented interface. |
|
480 //Private, so not inherited. |
|
481 return false; |
|
482 } else if (utils.isPackagePrivate(element)) { |
|
483 //Member is package private. Only return true if its class is in |
|
484 //same package. |
|
485 return utils.containingPackage(element).equals(utils.containingPackage(VisibleMemberMap.this.typeElement)); |
|
486 } else { |
|
487 //Public members are always inherited. |
|
488 return true; |
|
489 } |
|
490 } |
|
491 |
|
492 /** |
|
493 * Return all available class members. |
|
494 */ |
|
495 private List<? extends Element> getClassMembers(TypeElement te, boolean filter) { |
|
496 if (utils.isEnum(te) && kind == Kind.CONSTRUCTORS) { |
|
497 //If any of these rules are hit, return empty array because |
|
498 //we don't document these members ever. |
|
499 return Collections.emptyList(); |
|
500 } |
|
501 List<? extends Element> list; |
|
502 switch (kind) { |
|
503 case ANNOTATION_TYPE_FIELDS: |
|
504 list = (filter) |
|
505 ? utils.getAnnotationFields(te) |
|
506 : utils.getAnnotationFieldsUnfiltered(te); |
|
507 break; |
|
508 case ANNOTATION_TYPE_MEMBER_OPTIONAL: |
|
509 list = utils.isAnnotationType(te) |
|
510 ? filterAnnotations(te, false) |
|
511 : Collections.emptyList(); |
|
512 break; |
|
513 case ANNOTATION_TYPE_MEMBER_REQUIRED: |
|
514 list = utils.isAnnotationType(te) |
|
515 ? filterAnnotations(te, true) |
|
516 : Collections.emptyList(); |
|
517 break; |
|
518 case INNER_CLASSES: |
|
519 List<TypeElement> xlist = filter |
|
520 ? utils.getInnerClasses(te) |
|
521 : utils.getInnerClassesUnfiltered(te); |
|
522 list = new ArrayList<>(xlist); |
|
523 break; |
|
524 case ENUM_CONSTANTS: |
|
525 list = utils.getEnumConstants(te); |
|
526 break; |
|
527 case FIELDS: |
|
528 if (filter) { |
|
529 list = utils.isAnnotationType(te) |
|
530 ? utils.getAnnotationFields(te) |
|
531 : utils.getFields(te); |
|
532 } else { |
|
533 list = utils.isAnnotationType(te) |
|
534 ? utils.getAnnotationFieldsUnfiltered(te) |
|
535 : utils.getFieldsUnfiltered(te); |
|
536 } |
|
537 break; |
|
538 case CONSTRUCTORS: |
|
539 list = utils.getConstructors(te); |
|
540 break; |
|
541 case METHODS: |
|
542 list = filter ? utils.getMethods(te) : utils.getMethodsUnfiltered(te); |
|
543 checkOnPropertiesTags(list); |
|
544 break; |
|
545 case PROPERTIES: |
|
546 list = properties(te, filter); |
|
547 break; |
|
548 default: |
|
549 list = Collections.emptyList(); |
|
550 } |
|
551 // Deprected members should be excluded or not? |
|
552 if (configuration.nodeprecated) { |
|
553 return utils.excludeDeprecatedMembers(list); |
|
554 } |
|
555 return list; |
|
556 } |
|
557 |
|
558 /** |
|
559 * Filter the annotation type members and return either the required |
|
560 * members or the optional members, depending on the value of the |
|
561 * required parameter. |
|
562 * |
|
563 * @param typeElement The annotation type to process. |
|
564 * @param required |
|
565 * @return the annotation type members and return either the required |
|
566 * members or the optional members, depending on the value of the |
|
567 * required parameter. |
|
568 */ |
|
569 private List<Element> filterAnnotations(TypeElement typeElement, boolean required) { |
|
570 List<Element> members = utils.getAnnotationMethods(typeElement); |
|
571 List<Element> targetMembers = new ArrayList<>(); |
|
572 for (Element member : members) { |
|
573 ExecutableElement ee = (ExecutableElement)member; |
|
574 if ((required && ee.getDefaultValue() == null) |
|
575 || ((!required) && ee.getDefaultValue() != null)) { |
|
576 targetMembers.add(member); |
|
577 } |
|
578 } |
|
579 return targetMembers; |
|
580 } |
|
581 |
|
582 /** |
|
583 * Is member overridden? The member is overridden if it is found in the |
|
584 * same level hierarchy e.g. member at level "11" overrides member at |
|
585 * level "111". |
|
586 */ |
|
587 private boolean isOverridden(Element element, String level) { |
|
588 Object key = getMemberKey(element); |
|
589 Map<?, String> memberLevelMap = (Map<?, String>) memberNameMap.get(key); |
|
590 if (memberLevelMap == null) |
|
591 return false; |
|
592 for (String mappedlevel : memberLevelMap.values()) { |
|
593 if (mappedlevel.equals(STARTLEVEL) |
|
594 || (level.startsWith(mappedlevel) |
|
595 && !level.equals(mappedlevel))) { |
|
596 return true; |
|
597 } |
|
598 } |
|
599 return false; |
|
600 } |
|
601 |
|
602 private List<Element> properties(final TypeElement typeElement, final boolean filter) { |
|
603 final List<ExecutableElement> allMethods = filter |
|
604 ? utils.getMethods(typeElement) |
|
605 : utils.getMethodsUnfiltered(typeElement); |
|
606 final List<VariableElement> allFields = utils.getFieldsUnfiltered(typeElement); |
|
607 |
|
608 if (propertiesCache.containsKey(typeElement)) { |
|
609 return propertiesCache.get(typeElement); |
|
610 } |
|
611 |
|
612 final List<Element> result = new ArrayList<>(); |
|
613 |
|
614 for (final Element propertyMethod : allMethods) { |
|
615 ExecutableElement ee = (ExecutableElement)propertyMethod; |
|
616 if (!isPropertyMethod(ee)) { |
|
617 continue; |
|
618 } |
|
619 |
|
620 final ExecutableElement getter = getterForField(allMethods, ee); |
|
621 final ExecutableElement setter = setterForField(allMethods, ee); |
|
622 final VariableElement field = fieldForProperty(allFields, ee); |
|
623 |
|
624 addToPropertiesMap(setter, getter, ee, field); |
|
625 getterSetterMap.put(propertyMethod, new GetterSetter(getter, setter)); |
|
626 result.add(ee); |
|
627 } |
|
628 propertiesCache.put(typeElement, result); |
|
629 return result; |
|
630 } |
|
631 |
|
632 private void addToPropertiesMap(ExecutableElement setter, |
|
633 ExecutableElement getter, |
|
634 ExecutableElement propertyMethod, |
|
635 VariableElement field) { |
|
636 if (field == null || utils.getDocCommentTree(field) == null) { |
|
637 addToPropertiesMap(setter, propertyMethod); |
|
638 addToPropertiesMap(getter, propertyMethod); |
|
639 addToPropertiesMap(propertyMethod, propertyMethod); |
|
640 } else { |
|
641 addToPropertiesMap(getter, field); |
|
642 addToPropertiesMap(setter, field); |
|
643 addToPropertiesMap(propertyMethod, field); |
|
644 } |
|
645 } |
|
646 |
|
647 private void addToPropertiesMap(Element propertyMethod, |
|
648 Element commentSource) { |
|
649 if (null == propertyMethod || null == commentSource) { |
|
650 return; |
|
651 } |
|
652 DocCommentTree docTree = utils.getDocCommentTree(propertyMethod); |
|
653 |
|
654 /* The second condition is required for the property buckets. In |
|
655 * this case the comment is at the property method (not at the field) |
|
656 * and it needs to be listed in the map. |
|
657 */ |
|
658 if ((docTree == null) || propertyMethod.equals(commentSource)) { |
|
659 classPropertiesMap.put(propertyMethod, commentSource); |
|
660 } |
|
661 } |
|
662 |
|
663 private ExecutableElement getterForField(List<ExecutableElement> methods, |
|
664 ExecutableElement propertyMethod) { |
|
665 final String propertyMethodName = utils.getSimpleName(propertyMethod); |
|
666 final String fieldName = propertyMethodName.substring(0, |
|
667 propertyMethodName.lastIndexOf("Property")); |
|
668 final String fieldNameUppercased = |
|
669 "" + Character.toUpperCase(fieldName.charAt(0)) |
|
670 + fieldName.substring(1); |
|
671 final String getterNamePattern; |
|
672 final String fieldTypeName = propertyMethod.getReturnType().toString(); |
|
673 if ("boolean".equals(fieldTypeName) |
|
674 || fieldTypeName.endsWith("BooleanProperty")) { |
|
675 getterNamePattern = "(is|get)" + fieldNameUppercased; |
|
676 } else { |
|
677 getterNamePattern = "get" + fieldNameUppercased; |
|
678 } |
|
679 |
|
680 for (ExecutableElement method : methods) { |
|
681 if (Pattern.matches(getterNamePattern, utils.getSimpleName(method))) { |
|
682 if (method.getParameters().isEmpty() && |
|
683 utils.isPublic(method) || utils.isProtected(method)) { |
|
684 return method; |
|
685 } |
|
686 } |
|
687 } |
|
688 return null; |
|
689 } |
|
690 |
|
691 private ExecutableElement setterForField(List<ExecutableElement> methods, |
|
692 ExecutableElement propertyMethod) { |
|
693 final String propertyMethodName = utils.getSimpleName(propertyMethod); |
|
694 final String fieldName = |
|
695 propertyMethodName.substring(0, |
|
696 propertyMethodName.lastIndexOf("Property")); |
|
697 final String fieldNameUppercased = |
|
698 "" + Character.toUpperCase(fieldName.charAt(0)) |
|
699 + fieldName.substring(1); |
|
700 final String setter = "set" + fieldNameUppercased; |
|
701 |
|
702 for (ExecutableElement method : methods) { |
|
703 if (setter.equals(utils.getSimpleName(method))) { |
|
704 if (method.getParameters().size() == 1 |
|
705 && method.getReturnType().getKind() == TypeKind.VOID |
|
706 && (utils.isPublic(method) || utils.isProtected(method))) { |
|
707 return method; |
|
708 } |
|
709 } |
|
710 } |
|
711 return null; |
|
712 } |
|
713 |
|
714 private VariableElement fieldForProperty(List<VariableElement> fields, ExecutableElement property) { |
|
715 |
|
716 for (VariableElement field : fields) { |
|
717 final String fieldName = utils.getSimpleName(field); |
|
718 final String propertyName = fieldName + "Property"; |
|
719 if (propertyName.equals(utils.getSimpleName(property))) { |
|
720 return field; |
|
721 } |
|
722 } |
|
723 return null; |
|
724 } |
|
725 |
|
726 private boolean isPropertyMethod(ExecutableElement method) { |
|
727 if (!configuration.javafx) { |
|
728 return false; |
|
729 } |
|
730 if (!utils.getSimpleName(method).endsWith("Property")) { |
|
731 return false; |
|
732 } |
|
733 |
|
734 if (!memberIsVisible(method)) { |
|
735 return false; |
|
736 } |
|
737 |
|
738 if (GETTERSETTERPATTERN.matcher(utils.getSimpleName(method)).matches()) { |
|
739 return false; |
|
740 } |
|
741 if (!method.getTypeParameters().isEmpty()) { |
|
742 return false; |
|
743 } |
|
744 return method.getParameters().isEmpty() |
|
745 && method.getReturnType().getKind() != TypeKind.VOID; |
|
746 } |
|
747 |
|
748 private void checkOnPropertiesTags(List<? extends Element> members) { |
|
749 for (Element e: members) { |
|
750 ExecutableElement ee = (ExecutableElement)e; |
|
751 if (utils.isIncluded(ee)) { |
|
752 CommentHelper ch = utils.getCommentHelper(ee); |
|
753 for (DocTree tree: utils.getBlockTags(ee)) { |
|
754 String tagName = ch.getTagName(tree); |
|
755 if (tagName.equals("@propertySetter") |
|
756 || tagName.equals("@propertyGetter") |
|
757 || tagName.equals("@propertyDescription")) { |
|
758 if (!isPropertyGetterOrSetter(members, ee)) { |
|
759 messages.warning(ch.getDocTreePath(tree), |
|
760 "doclet.javafx_tag_misuse"); |
|
761 } |
|
762 break; |
|
763 } |
|
764 } |
|
765 } |
|
766 } |
|
767 } |
|
768 |
|
769 private boolean isPropertyGetterOrSetter(List<? extends Element> members, |
|
770 ExecutableElement method) { |
|
771 String propertyName = utils.propertyName(method); |
|
772 if (!propertyName.isEmpty()) { |
|
773 String propertyMethodName = propertyName + "Property"; |
|
774 for (Element member: members) { |
|
775 if (utils.getSimpleName(member).equals(propertyMethodName)) { |
|
776 return true; |
|
777 } |
|
778 } |
|
779 } |
|
780 return false; |
|
781 } |
|
782 } |
|
783 |
|
784 public class GetterSetter { |
|
785 private final Element getter; |
|
786 private final Element setter; |
|
787 |
|
788 public GetterSetter(Element getter, Element setter) { |
|
789 this.getter = getter; |
|
790 this.setter = setter; |
|
791 } |
|
792 |
|
793 public Element getGetter() { |
|
794 return getter; |
|
795 } |
|
796 |
|
797 public Element getSetter() { |
|
798 return setter; |
|
799 } |
|
800 } |
|
801 |
|
802 /** |
|
803 * Return true if this map has no visible members. |
|
804 * |
|
805 * @return true if this map has no visible members. |
|
806 */ |
|
807 public boolean noVisibleMembers() { |
|
808 return noVisibleMembers; |
|
809 } |
|
810 |
|
811 private ClassMember getClassMember(ExecutableElement member) { |
|
812 for (Object key : memberNameMap.keySet()) { |
|
813 if (key instanceof String) { |
|
814 continue; |
|
815 } |
|
816 if (((ClassMember) key).isEqual(member)) { |
|
817 return (ClassMember) key; |
|
818 } |
|
819 } |
|
820 return new ClassMember(member); |
|
821 } |
|
822 |
|
823 /** |
|
824 * Return the key to the member map for the given member. |
|
825 */ |
|
826 private Object getMemberKey(Element element) { |
|
827 if (utils.isConstructor(element)) { |
|
828 return utils.getSimpleName(element) + utils.flatSignature((ExecutableElement)element); |
|
829 } else if (utils.isMethod(element)) { |
|
830 return getClassMember((ExecutableElement) element); |
|
831 } else if (utils.isField(element) || utils.isEnumConstant(element) || utils.isAnnotationType(element)) { |
|
832 return utils.getSimpleName(element); |
|
833 } else { // it's a class or interface |
|
834 String classOrIntName = utils.getSimpleName(element); |
|
835 //Strip off the containing class name because we only want the member name. |
|
836 classOrIntName = classOrIntName.indexOf('.') != 0 |
|
837 ? classOrIntName.substring(classOrIntName.lastIndexOf('.')) |
|
838 : classOrIntName; |
|
839 return "clint" + classOrIntName; |
|
840 } |
|
841 } |
|
842 } |
|