001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2017, Jean-Marie Dautelle, Werner Keil, V2COM. 004 * 005 * All rights reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without modification, 008 * are permitted provided that the following conditions are met: 009 * 010 * 1. Redistributions of source code must retain the above copyright notice, 011 * this list of conditions and the following disclaimer. 012 * 013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 014 * and the following disclaimer in the documentation and/or other materials provided with the distribution. 015 * 016 * 3. Neither the name of JSR-363 nor the names of its contributors may be used to endorse or promote products 017 * derived from this software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package tec.units.ri.format; 031 032import java.io.IOException; 033 034import javax.measure.MeasurementException; 035import javax.measure.Quantity; 036import javax.measure.Unit; 037import javax.measure.format.ParserException; 038import tec.units.ri.AbstractQuantity; 039import tec.units.ri.AbstractUnit; 040import tec.units.ri.quantity.NumberQuantity; 041import tec.uom.lib.common.function.Parser; 042 043/** 044 * <p> 045 * This class provides the interface for formatting and parsing {@link Quantity quantities}. 046 * </p> 047 * 048 * <p> 049 * Instances of this class should be able to format quantities stated in {@link CompoundUnit}. See {@link #formatCompound formatCompound(...)}. 050 * </p> 051 * 052 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 053 * @author <a href="mailto:units@catmedia.us">Werner Keil</a> 054 * @version 1.0, $Date: 2016-04-20 $ 055 * @since 1.0 056 */ 057@SuppressWarnings("rawtypes") 058public abstract class QuantityFormat implements Parser<CharSequence, Quantity> { 059 060 /** 061 * 062 */ 063 // private static final long serialVersionUID = -4628006924354248662L; 064 065 /** 066 * Holds the default format instance. 067 */ 068 private static final QuantityFormat DEFAULT = new Standard(); 069 070 /** 071 * Holds the Number-Space-Unit format instance. 072 */ 073 // private static final QuantityFormat NUM_SPACE = new NumberSpaceUnit(NumberFormat.getInstance(), SimpleUnitFormat.getInstance()); 074 075 // TODO use it as an option (after fixing parse()) 076 077 /** 078 * Returns the quantity format for the default locale. The default format assumes the quantity is composed of a decimal number and a {@link Unit} 079 * separated by whitespace(s). 080 * 081 * @return <code>MeasureFormat.getInstance(NumberFormat.getInstance(), UnitFormat.getInstance())</code> 082 */ 083 public static QuantityFormat getInstance() { 084 return DEFAULT; 085 } 086 087 /** 088 * Formats the specified quantity into an <code>Appendable</code>. 089 * 090 * @param quantity 091 * the quantity to format. 092 * @param dest 093 * the appendable destination. 094 * @return the specified <code>Appendable</code>. 095 * @throws IOException 096 * if an I/O exception occurs. 097 */ 098 public abstract Appendable format(Quantity<?> quantity, Appendable dest) throws IOException; 099 100 /** 101 * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the index 102 * of the <code>cursor</code> argument is updated to the index after the last character used. 103 * 104 * @param csq 105 * the <code>CharSequence</code> to parse. 106 * @param index 107 * the current parsing index. 108 * @return the object parsed from the specified character sub-sequence. 109 * @throws IllegalArgumentException 110 * if any problem occurs while parsing the specified character sequence (e.g. illegal syntax). 111 */ 112 abstract Quantity<?> parse(CharSequence csq, int index) throws IllegalArgumentException, ParserException; 113 114 /** 115 * Convenience method equivalent to {@link #format(AbstractQuantity, Appendable)} except it does not raise an IOException. 116 * 117 * @param q 118 * the quantity to format. 119 * @param dest 120 * the appendable destination. 121 * @return the specified <code>StringBuilder</code>. 122 */ 123 public final StringBuilder format(Quantity<?> q, StringBuilder dest) { 124 try { 125 return (StringBuilder) this.format(q, (Appendable) dest); 126 } catch (IOException ex) { 127 throw new MeasurementException(ex); // Should not happen. 128 } 129 } 130 131 /** 132 * Formats an object to produce a string. This is equivalent to <blockquote> {@link #format(Unit, StringBuilder) format}<code>(unit, 133 * new StringBuilder()).toString();</code> </blockquote> 134 * 135 * @param obj 136 * The object to format 137 * @return Formatted string. 138 * @exception IllegalArgumentException 139 * if the Format cannot format the given object 140 */ 141 public final String format(Quantity q) { 142 if (q instanceof AbstractQuantity) { 143 return format((AbstractQuantity<?>) q, new StringBuilder()).toString(); 144 } else { 145 return (this.format(q, new StringBuilder())).toString(); 146 } 147 } 148 149 static int getFractionDigitsCount(double d) { 150 if (d >= 1) { // we only need the fraction digits 151 d = d - (long) d; 152 } 153 if (d == 0) { // nothing to count 154 return 0; 155 } 156 d *= 10; // shifts 1 digit to left 157 int count = 1; 158 while (d - (long) d != 0) { // keeps shifting until there are no more 159 // fractions 160 d *= 10; 161 count++; 162 } 163 return count; 164 } 165 166 // Holds standard implementation. 167 private static final class Standard extends QuantityFormat { 168 169 /** 170 * 171 */ 172 // private static final long serialVersionUID = 2758248665095734058L; 173 174 @Override 175 public Appendable format(Quantity q, Appendable dest) throws IOException { 176 Unit unit = q.getUnit(); 177 // if (unit instanceof CompoundUnit) 178 // return formatCompound(q.doubleValue(unit), 179 // (CompoundUnit) unit, dest); 180 // else { 181 182 Number number = q.getValue(); 183 dest.append(number.toString()); 184 // } 185 if (q.getUnit().equals(AbstractUnit.ONE)) 186 return dest; 187 dest.append(' '); 188 return SimpleUnitFormat.getInstance().format(unit, dest); 189 // } 190 } 191 192 @SuppressWarnings("unchecked") 193 @Override 194 Quantity<?> parse(CharSequence csq, int index) throws ParserException { 195 int startDecimal = index; // cursor.getIndex(); 196 while ((startDecimal < csq.length()) && Character.isWhitespace(csq.charAt(startDecimal))) { 197 startDecimal++; 198 } 199 int endDecimal = startDecimal + 1; 200 while ((endDecimal < csq.length()) && !Character.isWhitespace(csq.charAt(endDecimal))) { 201 endDecimal++; 202 } 203 Double decimal = new Double(csq.subSequence(startDecimal, endDecimal).toString()); 204 // cursor.setIndex(endDecimal + 1); 205 int startUnit = endDecimal + 1;// csq.toString().indexOf(' ') + 1; 206 Unit unit = SimpleUnitFormat.getInstance().parse(csq, startUnit); 207 return NumberQuantity.of(decimal.doubleValue(), unit); 208 } 209 210 public Quantity<?> parse(CharSequence csq) throws ParserException { 211 return parse(csq, 0); 212 } 213 } 214}