All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cdc.impex.core.ImportRowImpl Maven / Gradle / Ivy

There is a newer version: 0.54.0
Show newest version
package cdc.impex.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import cdc.impex.ImpExNames;
import cdc.impex.imports.ImportIssueType;
import cdc.impex.imports.ImportIssues;
import cdc.impex.imports.ImportRow;
import cdc.impex.templates.ColumnTemplate;
import cdc.impex.templates.ImportAction;
import cdc.impex.templates.SheetTemplate;
import cdc.impex.templates.SheetTemplateInstance;
import cdc.issues.Issue;
import cdc.issues.IssueSeverity;
import cdc.issues.locations.WorkbookLocation;
import cdc.util.lang.Checks;

public final class ImportRowImpl implements ImportRow {
    private final String systemId;
    private final SheetTemplateInstance templateInstance;
    private final String sheetName;
    private final int number;
    private final Map rawData;
    private final Map data = new HashMap<>();
    private List issues = null;

    private ImportRowImpl(Builder builder) {
        this.systemId = builder.systemId;
        this.templateInstance = Checks.isNotNull(builder.templateInstance, "templateInstance");
        this.sheetName = builder.sheetName;
        this.number = builder.number;
        this.rawData = new HashMap<>(builder.rawData);

        final SheetTemplate template = templateInstance.getTemplate();

        // Don't want to pay the cost of patterns when they are not used
        final boolean hasPatterns = template.hasPatterns();

        // Names associated to each column
        final Map, List> columnToNames = hasPatterns ? new HashMap<>() : null;

        // Read raw data, convert it, check it, and put converted result into data
        for (final Map.Entry entry : rawData.entrySet()) {
            // Retrieve column name
            final String name = entry.getKey();
            if (template.containsMatchingColumn(name)) {
                // Retrieve associated column template
                final ColumnTemplate column = template.getMatchingColumn(name);

                if (hasPatterns) {
                    // Associate name to this column
                    columnToNames.computeIfAbsent(column, k -> new ArrayList<>()).add(name);
                }

                Object value;
                if (ERASE.equals(entry.getValue())) {
                    value = ERASE;
                } else {
                    // Convert raw data
                    try {
                        value = column.getImportConverter().apply(entry.getValue());
                    } catch (final RuntimeException e) {
                        // Conversion failed
                        addIssue(ImportIssueType.NON_CONVERTIBLE_DATA,
                                 "Failed to convert '" + entry.getValue() + "' to " + column.getDataType().getCanonicalName() + ".",
                                 name);
                        value = null;
                    }
                }

                // Check converted data
                if (value != null && value != ERASE && column.hasChecker()) {
                    final boolean valid = column.getCheckerOrNull().testRaw(value);
                    if (!valid) {
                        // Check failed
                        addIssue(ImportIssueType.NON_COMPLIANT_DATA,
                                 column.getCheckFailureSeverity(),
                                 "Check [" + column.getCheckerOrNull().explain(true, "?") + "] failed for '?'=" + value + ".",
                                 name);
                    }
                }
                // Put converted data, even if check failed
                if (value != null) {
                    this.data.put(name, value);
                }
            }
            // The raw data is ignored and not converted: should we generate an issue?
        }

        final ImportAction action = getAction(Checks.isNotNull(builder.defaultAction, "defaultAction"));

        // Check that columns are set / unset in accordance with their usage (that depends on action)
        // If action is not correctly set, this may be incomplete
        if (hasPatterns) {
            for (final ColumnTemplate column : template.getColumns()) {
                final List names = columnToNames.computeIfAbsent(column, k -> new ArrayList<>());
                if (names.isEmpty()) {
                    if (column.isName()) {
                        names.add(column.getName());
                    } else {
                        // Pattern column without any associated data
                        checkMissingPattern(column, action);
                    }
                }
                for (final String name : names) {
                    check(column, name, action);
                }
            }
        } else {
            for (final ColumnTemplate column : template.getColumns()) {
                check(column, column.getName(), action);
            }
        }
    }

    private void check(ColumnTemplate column,
                       String name,
                       ImportAction action) {
        final Object value = this.data.get(name);
        if (column.getUsage().isMandatoryFor(action)) {
            // Column is mandatory for action
            if (value == null) {
                addIssue(ImportIssueType.MISSING_MANDATORY_DATA,
                         "No data for mandatory column '" + name + "'.",
                         column.getName());
            } else if (value == ERASE) {
                addIssue(ImportIssueType.UNEXPECTED_ERASE,
                         unexpected(ERASE, "mandatory", name, action),
                         name);
            }
        } else if (column.getUsage().isOptionalFor(action)) {
            // Column is optional for action
            if (value == ERASE
                    && (action == ImportAction.CREATE
                            || action == ImportAction.DELETE
                            || action == ImportAction.UPDATE && !column.getUsage().isErasable())) {
                addIssue(ImportIssueType.UNEXPECTED_ERASE,
                         unexpected(ERASE, "optional", name, action),
                         name);
            }
        } else {
            // Column is ignored for action
            if (value == ERASE) {
                addIssue(ImportIssueType.UNEXPECTED_ERASE,
                         unexpected(ERASE, "ignored", name, action),
                         name);
            } else if (value != null) {
                addIssue(ImportIssueType.UNEXPECTED_DATA,
                         unexpected("data", "ignored", name, action),
                         name);
            }
        }
    }

