no.ssb.jsonstat.v2.Dimension Maven / Gradle / Ivy
Show all versions of json-stat-java Show documentation
package no.ssb.jsonstat.v2;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonValue;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import me.yanaga.guava.stream.MoreCollectors;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Model for the dimension.
*
* @see https://json-stat.org/format/#dimension
*/
public class Dimension {
private final Category category;
// https://json-stat.org/format/#label
private String label;
private final Roles role;
public Dimension(Category category, Roles role) {
this.category = checkNotNull(category, "category cannot be null");
this.role = role;
}
public static Builder create(final String name) {
return new Builder(name);
}
public Optional getLabel() {
// "label content should be written in lowercase except when it is a dataset label"
return Optional.ofNullable(label).map(String::toLowerCase);
}
public void setLabel(String label) {
this.label = label;
}
public Category getCategory() {
return category;
}
@JsonIgnore
public Roles getRole() {
return role;
}
public enum Roles {
TIME, GEO, METRIC;
@JsonValue
@Override
public String toString() {
return super.toString().toLowerCase();
}
}
// https://json-stat.org/format/#category
public static class Category {
// TODO: Without label, index must be map.
// TODO: When label is map, index must be a map.
// TODO: When label is map, its keys must match those of index.
// TODO: Index can be omitted if the dimension is constant.
// TODO: If index is absent, label is required.
// https://json-stat.org/format/#label
// Optional, unless no index
private ImmutableMap label;
// https://json-stat.org/format/#index
// This can be Map or List. The order matters, and is linked to the
// role of the dimension.
// Optional if dimension is constant.
private ImmutableSet index;
// TODO: Any key must be in the index.
// TODO: If present, index should be a map
// TODO: Values can be from the index, or from itself (index backed impl?)
private Multimap child;
private Map coordinates;
// TODO: Only valid for dimension with metric role.
// TODO: Implies that index is index is a map.
private Map unit;
public ImmutableMap getLabel() {
return label;
}
public ImmutableSet getIndex() {
return index;
}
}
// https://json-stat.org/format/#unit
public static class Unit {
// TODO: Documentation says, if unit is present, decimals is required?
// https://json-stat.org/format/#decimals
Integer decimals;
// https://json-stat.org/format/#symbol
String symbol;
// https://json-stat.org/format/#position
String position;
}
public static class Coordinate {
final Double longitude;
final Double latitude;
public Coordinate(Double longitude, Double latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
public Double getLongitude() {
return longitude;
}
public Double getLatitude() {
return latitude;
}
}
public static class Builder {
// TODO: hasRole
private final String id;
private final ImmutableSet.Builder index;
private final ImmutableMap.Builder labels;
private String label;
private Roles role;
private Builder(String id) {
this.id = id;
this.index = ImmutableSet.builder();
this.labels = ImmutableMap.builder();
// Use Dimension.create()
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Builder builder = (Builder) o;
return Objects.equal(id, builder.id);
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
@Override
public String toString() {
return "DimensionBuilder{" +
"id='" + id + '\'' +
'}';
}
protected String getId() {
return id;
}
// TODO: Should this be accessible at this stage? Maybe best to delay until dimension are build.
protected Integer size() {
return index.build().size();
}
public Builder withRole(final Roles role) {
this.role = role;
return this;
}
public Builder withLabel(final String label) {
this.label = label;
return this;
}
public Builder withCategories(String... categories) {
return withCategories(ImmutableSet.copyOf(categories));
}
public Builder withCategories(ImmutableSet categories) {
Map newIndexedLabels = categories.stream()
.collect(
MoreCollectors.toImmutableMap(
Function.identity(),
Function.identity()
)
);
return withIndexedLabels(ImmutableMap.copyOf(newIndexedLabels));
}
public Builder withLabels(String... categories) {
return withLabels(ImmutableList.copyOf(categories));
}
public Builder withLabels(ImmutableList categories) {
final Integer[] size = {labels.build().size()};
Map newIndexedLabels = categories.stream()
.collect(
MoreCollectors.toImmutableMap(s ->
Integer.toString(size[0]++, 36),
Function.identity()
)
);
return withIndexedLabels(ImmutableMap.copyOf(newIndexedLabels));
}
public Builder withIndex(ImmutableSet index) {
this.index.addAll(index);
return this;
}
/**
* Set the values of the dimension builder in index/label form.
*
* @param indexedLabels
*/
public Builder withIndexedLabels(ImmutableMap indexedLabels) {
// TODO: index seems unnecessary, we could use index.keySet()
index.addAll(indexedLabels.keySet());
labels.putAll(indexedLabels);
return this;
}
/**
* Set GEO role.
*
* Equivalent to {@code this.withRole(Roles.GEO);}
*
* @return the builder
*/
public Builder withGeoRole() {
return this.withRole(Roles.GEO);
}
/**
* Set METRIC role.
*
* Equivalent to {@code this.withRole(Roles.METRIC);}
*
* @return the builder
*/
public Builder withMetricRole() {
return this.withRole(Roles.METRIC);
}
/**
* Set TIME role.
*
* Equivalent to {@code this.withRole(Roles.TIME);}
*
* @return the builder
*/
public Builder withTimeRole() {
return this.withRole(Roles.TIME);
}
public Dimension build() {
Category category = new Category();
category.index = this.index.build();
category.label = this.labels.build();
Dimension dimension = new Dimension(category, this.role);
dimension.setLabel(this.label);
return dimension;
}
public ImmutableSet getIndex() {
return index.build();
}
protected boolean isMetric() {
return Roles.METRIC.equals(this.getRole());
}
protected Roles getRole() {
return this.role;
}
public boolean contains(String index) {
// TODO: Optimize this.
return this.labels.build().containsKey(index);
}
public Integer indexOf(String index) {
// TODO: Optimize this.
return Lists.newArrayList(this.labels.build().keySet()).indexOf(index);
}
}
}