cdc.impex.templates.ColumnTemplate Maven / Gradle / Ivy
package cdc.impex.templates;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import cdc.issues.IssueSeverity;
import cdc.office.ss.ContentValidation;
import cdc.office.tables.HeaderCell;
import cdc.office.tables.HeaderCell.NameCell;
import cdc.office.tables.HeaderCell.PatternCell;
import cdc.util.lang.Checks;
import cdc.util.strings.StringConversion;
import cdc.validation.checkers.Checker;
/**
* Description of a column template.
*
* A column is described by:
*
* - A header (name or pattern).
* A name column represents a single actual column.
* A pattern column represents a set of actual columns.
* - A {@link Usage}.
*
- A data type.
*
- An optional default value.
*
- An optional description.
* It is used when sample files are generated.
* - An optional {@link Checker}.
* It can be used to add more checks to data type, for example, to check the length of string, the domain of a number, ...
* - The severity of a failed check.
*
- A string to data type converter.
*
- A data type to string converter.
*
- An optional Content Validation type.
*
- An optional Content Validation operator.
*
- An optional list of Content Validation values.
*
* WARNING: content validation should be compliant with data type and checker.
*
* If data type is a primitive or enum data type, default content validation data are automatically set.
*
* @author Damien Carbonne
*
* @param The column data type.
*/
public final class ColumnTemplate {
private final HeaderCell header;
private final Usage usage;
private final Class dataType;
private final T def;
private final Primitive primitive;
private final String description;
private final Checker checker;
private final IssueSeverity checkFailureSeverity;
private final Function importConverter;
private final Function exportConverter;
private final ContentValidation.Type cvType;
private final ContentValidation.Operator cvOperator;
private final List cvValues;
private ColumnTemplate(Builder builder) {
Checks.isTrue(builder.cvType.accepts(builder.cvOperator),
builder.cvType + " is not compliant with " + builder.cvOperator + " operator.");
Checks.isTrue(builder.cvOperator.isCompliantWithValues(builder.cvValues.size()),
builder.cvOperator + " is not compliant with " + builder.cvValues.size() + " values.");
this.header = Checks.isNotNull(builder.header, "header");
this.usage = Checks.isNotNull(builder.usage, "usage");
this.dataType = Checks.isNotNull(builder.dataType, "dataType");
this.def = builder.def;
this.primitive = Primitive.getPrimitive(builder.dataType);
this.description = builder.description;
this.checker = builder.checker;
this.checkFailureSeverity = Checks.isNotNull(builder.checkFailureSeverity, "checkFailureSeverity");
this.importConverter = Checks.isNotNull(builder.importConverter, "importConverter");
this.exportConverter = builder.exportConverter == null ? Object::toString : builder.exportConverter;
this.cvType = builder.cvType;
this.cvOperator = builder.cvOperator;
this.cvValues = Collections.unmodifiableList(builder.cvValues);
}
public HeaderCell getHeader() {
return header;
}
/**
* @return {@code true} if the header of the column is a name.
*/
public boolean isName() {
return header instanceof HeaderCell.NameCell;
}
public void checkIsName() {
Checks.isTrue(isName(), "{} is not a name.", this);
}
/**
* @return {@code true} if the header of the column is a pattern.
*/
public boolean isPattern() {
return header instanceof HeaderCell.PatternCell;
}
public void checkIsPattern() {
Checks.isTrue(isPattern(), "{} is not a pattern.", this);
}
/**
* @return The column label (its name or pattern).
*/
public String getLabel() {
return header.getLabel();
}
/**
* Returns the column name.
*
* WARNING: column must have a {@link NameCell name header}.
*
* @return The column name.
* @throws AssertionError When the header of this template is not a name.
*/
public String getName() {
Checks.assertTrue(isName(), "'{}' is not a name column.", this);
return ((HeaderCell.NameCell) header).getName();
}
/**
* Returns the column pattern.
*
* WARNING: column must have a {@link PatternCell pattern header}.
*
* @return The column pattern.
* @throws AssertionError When the header of this template is not a pattern.
*/
public Pattern getPattern() {
Checks.assertTrue(isPattern(), "'{}' is not a pattern column.", this);
return ((HeaderCell.PatternCell) header).getPattern();
}
/**
* @return The column usage.
*/
public Usage getUsage() {
return usage;
}
/**
* @return The column data type.
*/
public Class getDataType() {
return dataType;
}
/**
* @return The column default value.
*/
public T getDef() {
return def;
}
/**
* @return The {@link Primitive} type of this column, or {@code null}.
*/
public Primitive getPrimitive() {
return primitive;
}
/**
* @return The column description.
*/
public String getDescription() {
return description;
}
/**
* @return The checker or {@code null}.
*/
public Checker getCheckerOrNull() {
return checker;
}
public boolean hasChecker() {
return checker != null;
}
/**
* @return The severity of a check failure.
*/
public IssueSeverity getCheckFailureSeverity() {
return checkFailureSeverity;
}
/**
* @return the converter used to convert a string to data type.
*/
public Function getImportConverter() {
return importConverter;
}
/**
* @return the converter used to convert a data type to a string.
*/
public Function getExportConverter() {
return exportConverter;
}
/**
* @return The comment that should be used to describe this column.
*/
public String getComment() {
final StringBuilder builder = new StringBuilder();
builder.append("Usage: ")
.append(getUsage().getName())
.append('\n')
.append("Erasable: ")
.append(getUsage().isErasable() ? "Yes" : "No")
.append('\n')
.append("Type: ")
.append(getDataType().getSimpleName());
if (getCheckerOrNull() != null) {
builder.append('\n')
.append("Check: ")
.append(getCheckerOrNull().explain());
}
if (getDescription() != null) {
builder.append("\n\n")
.append(getDescription());
}
return builder.toString();
}
/**
* @return The content validation type.
*/
public ContentValidation.Type getContentValidationType() {
return cvType;
}
/**
* @return The content validation operator.
*/
public ContentValidation.Operator getContentValidationOperator() {
return cvOperator;
}
/**
* @return The content validation values.
*/
public List getContentValidationValues() {
return cvValues;
}
@Override
public String toString() {
return "[" + getLabel()
+ " " + getUsage()
+ " " + getDataType().getSimpleName()
+ "]";
}
/**
* @return A new Builder initialized with data from this Template.
*/
public Builder newBuilder() {
return builder(this);
}
/**
* Creates an empty new Builder.
*
* @param The data type.
* @param dataType The data class.
* @return A new Builder typed with {@code dataType}.
*/
public static Builder builder(Class dataType) {
return new Builder<>(dataType);
}
/**
* Creates a new Builder initialized from a Template.
*
* @param The data type.
* @param model The Template model.
* @return A new Builder initialized with {@code model}.
*/
public static Builder builder(ColumnTemplate model) {
return new Builder<>(model);
}
public static final class Builder {
private final Class dataType;
private HeaderCell header;
private Usage usage = Usage.OPTIONAL_RW_ATT;
private T def = null;
private String description;
private Checker checker;
private IssueSeverity checkFailureSeverity = IssueSeverity.CRITICAL;
private Function importConverter;
private Function exportConverter;
private ContentValidation.Type cvType = ContentValidation.Type.ANY;
private ContentValidation.Operator cvOperator = ContentValidation.Operator.NONE;
private final List cvValues = new ArrayList<>();
private static class Bucket {
final Function importConverter;
final ContentValidation.Type cvType;
final ContentValidation.Operator cvOperator;
final List cvValues;
Bucket(Function importConverter,
ContentValidation.Type cvType,
ContentValidation.Operator cvOperator,
List cvValues) {
this.importConverter = importConverter;
this.cvType = cvType;
this.cvOperator = cvOperator;
this.cvValues = cvValues;
}
}
private static final Map, Bucket> BUCKETS = new HashMap<>();
static {
BUCKETS.put(Boolean.class,
new Bucket(StringConversion::asBoolean,
ContentValidation.Type.ANY,
ContentValidation.Operator.NONE,
Collections.emptyList()));
BUCKETS.put(boolean.class,
new Bucket(StringConversion::asBoolean,
ContentValidation.Type.ANY,
ContentValidation.Operator.NONE,
Collections.emptyList()));
BUCKETS.put(Character.class,
new Bucket(StringConversion::asChar,
ContentValidation.Type.TEXT_LENGTH,
ContentValidation.Operator.EQUAL,
List.of("1")));
BUCKETS.put(char.class,
new Bucket(StringConversion::asChar,
ContentValidation.Type.TEXT_LENGTH,
ContentValidation.Operator.EQUAL,
List.of("1")));
BUCKETS.put(Byte.class,
new Bucket(StringConversion::asByte,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Byte.toString(Byte.MIN_VALUE), Byte.toString(Byte.MAX_VALUE))));
BUCKETS.put(byte.class,
new Bucket(StringConversion::asByte,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Byte.toString(Byte.MIN_VALUE), Byte.toString(Byte.MAX_VALUE))));
BUCKETS.put(Short.class,
new Bucket(StringConversion::asShort,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Short.toString(Short.MIN_VALUE), Short.toString(Short.MAX_VALUE))));
BUCKETS.put(short.class,
new Bucket(StringConversion::asShort,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Short.toString(Short.MIN_VALUE), Short.toString(Short.MAX_VALUE))));
BUCKETS.put(Integer.class,
new Bucket(StringConversion::asInt,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Integer.toString(Integer.MIN_VALUE), Integer.toString(Integer.MAX_VALUE))));
BUCKETS.put(int.class,
new Bucket(StringConversion::asInt,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Integer.toString(Integer.MIN_VALUE), Integer.toString(Integer.MAX_VALUE))));
BUCKETS.put(Long.class,
new Bucket(StringConversion::asLong,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Long.toString(Long.MIN_VALUE), Long.toString(Long.MAX_VALUE))));
BUCKETS.put(long.class,
new Bucket(StringConversion::asLong,
ContentValidation.Type.INTEGER,
ContentValidation.Operator.BETWEEN,
List.of(Long.toString(Long.MIN_VALUE), Long.toString(Long.MAX_VALUE))));
BUCKETS.put(Float.class,
new Bucket(StringConversion::asFloat,
ContentValidation.Type.DECIMAL,
ContentValidation.Operator.BETWEEN,
List.of(Float.toString(-Float.MAX_VALUE), Float.toString(Float.MAX_VALUE))));
BUCKETS.put(float.class,
new Bucket(StringConversion::asFloat,
ContentValidation.Type.DECIMAL,
ContentValidation.Operator.BETWEEN,
List.of(Float.toString(-Float.MAX_VALUE), Float.toString(Float.MAX_VALUE))));
BUCKETS.put(Double.class,
new Bucket(StringConversion::asDouble,
ContentValidation.Type.DECIMAL,
ContentValidation.Operator.BETWEEN,
List.of(Double.toString(-Double.MAX_VALUE), Double.toString(Double.MAX_VALUE))));
BUCKETS.put(double.class,
new Bucket(StringConversion::asDouble,
ContentValidation.Type.DECIMAL,
ContentValidation.Operator.BETWEEN,
List.of(Double.toString(-Double.MAX_VALUE), Double.toString(Double.MAX_VALUE))));
BUCKETS.put(String.class,
new Bucket(Function.identity(),
ContentValidation.Type.ANY,
ContentValidation.Operator.NONE,
Collections.emptyList()));
}
private void buildDefaultData() {
final Function f;
final ContentValidation.Type type;
final ContentValidation.Operator operator;
final List values;
if (this.dataType.isEnum()) {
@SuppressWarnings("unchecked")
final Class> c = (Class>) dataType;
f = s -> StringConversion.asRawEnum(s, c);
type = ContentValidation.Type.LIST;
operator = ContentValidation.Operator.NONE;
values = new ArrayList<>();
for (final Enum> e : c.getEnumConstants()) {
values.add(e.name());
}
} else {
final Bucket bucket = BUCKETS.get(dataType);
if (bucket == null) {
f = null;
type = ContentValidation.Type.ANY;
operator = ContentValidation.Operator.NONE;
values = Collections.emptyList();
} else {
f = bucket.importConverter;
type = bucket.cvType;
operator = bucket.cvOperator;
values = bucket.cvValues;
}
}
@SuppressWarnings("unchecked")
final Function g = (Function) f;
this.importConverter = g;
this.cvType = type;
this.cvOperator = operator;
this.cvValues.addAll(values);
}
private Builder(Class dataType) {
this.dataType = dataType;
buildDefaultData();
}
private Builder(ColumnTemplate model) {
this(model.dataType);
header(model.header);
usage(model.usage);
def(model.def);
description(model.description);
checker(model.checker);
checkFailureSeverity(model.checkFailureSeverity);
importConverter(model.importConverter);
exportConverter(model.exportConverter);
cvType(model.cvType);
cvOperator(model.cvOperator);
cvValues(model.cvValues);
}
public Builder header(HeaderCell header) {
this.header = header;
return this;
}
public Builder name(String name) {
this.header = HeaderCell.name(name);
return this;
}
public Builder pattern(String pattern) {
this.header = HeaderCell.pattern(pattern);
return this;
}
public Builder usage(Usage usage) {
this.usage = usage;
return this;
}
public Builder def(T def) {
this.def = def;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder checker(Checker checker) {
this.checker = checker;
return this;
}
public Builder checkFailureSeverity(IssueSeverity checkFailureSeverity) {
this.checkFailureSeverity = checkFailureSeverity;
return this;
}
public Builder importConverter(Function converter) {
this.importConverter = converter;
return this;
}
public Builder exportConverter(Function converter) {
this.exportConverter = converter;
return this;
}
public Builder cvType(ContentValidation.Type cvType) {
this.cvType = cvType;
return this;
}
public Builder cvOperator(ContentValidation.Operator cvOperator) {
this.cvOperator = cvOperator;
return this;
}
public Builder cvValue(String value) {
this.cvValues.clear();
this.cvValues.add(value);
return this;
}
public Builder cvValues(String value1,
String value2) {
this.cvValues.clear();
this.cvValues.add(value1);
this.cvValues.add(value2);
return this;
}
public Builder cvValues(String... values) {
this.cvValues.clear();
Collections.addAll(this.cvValues, values);
return this;
}
public Builder cvValues(List values) {
this.cvValues.clear();
this.cvValues.addAll(values);
return this;
}
public ColumnTemplate build() {
return new ColumnTemplate<>(this);
}
}
}