    /**
     * Checks a pattern column that has no actual data.
     *
     * @param column The pattern column.
     * @param action The action.
     */
    private void checkMissingPattern(ColumnTemplate column,
                                     ImportAction action) {
        if (column.getUsage().isMandatoryFor(action)) {
            // Column is mandatory for action
            addIssue(ImportIssueType.MISSING_MANDATORY_DATA,
                     "No data for mandatory pattern column '" + column.getLabel() + "'.",
                     column.getLabel());
        }
    }

    private static String unexpected(String content,
                                     String columnKind,
                                     String columnName,
                                     ImportAction action) {
        return "Unexpected " + content + " in " + columnKind + "column '" + columnName + "' when action is " + action + ".";
    }

    private void addIssue(ImportIssueType type,
                          IssueSeverity severity,
                          String description,
                          String columnName) {
        if (issues == null) {
            issues = new ArrayList<>();
        }
        issues.add(ImportIssues.builder()
                               .name(type)
                               .severity(severity)
                               .description(description)
                               .addLocation(WorkbookLocation.builder()
                                                            .sheetName(getSheetName())
                                                            .columnName(columnName)
                                                            .rowNumber(getNumber())
                                                            .systemId(getSystemId())
                                                            .build())
                               .build());
    }

    private void addIssue(ImportIssueType type,
                          String description,
                          String columnName) {
        addIssue(type,
                 type.getSeverity(),
                 description,
                 columnName);
    }

    @Override
    public String getSystemId() {
        return systemId;
    }

    @Override
    public String getSheetName() {
        return sheetName == null ? getTemplate().getName() : sheetName;
    }

    @Override
    public SheetTemplateInstance getTemplateInstance() {
        return templateInstance;
    }

    @Override
    public SheetTemplate getTemplate() {
        return templateInstance.getTemplate();
    }

    @Override
    public Set getNames() {
        return rawData.keySet();
    }

    List getSortedNames() {
        return getNames().stream().sorted().toList();
    }

    @Override
    public int getNumber() {
        return number;
    }

    @Override
    public ImportAction getAction(Optional defaultAction) {
        final ImportAction def = defaultAction.isPresent()
                ? defaultAction.get()
                : getTemplate().getDefaultAction();
        return getData(ImportAction.class,
                       getTemplate().getActionColumnName(),
                       def);
    }

    @Override
    public boolean isErase(String name) {
        Checks.isNotNull(name, ImpExNames.NAME);

        return data.get(name) == ERASE;
    }

    @Override
    public String getRawDataOrNull(String name) {
        Checks.isNotNull(name, ImpExNames.NAME);

        return rawData.get(name);
    }

    @Override
    public Object getDataOrNull(String name) {
        Checks.isNotNull(name, ImpExNames.NAME);

        final Object value = data.get(name);
        return value == ERASE ? null : value;
    }

    @Override
    public  T getDataOrNull(Class cls,
                               String name) {
        Checks.isNotNull(cls, ImpExNames.CLS);
        Checks.isNotNull(name, ImpExNames.NAME);

        return cls.cast(getDataOrNull(name));
    }

    @Override
    public List getIssues() {
        return issues == null
                ? Collections.emptyList()
                : issues;
    }

    @Override
    public boolean canBeProcessed() {
        if (issues == null) {
            return true;
        } else {
            for (final Issue issue : issues) {
                if (issue.getSeverity().isAtLeast(IssueSeverity.CRITICAL)) {
                    return false;
                }
            }
            return true;
        }
    }

    @Override
    public boolean isEmpty() {
        return data.isEmpty();
    }

    @Override
    public WorkbookLocation getLocation() {
        return WorkbookLocation.builder()
                               .systemId(getSystemId())
                               .sheetName(getSheetName())
                               .rowNumber(getNumber())
                               .build();
    }

    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("[")
               .append(getNumber())
               .append(" {");
        boolean first = true;
        for (final String name : getSortedNames()) {
            if (first) {
                first = false;
            } else {
                builder.append(", ");
            }
            builder.append(name)
                   .append("=")
                   .append(getDataOrNull(name));
        }
        builder.append("}]");
        return builder.toString();
    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder {
        private Optional defaultAction = Optional.empty();
        private String systemId;
        private SheetTemplateInstance templateInstance;
        private String sheetName;
        private int number;
        private final Map rawData = new HashMap<>();

        private Builder() {
        }

        public Builder defaultAction(ImportAction defaultAction) {
            return defaultAction(Optional.ofNullable(defaultAction));
        }

        public Builder defaultAction(Optional defaultAction) {
            this.defaultAction = defaultAction;
            return this;
        }

        public Builder systemId(String systemId) {
            this.systemId = systemId;
            return this;
        }

        public Builder templateInstance(SheetTemplateInstance templateInstance) {
            this.templateInstance = templateInstance;
            return this;
        }

        public Builder sheetName(String sheetName) {
            this.sheetName = sheetName;
            return this;
        }

        public Builder number(int number) {
            this.number = number;
            return this;
        }

        public Builder put(String name,
                           String value) {
            this.rawData.put(name, value);
            return this;
        }

        public Builder put(ColumnTemplate column,
                           String value) {
            column.checkIsName();
            this.rawData.put(column.getName(), value);
            return this;
        }

        public ImportRow build() {
            return new ImportRowImpl(this);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy