no.ssb.jsonstat.v2.Dimension.java Source code

Java tutorial

Introduction

Here is the source code for no.ssb.jsonstat.v2.Dimension.java

Source

/**
 * Copyright (C) 2016 Hadrien Kohl (hadrien.kohl@gmail.com) and contributors
 *
 *     Dimension.java
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
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 <a href="https://json-stat.org/format/#dimension">https://json-stat.org/format/#dimension</a>
 */
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<String> 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<String, String> 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<String> 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<String, String> child;

        private Map<String, Coordinate> coordinates;

        // TODO: Only valid for dimension with metric role.
        // TODO: Implies that index is index is a map.
        private Map<String, String> unit;

        public ImmutableMap<String, String> getLabel() {
            return label;
        }

        public ImmutableSet<String> 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<String> index;
        private final ImmutableMap.Builder<String, String> 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<String> categories) {
            Map<String, String> 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<String> categories) {
            final Integer[] size = { labels.build().size() };
            Map<String, String> newIndexedLabels = categories.stream().collect(
                    MoreCollectors.toImmutableMap(s -> Integer.toString(size[0]++, 36), Function.identity()));

            return withIndexedLabels(ImmutableMap.copyOf(newIndexedLabels));

        }

        public Builder withIndex(ImmutableSet<String> index) {
            this.index.addAll(index);
            return this;
        }

        /**
         * Set the values of the dimension builder in index/label form.
         *
         * @param indexedLabels
         */
        public Builder withIndexedLabels(ImmutableMap<String, String> indexedLabels) {
            // TODO: index seems unnecessary, we could use index.keySet()
            index.addAll(indexedLabels.keySet());
            labels.putAll(indexedLabels);
            return this;
        }

        /**
         * Set GEO role.
         * <p>
         * Equivalent to {@code this.withRole(Roles.GEO);}
         *
         * @return the builder
         */
        public Builder withGeoRole() {
            return this.withRole(Roles.GEO);
        }

        /**
         * Set METRIC role.
         * <p>
         * Equivalent to {@code this.withRole(Roles.METRIC);}
         *
         * @return the builder
         */
        public Builder withMetricRole() {
            return this.withRole(Roles.METRIC);
        }

        /**
         * Set TIME role.
         * <p>
         * 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<String> 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);
        }
    }
}