• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hee9841 / excel-module / #35

25 Apr 2025 09:05AM UTC coverage: 82.791%. First build
#35

Pull #58

github

hee9841
Revert "Chore: 배포를 위한 PR template 추가 (#54)"

This reverts commit bd34b2c5e.
Pull Request #58: Release 0.0.1

105 of 168 new or added lines in 16 files covered. (62.5%)

534 of 645 relevant lines covered (82.79%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/io/github/hee9841/excel/annotation/processor/ExcelAnnotationProcessor.java
1
package io.github.hee9841.excel.annotation.processor;
2

3
import static io.github.hee9841.excel.global.SystemValues.ALLOWED_FIELD_TYPES;
4
import static io.github.hee9841.excel.global.SystemValues.ALLOWED_FIELD_TYPES_STRING;
5

6
import io.github.hee9841.excel.annotation.Excel;
7
import io.github.hee9841.excel.annotation.ExcelColumn;
8
import java.util.HashMap;
9
import java.util.Map;
10
import java.util.Objects;
11
import java.util.Set;
12
import javax.annotation.processing.AbstractProcessor;
13
import javax.annotation.processing.Messager;
14
import javax.annotation.processing.ProcessingEnvironment;
15
import javax.annotation.processing.RoundEnvironment;
16
import javax.annotation.processing.SupportedAnnotationTypes;
17
import javax.lang.model.SourceVersion;
18
import javax.lang.model.element.Element;
19
import javax.lang.model.element.ElementKind;
20
import javax.lang.model.element.Modifier;
21
import javax.lang.model.element.TypeElement;
22
import javax.lang.model.type.TypeKind;
23
import javax.lang.model.type.TypeMirror;
24
import javax.lang.model.util.Elements;
25
import javax.lang.model.util.Types;
26
import javax.tools.Diagnostic;
27

28
/**
29
 * Annotation processor for handling {@code @Excel} and {@code @ExcelColumn} annotations.
30
 * This processor validates the relationship between these annotations and ensures they meet
31
 * the required criteria for Excel processing.
32
 *
33
 * <p>Validation rules:
34
 * <ul>
35
 *     <li>Classes annotated with {@code @Excel} must have at least one field annotated with {@code @ExcelColumn}</li>
36
 *     <li>Fields annotated with {@code @ExcelColumn} must be in a class annotated with {@code @Excel}</li>
37
 *     <li>Classes annotated with {@code @Excel} must be either regular classes or record classes</li>
38
 *     <li>Regular classes annotated with {@code @Excel} must not be abstract</li>
39
 *     <li>Fields annotated with {@code @ExcelColumn} must be of supported types</li>
40
 * </ul>
41
 *
42
 * <p>Supported field types for {@code @ExcelColumn}:
43
 * <ul>
44
 *   <li>String</li>
45
 *   <li>Character/char</li>
46
 *   <li>Numeric types (Byte, Short, Integer, Long, Float, Double and their primitives)</li>
47
 *   <li>Boolean/boolean</li>
48
 *   <li>Date/Time types (LocalDate, LocalDateTime, Date, java.sql.Date)</li>
49
 *   <li>Enum types</li>
50
 * </ul>
51
 *
52
 * <p>Note: Array types are not supported for {@code @ExcelColumn} fields.
53
 */
54
@SupportedAnnotationTypes({
55
    "io.github.hee9841.excel.annotation.Excel",
56
    "io.github.hee9841.excel.annotation.ExcelColumn"
57
})
58
public class ExcelAnnotationProcessor extends AbstractProcessor {
×
59

60
    private Messager messager;
61
    private Types typeUtils;
62
    private Elements elementUtils;
63

64
    @Override
65
    public synchronized void init(ProcessingEnvironment processingEnv) {
66
        super.init(processingEnv);
×
67
        messager = processingEnv.getMessager();
×
NEW
68
        typeUtils = processingEnv.getTypeUtils();
×
NEW
69
        elementUtils = processingEnv.getElementUtils();
×
NEW
70
    }
×
71

72
    @Override
73
    public SourceVersion getSupportedSourceVersion() {
NEW
74
        return SourceVersion.latestSupported();
×
75
    }
76

77
    @Override
78
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
79
        boolean hasError = false;
×
80

81
        // Map to track Excel-annotated classes and their ExcelColumn fields
NEW
82
        Map<TypeElement, Boolean> excelClasses = new HashMap<>();
×
83

84
        // First pass: collect all Excel-annotated classes
85
        for (Element element : roundEnv.getElementsAnnotatedWith(Excel.class)) {
×
NEW
86
            TypeElement typeElement = (TypeElement) element;
×
NEW
87
            excelClasses.put(typeElement, false);
×
88

89
            if (!isValidExcelClass(element)) {
×
90
                hasError = true;
×
91
            }
92
        }
×
93

94
        // Second pass: validate ExcelColumn annotations
NEW
95
        for (Element element : roundEnv.getElementsAnnotatedWith(ExcelColumn.class)) {
×
NEW
96
            if (!isAllowedType(element)) {
×
NEW
97
                hasError = true;
×
98
            }
99

NEW
100
            TypeElement enclosingClass = (TypeElement) element.getEnclosingElement();
×
101

102
            // Check if the enclosing class has @Excel annotation
NEW
103
            if (!excelClasses.containsKey(enclosingClass)) {
×
NEW
104
                error(element,
×
105
                    "Field %s annotated with @ExcelColumn must be in a class annotated with @Excel",
NEW
106
                    element.getSimpleName());
×
NEW
107
                hasError = true;
×
NEW
108
                continue;
×
109
            }
110

111
            // Add the ExcelColumn field to the class's set
NEW
112
            excelClasses.put(enclosingClass, true);
×
NEW
113
        }
×
114

115
        // Third pass: check if Excel-annotated classes have at least one ExcelColumn
NEW
116
        for (Map.Entry<TypeElement, Boolean> entry : excelClasses.entrySet()) {
×
NEW
117
            if (!entry.getValue()) {
×
NEW
118
                error(entry.getKey(),
×
119
                    "Class %s annotated with @Excel must have at least one field annotated with @ExcelColumn",
NEW
120
                    entry.getKey().getSimpleName());
×
NEW
121
                hasError = true;
×
122
            }
NEW
123
        }
×
124

125
        return !hasError;
×
126
    }
127

128
    /**
129
     * Validates if the element annotated with {@code @Excel} meets the required criteria.
130
     * The following conditions are checked:
131
     * <ul>
132
     *     <li>Element must be either a regular class or a record class</li>
133
     *     <li>If it's a regular class, it must not be abstract</li>
134
     * </ul>
135
     *
136
     * @param element the element to validate, typically a class or record annotated with
137
     *                {@code @Excel}
138
     * @return true if the element meets all validation criteria, false if any validation fails
139
     * (error messages will be reported via {@link #error(Element, String, Object...)})
140
     * @see Excel
141
     */
142
    private boolean isValidExcelClass(Element element) {
143

144
        // Check for record class in Java 14 and above
145
        if (element.getKind().name().equals("RECORD")) {
×
146
            return true;
×
147
        }
148

149
        if (element.getKind() != ElementKind.CLASS) {
×
150
            error(element,
×
151
                "@%s can only be applied to classes or record classes",
152
                Excel.class.getSimpleName()
×
153
            );
154
            return false;
×
155
        }
156

157
        TypeElement typeElement = (TypeElement) element;
×
158

159
        // The class must not be an abstract class.
160
        if (typeElement.getModifiers().contains(Modifier.ABSTRACT)) {
×
161
            error(element,
×
162
                "The class %s is abstract. You can't annotate abstract classes with @%s",
163
                typeElement.getQualifiedName().toString(), Excel.class.getSimpleName());
×
164

165
            return false;
×
166
        }
167

168
        return true;
×
169
    }
170

171

172
    /**
173
     * Checks if the annotated element has an allowed field type.
174
     * Validates that the element is a field and checks if its type is either an enum
175
     * or one of the supported types defined in ALLOWED_TYPES. Array types are not allowed.
176
     *
177
     * @param element the element to check
178
     * @return true if the element has an allowed type, false otherwise
179
     */
180
    private boolean isAllowedType(Element element) {
181
        // 1.If element is enum type, return true
NEW
182
        if (!element.getKind().isField()) {
×
NEW
183
            error(element, "@ExcelColumn can only be applied to field type");
×
NEW
184
            return false;
×
185
        }
186

NEW
187
        TypeMirror typeMirror = element.asType();
×
188

189
        // 3. If element is array type, return false
NEW
190
        if (typeMirror.getKind() == TypeKind.ARRAY) {
×
NEW
191
            error(element, "@ExcelColumn cannot be applied to array type");
×
NEW
192
            return false;
×
193
        }
194

NEW
195
        if (isEnumType(typeMirror)) {
×
NEW
196
            return true;
×
197
        }
198

199
        // 4.Primitive type check
NEW
200
        if (typeMirror.getKind().isPrimitive()) {
×
NEW
201
            return true;
×
202
        }
203

204
        //5.Check if type is in allowed types list
NEW
205
        if (typeMirror.getKind() == TypeKind.DECLARED) {
×
NEW
206
            boolean isAllowed = ALLOWED_FIELD_TYPES.stream()
×
NEW
207
                .map(clazz -> elementUtils.getTypeElement(clazz.getTypeName()))
×
NEW
208
                .filter(Objects::nonNull)
×
NEW
209
                .map(TypeElement::asType)
×
NEW
210
                .anyMatch(allowedType ->
×
NEW
211
                    typeUtils.isAssignable(typeMirror, allowedType));
×
212

NEW
213
            if (!isAllowed) {
×
NEW
214
                error(element, "@ExcelColumn can only be applied to allowed types(%s).",
×
215
                    ALLOWED_FIELD_TYPES_STRING);
NEW
216
                return false;
×
217
            }
218

NEW
219
            return true;
×
220
        }
221

NEW
222
        return false;
×
223
    }
224

225
    /**
226
     * Checks if the given TypeMirror represents an enum type.
227
     *
228
     * @param typeMirror the type to check
229
     * @return true if the type is an enum, false otherwise
230
     */
231
    private boolean isEnumType(TypeMirror typeMirror) {
NEW
232
        TypeElement typeElement = (TypeElement) typeUtils.asElement(typeMirror);
×
NEW
233
        return typeElement != null && typeElement.getKind() == ElementKind.ENUM;
×
234
    }
235

236

237
    /**
238
     * Reports an error for the given element using the processor's message.
239
     *
240
     * @param e    the element for which to report the error
241
     * @param msg  the error message format string
242
     * @param args the arguments to be used in the formatted message
243
     */
244
    private void error(Element e, String msg, Object... args) {
245
        messager.printMessage(
×
246
            Diagnostic.Kind.ERROR,
247
            String.format(msg, args),
×
248
            e);
249
    }
×
250
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc