diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java index f17d9b62c..dbebb5d8d 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/DefaultConverterLoader.java @@ -44,6 +44,7 @@ import org.apache.fesod.sheet.converters.byteconverter.ByteBooleanConverter; import org.apache.fesod.sheet.converters.byteconverter.ByteNumberConverter; import org.apache.fesod.sheet.converters.byteconverter.ByteStringConverter; +import org.apache.fesod.sheet.converters.charconverter.CharacterStringConverter; import org.apache.fesod.sheet.converters.date.DateDateConverter; import org.apache.fesod.sheet.converters.date.DateNumberConverter; import org.apache.fesod.sheet.converters.date.DateStringConverter; @@ -78,6 +79,7 @@ import org.apache.fesod.sheet.converters.string.StringNumberConverter; import org.apache.fesod.sheet.converters.string.StringStringConverter; import org.apache.fesod.sheet.converters.url.UrlImageConverter; +import org.apache.fesod.sheet.converters.year.YearStringConverter; /** * Load default handler @@ -111,6 +113,8 @@ private static void initAllConverter() { putAllConverter(new ByteNumberConverter()); putAllConverter(new ByteStringConverter()); + putAllConverter(new CharacterStringConverter()); + putAllConverter(new DateNumberConverter()); putAllConverter(new DateStringConverter()); @@ -147,6 +151,8 @@ private static void initAllConverter() { putAllConverter(new StringNumberConverter()); putAllConverter(new StringStringConverter()); putAllConverter(new StringErrorConverter()); + + putAllConverter(new YearStringConverter()); allConverter = Collections.unmodifiableMap(allConverter); } @@ -156,6 +162,7 @@ private static void initDefaultWriteConverter() { putWriteConverter(new BigIntegerNumberConverter()); putWriteConverter(new BooleanBooleanConverter()); putWriteConverter(new ByteNumberConverter()); + putWriteConverter(new CharacterStringConverter()); putWriteConverter(new DateDateConverter()); putWriteConverter(new LocalDateTimeDateConverter()); putWriteConverter(new LocalDateDateConverter()); @@ -166,6 +173,7 @@ private static void initDefaultWriteConverter() { putWriteConverter(new LongNumberConverter()); putWriteConverter(new ShortNumberConverter()); putWriteConverter(new StringStringConverter()); + putWriteConverter(new YearStringConverter()); putWriteConverter(new FileImageConverter()); putWriteConverter(new InputStreamImageConverter()); putWriteConverter(new ByteArrayImageConverter()); @@ -177,6 +185,7 @@ private static void initDefaultWriteConverter() { putWriteStringConverter(new BigIntegerStringConverter()); putWriteStringConverter(new BooleanStringConverter()); putWriteStringConverter(new ByteStringConverter()); + putWriteStringConverter(new CharacterStringConverter()); putWriteStringConverter(new DateStringConverter()); putWriteStringConverter(new LocalDateStringConverter()); putWriteStringConverter(new LocalDateTimeStringConverter()); @@ -187,6 +196,7 @@ private static void initDefaultWriteConverter() { putWriteStringConverter(new LongStringConverter()); putWriteStringConverter(new ShortStringConverter()); putWriteStringConverter(new StringStringConverter()); + putWriteStringConverter(new YearStringConverter()); defaultWriteConverter = Collections.unmodifiableMap(defaultWriteConverter); } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/charconverter/CharacterStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/charconverter/CharacterStringConverter.java new file mode 100644 index 000000000..d25ac1e34 --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/charconverter/CharacterStringConverter.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.sheet.converters.charconverter; + +import org.apache.fesod.sheet.converters.Converter; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; + +/** + * Character and string converter + */ +public class CharacterStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return Character.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Character convertToJavaData( + ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + String stringValue = cellData.getStringValue(); + if (stringValue == null || stringValue.isEmpty()) { + return null; + } + if (stringValue.length() > 1) { + throw new IllegalArgumentException( + "Can not convert '" + stringValue + "' to a character, the length must be 1"); + } + return stringValue.charAt(0); + } + + @Override + public WriteCellData convertToExcelData( + Character value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value.toString()); + } +} diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/year/YearStringConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/year/YearStringConverter.java new file mode 100644 index 000000000..4dd2f07e7 --- /dev/null +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/year/YearStringConverter.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.sheet.converters.year; + +import java.time.Year; +import org.apache.fesod.sheet.converters.Converter; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; +import org.apache.fesod.sheet.util.DateUtils; + +/** + * Year and string converter + */ +public class YearStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return Year.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public Year convertToJavaData( + ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + return DateUtils.parseYear( + cellData.getStringValue(), getYearFormat(contentProperty), globalConfiguration.getLocale()); + } + + @Override + public WriteCellData convertToExcelData( + Year value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { + return new WriteCellData<>( + DateUtils.format(value, getYearFormat(contentProperty), globalConfiguration.getLocale())); + } + + private static String getYearFormat(ExcelContentProperty contentProperty) { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return null; + } + return contentProperty.getDateTimeFormatProperty().getFormat(); + } +} diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java index 9fd5365a1..05996158f 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/DateUtils.java @@ -32,6 +32,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.Year; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; @@ -91,6 +92,8 @@ public class DateUtils { public static String defaultLocalDateFormat = DATE_FORMAT_10; + public static String defaultYearFormat = "yyyy"; + public static final String DEFAULT_LOCAL_TIME_FORMAT = TIME_FORMAT_8; public static final int SECONDS_PER_MINUTE = 60; @@ -328,6 +331,39 @@ public static String format(LocalTime time, String timeFormat, Locale local) { return time.format(getCacheDateTimeFormat(timeFormat, local)); } + /** + * convert string to year + * + * @param yearString + * @param yearFormat + * @param local + * @return + */ + public static Year parseYear(String yearString, String yearFormat, Locale local) { + if (StringUtils.isEmpty(yearFormat)) { + yearFormat = defaultYearFormat; + } + return Year.parse(yearString, getCacheDateTimeFormat(yearFormat, local)); + } + + /** + * Format year + * + * @param year Year + * @param yearFormat year format + * @param local local + * @return format string + */ + public static String format(Year year, String yearFormat, Locale local) { + if (year == null) { + return null; + } + if (StringUtils.isEmpty(yearFormat)) { + yearFormat = defaultYearFormat; + } + return year.format(getCacheDateTimeFormat(yearFormat, local)); + } + /** * Format date * diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/charconverter/CharacterStringConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/charconverter/CharacterStringConverterTest.java new file mode 100644 index 000000000..b8143722b --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/charconverter/CharacterStringConverterTest.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.sheet.converters.charconverter; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import org.apache.fesod.sheet.FesodSheet; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.context.AnalysisContext; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.event.AnalysisEventListener; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.testkit.Tags; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.UNIT) +class CharacterStringConverterTest { + + private static final GlobalConfiguration GLOBAL_CONFIGURATION = new GlobalConfiguration(); + private final CharacterStringConverter converter = new CharacterStringConverter(); + + @Test + void supportJavaTypeKey() { + Assertions.assertEquals(Character.class, converter.supportJavaTypeKey()); + } + + @Test + void supportExcelTypeKey() { + Assertions.assertEquals(CellDataTypeEnum.STRING, converter.supportExcelTypeKey()); + } + + @Test + void convertToJavaDataReturnsSingleCharacter() { + Assertions.assertEquals( + Character.valueOf('A'), + converter.convertToJavaData(new ReadCellData<>("A"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToJavaDataReturnsNullForEmptyString() { + Assertions.assertNull(converter.convertToJavaData(new ReadCellData<>(""), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToJavaDataThrowsOnLongerStrings() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> converter.convertToJavaData(new ReadCellData<>("AB"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToExcelDataWritesStringCell() { + WriteCellData cellData = converter.convertToExcelData('A', null, GLOBAL_CONFIGURATION); + Assertions.assertEquals(CellDataTypeEnum.STRING, cellData.getType()); + Assertions.assertEquals("A", cellData.getStringValue()); + } + + @Test + void characterFieldRoundTripsThroughFesodSheet() throws Exception { + File file = File.createTempFile("fesod-character", ".xlsx"); + file.deleteOnExit(); + CharacterWriteData row = new CharacterWriteData(); + row.setFlag('A'); + + FesodSheet.write(file, CharacterWriteData.class).sheet().doWrite(Collections.singletonList(row)); + + List rows = FesodSheet.read(file, CharacterReadData.class, new CharacterReadListener()) + .sheet() + .doReadSync(); + Assertions.assertEquals(1, rows.size()); + Assertions.assertEquals(Character.valueOf('A'), rows.get(0).getFlag()); + } + + @Getter + @Setter + public static class CharacterWriteData { + + @ExcelProperty("flag") + private Character flag; + } + + @Getter + @Setter + public static class CharacterReadData { + + @ExcelProperty("flag") + private Character flag; + } + + public static class CharacterReadListener extends AnalysisEventListener { + + @Override + public void invoke(CharacterReadData data, AnalysisContext context) {} + + @Override + public void doAfterAllAnalysed(AnalysisContext context) {} + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/year/YearStringConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/year/YearStringConverterTest.java new file mode 100644 index 000000000..44959862d --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converters/year/YearStringConverterTest.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.sheet.converters.year; + +import java.io.File; +import java.time.DateTimeException; +import java.time.Year; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import lombok.Getter; +import lombok.Setter; +import org.apache.fesod.sheet.FesodSheet; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.context.AnalysisContext; +import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.event.AnalysisEventListener; +import org.apache.fesod.sheet.metadata.GlobalConfiguration; +import org.apache.fesod.sheet.metadata.data.ReadCellData; +import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.metadata.property.DateTimeFormatProperty; +import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; +import org.apache.fesod.sheet.testkit.Tags; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.UNIT) +class YearStringConverterTest { + + private static final GlobalConfiguration GLOBAL_CONFIGURATION = new GlobalConfiguration(); + private final YearStringConverter converter = new YearStringConverter(); + + @Test + void supportJavaTypeKey() { + Assertions.assertEquals(Year.class, converter.supportJavaTypeKey()); + } + + @Test + void supportExcelTypeKey() { + Assertions.assertEquals(CellDataTypeEnum.STRING, converter.supportExcelTypeKey()); + } + + @Test + void convertToJavaDataUsesDefaultFormat() { + Assertions.assertEquals( + Year.of(2026), converter.convertToJavaData(new ReadCellData<>("2026"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToJavaDataThrowsOnInvalidValue() { + Assertions.assertThrows( + DateTimeException.class, + () -> converter.convertToJavaData(new ReadCellData<>("not-a-year"), null, GLOBAL_CONFIGURATION)); + } + + @Test + void convertToExcelDataUsesDefaultFormat() { + WriteCellData cellData = converter.convertToExcelData(Year.of(2026), null, GLOBAL_CONFIGURATION); + Assertions.assertEquals(CellDataTypeEnum.STRING, cellData.getType()); + Assertions.assertEquals("2026", cellData.getStringValue()); + } + + @Test + void convertToExcelDataUsesCustomFormat() { + ExcelContentProperty contentProperty = new ExcelContentProperty(); + contentProperty.setDateTimeFormatProperty(new DateTimeFormatProperty("'Y'yyyy", null)); + WriteCellData cellData = converter.convertToExcelData(Year.of(2026), contentProperty, GLOBAL_CONFIGURATION); + Assertions.assertEquals("Y2026", cellData.getStringValue()); + } + + @Test + void convertToExcelDataRespectsLocaleForEraPatterns() { + GlobalConfiguration chinaConfiguration = new GlobalConfiguration(); + chinaConfiguration.setLocale(Locale.CHINA); + GlobalConfiguration usConfiguration = new GlobalConfiguration(); + usConfiguration.setLocale(Locale.US); + ExcelContentProperty contentProperty = new ExcelContentProperty(); + contentProperty.setDateTimeFormatProperty(new DateTimeFormatProperty("G y", null)); + + WriteCellData chinaCellData = + converter.convertToExcelData(Year.of(2026), contentProperty, chinaConfiguration); + Assertions.assertEquals("公元 2026", chinaCellData.getStringValue()); + + WriteCellData usCellData = converter.convertToExcelData(Year.of(2026), contentProperty, usConfiguration); + Assertions.assertEquals("AD 2026", usCellData.getStringValue()); + } + + @Test + void yearFieldRoundTripsThroughFesodSheet() throws Exception { + File file = File.createTempFile("fesod-year", ".xlsx"); + file.deleteOnExit(); + YearWriteData row = new YearWriteData(); + row.setFlag(Year.of(2026)); + + // A Year field is written through the full write path: without a wildcard registration + // (putWriteConverter) the xlsx lookup key (Year, null) finds no converter and throws. + FesodSheet.write(file, YearWriteData.class).sheet().doWrite(Collections.singletonList(row)); + + List rows = FesodSheet.read(file, YearReadData.class, new YearReadListener()) + .sheet() + .doReadSync(); + Assertions.assertEquals(1, rows.size()); + Assertions.assertEquals(Year.of(2026), rows.get(0).getFlag()); + } + + @Getter + @Setter + public static class YearWriteData { + + @ExcelProperty("flag") + private Year flag; + } + + @Getter + @Setter + public static class YearReadData { + + @ExcelProperty("flag") + private Year flag; + } + + public static class YearReadListener extends AnalysisEventListener { + + @Override + public void invoke(YearReadData data, AnalysisContext context) {} + + @Override + public void doAfterAllAnalysed(AnalysisContext context) {} + } +} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java index a31454da7..a14a38e64 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/DateUtilsTest.java @@ -26,6 +26,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.Year; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; @@ -499,4 +500,27 @@ void test_removeThreadLocalCache() throws NoSuchFieldException, IllegalAccessExc Assertions.assertNull(((ThreadLocal) f2.get(null)).get()); Assertions.assertNull(((ThreadLocal) f3.get(null)).get()); } + + @Test + void test_parseYearUsesDefaultFormat() { + Assertions.assertEquals(Year.of(2026), DateUtils.parseYear("2026", null, null)); + Assertions.assertEquals(Year.of(2026), DateUtils.parseYear("2026", null, Locale.US)); + } + + @Test + void test_parseYearUsesCustomFormat() { + Assertions.assertEquals(Year.of(2026), DateUtils.parseYear("26", "uu", null)); + } + + @Test + void test_formatYearUsesDefaultFormat() { + Assertions.assertEquals("2026", DateUtils.format(Year.of(2026), null, null)); + Assertions.assertEquals("2026", DateUtils.format(Year.of(2026), null, Locale.US)); + } + + @Test + void test_formatYearRespectsLocaleForEraPatterns() { + Assertions.assertEquals("公元 2026", DateUtils.format(Year.of(2026), "G y", Locale.CHINA)); + Assertions.assertEquals("AD 2026", DateUtils.format(Year.of(2026), "G y", Locale.US)); + } }