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

net.time4j.engine.AbstractMetric Maven / Gradle / Ivy

There is a newer version: 4.38
Show newest version
/*
 * -----------------------------------------------------------------------
 * Copyright © 2013-2016 Meno Hochschild, 
 * -----------------------------------------------------------------------
 * This file (AbstractMetric.java) is part of project Time4J.
 *
 * Time4J is free software: You can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Time4J is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Time4J. If not, see .
 * -----------------------------------------------------------------------
 */

package net.time4j.engine;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;


/**
 * 

Represents a metric suitable for the default algorithm of Time4J.

* *

If the starting time point is after the end time point then a * duration computed with this metric will be negative. In this case * the metric defined here will first toggle the time points to be * compared and then compare all elements in the order of ascending * precision. Elements which differ less than a full unit will cause * an amount of {@code 0} in related duration item. Convertible units * will be consolidated in one step. Finally the representation of * the duration will be normalized such that small units will be * converted to larger units if possible.

* * @param generic type of time unit ({@code ChronoUnit}) * @param

generic type of duration result * @author Meno Hochschild * @see AbstractDuration */ /*[deutsch] *

Repräsentiert eine Metrik passend zum Standardalgorithmus * von Time4J.

* *

Eine mit Hilfe der Metrik berechnete Zeitspanne ist negativ, wenn * der Start nach dem Endzeitpunkt liegt. Im Fall der negativen Zeitspanne * werden die zu vergleichenden Zeitpunkte zuerst vertauscht und dann ihre * Elemente miteinander verglichen, wieder in der Reihenfolge aufsteigender * Genauigkeit. Elemente, die sich um weniger als eine volle Zeiteinheit * unterscheiden, gelten als gleich. Konvertierbare Zeiteinheiten werden * dabei in einem Schritt zusammengefasst. Am Ende wird die Darstellung * in der Regel normalisiert, also kleine Zeiteinheiten so weit wie * möglich in große Einheiten umgerechnet.

* * @param generic type of time unit ({@code ChronoUnit}) * @param

generic type of duration result * @author Meno Hochschild * @see AbstractDuration */ public abstract class AbstractMetric> implements TimeMetric, Comparator { //~ Statische Felder/Initialisierungen -------------------------------- private static final int MIO = 1000000; //~ Instanzvariablen -------------------------------------------------- private final List sortedUnits; private final boolean normalizing; //~ Konstruktoren ----------------------------------------------------- /** *

Creates a new default metric with given array of time units.

* *

The given time units can be in any arbitrary order, but internally * the will be automatically sorted by their default estimated length.

* * @param normalizing Is normalizing required that is shall amounts * in small units be converted to bigger units? * @param units time units to be used for calculating time span * @throws IllegalArgumentException if any time unit is given more than * once or if there is no time unit at all */ /*[deutsch] *

Konstruiert eine neue Standardmetrik mit einem Array von * Zeiteinheiten.

* *

Die Zeiteinheiten können in beliebiger Reihenfolge * angegeben werden, aber intern werden sie über ihre * Standardlänge automatisch sortiert.

* * @param normalizing Is normalizing required that is shall amounts * in small units be converted to bigger units? * @param units time units to be used for calculating time span * @throws IllegalArgumentException if any time unit is given more than * once or if there is no time unit at all */ @SafeVarargs protected AbstractMetric( boolean normalizing, U... units ) { super(); if (units.length == 0) { throw new IllegalArgumentException("Missing units."); } for (int i = 0; i < units.length - 1; i++) { for (int j = i + 1; j < units.length; j++) { if (units[i].equals(units[j])) { throw new IllegalArgumentException( "Duplicate unit: " + units[i]); } } } Arrays.sort(units, this); this.sortedUnits = Collections.unmodifiableList(Arrays.asList(units)); this.normalizing = normalizing; } /** *

Creates a new default metric with given set of time units.

* *

The given time units can be in any arbitrary order, but internally * the will be automatically sorted by their default estimated length.

* * @param normalizing Is normalizing required that is shall amounts * in small units be converted to bigger units? * @param units time units to be used for calculating time span * @throws IllegalArgumentException if there is not time unit at all */ /*[deutsch] *

Konstruiert eine neue Standardmetrik mit einem {@code Set} * von Zeiteinheiten.

* *

Die Zeiteinheiten können in beliebiger Reihenfolge * angegeben werden, aber intern werden sie über ihre * Standardlänge automatisch sortiert.

* * @param normalizing Is normalizing required that is shall amounts * in small units be converted to bigger units? * @param units time units to be used for calculating time span * @throws IllegalArgumentException if there is not time unit at all */ protected AbstractMetric( boolean normalizing, Set units ) { super(); if (units.isEmpty()) { throw new IllegalArgumentException("Missing units."); } List list = new ArrayList<>(units); Collections.sort(list, this); this.sortedUnits = Collections.unmodifiableList(list); this.normalizing = normalizing; } //~ Methoden ---------------------------------------------------------- /** *

Compares time units by their length in descending order.

* * @param u1 first time unit * @param u2 second time unit * @return negative, zero or positive if u1 is greater, equal to * or smaller than u2 */ /*[deutsch] *

Vergleicht Zeiteinheiten absteigend nach ihrer Länge.

* * @param u1 first time unit * @param u2 second time unit * @return negative, zero or positive if u1 is greater, equal to * or smaller than u2 */ @Override public int compare(U u1, U u2) { return Double.compare(u2.getLength(), u1.getLength()); // descending } @Override public > P between( T start, T end ) { if (end.equals(start)) { return this.createEmptyTimeSpan(); } T t1 = start; T t2 = end; boolean negative = false; // Lage von Start und Ende bestimmen if (t1.compareTo(t2) > 0) { T temp = t1; t1 = end; t2 = temp; negative = true; } List> resultList = new ArrayList<>(10); TimeAxis engine = start.getChronology(); U unit = null; long amount = 0; int index = 0; int endIndex = this.sortedUnits.size(); while (index < endIndex) { // Nächste Subtraktion vorbereiten if (amount > 0) { t1 = t1.plus(amount, unit); } // Aktuelle Zeiteinheit bestimmen unit = this.sortedUnits.get(index); if ( (this.getLength(engine, unit) < 1.0) && (index < endIndex - 1) ) { amount = 0; // Millis oder Mikros vor Nanos nicht berechnen (maximal eine fraktionale Einheit) } else { // konvertierbare Einheiten zusammenfassen int k = index + 1; long factor = 1; while (k < endIndex) { U nextUnit = this.sortedUnits.get(k); factor *= this.getFactor(engine, unit, nextUnit); if ( (factor < MIO) && engine.isConvertible(unit, nextUnit) ) { unit = nextUnit; } else { break; } k++; } index = k - 1; // Differenz in einer Einheit berechnen amount = t1.until(t2, unit); if (amount > 0) { resultList.add(this.resolve(TimeSpan.Item.of(amount, unit))); } else if (amount < 0) { throw new IllegalStateException( "Implementation error: " + "Cannot compute timespan " + "due to illegal negative timespan amounts."); } } index++; } if (this.normalizing) { this.normalize(engine, this.sortedUnits, resultList); } return this.createTimeSpan(resultList, negative); } /** *

Creates an empty time span.

* * @return empty time span without any time units * @see TimeSpan#isEmpty() */ /*[deutsch] *

Erzeugt eine leere Zeitspanne.

* * @return empty time span without any time units * @see TimeSpan#isEmpty() */ protected abstract P createEmptyTimeSpan(); /** *

Creates a time span with the given units and amounts.

* * @param items elements of time span * @param negative sign of time span * @return new time span */ /*[deutsch] *

Erzeugt eine Zeitspanne mit den angegebenen Einheiten und * Beträgen.

* * @param items elements of time span * @param negative sign of time span * @return new time span */ protected abstract P createTimeSpan( List> items, boolean negative ); /** *

Hook for adjustments like resolving millis or micros to nanos.

* * @param item item to be adjusted * @return adjusted item (usually the same as the argument) * @since 3.21/4.17 */ /*[deutsch] *

Einsprungpunkt für Anpassungen wie die Auflösung von Milli- und Mikrosekunden zu Nanosekunden.

* * @param item item to be adjusted * @return adjusted item (usually the same as the argument) * @since 3.21/4.17 */ protected TimeSpan.Item resolve(TimeSpan.Item item) { return item; } private > void normalize( TimeAxis engine, List sortedUnits, List> resultList ) { Comparator comparator = engine.unitComparator(); for (int i = sortedUnits.size() - 1; i >= 0; i--) { if (i > 0) { U currentUnit = sortedUnits.get(i); U nextUnit = sortedUnits.get(i - 1); long factor = this.getFactor(engine, nextUnit, currentUnit); if ( (factor < MIO) && engine.isConvertible(nextUnit, currentUnit) ) { TimeSpan.Item currentItem = getItem(resultList, currentUnit); if (currentItem != null) { long currentValue = currentItem.getAmount(); long overflow = currentValue / factor; if (overflow > 0) { long a = currentValue % factor; if (a == 0) { removeItem(resultList, currentUnit); } else { putItem(resultList, comparator, a, currentUnit); } TimeSpan.Item nextItem = getItem(resultList, nextUnit); if (nextItem == null) { putItem(resultList, comparator, overflow, nextUnit); } else { putItem( resultList, comparator, Math.addExact(nextItem.getAmount(), overflow), nextUnit ); } } } } } } } private static TimeSpan.Item getItem( List> items, U unit ) { for (int i = 0, n = items.size(); i < n; i++) { TimeSpan.Item item = items.get(i); if (item.getUnit().equals(unit)) { return item; } } return null; } private static void putItem( List> items, Comparator comparator, long amount, U unit ) { TimeSpan.Item item = TimeSpan.Item.of(amount, unit); int insert = 0; for (int i = 0, n = items.size(); i < n; i++) { U u = items.get(i).getUnit(); if (u.equals(unit)) { items.set(i, item); return; } else if ( (insert == i) && (comparator.compare(u, unit) < 0) ) { insert++; } } items.add(insert, item); } private static void removeItem( List> items, U unit ) { for (int i = 0, n = items.size(); i < n; i++) { if (items.get(i).getUnit().equals(unit)) { items.remove(i); return; } } } private > long getFactor( TimeAxis engine, U unit1, U unit2 ) { double d1 = this.getLength(engine, unit1); double d2 = this.getLength(engine, unit2); return Math.round(d1 / d2); } private > double getLength( TimeAxis engine, U unit ) { return engine.getLength(unit); } }