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

no.ssb.jsonstat.v2.Dimension Maven / Gradle / Ivy

The newest version!
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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy