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

hee9841 / excel-module / #37

25 Apr 2025 09:07AM UTC coverage: 82.791%. First build
#37

push

github

web-flow
Release 0.0.1 (#58)

* Docs: 초기 배포를 위한 docs 추가 (#21)

* Docs: README 추가

* Chore: gitignore에 maven pushing에 사용할 설정 파일 추가

* Deploy: Coveralls 추가, CI 추가 (#22)

* Release 0.0.1

* Deploy: 배포시 Readme 버전 설정 추가 (#25)

* Deploy: 베포시 readme 버전 설정

* Fix: 테스트로 추가했던 dev 브런치 삭제

* Docs: javadocs 관련 의존성 추가 및 배포된 maven repository url 추가 (#30)

* Docs: javaDocs 만들기 위한 추가 의존성 추가

* Docs: Maven Central url README에 추가

* Fix: slf4j-api 의존성의 implementation으로 변경 (#33)

* Feat: 로그 info 메세지 변경 (#34)

* Feat: 로깅 메세지 변경

* Feat: 로그 info 메세지 변경(dto 클래스명 SimpleName에서 패키지 포함으로 변경)

* Feat: Excel, ExcelColumn 어노테이션 정합성 예외 처리 (#35)

* Feat: SystemValues 클레스에 ExcelColumn에 허용되는 타입들 추가

* Feat: Excel, ExcelColumn 어노테이션 관련 적합성에 대한 예외 처리 추가

* Fix: ExcelColumnAnnotationProcessor에 AutoService import

* Fix: Supported source version에 대한 경고 제거 (#36)

* Fix: AutoService 제거

- 의존성 삭제
- 프로세서에 어노테이션 제거

* Fix: 어노테이션 프로세서 SourceVersion을 latestSupported로 변경

* Fix: poi 의존성을 implementation에서 api로 변경 (#38)

* Fix: 에러 메세지 수정(STY_CU_003_B, STG_CT_001_B,STG_ID_002_B 테스트 사항) (#46)

* Fix: 에러 메세지 수정(STY_CU_003_B, STG_CT_001_B,STG_ID_002_B 테스트 사항)

* Style: comment, 및 포멧 수정

* Fix: compile 옵션 변경, toolchain java 버전 변경 (#47)

* Chore: gradle 버전 변경 gradle-8.5 -> gradle-8.10

* Chore: java compile source, target 8 에서 release 8로 변경

- toolchain을 23으로 변경

* Refactor: ExcelExporter의 validate 메서드 추가(data size, sheet Strategy에 따른) (#48)

- validate 오버라이딩으로 생성자의 setSheetStrategy 메서드호출과 initialize 호출 시점 변경,

* Refactor: VerticalAlignment의 default 값을 CENTER로 설정 (#49)

* Refactor: poi 라이브러리의 클래스명과 겹치는 클래스명 변경 (#50)

* Rename: IndexedColor 관련 클래스명 rename

- IndexedColors.java ->  ColorPalette.java
- IndexedExcelColor.java -> PaletteExcelColor.java

* Rename: CellType.java -> ColumnDataType.java으로 클래스명 변경

* Rename: Alignment 관련 Enum 클래스 'Excel' 접두사 추가

* Rename: BorderStyle.java -> ExcelBorderStyle.java로 변경

* Refactor: Excel, ExcelColumn 어노테이션 프로세서 추가 구현 (#52)

* Refactor: ExcelAnnotationProcessor와 ExcelColumnAn... (continued)

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