diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 5b60111..57e6834 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -17,12 +17,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: 'liberica' cache: maven @@ -30,7 +30,7 @@ jobs: run: mvn --batch-mode clean test - name: Test Coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index d58dfb7..c0bcafe 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,3 @@ -# 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. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/README.md b/README.md index 54349cb..c75986d 100644 --- a/README.md +++ b/README.md @@ -93,11 +93,12 @@ void parse() { // ... или reportPageFactory.create("1.xml"); // ... или reportPageFactory.create("1.csv"); - // Метод найдет ячейку с текстом "Таблица 1", - // воспринимает следующую за ней строку как заголовок таблицы (который описан через enum TableHeader). - // Из последующих строк (до пустой строки или конца файла) извлекаются данные - // (метод использует бин ExcelTableFactory для создания таблицы Table на основе ReportPage) - Table table = reportPage.create("Таблица 1", TableHeader.class); + // Регистронезависимо найдет ячейку с текстом "Таблица 1" - это имя таблицы. + // Имя таблицы описано в единственной строке (2ой аргумент). + // Парсит следующую 1 строку (5ый аргумент) как заголовок таблицы (заголовок описан с помощью enum TableHeader). + // Последующие строки парсятся как данные до пустой строки или конца файла (3ий аргумент 'null') + // (метод использует бин ExcelTableFactory для создания таблицы Table на основе ReportPage). + Table table = reportPage.createTable("Таблица 1", 1, null, TableHeader.class, 1); // Итерируемся по строкам таблицы и извлекаем ячейки из строк по заголовку таблицы table.stream() @@ -107,3 +108,24 @@ void parse() { }); } ``` + +### Чтение таблиц из файла с вашим форматом данных +Автоконфигурация поставляет зависимости для чтения таблиц из excel, xml и csv файлов. +Если у вас есть собственная реализация формата [Table Wrapper API](https://github.com/spacious-team/table-wrapper-api), +прочтите таблицу следующим образом: +```java +// 1. Выполните регистрацию фабрики вашего формата данных +CustomTableFactory factory = new CustomTableFactory(); // CustomTableFactory implements TableFactory +TableFactoryRegistry.add(factory); + +// 2. Создайте объект вашего класса доступа к данным, например из файла (может быть любой источник, кроме файла) +CustomReportPage reportPage = new CustomReportPage("My file.txt"); // CustomReportPage implements ReportPage + +// 3. Прочитайте таблицу из вашего файла тем же API, что и для excel, xml, csv файла +Table table = reportPage.createTable("Таблица 1", 1, null, TableHeader.class, 1); +table.stream() + .forEach(row -> { + String product = row.getStringCellValue(TableHeader.PRODUCT); + BigDecimal price = getBigDecimalCellValue(TableHeader.PRICE); + }); +``` diff --git a/checkerframework.astub b/checkerframework.astub new file mode 100644 index 0000000..0b9b199 --- /dev/null +++ b/checkerframework.astub @@ -0,0 +1,40 @@ +/* + * Table Wrapper Spring Boot Starter + * Copyright (C) 2026 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.function.Supplier; + +package java.util; +public class Objects { + + @EnsuresNonNull("#1") + static T requireNonNull(@Nullable T obj); + + @EnsuresNonNull("#1") + static T requireNonNull(@Nullable T obj, String message); + + @EnsuresNonNull("#1") + static T requireNonNull(@Nullable T obj, Supplier messageSupplier); + + @EnsuresNonNullIf(expression="#1", result=true) + static boolean nonNull(@Nullable Object obj); + + @EnsuresNonNullIf(expression="#1", result=false) + static boolean isNull(@Nullable Object obj); +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..2a54778 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,9 @@ +# https://docs.codecov.com/docs/codecov-yaml +coverage: + status: + project: + default: + threshold: 5% # allows to drop coverage (https://docs.codecov.com/docs/commit-status#project-status) + patch: + default: + target: 0% # allows no tests in PR (https://docs.codecov.com/docs/commit-status#patch-status) diff --git a/mvnw b/mvnw index 19529dd..bd8896b 100644 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- @@ -105,14 +105,17 @@ trim() { printf "%s" "${1}" | tr -d '[:space:]' } +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) @@ -130,7 +133,7 @@ maven-mvnd-*bin.*) distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME @@ -227,7 +230,7 @@ if [ -n "${distributionSha256Sum-}" ]; then echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then @@ -252,8 +255,41 @@ if command -v unzip >/dev/null; then else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 249bdf3..92450f9 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -19,7 +19,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @@ -40,7 +40,7 @@ @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> @@ -73,16 +73,30 @@ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" + +$MAVEN_M2_PATH = "$HOME/.m2" if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" } -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { @@ -134,7 +148,33 @@ if ($distributionSha256Sum) { # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { diff --git a/pom.xml b/pom.xml index d4a2572..1a59b80 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.spacious-team table-wrapper-spring-boot-starter - 2025.2 + 2026.1 jar Spring Boot Starter for Table Wrapper @@ -31,7 +31,8 @@ UTF-8 UTF-8 2.7.18 - 1.18.38 + 1.18.46 + 3.55.1 @@ -54,7 +55,7 @@ com.github.spacious-team table-wrapper-api - 2025.1 + 2026.1 @@ -63,17 +64,17 @@ com.github.spacious-team table-wrapper-excel-impl - 2025.1 + 2026.1 com.github.spacious-team table-wrapper-xml-impl - 2023.1 + 2026.1 com.github.spacious-team table-wrapper-csv-impl - 2024.1 + 2026.1 org.springframework.boot @@ -93,16 +94,22 @@ provided true + + org.checkerframework + checker-qual + ${checkerframework.version} + provided + org.junit.jupiter junit-jupiter - 5.12.2 + 5.14.4 test org.mockito mockito-junit-jupiter - 5.18.0 + 5.23.0 test @@ -118,7 +125,7 @@ maven-surefire-plugin - 3.5.3 + 3.5.6 @@ -126,12 +133,54 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.0 + 3.15.0 + + true + true + true + + + org.projectlombok + lombok + ${lombok.version} + + + org.checkerframework + checker + ${checkerframework.version} + + + + + lombok.launch.AnnotationProcessorHider$AnnotationProcessor + + + org.checkerframework.checker.nullness.NullnessChecker + + + + + -AskipDefs=.*Test + + -Astubs=${project.basedir}/checkerframework.astub + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + + org.jacoco jacoco-maven-plugin - 0.8.13 + 0.8.15 prepare-agent @@ -151,7 +200,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + 3.4.0 attach-sources diff --git a/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactory.java b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactory.java index 12e7ecf..fe29230 100644 --- a/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactory.java +++ b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactory.java @@ -20,6 +20,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.spacious_team.table_wrapper.api.ReportPage; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; @@ -63,12 +64,19 @@ private BeanDefinitionRegistry getBeanDefinitionRegistry() { @Override public ReportPage create(Object... args) { + @MonotonicNonNull Exception last = null; for (Class clazz : registeredReportPageTypes) { try { return context.getBean(clazz, args); - } catch (Exception ignore) { + } catch (Exception e) { + last = e; } } - throw new ReportPageInstantiationException("Can't create ReportPage with arguments: " + List.of(args)); + String message = "Can't create ReportPage with arguments: " + List.of(args); + if (last == null) { + throw new ReportPageInstantiationException(message); + } else { + throw new ReportPageInstantiationException(message, last); + } } } diff --git a/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactory.java b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactory.java index c9e7cf1..df110a0 100644 --- a/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactory.java +++ b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactory.java @@ -25,14 +25,18 @@ import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spacious_team.table_wrapper.api.ReportPage; +import org.spacious_team.table_wrapper.api.TableCell; import org.spacious_team.table_wrapper.api.TableCellAddress; import org.spacious_team.table_wrapper.csv.CsvReportPage; import org.spacious_team.table_wrapper.excel.ExcelSheet; import org.spacious_team.table_wrapper.xml.XmlReportPage; import org.springframework.util.Assert; import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import javax.xml.parsers.ParserConfigurationException; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -41,7 +45,7 @@ import java.nio.file.Path; import static java.nio.file.StandardOpenOption.READ; -import static java.util.Objects.nonNull; +import static java.util.Objects.requireNonNull; import static org.spacious_team.table_wrapper.autoconfigure.DefaultReportPageFactory.KnownFileExtension.XLS; import static org.spacious_team.table_wrapper.autoconfigure.DefaultReportPageFactory.KnownFileExtension.XLSX; @@ -69,12 +73,12 @@ protected ReportPage doCreate(Path path, Object sheetId) { switch (extension) { case XLS: case XLSX: - Assert.isTrue(nonNull(sheetId), "Excel file's sheet number or name expected"); + Assert.notNull(sheetId, "Excel file's sheet number or name expected"); is = openForRead(path); Sheet sheet = getExcelSheet(is, sheetId, extension); return new ExcelSheet(sheet); case XML: - Assert.isTrue(nonNull(sheetId), "Xml file's sheet number or name expected"); + Assert.notNull(sheetId, "Xml file's sheet number or name expected"); is = openForRead(path); Worksheet worksheet = getXmlSheet(is, sheetId); return new XmlReportPage(worksheet); @@ -124,7 +128,8 @@ private static Workbook getExcelWorkbook(InputStream is, KnownFileExtension exte } } - private static Worksheet getXmlSheet(InputStream is, Object sheetId) throws Exception { + private static Worksheet getXmlSheet(InputStream is, Object sheetId) + throws ParserConfigurationException, IOException, SAXException { nl.fountain.xelem.excel.Workbook workbook = getXmlWorkbook(is); if (sheetId instanceof Integer) { Worksheet worksheet = workbook.getWorksheetAt((Integer) sheetId); @@ -140,7 +145,8 @@ private static Worksheet getXmlSheet(InputStream is, Object sheetId) throws Exce throw new ReportPageInstantiationException("Unexpected Excel Sheet identifier type:" + sheetIdType); } - private static nl.fountain.xelem.excel.Workbook getXmlWorkbook(InputStream is) throws Exception { + private static nl.fountain.xelem.excel.Workbook getXmlWorkbook(InputStream is) + throws ParserConfigurationException, SAXException, IOException { ExcelReader reader = new ExcelReader(); is = skipNewLines(is); // required by ExcelReader InputSource source = new InputSource(is); @@ -169,7 +175,7 @@ public ReportPage create(InputStream is, String sheetName) { return doCreate(is, sheetName); } - public ReportPage doCreate(InputStream is, Object sheetId) { + protected ReportPage doCreate(InputStream is, Object sheetId) { try { ByteArrayInputStream bais = convertToByteArrayInputStream(is); // try Excel file @@ -212,15 +218,15 @@ public static ByteArrayInputStream convertToByteArrayInputStream(InputStream inp } } - @SuppressWarnings("DataFlowIssue") private static boolean isEmptyCsvReportPage(CsvReportPage reportPage) { if (reportPage.getLastRowNum() == -1) { return true; } // has only one Cell with NULL value + @Nullable TableCell cell; return reportPage.getLastRowNum() == 0 && - reportPage.getCell(TableCellAddress.of(0, 0)) != null && - reportPage.getRow(0).getCell(0).getValue() == null && + (cell = reportPage.getCell(TableCellAddress.of(0, 0))) != null && + cell.getValue() == null && reportPage.getCell(TableCellAddress.of(0, 1)) == null && reportPage.getCell(TableCellAddress.of(1, 0)) == null; } @@ -229,8 +235,8 @@ enum KnownFileExtension { XLS, XLSX, XML, CSV; static KnownFileExtension valueOf(Path path) { - String fileName = path.getFileName().toString(); - String[] fileNameParts = fileName.split("\\."); + Path fileName = requireNonNull(path.getFileName(), () -> "No file name: " + path); + String[] fileNameParts = fileName.toString().split("\\."); String extension = fileNameParts[fileNameParts.length - 1]; return valueOf(extension.toUpperCase()); } diff --git a/src/main/java/org/spacious_team/table_wrapper/autoconfigure/TableWrapperAutoConfiguration.java b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/TableWrapperAutoConfiguration.java index 17d6189..732c256 100644 --- a/src/main/java/org/spacious_team/table_wrapper/autoconfigure/TableWrapperAutoConfiguration.java +++ b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/TableWrapperAutoConfiguration.java @@ -19,6 +19,7 @@ package org.spacious_team.table_wrapper.autoconfigure; +import lombok.NoArgsConstructor; import org.spacious_team.table_wrapper.api.TableFactoryRegistry; import org.spacious_team.table_wrapper.csv.CsvReportPage; import org.spacious_team.table_wrapper.csv.CsvTableFactory; @@ -35,8 +36,11 @@ import java.util.stream.Stream; +import static lombok.AccessLevel.PRIVATE; + @AutoConfiguration @SuppressWarnings("unused") +@NoArgsConstructor(access = PRIVATE) @ConditionalOnClass(TableFactoryRegistry.class) public class TableWrapperAutoConfiguration { diff --git a/src/main/java/org/spacious_team/table_wrapper/autoconfigure/package-info.java b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/package-info.java new file mode 100644 index 0000000..b5d3811 --- /dev/null +++ b/src/main/java/org/spacious_team/table_wrapper/autoconfigure/package-info.java @@ -0,0 +1,23 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@DefaultQualifier(NonNull.class) +package org.spacious_team.table_wrapper.autoconfigure; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; diff --git a/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactoryTest.java index 4a52d79..6867dbf 100644 --- a/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactoryTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultContextAwareReportPageFactoryTest.java @@ -24,6 +24,7 @@ import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -45,7 +46,7 @@ import static org.spacious_team.table_wrapper.autoconfigure.ReportPageFactoryTestFileCreator.*; @SpringBootTest(classes = TableWrapperAutoConfiguration.class) -public class DefaultContextAwareReportPageFactoryTest { +class DefaultContextAwareReportPageFactoryTest { @Autowired DefaultContextAwareReportPageFactory factory; @@ -155,6 +156,20 @@ void create_unknownConstructorArgTypes_exception() { assertThrows(ReportPageInstantiationException.class, () -> factory.create("arg1", "arg2", "arg3")); } + @Test + void create_noRegisteredReportPageTypes_exception() throws IOException { + List> registeredTypes = List.copyOf(factory.getRegisteredReportPageTypes()); + try { + factory.getRegisteredReportPageTypes().clear(); + createCsvFile("file1.any"); + Object path1 = getPath("file1.any"); + + assertThrows(ReportPageInstantiationException.class, () -> factory.create(path1)); + } finally { + factory.getRegisteredReportPageTypes().addAll(registeredTypes); + } + } + @Test void registerBeanDefinition() { int registeredTypes = factory.getRegisteredReportPageTypes().size(); @@ -187,8 +202,7 @@ public TableCellAddress find(int startRow, int endRow, } @Override - @SuppressWarnings("ReturnOfNull") - public ReportPageRow getRow(int i) { + public @Nullable ReportPageRow getRow(int i) { return null; } diff --git a/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactoryTest.java index 1c84863..fe4d03a 100644 --- a/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactoryTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/autoconfigure/DefaultReportPageFactoryTest.java @@ -34,7 +34,7 @@ import static org.mockito.Mockito.*; import static org.spacious_team.table_wrapper.autoconfigure.ReportPageFactoryTestFileCreator.*; -public class DefaultReportPageFactoryTest { +class DefaultReportPageFactoryTest { DefaultReportPageFactory factory = new DefaultReportPageFactory(); @@ -54,7 +54,7 @@ void create_firstSheetByPath_ok(String fileName) { Path path = getPath(fileName); assertNotNull(factory.create(path)); assertNotNull(factory.create(path, 0)); - assertNotNull(factory.create(path, sheetName)); + assertNotNull(factory.create(path, SHEET_NAME)); } @ParameterizedTest @@ -66,7 +66,7 @@ void create_firstSheetByInputStream_ok(String fileName) throws IOException { is.reset(); assertNotNull(factory.create(is, 0)); is.reset(); - assertNotNull(factory.create(is, sheetName)); + assertNotNull(factory.create(is, SHEET_NAME)); } @ParameterizedTest @@ -129,6 +129,12 @@ void create_namedSheetForCsvByInputStream_ok(String fileName) { assertNotNull(factory.create(is, "SheetB")); } + @Test + void create_emptyFile_exception() { + InputStream is = getInputStream("empty.txt"); + assertThrows(ReportPageInstantiationException.class, () -> factory.create(is, "SheetB")); + } + // Test unexpected type sheet id @ParameterizedTest diff --git a/src/test/java/org/spacious_team/table_wrapper/autoconfigure/ReportPageFactoryTestFileCreator.java b/src/test/java/org/spacious_team/table_wrapper/autoconfigure/ReportPageFactoryTestFileCreator.java index 9e5ce01..152c5c0 100644 --- a/src/test/java/org/spacious_team/table_wrapper/autoconfigure/ReportPageFactoryTestFileCreator.java +++ b/src/test/java/org/spacious_team/table_wrapper/autoconfigure/ReportPageFactoryTestFileCreator.java @@ -40,7 +40,7 @@ class ReportPageFactoryTestFileCreator { static final Path root = Path.of("target", "test-classes", "test-data"); - static final String sheetName = "SheetA"; + static final String SHEET_NAME = "SheetA"; @SneakyThrows static void creteFiles() { @@ -48,6 +48,7 @@ static void creteFiles() { createBinFile("test.bin"); createCsvFile("test.csv"); createCsvFile("test.txt"); + createEmptyFile("empty.txt"); createXmlFile("test.xml"); try (HSSFWorkbook workbook = new HSSFWorkbook()) { createExcelFile("test.xls", workbook); @@ -91,12 +92,18 @@ static void createCsvFile(String fileName) throws IOException { Files.write(path, lines); } + static void createEmptyFile(@SuppressWarnings("SameParameterValue") String fileName) throws IOException { + List lines = List.of(""); + Path path = getPath(fileName); + Files.write(path, lines); + } + static void createXmlFile(@SuppressWarnings("SameParameterValue") String fileName) throws XelemException { Workbook workbook = new XLWorkbook(); Path path = getPath(fileName); workbook.setFileName(path.toString()); - Worksheet worksheet = workbook.addSheet(sheetName); + Worksheet worksheet = workbook.addSheet(SHEET_NAME); Row row = worksheet.addRow(); row.addCell(); row.addCell().setData("Table 1"); @@ -120,7 +127,7 @@ static void createExcelFile(String fileName, org.apache.poi.ss.usermodel.Workboo Path path = getPath(fileName); try (FileOutputStream fos = new FileOutputStream(path.toFile())) { - Sheet sheet = workbook.createSheet(sheetName); + Sheet sheet = workbook.createSheet(SHEET_NAME); org.apache.poi.ss.usermodel.Row row = sheet.createRow(0); row.createCell(0).setCellValue("Table 1"); row = sheet.createRow(1);