|
42 | 42 |
|
43 | 43 | import static io.soabase.recordbuilder.processor.ElementUtils.generateName; |
44 | 44 | import static io.soabase.recordbuilder.processor.ElementUtils.hasAnnotationTarget; |
| 45 | +import static io.soabase.recordbuilder.processor.InternalRecordBuilderProcessor.capitalize; |
45 | 46 | import static io.soabase.recordbuilder.processor.ParameterSpecUtil.createParameterSpec; |
46 | 47 | import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation; |
47 | 48 | import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation; |
@@ -145,60 +146,120 @@ private void addVisibility(Set<Modifier> modifiers) { |
145 | 146 | // is package-private |
146 | 147 | } |
147 | 148 |
|
148 | | - private List<RecordClassType> buildRecordComponents(TypeElement typeElement) { |
149 | | - List<RecordClassType> components = typeElement.getEnclosedElements().stream() |
| 149 | + private static boolean isGetter(Name methodName, Name fieldName) { |
| 150 | + return methodName.toString().equals("get" + capitalize(fieldName.toString())) |
| 151 | + || methodName.toString().equals("is" + capitalize(fieldName.toString())); |
| 152 | + } |
| 153 | + |
| 154 | + private List<ExecutableElement> methodsInClass(TypeElement typeElement) { |
| 155 | + return typeElement.getEnclosedElements().stream() |
150 | 156 | .flatMap(e -> (e.getKind() == ElementKind.METHOD) ? Stream.of((ExecutableElement) e) : Stream.empty()) |
151 | | - .flatMap(executableElement -> { |
152 | | - DeconstructorAccessor deconstructorAccessor = executableElement |
153 | | - .getAnnotation(DeconstructorAccessor.class); |
154 | | - if (deconstructorAccessor == null) { |
155 | | - return Stream.empty(); |
156 | | - } |
| 157 | + .toList(); |
| 158 | + } |
157 | 159 |
|
158 | | - if (!executableElement.getModifiers().contains(Modifier.PUBLIC)) { |
159 | | - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
160 | | - "@DeconstructorAccessor methods must be public.", executableElement); |
161 | | - return Stream.empty(); |
162 | | - } |
| 160 | + private List<RecordClassType> buildRecordComponents(TypeElement typeElement) { |
| 161 | + List<ExecutableElement> methods = methodsInClass(typeElement); |
| 162 | + |
| 163 | + boolean isARecord = typeElement.getKind() == ElementKind.RECORD; |
| 164 | + |
| 165 | + var accessorOrdinal = new Object() { |
| 166 | + int value; |
| 167 | + }; |
| 168 | + List<RecordClassType> components = typeElement.getEnclosedElements().stream().flatMap(element -> { |
| 169 | + DeconstructorAccessor deconstructorAccessor = element.getAnnotation(DeconstructorAccessor.class); |
| 170 | + if (deconstructorAccessor == null) { |
| 171 | + return Stream.empty(); |
| 172 | + } |
| 173 | + |
| 174 | + ExecutableElement executableElement; |
| 175 | + |
| 176 | + if (element.getKind() == ElementKind.METHOD) { |
| 177 | + executableElement = (ExecutableElement) element; |
| 178 | + } else if (element.getKind() == ElementKind.FIELD) { |
| 179 | + if (isARecord) { |
| 180 | + return Stream.empty(); |
| 181 | + } |
| 182 | + |
| 183 | + List<ExecutableElement> candidateGetters = methods.stream() |
| 184 | + .filter(method -> method.getModifiers().contains(Modifier.PUBLIC) |
| 185 | + && !method.getModifiers().contains(Modifier.STATIC)) |
| 186 | + .filter(method -> processingEnv.getTypeUtils().isSameType(method.getReturnType(), |
| 187 | + element.asType())) |
| 188 | + .filter(method -> method.getSimpleName().equals(element.getSimpleName()) |
| 189 | + || isGetter(method.getSimpleName(), element.getSimpleName())) |
| 190 | + .toList(); |
| 191 | + |
| 192 | + if (candidateGetters.isEmpty()) { |
| 193 | + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
| 194 | + "No public getter method found for field annotated with @DeconstructorAccessor: %s" |
| 195 | + .formatted(element.getSimpleName()), |
| 196 | + element); |
| 197 | + return Stream.empty(); |
| 198 | + } else if (candidateGetters.size() > 1) { |
| 199 | + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
| 200 | + "Multiple public getter methods found for field annotated with @DeconstructorAccessor: %s. %s" |
| 201 | + .formatted(element.getSimpleName(), candidateGetters.stream() |
| 202 | + .map(ExecutableElement::getSimpleName).collect(Collectors.joining(", "))), |
| 203 | + element); |
| 204 | + return Stream.empty(); |
| 205 | + } |
| 206 | + |
| 207 | + executableElement = candidateGetters.get(0); |
| 208 | + } else { |
| 209 | + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
| 210 | + "@DeconstructorAccessor can only be applied to methods or fields.", element); |
| 211 | + return Stream.empty(); |
| 212 | + } |
163 | 213 |
|
164 | | - if (executableElement.getModifiers().contains(Modifier.STATIC)) { |
| 214 | + if (!executableElement.getModifiers().contains(Modifier.PUBLIC)) { |
| 215 | + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
| 216 | + "@DeconstructorAccessor methods must be public.", executableElement); |
| 217 | + return Stream.empty(); |
| 218 | + } |
| 219 | + |
| 220 | + if (executableElement.getModifiers().contains(Modifier.STATIC)) { |
| 221 | + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
| 222 | + "@DeconstructorAccessor only valid for non-static methods.", executableElement); |
| 223 | + return Stream.empty(); |
| 224 | + } |
| 225 | + |
| 226 | + TypeName typeName = TypeName.get(executableElement.getReturnType()); |
| 227 | + TypeName rawTypeName = TypeName |
| 228 | + .get(processingEnv.getTypeUtils().erasure(executableElement.getReturnType())); |
| 229 | + |
| 230 | + String name; |
| 231 | + if (deconstructorAccessor.name().isEmpty()) { |
| 232 | + name = executableElement.getSimpleName().toString(); |
| 233 | + if (!deconstructorAccessor.prefixPattern().isEmpty()) { |
| 234 | + try { |
| 235 | + name = extractAndLowercase(Pattern.compile(deconstructorAccessor.prefixPattern()), name); |
| 236 | + } catch (Exception e) { |
165 | 237 | processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
166 | | - "@DeconstructorAccessor only valid for non-static methods.", executableElement); |
167 | | - return Stream.empty(); |
| 238 | + "Invalid prefix pattern: " + deconstructorAccessor.prefixPattern(), element); |
168 | 239 | } |
| 240 | + } |
| 241 | + } else { |
| 242 | + name = deconstructorAccessor.name(); |
| 243 | + } |
169 | 244 |
|
170 | | - TypeName typeName = TypeName.get(executableElement.getReturnType()); |
171 | | - TypeName rawTypeName = TypeName |
172 | | - .get(processingEnv.getTypeUtils().erasure(executableElement.getReturnType())); |
173 | | - |
174 | | - String name; |
175 | | - if (deconstructorAccessor.name().isEmpty()) { |
176 | | - name = executableElement.getSimpleName().toString(); |
177 | | - if (!deconstructorAccessor.prefixPattern().isEmpty()) { |
178 | | - try { |
179 | | - name = extractAndLowercase(Pattern.compile(deconstructorAccessor.prefixPattern()), |
180 | | - name); |
181 | | - } catch (Exception e) { |
182 | | - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
183 | | - "Invalid prefix pattern: " + deconstructorAccessor.prefixPattern(), element); |
184 | | - } |
185 | | - } |
186 | | - } else { |
187 | | - name = deconstructorAccessor.name(); |
188 | | - } |
| 245 | + List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors().stream() |
| 246 | + .filter(annotation -> !annotation.getAnnotationType().asElement().getSimpleName().toString() |
| 247 | + .equals(DeconstructorAccessor.class.getSimpleName())) |
| 248 | + .toList(); |
| 249 | + var type = new RecordClassType(typeName, rawTypeName, name, executableElement.getSimpleName().toString(), |
| 250 | + annotationMirrors, List.of()); |
| 251 | + |
| 252 | + int order = deconstructorAccessor.order(); |
| 253 | + if (order == Integer.MAX_VALUE) { |
| 254 | + order = accessorOrdinal.value++; |
| 255 | + } |
189 | 256 |
|
190 | | - List<? extends AnnotationMirror> annotationMirrors = executableElement.getAnnotationMirrors() |
191 | | - .stream().filter(annotation -> !annotation.getAnnotationType().asElement().getSimpleName() |
192 | | - .toString().equals(DeconstructorAccessor.class.getSimpleName())) |
193 | | - .toList(); |
194 | | - var type = new RecordClassType(typeName, rawTypeName, name, |
195 | | - executableElement.getSimpleName().toString(), annotationMirrors, List.of()); |
196 | | - var orderedType = Map.entry(deconstructorAccessor.order(), type); |
197 | | - return Stream.of(orderedType); |
198 | | - }).sorted((o1, o2) -> { |
199 | | - int diff = o1.getKey().compareTo(o2.getKey()); |
200 | | - return (diff == 0) ? o1.getValue().name().compareTo(o2.getValue().name()) : diff; |
201 | | - }).map(Map.Entry::getValue).toList(); |
| 257 | + var orderedType = Map.entry(order, type); |
| 258 | + return Stream.of(orderedType); |
| 259 | + }).sorted((o1, o2) -> { |
| 260 | + int diff = o1.getKey().compareTo(o2.getKey()); |
| 261 | + return (diff == 0) ? o1.getValue().name().compareTo(o2.getValue().name()) : diff; |
| 262 | + }).map(Map.Entry::getValue).toList(); |
202 | 263 |
|
203 | 264 | if (components.isEmpty()) { |
204 | 265 | processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, |
|
0 commit comments