fr.insee.vtl.model.Structured Maven / Gradle / Ivy
package fr.insee.vtl.model;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
/**
* Structured
is the base interface for representing structured data.
*/
public interface Structured {
/**
* Returns the structure associated to the data as a list of structure components.
*
* @return The structure associated to the data as a list of structure components.
*/
DataStructure getDataStructure();
/**
* Returns the list of column names.
*
* @return The column names as a list of strings.
*/
default List getColumnNames() {
return new ArrayList<>(getDataStructure().keySet());
}
/**
* Returns the map of column names & roles.
*
* @return The column names & roles.
*/
default Map getRoles() {
return getDataStructure().values().stream().collect(Collectors.toMap(Component::getName, Component::getRole));
}
default List getIdentifiers() {
return getDataStructure().getIdentifiers();
}
default List getMeasures() {
return getDataStructure().getMeasures();
}
default List getAttributes() {
return getDataStructure().getAttributes();
}
default Boolean isMonoMeasure() {
return getDataStructure().isMonoMeasure();
}
/**
* The Structure
class represent a structure component with its name, type, role and nullable.
*/
class Component implements Serializable {
private final String name;
private final Class> type;
private final Dataset.Role role;
private final Boolean nullable;
private final String valuedomain;
/**
* Constructor taking the name, type and role of the component.
*
* @param name A string giving the name of the structure component to create
* @param type A Class
giving the type of the structure component to create
* @param role A Role
giving the role of the structure component to create
*/
public Component(String name, Class> type, Dataset.Role role) {
this.name = Objects.requireNonNull(name);
this.type = Objects.requireNonNull(type);
this.role = Objects.requireNonNull(role);
this.nullable = buildNullable(null, role);
this.valuedomain = null;
}
/**
* Constructor taking the name, type and role of the component.
*
* @param name A string giving the name of the structure component to create
* @param type A Class
giving the type of the structure component to create
* @param role A Role
giving the role of the structure component to create
* @param nullable A Nullable
giving the nullable of the structure component to create
* @param valuedomain A Valuedomain
giving the valuedomain of the structure component to create
*/
public Component(String name, Class> type, Dataset.Role role, Boolean nullable, String valuedomain) {
this.name = Objects.requireNonNull(name);
this.type = Objects.requireNonNull(type);
this.role = Objects.requireNonNull(role);
this.nullable = buildNullable(nullable, role);
this.valuedomain = valuedomain;
}
/**
* Constructor taking the name, type, role and nullable of the component.
*
* @param name A string giving the name of the structure component to create
* @param type A Class
giving the type of the structure component to create
* @param role A Role
giving the role of the structure component to create
* @param nullable A Nullable
giving the nullable of the structure component to create
*/
public Component(String name, Class> type, Dataset.Role role, Boolean nullable) {
this.name = Objects.requireNonNull(name);
this.type = Objects.requireNonNull(type);
this.role = Objects.requireNonNull(role);
this.nullable = buildNullable(nullable, role);
this.valuedomain = null;
}
/**
* Constructor taking an existing component.
*
* @param component The component to copy.
*/
public Component(Component component) {
this.name = component.getName();
this.type = component.getType();
this.role = component.getRole();
this.nullable = component.getNullable();
this.valuedomain = component.getValuedomain();
}
/**
* Refines the nullable attribute of a Component
regarding its role.
*
* @param initialNullable The dataset nullable attribute.
* @param role The role of the component as a value of the Role
enumeration
* @return A boolean which is true
if the component values can be null, false
otherwise.
*/
private Boolean buildNullable(Boolean initialNullable, Dataset.Role role) {
if (role.equals(Dataset.Role.IDENTIFIER)) return false;
if (initialNullable == null) return true;
return initialNullable;
}
/**
* Tests if a component is an identifier.
*
* @return true
if the component is an identifier, false
.
*/
public boolean isIdentifier() {
return Dataset.Role.IDENTIFIER.equals(this.role);
}
/**
* Tests if a component is a measure.
*
* @return true
if the component is a measure, false
.
*/
public boolean isMeasure() {
return Dataset.Role.MEASURE.equals(this.role);
}
/**
* Tests if a component is an attribute.
*
* @return true
if the component is an attribute, false
.
*/
public boolean isAttribute() {
return Dataset.Role.ATTRIBUTE.equals(this.role);
}
/**
* Returns the name of the component.
*
* @return The name of the component as a string.
*/
public String getName() {
return name;
}
/**
* Returns the type of the component.
*
* @return The type of the component as an instance of Class
*/
public Class> getType() {
return type;
}
/**
* Returns the role of component.
*
* @return The role of the component as a value of the Role
enumeration
*/
public Dataset.Role getRole() {
return role;
}
/**
* Returns the nullable of component.
*
* @return The nullable of the component as a Boolean
*/
public Boolean getNullable() {
return nullable;
}
/**
* Returns the valuedomain of component.
*
* @return The valuedomain of the component as a String
*/
public String getValuedomain() {
return valuedomain;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Component component = (Component) o;
return name.equals(component.name) &&
type.equals(component.type) &&
role == component.role;
}
@Override
public int hashCode() {
return Objects.hash(name, type, role);
}
@Override
public String toString() {
return "Component{" + name +
", type=" + type +
", role=" + role +
'}';
}
}
/**
* The DataStructure
represents the structure of a Dataset.
*
* A DataStructure helps with the indexing of a {@link DataPoint}. It keeps
* the position of each component.
*/
class DataStructure extends IndexedHashMap {
/**
* Creates a DataStructure with type and role maps.
*
* @param types The types of each component, by name
* @param roles The roles of each component, by name
* @throws IllegalArgumentException if the key set of types and roles are not equal.
*/
public DataStructure(Map> types, Map roles) {
super(types.size());
if (!types.keySet().equals(roles.keySet())) {
throw new IllegalArgumentException("type and roles key sets inconsistent");
}
for (String column : types.keySet()) {
Component component = new Component(column, types.get(column), roles.get(column));
put(column, component);
}
}
/**
* Creates a DataStructure with type, role and nullable maps.
*
* @param types The types of each component, by name
* @param roles The roles of each component, by name
* @param nullables The nullables of each component, by name
* @throws IllegalArgumentException if the key set of types and roles are not equal.
*/
public DataStructure(Map> types, Map roles,
Map nullables) {
super(types.size());
if (!types.keySet().equals(roles.keySet())) {
throw new IllegalArgumentException("type and roles key sets inconsistent");
}
for (String column : types.keySet()) {
Component component = new Component(column, types.get(column), roles.get(column), nullables.get(column));
put(column, component);
}
}
/**
* Creates a DataStructure with a collection of components.
*
* @param components A collection of components
* @throws IllegalArgumentException in case of duplicate column names
*/
public DataStructure(Collection components) {
super(components.size());
Set duplicates = new HashSet<>();
for (Component component : components) {
Component newComponent = new Component(component);
Component old = put(newComponent.getName(), newComponent);
if (old != null) {
duplicates.add(old);
}
}
if (!duplicates.isEmpty()) {
throw new IllegalArgumentException("duplicate column " + duplicates);
}
}
// TODO: Remove. We can simply use a Map of the
// constructor with Collection
public DataStructure(DataStructure dataStructure) {
super(dataStructure);
}
public List getIdentifiers() {
return values().stream().filter(Component::isIdentifier).collect(Collectors.toList());
}
public List getMeasures() {
return values().stream().filter(Component::isMeasure).collect(Collectors.toList());
}
public List getAttributes() {
return values().stream().filter(Component::isAttribute).collect(Collectors.toList());
}
public Map getRoles() {
return entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getRole()));
}
public List getByValuedomain(String valuedomain) {
return values().stream().filter(c -> valuedomain.equals(c.getValuedomain())).collect(Collectors.toList());
}
public Boolean isMonoMeasure() {
return getMeasures().size() == 1;
}
}
/**
* A structured row of a {@link Dataset}.
*
* A point is composed of a structure and a list of values. Values
* can be accessed by position or by name.
*
* Two DataPoint
instances are considered equal if all of their
* identifier values are equal.
*/
class DataPoint extends ArrayList