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

com.arpnetworking.metrics.impl.TsdCompoundUnit Maven / Gradle / Ivy

/**
 * Copyright 2015 Groupon.com
 *
 * 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 com.arpnetworking.metrics.impl;

import com.arpnetworking.metrics.CompoundUnit;
import com.arpnetworking.metrics.Unit;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nullable;

/**
 * Default implementation of CompoundUnit a subtype of
 * Unit which is expressed with a List of
 * numerator and denominator Unit instances. It is
 * permissible to build a TsdCompoundUnit from other
 * CompoundUnit instances; however, this representation
 * will flatten on build. Consequently, any redundant units are removed
 * which could result in an empty compound unit (e.g. Unit.NO_UNIT).
 * Finally, two compound units are considered equal if their components
 * are equal regardless of order (e.g. foot,pounds equals pound,feet).
 *
 * @author Ville Koskela (vkoskela at groupon dot com)
 */
public final class TsdCompoundUnit implements CompoundUnit {

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName() {
        final StringBuilder stringBuilder = new StringBuilder();
        final boolean numeratorParenthesis = _numeratorUnits.size() > 1 && !_denominatorUnits.isEmpty();
        final boolean denominatorParenthesis = _denominatorUnits.size() > 1;
        if (!_numeratorUnits.isEmpty()) {
            if (numeratorParenthesis) {
                stringBuilder.append("(");
            }
            for (final Unit unit : _numeratorUnits) {
                stringBuilder.append(unit.getName());
                stringBuilder.append("*");
            }
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
            if (numeratorParenthesis) {
                stringBuilder.append(")");
            }
        } else {
            stringBuilder.append("1");
        }
        if (!_denominatorUnits.isEmpty()) {
            stringBuilder.append("/");
            if (denominatorParenthesis) {
                stringBuilder.append("(");
            }
            for (final Unit unit : _denominatorUnits) {
                stringBuilder.append(unit.getName());
                stringBuilder.append("*");
            }
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
            if (denominatorParenthesis) {
                stringBuilder.append(")");
            }
        }
        return stringBuilder.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List getNumeratorUnits() {
        return Collections.unmodifiableList(_numeratorUnits);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List getDenominatorUnits() {
        return Collections.unmodifiableList(_denominatorUnits);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(final Object other) {
        if (this == other) {
            return true;
        }

        if (!(other instanceof TsdCompoundUnit)) {
            return false;
        }

        final TsdCompoundUnit otherCompoundUnit = (TsdCompoundUnit) other;
        return Objects.equals(_numeratorUnits, otherCompoundUnit._numeratorUnits)
                && Objects.equals(_denominatorUnits, otherCompoundUnit._denominatorUnits);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return Objects.hash(_numeratorUnits, _denominatorUnits);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return String.format(
                "TsdCompoundUnit{NumeratorUnits=%s, DenominatorUnits=%s}",
                getNumeratorUnits(),
                getDenominatorUnits());
    }

    private TsdCompoundUnit(final Builder builder) {
        _numeratorUnits = new ArrayList<>(builder._numeratorUnits);
        _denominatorUnits = new ArrayList<>(builder._denominatorUnits);
    }

    private final List _numeratorUnits;
    private final List _denominatorUnits;

    private static final Comparator UNIT_COMPARATOR = new UnitNameComparator();

    /**
     * Builder for TsdCompoundUnit.
     *
     * @author Ville Koskela (vkoskela at groupon dot com)
     */
    public static class Builder {

        /**
         * Create an instance of Unit. The instance may be
         * null if the Unit reduces to a constant
         * (e.g. Byte / Byte).
         *
         * @return Instance of Unit.
         */
        @Nullable public Unit build() {
            // Defaults
            if (_numeratorUnits == null) {
                _numeratorUnits = new ArrayList<>();
            }
            if (_denominatorUnits == null) {
                _denominatorUnits = new ArrayList<>();
            }

            // = Simplify =
            // The use of TreeMap is to ensure CompoundUnit instances are
            // created in a consistent manner (e.g. bytes * seconds vs.
            // seconds * bytes). Alternatively, we could use a MultiMap as
            // the representation and ignore this issue; however, no
            // implementation of MultiMap exists in core Java libraries.
            final Map numeratorUnitCount = new TreeMap<>(UNIT_COMPARATOR);
            final Map denominatorUnitCount = new TreeMap<>(UNIT_COMPARATOR);

            // 1. Flatten any nested compound units
            flattenUnits(_numeratorUnits, numeratorUnitCount, denominatorUnitCount);
            flattenUnits(_denominatorUnits, denominatorUnitCount, numeratorUnitCount);

            // 2. Remove any redundant units (e.g. in both the numerator and
            // denominator)
            reduceCommonUnits(numeratorUnitCount, denominatorUnitCount);

            // 3. Flatten the unit counts
            _numeratorUnits.clear();
            for (final Map.Entry unitCount : numeratorUnitCount.entrySet()) {
                for (int i = 0; i < unitCount.getValue().intValue(); ++i) {
                    _numeratorUnits.add(unitCount.getKey());
                }
            }
            _denominatorUnits.clear();
            for (final Map.Entry unitCount : denominatorUnitCount.entrySet()) {
                for (int i = 0; i < unitCount.getValue().intValue(); ++i) {
                    _denominatorUnits.add(unitCount.getKey());
                }
            }

            // Return a no unit if possible
            if (_denominatorUnits.isEmpty() && _numeratorUnits.isEmpty()) {
                return null;
            }

            // Return a base unit if possible
            if (_denominatorUnits.isEmpty() && _numeratorUnits.size() == 1) {
                return _numeratorUnits.get(0);
            }

            // Return the simplified compound unit
            return new TsdCompoundUnit(this);
        }

        /**
         * Set the numerator units.
         *
         * @param value The numerator units.
         * @return This Builder instance.
         */
        public Builder setNumeratorUnits(final List value) {
            _numeratorUnits = new ArrayList<>(value);
            return this;
        }

        /**
         * Add a numerator unit. Helper for merging units.
         *
         * @param values The numerator unit(s) to add.
         * @return This Builder instance.
         */
        public Builder addNumeratorUnit(final Unit... values) {
            if (_numeratorUnits == null) {
                _numeratorUnits = new ArrayList<>();
            }
            for (final Unit value : values) {
                _numeratorUnits.add(value);
            }
            return this;
        }

        /**
         * Set the denominator units.
         *
         * @param value The denominator units.
         * @return This Builder instance.
         */
        public Builder setDenominatorUnits(final List value) {
            _denominatorUnits = new ArrayList<>(value);
            return this;
        }

        /**
         * Add a denominator unit. Helper for merging units.
         *
         * @param values The denominator unit(s) to add.
         * @return This Builder instance.
         */
        public Builder addDenominatorUnit(final Unit... values) {
            if (_denominatorUnits == null) {
                _denominatorUnits = new ArrayList<>();
            }
            for (final Unit value : values) {
                _denominatorUnits.add(value);
            }
            return this;
        }

        private void flattenUnits(
                final List units,
                final Map numeratorUnitCount,
                final Map denominatorUnitCount) {
            for (final Unit unit : units) {
                if (unit instanceof CompoundUnit) {
                    final CompoundUnit compoundUnit = (CompoundUnit) unit;
                    splitCompoundUnit(compoundUnit, numeratorUnitCount, denominatorUnitCount);
                } else {
                    final Integer count = numeratorUnitCount.get(unit);
                    numeratorUnitCount.put(unit, Integer.valueOf(count == null ? 1 : count.intValue() + 1));
                }

            }
        }

        private void reduceCommonUnits(
                final Map numeratorUnitCount,
                final Map denominatorUnitCount) {
            // CHECKSTYLE.OFF: IllegalInstantiation - No Guava dependency here.
            final Set commonUnits = new HashSet<>(numeratorUnitCount.keySet());
            // CHECKSTYLE.ON: IllegalInstantiation
            commonUnits.retainAll(denominatorUnitCount.keySet());
            for (final Unit unit : commonUnits) {
                final int numeratorCount = numeratorUnitCount.get(unit).intValue();
                final int denominatorCount = denominatorUnitCount.get(unit).intValue();
                if (numeratorCount > denominatorCount) {
                    denominatorUnitCount.remove(unit);
                    numeratorUnitCount.put(unit, Integer.valueOf(numeratorCount - denominatorCount));
                } else if (denominatorCount > numeratorCount) {
                    numeratorUnitCount.remove(unit);
                    denominatorUnitCount.put(unit, Integer.valueOf(denominatorCount - numeratorCount));
                } else { // numeratorCount == denominatorCount
                    numeratorUnitCount.remove(unit);
                    denominatorUnitCount.remove(unit);
                }
            }
        }

        private void splitCompoundUnit(
                final CompoundUnit compoundUnit,
                final Map numeratorUnits,
                final Map denominatorUnits) {

            for (final Unit unit : compoundUnit.getNumeratorUnits()) {
                if (unit instanceof CompoundUnit) {
                    final CompoundUnit numeratorCompoundUnit = (CompoundUnit) unit;
                    splitCompoundUnit(numeratorCompoundUnit, numeratorUnits, denominatorUnits);
                } else {
                    final Integer count = numeratorUnits.get(unit);
                    numeratorUnits.put(unit, Integer.valueOf(count == null ? 1 : count.intValue() + 1));
                }
            }
            for (final Unit unit : compoundUnit.getDenominatorUnits()) {
                if (unit instanceof CompoundUnit) {
                    final CompoundUnit denominatorCompoundUnit = (CompoundUnit) unit;
                    splitCompoundUnit(denominatorCompoundUnit, denominatorUnits, numeratorUnits);
                } else {
                    final Integer count = denominatorUnits.get(unit);
                    denominatorUnits.put(unit, Integer.valueOf(count == null ? 1 : count.intValue() + 1));
                }
            }
        }

        private List _numeratorUnits;
        private List _denominatorUnits;
    }

    private static final class UnitNameComparator implements Comparator, Serializable {

        /**
         * {@inheritDoc}
         */
        @Override
        public int compare(final Unit unit1, final Unit unit2) {
            return unit1.getName().compareTo(unit2.getName());
        }

        private static final long serialVersionUID = -5279368571532165819L;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy