InventoryCsvWriter.java
/*
* discogs-java-client - A Java SDK to access the Discogs API
* Copyright © 2025 Andy Miles (andy.miles@amilesend.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.amilesend.discogs.csv;
import com.amilesend.client.util.Validate;
import com.amilesend.discogs.csv.type.InventoryHeader;
import com.amilesend.discogs.csv.type.InventoryRecord;
import com.amilesend.discogs.csv.type.InventoryRecordType;
import com.amilesend.discogs.csv.validation.ValidationException;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Utility to write an inventory CSV file used for Discogs inventory management (i.e., creating new inventory items,
* or update existing inventory items).
*/
@Getter
@EqualsAndHashCode
public class InventoryCsvWriter implements AutoCloseable {
/** The path of the CSV file to write. */
private final Path csvFile;
/** Indicator to append an existing file, or to overwrite and create a new CSV file. */
private final boolean isAppended;
/**
* The list of defined CSV headers to be included in the CSV file.
*
* @see InventoryHeader
*/
private final List<InventoryHeader> headers;
/** The printer used to format and write to the file. */
private final CSVPrinter csvPrinter;
/**
* The record type (i.e., for new inventory items, or to update existing items).
*
* @see InventoryRecordType
*/
private final InventoryRecordType recordType;
@Builder
private InventoryCsvWriter(
@NonNull final Path csvFile,
@NonNull final List<InventoryHeader> headers,
@NonNull final InventoryRecordType recordType,
final CSVFormat csvFormat,
final boolean isAppended) throws IOException, ValidationException {
this.headers = new ArrayList<>(headers);
this.isAppended = isAppended;
this.recordType = recordType;
validateRequiredHeaders(headers, recordType);
if (isAppended) {
Validate.isTrue(Files.isRegularFile(csvFile), "CSV file must already exist and be regular");
Validate.isTrue(Files.isWritable(csvFile), "CSV file must be writable: " + csvFile);
this.csvFile = csvFile;
} else {
Files.deleteIfExists(csvFile);
Files.createDirectories(csvFile);
this.csvFile = Files.createFile(csvFile);
}
this.csvPrinter = Optional.ofNullable(csvFormat)
.orElse(CSVFormat.RFC4180)
.builder()
.setHeader(toCsvHeaders(this.headers))
.get()
.print(this.csvFile, StandardCharsets.UTF_8);
}
/**
* Writes a record to the CSV file.
*
* @param record the record to write
* @throws ValidationException if there is an error with the record
* @throws IOException if there is an issue writing the record to the file
*/
public void write(@NonNull final InventoryRecord record) throws ValidationException, IOException {
record.validate(recordType);
csvPrinter.printRecord(record.toCsvRow(headers, recordType));
}
@Override
public void close() throws Exception {
csvPrinter.close();
}
@Override
public String toString() {
return new StringBuilder("InventoryCsvWriter [")
.append(recordType.name())
.append("]: ")
.append(csvFile)
.toString();
}
private static void validateRequiredHeaders(
final List<InventoryHeader> headers,
final InventoryRecordType type) {
final List<InventoryHeader> required = InventoryHeader.getRequiredHeaders(type);
if (!headers.containsAll(required)) {
throw new IllegalArgumentException("Parsed headers must contain all required headers: " + required);
}
}
private static String[] toCsvHeaders(final List<InventoryHeader> headers) {
final List<String> strHeaders = headers.stream()
.map(InventoryHeader::getHeader)
.collect(Collectors.toList());
final String[] csvHeaders = new String[headers.size()];
return strHeaders.toArray(csvHeaders);
}
}