/******************************************************************************* * 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 v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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 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 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 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) { if (this.compareTo(range.getStartTime()) >= 0 && this.compareTo(range.getEndTime()) <= 0) { return true; } return false; } // ------------------------------------------------------------------------ // 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(); } /** * Saturated addition. It will not overflow but instead clamp the result to * {@link Long#MAX_VALUE} and {@link Long#MIN_VALUE}. * * @param left * The left long to add * @param right * The right long to add * @return The saturated addition result. The mathematical, not Java version * of Min(Max(MIN_VALUE, left+right), MAX_VALUE). * @see <a href="http://en.wikipedia.org/wiki/Saturation_arithmetic"> * Saturation arithmetic</a> * @since 2.0 * @deprecated use {@link SaturatedArithmetic#add(long, long)} instead */ @Deprecated protected static final long saturatedAdd(final long left, final long right) { return SaturatedArithmetic.add(left, right); } // ------------------------------------------------------------------------ // 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); } } }