/******************************************************************************* * Copyright (c) 2011 Oak Ridge National Laboratory. * 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.csstudio.data.values.internal; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.HashMap; import java.util.Map; import org.csstudio.data.values.IDoubleValue; import org.csstudio.data.values.INumericMetaData; import org.csstudio.data.values.ISeverity; import org.csstudio.data.values.ITimestamp; import org.csstudio.data.values.Messages; /** Implementation of {@link IDoubleValue}. * @author Kay Kasemir, Xihui Chen */ public class DoubleValue extends Value implements IDoubleValue { private static final long serialVersionUID = 1L; /** Map of NumberFormats by precision. * * JProfiler tests showed that about _half_ of the string formatting * is spent in creating the suitable NumberFormat, * so they are cached for re-use. * The key is the 'precision', where a precision >= 0 means decimal format, * and a precision < 0 means exponential notation. * * Access must sync on the hash, then sync on the format while using it. */ final private static Map<Integer, NumberFormat> fmt_cache = new HashMap<Integer, NumberFormat>(); /** The values. */ final private double values[]; /** Constructor from pieces. */ public DoubleValue(final ITimestamp time, final ISeverity severity, final String status, final INumericMetaData meta_data, final Quality quality, final double values[]) { super(time, severity, status, meta_data, quality); this.values = values; } /** {@inheritDoc} */ @Override final public double[] getValues() { return values; } /** {@inheritDoc} */ @Override final public double getValue() { return values[0]; } /** {@inheritDoc} * <br> * If precision is less than zero, the precision from meta data will be used. */ @Override public String format(final Format how, int precision) { // Any value at all? if (!getSeverity().hasValue()) return Messages.NoValue; final StringBuilder buf = new StringBuilder(); if (how == Format.String) { // Handle array elements as characters for (int i = 0; i<values.length; i++) { final char c = getDisplayChar((char) values[i]); if (c == 0) break; buf.append(c); } return buf.toString(); } // Show array elements as numbers NumberFormat fmt; // When requested via <0 prec., // use the precision from meta data if (precision < 0) { final INumericMetaData num_meta = (INumericMetaData)getMetaData(); // Should have numeric meta data, but in case of errors // that might be null. if (num_meta != null) precision = num_meta.getPrecision(); } if (how == Format.Exponential) { // Assert positive precision precision = Math.abs(precision); synchronized (fmt_cache) { // Exponential notation itentified as 'negative' precision in cached fmt = fmt_cache.get(-precision); if (fmt == null) { // Is there a better way to get this silly format? final StringBuffer pattern = new StringBuffer(10); pattern.append("0."); //$NON-NLS-1$ for (int i=0; i<precision; ++i) pattern.append('0'); pattern.append("E0"); //$NON-NLS-1$ fmt = new DecimalFormat(pattern.toString()); fmt_cache.put(-precision, fmt); } } } else { // If precision is still less than 0, which means // no meta data on this value, and fall back to Double.toString if (how == Format.Default && precision < 0) { fmt = null; } else { synchronized (fmt_cache) { fmt = fmt_cache.get(precision); if (fmt == null) { fmt = NumberFormat.getNumberInstance(); fmt.setMinimumFractionDigits(precision); fmt.setMaximumFractionDigits(precision); fmt_cache.put(precision, fmt); } } } } buf.append(formatDouble(fmt, values[0])); for (int i = 1; i < values.length; i++) { buf.append(Messages.ArrayElementSeparator); buf.append(formatDouble(fmt, values[i])); if(i >= MAX_FORMAT_VALUE_COUNT){ buf.append(Messages.ArrayElementSeparator); buf.append("..."); //$NON-NLS-1$ break; } } return buf.toString(); } /** @param fmt NumberFormat or <code>null</code> to use Double.toString * @param d Number to format * @return String */ private String formatDouble(final NumberFormat fmt, double d) { if (Double.isInfinite(d)) return Messages.Infinite; if (Double.isNaN(d)) return Messages.NaN; if (fmt == null) return Double.toString(d); synchronized (fmt) { return fmt.format(d); } } /** {@inheritDoc} */ @Override public boolean equals(final Object obj) { if (! (obj instanceof DoubleValue)) return false; final DoubleValue rhs = (DoubleValue) obj; if (rhs.values.length != values.length) return false; // Compare individual values, using the hint from // page 33 of the "Effective Java" book to handle // NaN and Infinity for (int i=0; i<values.length; ++i) if (Double.doubleToLongBits(values[i]) != Double.doubleToLongBits(rhs.values[i])) return false; return super.equals(obj); } /** {@inheritDoc} */ @Override public int hashCode() { int h = super.hashCode(); for (int i=0; i<values.length; ++i) h += values[i]; return h; } }