/*******************************************************************************
* Copyright (c) 2012-2015 INRIA.
* 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:
* Generoso Pagano - initial API and implementation
******************************************************************************/
package fr.inria.soctrace.lib.model.utils;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import fr.inria.soctrace.lib.model.utils.ModelConstants.TimeUnit;
/**
* Number format for timestamps.
*
* Note, in all the methods, the position parameter is ignored.
*
* @author "Generoso Pagano <generoso.pagano@inria.fr>"
*/
public class TimestampFormat extends NumberFormat {
/**
* Generated serial version ID
*/
private static final long serialVersionUID = -5615549237196509700L;
/**
* Default number of decimalss
*/
private static final int DEFAULT_DECIMALS = 3;
/**
* Constant for not set engineering notation exponent
*/
private static final int ENG_NOT_SET = -1;
/**
* Maximum number of decimal digits
*/
private static final int MAX_DECIMALS = 9;
/**
* Decimal format without exponent part
*/
private final DecimalFormat noExpFormat = new DecimalFormat("###.#");
/**
* Decimal format with exponent part
*/
private final DecimalFormat expFormat = new DecimalFormat("###.#E0");
/**
* Number of fraction digits
*/
private int decimals = DEFAULT_DECIMALS;
/**
* Time unit
*/
private TimeUnit unit;
/**
* Engineering notation exponent to be used
*/
private int eng = ENG_NOT_SET;
/**
* Create a timestamp format with a <code>TimeUnit.UNKNOWN</code> time unit.
*/
public TimestampFormat() {
this.unit = TimeUnit.UNKNOWN;
}
/**
* Create a timestamp format with the passed time unit
*
* @param unit
* time unit
*/
public TimestampFormat(TimeUnit unit) {
this.unit = unit;
}
/**
* @param unit
* the time unit to set
*/
public void setTimeUnit(TimeUnit unit) {
this.unit = unit;
}
/**
* @return the time unit
*/
public TimeUnit getTimeUnit() {
return unit;
}
/**
* Note: Ignoring parameter pos.
*/
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
switch (unit) {
case UNKNOWN:
case CYCLE:
case TICK:
expFormat.setMaximumIntegerDigits(3);
noExpFormat.setMaximumFractionDigits(1);
toAppendTo.append(expFormat.format(number));
return toAppendTo;
default:
break;
}
return formatCompact(number, toAppendTo);
}
/**
* Note: Ignoring parameter pos.
*/
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
return format(Double.valueOf(number), toAppendTo, pos);
}
/**
* Note: Ignoring parameter pos.
*/
@Override
public Number parse(String text, ParsePosition pos) {
return Double.valueOf(text);
}
/**
* Computes the good number of decimal digits to see a difference between numbers contained
* between the two numbers passed. This number is limited at {@value #MAX_DECIMALS}.
*
* @param t1
* lowest displayed timestamp
* @param t2
* highest displayed timestamp
*/
public void setContext(long t1, long t2) {
int e1 = getEngExp(t1);
int e2 = getEngExp(t2);
eng = Math.max(e1, e2);
StringBuffer sb1 = new StringBuffer();
StringBuffer sb2 = new StringBuffer();
decimals = 1;
for (; decimals < MAX_DECIMALS; decimals++) {
sb1.setLength(0);
sb2.setLength(0);
formatCompact(t1, sb1);
formatCompact(t2, sb2);
if (!sb1.toString().equals(sb2.toString()))
break;
}
decimals = Math.min(decimals + 1, MAX_DECIMALS);
}
/**
* Get the engineering notation exponent
*
* @param number
* timestamp
* @return the smallest engineering notation exponent
*/
private int getEngExp(long number) {
int eng = 0;
while (number > 1000) {
number /= 1000.0;
eng++;
}
return eng;
}
/**
* Format the number using the default settings or the ones computed when setting the context.
*
* @param number
* timestamp
* @param toAppendTo
* output string buffer
* @return string buffer containing the formatted value
*/
private StringBuffer formatCompact(double number, StringBuffer toAppendTo) {
// compute an exponent in engineering notation, or use the one set by the context
// transforming the number accordingly
int usedEng = 0;
Double tmp = number;
if (eng == -1) {
// a fixed context has not been set, find the lowest exponent in engineering notation
while (tmp.longValue() > 1000) {
tmp /= 1000.0;
usedEng++;
}
} else {
// a fixed context has been set, divide by 1000 according to the context
for (int i = 0; i < eng; i++) {
tmp /= 1000.0;
}
usedEng = eng;
}
// compute the real exponent, when expressing the number in seconds
int realExp = usedEng * 3 + unit.getInt();
if (realExp > 0) {
// number is more than seconds
tmp *= Math.pow(10, realExp);
if (realExp < 3) {
noExpFormat.setMaximumFractionDigits(decimals);
toAppendTo.append(noExpFormat.format(tmp));
} else {
expFormat.setMaximumFractionDigits(Math.max(1, decimals - 2));
toAppendTo.append(expFormat.format(tmp));
}
toAppendTo.append(" s");
} else {
// number is seconds or less
noExpFormat.setMaximumFractionDigits(decimals);
toAppendTo.append(noExpFormat.format(tmp));
toAppendTo.append(" ");
toAppendTo.append(TimeUnit.getLabel(realExp));
}
return toAppendTo;
}
/**
* Compute a GradDescriptor to be used in timebar displaying.
*
* This method implements the following algorithm.
*
* <pre>
* Input: t0, t1, # of ticks hint (N)
* Output: first tick (t0') and delta (D)
* Algorithm:
* - compute step: (t1-t0)/(N+1)
* - let D be the most significant digit in step
* - in t0, consider only the digit up to the one in D position, obtaining t0' (rounded)
* - the first tick is (t0')*10^(position of D in step) if this is bigger than t0,
* otherwise we sum D*10^(position of D in step)
* - to get the others, always sum D*10^(position of D in step)
*
* E.g.:
* - t0: 5129, t1: 7259, N: 9
* - step: 213
* - D = 2 (2|13)
* - position of D in step: 2nd position
* - t0'=51 (51|29)
* - first tick:
* - 51 * 10^2 = 5100 is less than t0 so we add 2 * 10^2: 5300
* - second tick: 5300 * 10^2 = 5500
* - other ticks: 5700, 5900, ....
*
* </pre>
*
* @param t0
* minimum value
* @param t1
* max value
* @param numberOfTicksHint
* hint on the desired number of ticks
* @return
*/
public TickDescriptor getTickDescriptor(long t0, long t1, int numberOfTicksHint) {
TickDescriptor des = new TickDescriptor();
des.delta = (t1 - t0) / (numberOfTicksHint + 1);
des.first = t0;
int exp = 0;
long step = des.delta;
while (step > 0) {
step /= 10;
exp++;
}
exp--;
long factor = (long) Math.pow(10, exp);
if (factor == 0) {
return des;
}
des.delta /= factor;
des.delta *= factor;
des.first /= factor;
des.first *= factor;
if (des.first < t0) {
des.first += des.delta;
}
return des;
}
/**
* Tick Descriptor for time bar displaying.
*/
public static class TickDescriptor {
/**
* First timestamp to display
*/
public long first;
/**
* Delta between timestamps
*/
public long delta;
}
}