blob: 4db06930f3f3b81b37a350ae68b9f036342f38c5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2014 Ericsson, École Polytechnique de Montréal
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Francois Chouinard - Initial API and implementation, refactoring and updates
* Thomas Gatterweh - Updated scaling / synchronization
* Geneviève Bastien - Added copy constructor with new value
* Alexandre Montplaisir - Removed concept of precision
*******************************************************************************/
package org.eclipse.tracecompass.tmf.core.timestamp;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.common.core.math.SaturatedArithmetic;
import org.eclipse.tracecompass.internal.tmf.core.timestamp.TmfNanoTimestamp;
import org.eclipse.tracecompass.internal.tmf.core.timestamp.TmfSecondTimestamp;
/**
* A generic timestamp implementation. The timestamp is represented by the tuple
* { value, scale, precision }.
*
* @author Francois Chouinard
*/
public abstract class TmfTimestamp implements ITmfTimestamp {
/**
* Default implementation of the tmf timestamp. We want this to be hidden.
*
* @author Matthew Khouzam
*
*/
private static final class Impl extends TmfTimestamp {
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
/**
* The timestamp raw value (mantissa)
*/
private final long fValue;
/**
* The timestamp scale (magnitude)
*/
private final int fScale;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Full constructor
*
* @param value
* the timestamp value
* @param scale
* the timestamp scale
*/
public Impl(final long value, final int scale) {
fValue = value;
fScale = scale;
}
@Override
public long getValue() {
return fValue;
}
@Override
public int getScale() {
return fScale;
}
}
/**
* Create a timestamp.
*
* @param value
* the value in nanoseconds
* @return the timestamp
* @since 2.0
*/
public static @NonNull ITmfTimestamp fromNanos(long value) {
return new TmfNanoTimestamp(value);
}
/**
* Create a timestamp.
*
* @param value
* the value in microseconds
* @return the timestamp
* @since 2.0
*/
public static @NonNull ITmfTimestamp fromMicros(long value) {
return create(value, ITmfTimestamp.MICROSECOND_SCALE);
}
/**
* Create a timestamp.
*
* @param value
* the value in milliseconds
* @return the timestamp
* @since 2.0
*/
public static @NonNull ITmfTimestamp fromMillis(long value) {
return create(value, ITmfTimestamp.MILLISECOND_SCALE);
}
/**
* Create a timestamp.
*
* @param value
* the value in seconds
* @return the timestamp
* @since 2.0
*/
public static @NonNull ITmfTimestamp fromSeconds(long value) {
return new TmfSecondTimestamp(value);
}
/**
* Create a timestamp.
*
* @param bufferIn
* the byte buffer to read the timestamp from.
* @return the timestamp
* @since 2.0
*/
public static @NonNull ITmfTimestamp create(ByteBuffer bufferIn) {
return create(bufferIn.getLong(), bufferIn.getInt());
}
/**
* Create a timestamp.
*
* @param value
* the value in time, the unit is specified by the scale
* @param scale
* the scale of the timestamp with respect to seconds, so a
* nanosecond would be -9 (10e-9) and a megasecond would be 6
* (10e6)
* @return the timestamp
* @since 2.0
*/
public static @NonNull ITmfTimestamp create(long value, int scale) {
if (scale == ITmfTimestamp.NANOSECOND_SCALE) {
return fromNanos(value);
}
if (scale == ITmfTimestamp.SECOND_SCALE) {
return fromSeconds(value);
}
if (value == 0) {
return ZERO;
}
return createOther(value, scale);
}
/**
* Write the time stamp to the ByteBuffer so that it can be saved to disk.
*
* @param bufferOut
* the buffer to write to
* @param ts
* the timestamp to write
* @since 2.0
*/
public static void serialize(ByteBuffer bufferOut, ITmfTimestamp ts) {
bufferOut.putLong(ts.getValue());
bufferOut.putInt(ts.getScale());
}
private static @NonNull ITmfTimestamp createOther(long value, int scale) {
return new Impl(value, scale);
}
// ------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------
/**
* Zero - a zero time constant. The value is zero, so this allows some
* interesting simplifications.
*/
public static final @NonNull ITmfTimestamp ZERO = new Zero();
private static final class Zero extends TmfTimestamp {
@Override
public long getValue() {
return 0;
}
@Override
public int getScale() {
return 0;
}
@Override
public @NonNull ITmfTimestamp normalize(long offset, int scale) {
if (offset == 0) {
return this;
}
return create(offset, scale);
}
@Override
public int compareTo(ITmfTimestamp ts) {
return Long.compare(0, ts.getValue());
}
}
/**
* The beginning of time will be lesser than any other timestamp
*/
public static final @NonNull ITmfTimestamp BIG_BANG = new BigBang();
private static final class BigBang extends TmfTimestamp {
@Override
public long getValue() {
return Long.MIN_VALUE;
}
@Override
public int getScale() {
return Integer.MAX_VALUE;
}
@Override
public int compareTo(ITmfTimestamp other) {
if (equals(other)) {
return 0;
}
return -1;
}
@Override
public ITmfTimestamp normalize(long offset, int scale) {
return this;
}
@Override
public boolean equals(Object other) {
return this == other;
}
}
/**
* The end of time will be greater than any other timestamp
*/
public static final @NonNull ITmfTimestamp BIG_CRUNCH = new BigCrunch();
private static final class BigCrunch extends TmfTimestamp {
@Override
public long getValue() {
return Long.MAX_VALUE;
}
@Override
public int getScale() {
return Integer.MAX_VALUE;
}
@Override
public int compareTo(ITmfTimestamp other) {
if (equals(other) == true) {
return 0;
}
return 1;
}
@Override
public ITmfTimestamp normalize(long offset, int scale) {
return this;
}
@Override
public boolean equals(Object other) {
return this == other;
}
}
// ------------------------------------------------------------------------
// ITmfTimestamp
// ------------------------------------------------------------------------
/**
* Scaling factors to help scale
*
* @since 2.0
*/
protected static final long SCALING_FACTORS[] = new long[] {
1L,
10L,
100L,
1000L,
10000L,
100000L,
1000000L,
10000000L,
100000000L,
1000000000L,
10000000000L,
100000000000L,
1000000000000L,
10000000000000L,
100000000000000L,
1000000000000000L,
10000000000000000L,
100000000000000000L,
1000000000000000000L,
};
@Override
public ITmfTimestamp normalize(final long offset, final int scale) {
long value = getValue();
// Handle the trivial case
if (getScale() == scale && offset == 0) {
return this;
}
if (value == 0) {
return create(offset, scale);
}
// First, scale the timestamp
if (getScale() != scale) {
final int scaleDiff = Math.abs(getScale() - scale);
if (scaleDiff >= SCALING_FACTORS.length) {
if (getScale() < scale) {
value = 0;
} else {
value = value > 0 ? Long.MAX_VALUE : Long.MIN_VALUE;
}
} else {
final long scalingFactor = SCALING_FACTORS[scaleDiff];
if (getScale() < scale) {
value /= scalingFactor;
} else {
value = SaturatedArithmetic.multiply(scalingFactor, value);
}
}
}
value = SaturatedArithmetic.add(value, offset);
return create(value, scale);
}
@Override
public ITmfTimestamp getDelta(final ITmfTimestamp ts) {
final int scale = getScale();
final ITmfTimestamp nts = ts.normalize(0, scale);
final long value = getValue() - nts.getValue();
return new TmfTimestampDelta(value, scale);
}
@Override
public boolean intersects(TmfTimeRange range) {
return ((this.compareTo(range.getStartTime()) >= 0 &&
this.compareTo(range.getEndTime()) <= 0));
}
// ------------------------------------------------------------------------
// Comparable
// ------------------------------------------------------------------------
@Override
public int compareTo(final ITmfTimestamp ts) {
long value = getValue();
int scale = getScale();
// Check the corner cases (we can't use equals() because it uses
// compareTo()...)
if (BIG_BANG.equals(ts)) {
return 1;
}
if (BIG_CRUNCH.equals(ts)) {
return -1;
}
if (this == ts || isIdentical(this, ts)) {
return 0;
}
if (scale == ts.getScale()) {
if (ts.getValue() == Long.MIN_VALUE) {
return 1;
}
final long delta = SaturatedArithmetic.add(getValue(), -ts.getValue());
return Long.compare(delta, 0);
}
final ITmfTimestamp largerScale = (scale > ts.getScale()) ? this : ts;
final ITmfTimestamp smallerScale = (scale < ts.getScale()) ? this : ts;
final ITmfTimestamp nts = largerScale.normalize(0, smallerScale.getScale());
if (hasSaturated(largerScale, nts)) {
// We've saturated largerScale.
if (smallerScale.getScale() == scale) {
return Long.compare(0, nts.getValue());
}
return Long.compare(nts.getValue(), 0);
}
if (smallerScale.getScale() == scale) {
return Long.compare(value, nts.getValue());
}
return Long.compare(nts.getValue(), smallerScale.getValue());
}
private static boolean hasSaturated(final ITmfTimestamp ts, final ITmfTimestamp nts) {
return (nts.getValue() == 0 && ts.getValue() != 0) || !isIdentical(ts, nts) && ((nts.getValue() == Long.MAX_VALUE) || (nts.getValue() == Long.MIN_VALUE));
}
private static boolean isIdentical(final ITmfTimestamp ts, final ITmfTimestamp nts) {
return ts.getValue() == nts.getValue() && ts.getScale() == nts.getScale();
}
// ------------------------------------------------------------------------
// Object
// ------------------------------------------------------------------------
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
final long value = getValue();
result = prime * result + (int) (value ^ (value >>> 32));
result = prime * result + getScale();
return result;
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
if (!(other instanceof ITmfTimestamp)) {
return false;
}
/* We allow comparing with other types of *I*TmfTimestamp though */
final ITmfTimestamp ts = (ITmfTimestamp) other;
if (getScale() == ts.getScale()) {
return getValue() == ts.getValue();
}
return (compareTo(ts) == 0);
}
@Override
public String toString() {
return toString(TmfTimestampFormat.getDefaulTimeFormat());
}
@Override
public String toString(final TmfTimestampFormat format) {
try {
return format.format(toNanos());
} catch (ArithmeticException e) {
return format.format(0);
}
}
}