--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS inc, Michael Jeanson
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.tests.format;
+
+import java.text.Format;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.junit.Test;
+
+/**
+ * Test the {@link DecimalUnitFormat} class
+ *
+ * @author Michael Jeanson
+ */
+public class DecimalUnitFormatErrorTest {
+
+ private static final @NonNull Format FORMATTER = new DecimalUnitFormat();
+
+ /**
+ * Test an illegal argument
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testException() {
+ FORMATTER.format(new String("Toto"));
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS inc, Michael Jeanson
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.tests.format;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.Format;
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test the {@link DecimalUnitFormat} class
+ *
+ * @author Michael Jeanson
+ */
+@RunWith(Parameterized.class)
+public class DecimalUnitFormatFactorTest {
+
+ private final @NonNull Format fFormat;
+
+ private final @NonNull Number fNumValue;
+ private final @NonNull String fExpected;
+
+ /**
+ * Constructor
+ *
+ * @param value
+ * The numeric value to format
+ * @param expected
+ * The expected formatted result
+ * @param factor
+ * The multiplication factor to apply before formatting
+ */
+ public DecimalUnitFormatFactorTest(@NonNull Number value, @NonNull String expected, @NonNull Double factor) {
+ fNumValue = value;
+ fExpected = expected;
+ fFormat = new DecimalUnitFormat(factor);
+ }
+
+ /**
+ * @return The arrays of parameters
+ */
+ @Parameters(name = "{index}: {0}")
+ public static Iterable<Object[]> getParameters() {
+ return Arrays.asList(new Object[][] {
+ { 0, "0", 10.0 },
+ { 3, "300", 100.0 },
+ { 975, "97.5", 0.1 },
+ { 1000, "1 k", 1.0 },
+ { 4000, "40", 0.01 },
+ { -4000, "-40", 0.01 },
+ { -0.04, "-4", 100.0 },
+ { 0.002, "20", 10000.0 },
+ { 0.0555, "5.5 k", 100000.0 },
+ { 0.0004928373928, "49.3 n", 0.0001 },
+ { 0.000000251, "251 p", 0.001 },
+ { Double.POSITIVE_INFINITY, "∞", 0.001 },
+ { Double.MAX_VALUE, "4", Double.MIN_NORMAL},
+ });
+ }
+
+ /**
+ * Test the {@link Format#format(Object)} method
+ */
+ @Test
+ public void testFormat() {
+ assertEquals("format value", fExpected, fFormat.format(fNumValue));
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS inc, Michael Jeanson
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.tests.format;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.Format;
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test the {@link DecimalUnitFormat} class
+ *
+ * @author Michael Jeanson
+ */
+@RunWith(Parameterized.class)
+public class DecimalUnitFormatTest {
+
+ private static final @NonNull Format FORMATTER = new DecimalUnitFormat();
+
+ private final @NonNull Number fNumValue;
+ private final @NonNull String fExpected;
+
+ /**
+ * Constructor
+ *
+ * @param value
+ * The numeric value to format
+ * @param expected
+ * The expected formatted result
+ */
+ public DecimalUnitFormatTest(@NonNull Number value, @NonNull String expected) {
+ fNumValue = value;
+ fExpected = expected;
+ }
+
+ /**
+ * @return The arrays of parameters
+ */
+ @Parameters(name = "{index}: {0}")
+ public static Iterable<Object[]> getParameters() {
+ return Arrays.asList(new Object[][] {
+ { 0, "0" },
+ { 3, "3" },
+ { 975, "975" },
+ { 1000, "1 k" },
+ { 4000, "4 k" },
+ { -4000, "-4 k" },
+ { 4000L, "4 k" },
+ { 4000.0, "4 k" },
+ { 12345678, "12.3 M" },
+ { Integer.MAX_VALUE, "2.1 G" },
+ { Integer.MIN_VALUE, "-2.1 G" },
+ { Long.MAX_VALUE, "9223.4 P" },
+ { 98765432.123456, "98.8 M" },
+ { -98765432.123456, "-98.8 M" },
+ { 555555555555L, "555.6 G" },
+ { 555555555555555L, "555.6 T" },
+ { 100100000, "100.1 M" },
+ { 0.1, "100 m" },
+ { 0.001, "1 m" },
+ { 0.000001, "1 µ" },
+ { 0.000000001, "1 n" },
+ { 0.000000000001, "1 p" },
+ { -0.04, "-40 m" },
+ { 0.002, "2 m" },
+ { 0.0555, "55.5 m" },
+ { 0.0004928373928, "492.8 µ" },
+ { 0.000000251, "251 n"},
+ { 0.000000000043, "43 p"},
+ { 0.000000045643, "45.6 n"},
+ { Double.MAX_VALUE, "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 P" },
+ { Double.POSITIVE_INFINITY, "∞" },
+ { Double.MIN_NORMAL, "0" },
+ { Double.NEGATIVE_INFINITY, "-∞" },
+ { Double.NaN, "�" },
+ });
+ }
+
+ /**
+ * Test the {@link Format#format(Object)} method
+ */
+ @Test
+ public void testFormat() {
+ assertEquals("format value", fExpected, FORMATTER.format(fNumValue));
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Michael Jeanson
+ *
+ * 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
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.format;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+
+/**
+ * Provides a formatter for decimal numbers with International System of Units
+ * prefixes up to peta (quadrillion). It receives a number and formats it in the
+ * closest thousand's unit, with at most 1 decimal.
+ *
+ * @author Michael Jeanson
+ * @since 2.0
+ */
+public class DecimalUnitFormat extends Format {
+
+ private static final long serialVersionUID = 3650332020346870384L;
+
+ /* International System of Units prefixes */
+ private static final String KILO_PREFIX = "k"; //$NON-NLS-1$
+ private static final String MEGA_PREFIX = "M"; //$NON-NLS-1$
+ private static final String GIGA_PREFIX = "G"; //$NON-NLS-1$
+ private static final String TERA_PREFIX = "T"; //$NON-NLS-1$
+ private static final String PETA_PREFIX = "P"; //$NON-NLS-1$
+
+ private static final String MILLI_PREFIX = "m"; //$NON-NLS-1$
+ private static final String MICRO_PREFIX = "µ"; //$NON-NLS-1$
+ private static final String NANO_PREFIX = "n"; //$NON-NLS-1$
+ private static final String PICO_PREFIX = "p"; //$NON-NLS-1$
+
+ private static final long KILO = 1000L;
+ private static final long MEGA = 1000000L;
+ private static final long GIGA = 1000000000L;
+ private static final long TERA = 1000000000000L;
+ private static final long PETA = 1000000000000000L;
+
+ private static final double MILLI = 0.001;
+ private static final double MICRO = 0.000001;
+ private static final double NANO = 0.000000001;
+ private static final double PICO = 0.000000000001;
+
+ private static final Format FORMAT = new DecimalFormat("#.#"); //$NON-NLS-1$
+ private final double fFactor;
+
+
+ /**
+ * Default constructor.
+ */
+ public DecimalUnitFormat() {
+ super();
+ fFactor = 1.0;
+ }
+
+ /**
+ * Constructor with multiplication factor.
+ *
+ * @param factor Multiplication factor to apply to the value
+ */
+ public DecimalUnitFormat(double factor) {
+ super();
+ fFactor = factor;
+ }
+
+ @Override
+ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+ if (obj instanceof Number) {
+ Number num = (Number) obj;
+
+ /* Apply the multiplication factor before formatting */
+ double value = num.doubleValue() * fFactor;
+
+ double abs = Math.abs(value);
+
+ if (Double.isInfinite(value) || Double.isNaN(value) || abs < PICO) {
+ return toAppendTo.append(FORMAT.format(value));
+ }
+
+ if (abs >= 1) {
+ if (abs >= PETA) {
+ return toAppendTo.append(FORMAT.format(value / PETA)).append(' ').append(PETA_PREFIX);
+ }
+ if (abs >= TERA) {
+ return toAppendTo.append(FORMAT.format(value / TERA)).append(' ').append(TERA_PREFIX);
+ }
+ if (abs >= GIGA) {
+ return toAppendTo.append(FORMAT.format(value / GIGA)).append(' ').append(GIGA_PREFIX);
+ }
+ if (abs >= MEGA) {
+ return toAppendTo.append(FORMAT.format(value / MEGA)).append(' ').append(MEGA_PREFIX);
+ }
+ if (abs >= KILO) {
+ return toAppendTo.append(FORMAT.format(value / KILO)).append(' ').append(KILO_PREFIX);
+ }
+
+ return toAppendTo.append(FORMAT.format(value));
+ }
+
+ if (abs < NANO) {
+ return toAppendTo.append(FORMAT.format(value * TERA)).append(' ').append(PICO_PREFIX);
+ }
+ if (abs < MICRO) {
+ return toAppendTo.append(FORMAT.format(value * GIGA)).append(' ').append(NANO_PREFIX);
+ }
+ if (abs < MILLI) {
+ return toAppendTo.append(FORMAT.format(value * MEGA)).append(' ').append(MICRO_PREFIX);
+ }
+
+ return toAppendTo.append(FORMAT.format(value * KILO)).append(' ').append(MILLI_PREFIX);
+ }
+
+ throw new IllegalArgumentException("Cannot format given Object as a Number: " + obj); //$NON-NLS-1$
+ }
+
+ @Override
+ public Object parseObject(String source, ParsePosition pos) {
+ return (source == null ? "" : source); //$NON-NLS-1$
+ }
+}