|
| 1 | +package io.quarkus.annotation.processor.generate_doc; |
| 2 | + |
| 3 | +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink; |
| 4 | +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getKnownGenericType; |
| 5 | +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate; |
| 6 | + |
| 7 | +import java.util.ArrayList; |
| 8 | +import java.util.List; |
| 9 | +import java.util.Map; |
| 10 | +import java.util.Properties; |
| 11 | +import java.util.Set; |
| 12 | + |
| 13 | +import javax.lang.model.element.AnnotationMirror; |
| 14 | +import javax.lang.model.element.AnnotationValue; |
| 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.Name; |
| 20 | +import javax.lang.model.element.TypeElement; |
| 21 | +import javax.lang.model.type.DeclaredType; |
| 22 | +import javax.lang.model.type.TypeMirror; |
| 23 | + |
| 24 | +import io.quarkus.annotation.processor.Constants; |
| 25 | + |
| 26 | +class ConfigDoItemFinder { |
| 27 | + |
| 28 | + private static final String NAMED_MAP_CONFIG_ITEM_FORMAT = ".\"%s\""; |
| 29 | + private final JavaDocParser javaDocParser = new JavaDocParser(); |
| 30 | + private final ScannedConfigDocsItemHolder holder = new ScannedConfigDocsItemHolder(); |
| 31 | + |
| 32 | + private final Set<ConfigRootInfo> configRoots; |
| 33 | + private final Map<String, TypeElement> configGroups; |
| 34 | + private final Properties javaDocProperties; |
| 35 | + |
| 36 | + public ConfigDoItemFinder(Set<ConfigRootInfo> configRoots, Map<String, TypeElement> configGroups, |
| 37 | + Properties javaDocProperties) { |
| 38 | + this.configRoots = configRoots; |
| 39 | + this.configGroups = configGroups; |
| 40 | + this.javaDocProperties = javaDocProperties; |
| 41 | + } |
| 42 | + |
| 43 | + /** |
| 44 | + * Find configuration items from current encountered configuration roots |
| 45 | + */ |
| 46 | + ScannedConfigDocsItemHolder findInMemoryConfigurationItems() { |
| 47 | + for (ConfigRootInfo configRootInfo : configRoots) { |
| 48 | + final TypeElement element = configRootInfo.getClazz(); |
| 49 | + /** |
| 50 | + * Config sections will start at level 2 i.e the section title will be prefixed with |
| 51 | + * ('='* (N + 1)) where N is section level. |
| 52 | + */ |
| 53 | + final int sectionLevel = 2; |
| 54 | + final List<ConfigDocItem> configDocItems = recursivelyFindConfigItems(element, configRootInfo.getName(), |
| 55 | + configRootInfo.getConfigPhase(), false, sectionLevel); |
| 56 | + holder.addToAllConfigItems(configRootInfo.getClazz().getQualifiedName().toString(), configDocItems); |
| 57 | + } |
| 58 | + |
| 59 | + return holder; |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Recursively find config item found in a config root given as {@link Element} |
| 64 | + * |
| 65 | + * @param element - root element |
| 66 | + * @param parentName - root name |
| 67 | + * @param configPhase - configuration phase see {@link ConfigPhase} |
| 68 | + * @param withinAMap - indicates if a a key is within a map or is a map configuration key |
| 69 | + * @param sectionLevel - section sectionLevel |
| 70 | + */ |
| 71 | + private List<ConfigDocItem> recursivelyFindConfigItems(Element element, String parentName, |
| 72 | + ConfigPhase configPhase, boolean withinAMap, int sectionLevel) { |
| 73 | + List<ConfigDocItem> configDocItems = new ArrayList<>(); |
| 74 | + for (Element enclosedElement : element.getEnclosedElements()) { |
| 75 | + if (!enclosedElement.getKind().isField()) { |
| 76 | + continue; |
| 77 | + } |
| 78 | + |
| 79 | + boolean isStaticField = enclosedElement |
| 80 | + .getModifiers() |
| 81 | + .stream() |
| 82 | + .anyMatch(Modifier.STATIC::equals); |
| 83 | + |
| 84 | + if (isStaticField) { |
| 85 | + continue; |
| 86 | + } |
| 87 | + |
| 88 | + String name = Constants.EMPTY; |
| 89 | + String defaultValue = Constants.NO_DEFAULT; |
| 90 | + String defaultValueDoc = Constants.EMPTY; |
| 91 | + final TypeMirror typeMirror = enclosedElement.asType(); |
| 92 | + String type = typeMirror.toString(); |
| 93 | + List<String> acceptedValues = null; |
| 94 | + Element configGroup = configGroups.get(type); |
| 95 | + ConfigDocSection configSection = null; |
| 96 | + boolean isConfigGroup = configGroup != null; |
| 97 | + final TypeElement clazz = (TypeElement) element; |
| 98 | + final String fieldName = enclosedElement.getSimpleName().toString(); |
| 99 | + final String javaDocKey = clazz.getQualifiedName().toString() + Constants.DOT + fieldName; |
| 100 | + final List<? extends AnnotationMirror> annotationMirrors = enclosedElement.getAnnotationMirrors(); |
| 101 | + final String rawJavaDoc = javaDocProperties.getProperty(javaDocKey); |
| 102 | + |
| 103 | + String hyphenatedFieldName = hyphenate(fieldName); |
| 104 | + String configDocMapKey = hyphenatedFieldName; |
| 105 | + |
| 106 | + for (AnnotationMirror annotationMirror : annotationMirrors) { |
| 107 | + String annotationName = annotationMirror.getAnnotationType().toString(); |
| 108 | + if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM) |
| 109 | + || annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY)) { |
| 110 | + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror |
| 111 | + .getElementValues().entrySet()) { |
| 112 | + final String key = entry.getKey().toString(); |
| 113 | + final String value = entry.getValue().getValue().toString(); |
| 114 | + if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY) && "value()".equals(key)) { |
| 115 | + configDocMapKey = value; |
| 116 | + } else if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM)) { |
| 117 | + if ("name()".equals(key)) { |
| 118 | + switch (value) { |
| 119 | + case Constants.HYPHENATED_ELEMENT_NAME: |
| 120 | + name = parentName + Constants.DOT + hyphenatedFieldName; |
| 121 | + break; |
| 122 | + case Constants.PARENT: |
| 123 | + name = parentName; |
| 124 | + break; |
| 125 | + default: |
| 126 | + name = parentName + Constants.DOT + value; |
| 127 | + } |
| 128 | + } else if ("defaultValue()".equals(key)) { |
| 129 | + defaultValue = value; |
| 130 | + } else if ("defaultValueDocumentation()".equals(key)) { |
| 131 | + defaultValueDoc = value; |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + } else if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_SECTION)) { |
| 136 | + final JavaDocParser.SectionHolder sectionHolder = javaDocParser.parseConfigSection(rawJavaDoc, |
| 137 | + sectionLevel); |
| 138 | + |
| 139 | + configSection = new ConfigDocSection(); |
| 140 | + configSection.setWithinAMap(withinAMap); |
| 141 | + configSection.setConfigPhase(configPhase); |
| 142 | + configSection.setSectionDetails(sectionHolder.details); |
| 143 | + configSection.setSectionDetailsTitle(sectionHolder.title); |
| 144 | + configSection.setName(parentName + Constants.DOT + hyphenatedFieldName); |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + if (name.isEmpty()) { |
| 149 | + name = parentName + Constants.DOT + hyphenatedFieldName; |
| 150 | + } |
| 151 | + |
| 152 | + if (Constants.NO_DEFAULT.equals(defaultValue)) { |
| 153 | + defaultValue = Constants.EMPTY; |
| 154 | + } |
| 155 | + if (Constants.EMPTY.equals(defaultValue)) { |
| 156 | + defaultValue = defaultValueDoc; |
| 157 | + } |
| 158 | + |
| 159 | + if (isConfigGroup) { |
| 160 | + List<ConfigDocItem> groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, configGroup, |
| 161 | + configSection, withinAMap, sectionLevel); |
| 162 | + configDocItems.addAll(groupConfigItems); |
| 163 | + } else { |
| 164 | + final ConfigDocKey configDocKey = new ConfigDocKey(); |
| 165 | + configDocKey.setWithinAMap(withinAMap); |
| 166 | + boolean optional = false; |
| 167 | + boolean list = false; |
| 168 | + if (!typeMirror.getKind().isPrimitive()) { |
| 169 | + DeclaredType declaredType = (DeclaredType) typeMirror; |
| 170 | + TypeElement typeElement = (TypeElement) declaredType.asElement(); |
| 171 | + Name qualifiedName = typeElement.getQualifiedName(); |
| 172 | + optional = qualifiedName.toString().startsWith("java.util.Optional"); |
| 173 | + list = qualifiedName.contentEquals("java.util.List"); |
| 174 | + |
| 175 | + List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments(); |
| 176 | + if (!typeArguments.isEmpty()) { |
| 177 | + // FIXME: this is super dodgy: we should check the type!! |
| 178 | + if (typeArguments.size() == 2) { |
| 179 | + final String mapKey = String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); |
| 180 | + type = typeArguments.get(1).toString(); |
| 181 | + configGroup = configGroups.get(type); |
| 182 | + name += mapKey; |
| 183 | + |
| 184 | + if (configGroup != null) { |
| 185 | + name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); |
| 186 | + List<ConfigDocItem> groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, |
| 187 | + configGroup, configSection, true, sectionLevel); |
| 188 | + configDocItems.addAll(groupConfigItems); |
| 189 | + continue; |
| 190 | + } else { |
| 191 | + configDocKey.setWithinAMap(true); |
| 192 | + } |
| 193 | + } else { |
| 194 | + // FIXME: this is for Optional<T> and List<T> |
| 195 | + TypeMirror realTypeMirror = typeArguments.get(0); |
| 196 | + type = simpleTypeToString(realTypeMirror); |
| 197 | + |
| 198 | + if (isEnumType(realTypeMirror)) { |
| 199 | + acceptedValues = extractEnumValues(realTypeMirror); |
| 200 | + } |
| 201 | + } |
| 202 | + } else { |
| 203 | + type = simpleTypeToString(declaredType); |
| 204 | + if (isEnumType(declaredType)) { |
| 205 | + acceptedValues = extractEnumValues(declaredType); |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + final String configDescription = javaDocParser.parseConfigDescription(rawJavaDoc); |
| 211 | + |
| 212 | + configDocKey.setKey(name); |
| 213 | + configDocKey.setType(type); |
| 214 | + configDocKey.setConfigPhase(configPhase); |
| 215 | + configDocKey.setDefaultValue(defaultValue); |
| 216 | + configDocKey.setOptional(optional); |
| 217 | + configDocKey.setList(list); |
| 218 | + configDocKey.setConfigDoc(configDescription); |
| 219 | + configDocKey.setAcceptedValues(acceptedValues); |
| 220 | + configDocKey.setJavaDocSiteLink(getJavaDocSiteLink(type)); |
| 221 | + ConfigDocItem configDocItem = new ConfigDocItem(); |
| 222 | + configDocItem.setConfigDocKey(configDocKey); |
| 223 | + configDocItems.add(configDocItem); |
| 224 | + } |
| 225 | + } |
| 226 | + |
| 227 | + return configDocItems; |
| 228 | + } |
| 229 | + |
| 230 | + private List<ConfigDocItem> recordConfigItemsFromConfigGroup(ConfigPhase configPhase, String name, Element configGroup, |
| 231 | + ConfigDocSection configSection, boolean withinAMap, int sectionLevel) { |
| 232 | + final List<ConfigDocItem> groupConfigItems; |
| 233 | + final List<ConfigDocItem> configDocItems = new ArrayList<>(); |
| 234 | + |
| 235 | + if (configSection != null) { |
| 236 | + final ConfigDocItem configDocItem = new ConfigDocItem(); |
| 237 | + configDocItem.setConfigDocSection(configSection); |
| 238 | + configDocItems.add(configDocItem); |
| 239 | + groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, |
| 240 | + sectionLevel + 1); |
| 241 | + configSection.addConfigDocItems(groupConfigItems); |
| 242 | + } else { |
| 243 | + groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, sectionLevel); |
| 244 | + configDocItems.addAll(groupConfigItems); |
| 245 | + } |
| 246 | + |
| 247 | + String configGroupName = configGroup.asType().toString(); |
| 248 | + List<ConfigDocItem> previousConfigGroupConfigItems = holder.getConfigGroupConfigItems().get(configGroupName); |
| 249 | + if (previousConfigGroupConfigItems == null) { |
| 250 | + holder.addConfigGroupItems(configGroupName, groupConfigItems); |
| 251 | + } else { |
| 252 | + previousConfigGroupConfigItems.addAll(configDocItems); |
| 253 | + } |
| 254 | + |
| 255 | + return configDocItems; |
| 256 | + } |
| 257 | + |
| 258 | + private String simpleTypeToString(TypeMirror typeMirror) { |
| 259 | + if (typeMirror.getKind().isPrimitive()) { |
| 260 | + return typeMirror.toString(); |
| 261 | + } |
| 262 | + |
| 263 | + final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror); |
| 264 | + return knownGenericType != null ? knownGenericType : typeMirror.toString(); |
| 265 | + } |
| 266 | + |
| 267 | + private List<String> extractEnumValues(TypeMirror realTypeMirror) { |
| 268 | + Element declaredTypeElement = ((DeclaredType) realTypeMirror).asElement(); |
| 269 | + List<String> acceptedValues = new ArrayList<>(); |
| 270 | + |
| 271 | + for (Element field : declaredTypeElement.getEnclosedElements()) { |
| 272 | + if (field.getKind() == ElementKind.ENUM_CONSTANT) { |
| 273 | + acceptedValues.add(DocGeneratorUtil.hyphenateEnumValue(field.getSimpleName().toString())); |
| 274 | + } |
| 275 | + } |
| 276 | + |
| 277 | + return acceptedValues; |
| 278 | + } |
| 279 | + |
| 280 | + private boolean isEnumType(TypeMirror realTypeMirror) { |
| 281 | + return realTypeMirror instanceof DeclaredType |
| 282 | + && ((DeclaredType) realTypeMirror).asElement().getKind() == ElementKind.ENUM; |
| 283 | + } |
| 284 | +} |
0 commit comments