/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.utils.strings.format; import java.io.Serializable; import java.text.DecimalFormat; import java.util.IllegalFormatConversionException; import java.util.IllegalFormatException; /** * Value metrics data * * Use 'S' Prefix to use String formatting rules http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax * Use 'D' Prefix to use DecimalFormat rules http://java.sun.com/javase/6/docs/api/java/text/DecimalFormat.html * * If no prefix is detected, String rules are used. * * @author Toni Kalajainen * @Author Marko Luukkainen */ public class MetricsFormat implements Serializable { /** * Define serial version of this class */ private static final long serialVersionUID = 8036591251300342995L; /** Presentation format */ private String pattern; /** Presentation format */ private String internalPattern; /** Presentation format */ private String[] internalPatternDivided; /** flag if format can adjust decimals */ private boolean canUseDecimals = false; /** scale */ private double scale; /** name */ private String name; /** hash */ private int hash; /** Is double or long */ private boolean isDouble = true; private boolean useDecimalFormat = false; /** Array reserved for formatter use */ private Object args[] = new Object[1]; public String getName() { return name; } public MetricsFormat(String pattern, double scale, String name) throws IllegalArgumentException { this.scale = scale; pattern = setPattern(pattern); this.internalPattern = formatPattern( pattern ); this.internalPatternDivided = formatPatternDivided( pattern ); this.name = name; this.hash = makeHash(); } public MetricsFormat(String pattern, double scale) throws IllegalArgumentException { this.scale = scale; pattern = setPattern(pattern); this.internalPattern = formatPattern( pattern ); this.internalPatternDivided = formatPatternDivided( pattern ); this.name = ""; this.hash = makeHash(); } public MetricsFormat(String pattern) throws IllegalArgumentException { this.scale = 1.0f; pattern = setPattern(pattern); this.internalPattern = formatPattern( pattern ); this.internalPatternDivided = formatPatternDivided( pattern ); this.name = ""; this.hash = makeHash(); } private String setPattern(String pattern) { this.pattern = pattern; if (pattern.startsWith("D")) { pattern = pattern.substring(1); useDecimalFormat = true; } else { if (pattern.startsWith("S")) pattern = pattern.substring(1); useDecimalFormat = false; } return pattern; } /** * Replacs all % with %1$ unless % precedes with \ * * @param pattern * @return */ private String formatPattern(String pattern) { int pos = 0; if (!useDecimalFormat) { while ( (pos = pattern.indexOf('%', pos)) >= 0 ) { if (pos==0 || pattern.indexOf(pos-1)!='\\') { pattern = pattern.substring(0, pos+1) + "1$" + pattern.substring(pos+1, pattern.length()); pos += 3; continue; } pos++; } } double value = 0; if (useDecimalFormat) { isDouble = true; useDecimalFormat = true; canUseDecimals = false; DecimalFormat format = new DecimalFormat(pattern); format.format(value); } else { try { isDouble = true; useDecimalFormat = false; args[0] = value; String.format(pattern, args); } catch(IllegalFormatConversionException e1) { try { isDouble = false; useDecimalFormat = false; args[0] = (long)value; String.format(pattern, args); } catch (Exception e2) { throw e1; } } } return pattern; } /** * Replacs all % with %1$ unless % precedes with \ * * Divides formatString into half so that * formats precision can be adjusted with .(n)
* Currently only format which supports adjustable * decimals is f/F (floating point as decimal number) * * * @param pattern * @return */ private String[] formatPatternDivided(String pattern) { String divPattern[] = new String[]{"",""}; if (useDecimalFormat) return divPattern; int pos = 0; while ( (pos = pattern.indexOf('%', pos)) >= 0 ) { if (pos==0 || pattern.indexOf(pos-1)!='\\') { divPattern[0] = pattern.substring(0, pos+1) + "1$"; divPattern[1] = pattern.substring(pos+1, pattern.length()); // parse flags int pos2 = 0; while(true) { char c = divPattern[1].charAt(pos2); if (c == '-' || c == '#' || c == '+' || c == ' ' || c == '0' || c == ',' || c == '(' || Character.isDigit(c)) pos2++; else break; } // get rid of possible precisision setting // TODO : maybe we should let the user to set the precision // and then use user given precision instead of dynamic precision int pos3 = pos2; while(true) { char c = divPattern[1].charAt(pos3); if (c == '.' || Character.isDigit(c)) { pos3++; } else if (c == 'f'|| c == 'F') { this.canUseDecimals = true; break; } else { this.canUseDecimals = false; break; } } if (pos2 > 0 || pos3 > 0) { divPattern[0] += divPattern[1].substring(0,pos2); divPattern[1] = divPattern[1].substring(pos3, divPattern[1].length()); } pos += 3; // divided patterns can have only one % break; } pos++; } double value = 0; try { // checking if format can be used args[0] = value; String.format(divPattern[0] + ".2" + divPattern[1] , args); } catch (IllegalFormatException e1) { try { pattern = formatPattern(pattern); isDouble = false; args[0] = (long)value; String.format(pattern, args); // if we get this far by not using decimals something strange has happened... divPattern[0] = null; divPattern[1] = null; canUseDecimals = false; } catch(Exception e2) { throw e1; } } return divPattern; } /** * Formats value * @param value * @return */ public String formatValue(double value) { return _formatValue(internalPattern, value); } /** * Format value * Note: cannot DecimalFormatter syntax does not work with this method * @param value * @param numDecimals * @return */ public String formatValue(double value, int numDecimals) { if (canUseDecimals) { if (numDecimals < 0) numDecimals = 0; return _formatValue(value, numDecimals); } else { return _formatValue(internalPattern, value); } } private String _formatValue(String pattern, double value) { //value *= scale; if (!useDecimalFormat) { if (isDouble) { args[0] = value; return String.format(pattern, args); } else { args[0] = new Long((long)value); return String.format(pattern, args); } } else { DecimalFormat format = new DecimalFormat(pattern); return format.format(value); } } private String _formatValue(double value, int decimals) { //value *= scale; args[0] = value; return String.format(internalPatternDivided[0] + "." + decimals + internalPatternDivided[1], args); } public String getPattern() { return pattern; } public double getScale() { return scale; } public String toString() { return name; } public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof MetricsFormat)) return false; MetricsFormat other = (MetricsFormat) obj; if (other.hash!=hash) return false; return other.pattern.equals(pattern) && other.scale==scale && other.name.equals(name); } private int makeHash() { long longBits = Double.doubleToLongBits(scale); int scaleHash = ((int)longBits)^(int)(longBits<<16); return pattern.hashCode() ^ scaleHash ^ name.hashCode(); } public int hashCode() { return hash; } /* private void changeContent(MetricsFormat newContent) { this.name = newContent.name; this.pattern = newContent.pattern; this.scale = newContent.scale; this.internalPattern = newContent.internalPattern; this.hash = newContent.hash; }*/ }