cdc.impex.templates.SheetTemplate Maven / Gradle / Ivy
package cdc.impex.templates;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import cdc.impex.ImpExNames;
import cdc.office.tables.Header;
import cdc.office.tables.HeaderCell;
import cdc.util.lang.Checks;
import cdc.util.strings.StringUtils;
/**
* Description of a sheet template.
*
* It always has an optional action column.
*
* @author Damien Carbonne
*/
public final class SheetTemplate {
/** The sheet domain. */
private final String domain;
/** The sheet name. */
private final String name;
/** The sheet description. */
private final String description;
/** The default action to execute when action is missing. */
private final ImportAction defaultAction;
/** The name of the action column. */
private final String actionColumnName;
/** The list of column templates. */
private final List> columns;
/** The header (built from columns headers). */
private final Header header;
private final Map> headerToColumn = new HashMap<>();
/** (name, template) map, for name columns. */
private final Map> nameToColumn = new HashMap<>();
public static final ImportAction DEFAULT_ACTION = ImportAction.IGNORE;
public static final String DEFAULT_ACTION_COLUMN_NAME = "Action";
public static final Comparator DOMAIN_NAME_COMPARATOR =
Comparator.comparing(SheetTemplate::getDomain)
.thenComparing(SheetTemplate::getName);
private SheetTemplate(Builder builder) {
this.domain = Checks.isNotNullOrEmpty(builder.domain, "domain");
this.name = Checks.isNotNullOrEmpty(builder.name, "name");
if (builder.name.contains("#")) {
throw new IllegalArgumentException("Invalid template name '" + builder.name + "'");
}
this.description = builder.description;
this.defaultAction = builder.defaultAction; // Already checked by builder
this.actionColumnName = Checks.isNotNull(builder.actionColumnName, "actionColumnName");
this.columns = Collections.unmodifiableList(Checks.isNotNull(builder.columns, "columns"));
final List headers = new ArrayList<>();
for (final ColumnTemplate> column : this.columns) {
if (column == null) {
throw new IllegalArgumentException("Null column");
}
headers.add(column.getHeader());
this.headerToColumn.put(column.getHeader(), column);
if (column.isName()) {
nameToColumn.put(column.getName(), column);
}
}
this.header = Header.builder().cells(headers).build();
if (this.headerToColumn.size() != this.columns.size()) {
throw new IllegalArgumentException("Duplicate column names");
}
}
/**
* @return The template domain.
*/
public String getDomain() {
return domain;
}
/**
* @return The template name.
*/
public String getName() {
return name;
}
/**
* @return The template qualified name (domain.name).
*/
public String getQName() {
return domain + "." + name;
}
/**
* @return The template description.
*/
public String getDescription() {
return description;
}
/**
* @return The default import action, used when action is not defined.
*/
public ImportAction getDefaultAction() {
return defaultAction;
}
/**
* @return The name of the action column.
*/
public String getActionColumnName() {
return actionColumnName;
}
/**
* @return The action column
*/
public ColumnTemplate getActionColumn() {
@SuppressWarnings("unchecked")
final ColumnTemplate tmp = (ColumnTemplate) getColumn(actionColumnName);
return tmp;
}
public boolean isActionColumn(ColumnTemplate> column) {
return column == getActionColumn();
}
/**
* @return The template columns.
*/
public List> getColumns() {
return columns;
}
public List> getColumns(Usage usage) {
final List> result = new ArrayList<>();
for (final ColumnTemplate> column : columns) {
if (column.getUsage() == usage) {
result.add(column);
}
}
return result;
}
/**
* @return The header.
*/
public Header getHeader() {
return header;
}
/**
* @return {@code true} if this sheet template contains pattern columns.
*/
public boolean hasPatterns() {
return header.hasPatterns();
}
/**
* @return The template column headers.
*/
@Deprecated(forRemoval = true, since = "2024-07-13")
public List getColumnHeaders() {
return header.getSortedCells();
}
/**
* Returns a list of column headers that match a usage.
*
* @param usage The usage.
* @return A list of column headers that match {@code usage}.
*/
public List getColumnHeaders(Usage usage) {
final List result = new ArrayList<>();
for (final ColumnTemplate> column : columns) {
if (column.getUsage() == usage) {
result.add(column.getHeader());
}
}
return result;
}
/**
* @param header The cell header.
* @return The template associated to {@code header}.
* @throws IllegalArgumentException When {@code header} is {@code null}.
* @throws NoSuchElementException When there is no associated template.
*/
public ColumnTemplate> getColumn(HeaderCell header) {
Checks.isNotNull(header, "header");
final ColumnTemplate> result = headerToColumn.get(header);
if (result == null) {
throw new NoSuchElementException("Invalid column header '" + header + "' for template '" + this.name + "'");
} else {
return result;
}
}
/**
* Returns the column that has a given name.
*
* @param name The name.
* @return The column that is named {@code name}.
* @throws IllegalArgumentException When {@code name} is {@code null}.
* @throws NoSuchElementException When there is no column named {@code name}.
*/
public ColumnTemplate> getColumn(String name) {
Checks.isNotNull(name, ImpExNames.NAME);
return getColumn(HeaderCell.name(name));
}
/**
* Returns {@code true} if this sheet template contains a column with a particular name.
*
* WARNING: This only works for name columns.
*
* @param name The name.
* @return {@code true} if this sheet template contains a column names {@code name}.
*/
public boolean containsColumn(String name) {
return headerToColumn.containsKey(HeaderCell.name(name));
}
/**
* @param name The actual name.
* @return The column that matches {@code name}.
* @throws NoSuchElementException When there is not a single column matching {@code name}.
*/
public ColumnTemplate> getMatchingColumn(String name) {
final Optional cell = header.getMatchingCell(name);
return getColumn(cell.orElseThrow());
}
/**
* @param name The actual name.
* @return {@code true} when there a single column matching {@code name}.
*/
public boolean containsMatchingColumn(String name) {
return header.matchesOne(name);
}
/**
* @param patternReplacement The list of names that must be used as actual columns names for pattern columns.
* @return A new SheetTemplateInstance whose template is this object and header is built from names of name columns
* and substitutions for pattern columns.
*/
public SheetTemplateInstance instantiate(List patternReplacement) {
return SheetTemplateInstance.replace(this, patternReplacement);
}
/**
* @param patternReplacement The array of names that must be used as actual columns names for pattern columns.
* @return A new SheetTemplateInstance whose template is this object and header is built from names of name columns
* and substitutions for pattern columns.
*/
public SheetTemplateInstance instantiate(String... patternReplacement) {
return SheetTemplateInstance.replace(this, patternReplacement);
}
public Builder newBuilder() {
return builder(this);
}
@Override
public String toString() {
return "[" + getQName()
+ " " + getColumns()
+ "]";
}
public static Builder builder() {
return new Builder();
}
public static Builder builder(SheetTemplate model) {
return new Builder(model);
}
public static final class Builder {
private String domain;
private String name;
private String description;
private ImportAction defaultAction = DEFAULT_ACTION;
private String actionColumnName = DEFAULT_ACTION_COLUMN_NAME;
private final List> columns = new ArrayList<>();
private Builder() {
}
private Builder(SheetTemplate model) {
domain(model.domain);
name(model.name);
description(model.description);
actionColumnName(model.actionColumnName);
columns.addAll(model.columns);
}
public Builder domain(String domain) {
this.domain = domain;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder defaultAction(ImportAction defaultAction) {
this.defaultAction = Checks.isNotNull(defaultAction, "defaultAction");
return this;
}
public Builder actionColumnName(String actionColumnName) {
this.actionColumnName = actionColumnName;
return this;
}
public Builder column(ColumnTemplate> column) {
this.columns.add(column);
return this;
}
public SheetTemplate build() {
for (final ColumnTemplate> column : columns) {
if (column.getLabel().equals(actionColumnName)) {
throw new IllegalArgumentException("Cannot create action column, a column named '" + actionColumnName
+ "' has been created.");
}
}
columns.add(0,
ColumnTemplate.builder(ImportAction.class)
.name(actionColumnName)
.usage(Usage.ACTION)
.description("Action to be taken " + Arrays.toString(ImportAction.values())
+ " for each row.\nAn empty cell is equivalent to " + ImportAction.IGNORE + ".")
.importConverter(s -> StringUtils.isNullOrEmpty(s)
? defaultAction
: ImportAction.valueOf(s))
.exportConverter(x -> x == null ? null : x.name())
.build());
return new SheetTemplate(this);
}
}
}