001    /*
002     *  Copyright 2001-2005 Stephen Colebourne
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package org.joda.time.format;
017    
018    import java.io.IOException;
019    import java.io.Writer;
020    
021    /**
022     * Utility methods used by formatters.
023     * <p>
024     * FormatUtils is thread-safe and immutable.
025     *
026     * @author Brian S O'Neill
027     * @since 1.0
028     */
029    public class FormatUtils {
030    
031        private static final double LOG_10 = Math.log(10);
032    
033        /**
034         * Restricted constructor.
035         */
036        private FormatUtils() {
037        }
038    
039        /**
040         * Converts an integer to a string, prepended with a variable amount of '0'
041         * pad characters, and appends it to the given buffer.
042         *
043         * <p>This method is optimized for converting small values to strings.
044         *
045         * @param buf receives integer converted to a string
046         * @param value value to convert to a string
047         * @param size minumum amount of digits to append
048         */
049        public static void appendPaddedInteger(StringBuffer buf, int value, int size) {
050            if (value < 0) {
051                buf.append('-');
052                if (value != Integer.MIN_VALUE) {
053                    value = -value;
054                } else {
055                    for (; size > 10; size--) {
056                        buf.append('0');
057                    }
058                    buf.append("" + -(long)Integer.MIN_VALUE);
059                    return;
060                }
061            }
062            if (value < 10) {
063                for (; size > 1; size--) {
064                    buf.append('0');
065                }
066                buf.append((char)(value + '0'));
067            } else if (value < 100) {
068                for (; size > 2; size--) {
069                    buf.append('0');
070                }
071                // Calculate value div/mod by 10 without using two expensive
072                // division operations. (2 ^ 27) / 10 = 13421772. Add one to
073                // value to correct rounding error.
074                int d = ((value + 1) * 13421772) >> 27;
075                buf.append((char) (d + '0'));
076                // Append remainder by calculating (value - d * 10).
077                buf.append((char) (value - (d << 3) - (d << 1) + '0'));
078            } else {
079                int digits;
080                if (value < 1000) {
081                    digits = 3;
082                } else if (value < 10000) {
083                    digits = 4;
084                } else {
085                    digits = (int)(Math.log(value) / LOG_10) + 1;
086                }
087                for (; size > digits; size--) {
088                    buf.append('0');
089                }
090                buf.append(Integer.toString(value));
091            }
092        }
093    
094        /**
095         * Converts an integer to a string, prepended with a variable amount of '0'
096         * pad characters, and appends it to the given buffer.
097         *
098         * <p>This method is optimized for converting small values to strings.
099         *
100         * @param buf receives integer converted to a string
101         * @param value value to convert to a string
102         * @param size minumum amount of digits to append
103         */
104        public static void appendPaddedInteger(StringBuffer buf, long value, int size) {
105            int intValue = (int)value;
106            if (intValue == value) {
107                appendPaddedInteger(buf, intValue, size);
108            } else if (size <= 19) {
109                buf.append(Long.toString(value));
110            } else {
111                if (value < 0) {
112                    buf.append('-');
113                    if (value != Long.MIN_VALUE) {
114                        value = -value;
115                    } else {
116                        for (; size > 19; size--) {
117                            buf.append('0');
118                        }
119                        buf.append("9223372036854775808");
120                        return;
121                    }
122                }
123                int digits = (int)(Math.log(value) / LOG_10) + 1;
124                for (; size > digits; size--) {
125                    buf.append('0');
126                }
127                buf.append(Long.toString(value));
128            }
129        }
130    
131        /**
132         * Converts an integer to a string, prepended with a variable amount of '0'
133         * pad characters, and writes it to the given writer.
134         *
135         * <p>This method is optimized for converting small values to strings.
136         *
137         * @param out receives integer converted to a string
138         * @param value value to convert to a string
139         * @param size minumum amount of digits to append
140         */
141        public static void writePaddedInteger(Writer out, int value, int size)
142            throws IOException
143        {
144            if (value < 0) {
145                out.write('-');
146                if (value != Integer.MIN_VALUE) {
147                    value = -value;
148                } else {
149                    for (; size > 10; size--) {
150                        out.write('0');
151                    }
152                    out.write("" + -(long)Integer.MIN_VALUE);
153                    return;
154                }
155            }
156            if (value < 10) {
157                for (; size > 1; size--) {
158                    out.write('0');
159                }
160                out.write(value + '0');
161            } else if (value < 100) {
162                for (; size > 2; size--) {
163                    out.write('0');
164                }
165                // Calculate value div/mod by 10 without using two expensive
166                // division operations. (2 ^ 27) / 10 = 13421772. Add one to
167                // value to correct rounding error.
168                int d = ((value + 1) * 13421772) >> 27;
169                out.write(d + '0');
170                // Append remainder by calculating (value - d * 10).
171                out.write(value - (d << 3) - (d << 1) + '0');
172            } else {
173                int digits;
174                if (value < 1000) {
175                    digits = 3;
176                } else if (value < 10000) {
177                    digits = 4;
178                } else {
179                    digits = (int)(Math.log(value) / LOG_10) + 1;
180                }
181                for (; size > digits; size--) {
182                    out.write('0');
183                }
184                out.write(Integer.toString(value));
185            }
186        }
187    
188        /**
189         * Converts an integer to a string, prepended with a variable amount of '0'
190         * pad characters, and writes it to the given writer.
191         *
192         * <p>This method is optimized for converting small values to strings.
193         *
194         * @param out receives integer converted to a string
195         * @param value value to convert to a string
196         * @param size minumum amount of digits to append
197         */
198        public static void writePaddedInteger(Writer out, long value, int size)
199            throws IOException
200        {
201            int intValue = (int)value;
202            if (intValue == value) {
203                writePaddedInteger(out, intValue, size);
204            } else if (size <= 19) {
205                out.write(Long.toString(value));
206            } else {
207                if (value < 0) {
208                    out.write('-');
209                    if (value != Long.MIN_VALUE) {
210                        value = -value;
211                    } else {
212                        for (; size > 19; size--) {
213                            out.write('0');
214                        }
215                        out.write("9223372036854775808");
216                        return;
217                    }
218                }
219                int digits = (int)(Math.log(value) / LOG_10) + 1;
220                for (; size > digits; size--) {
221                    out.write('0');
222                }
223                out.write(Long.toString(value));
224            }
225        }
226    
227        /**
228         * Converts an integer to a string, and appends it to the given buffer.
229         *
230         * <p>This method is optimized for converting small values to strings.
231         *
232         * @param buf receives integer converted to a string
233         * @param value value to convert to a string
234         */
235        public static void appendUnpaddedInteger(StringBuffer buf, int value) {
236            if (value < 0) {
237                buf.append('-');
238                if (value != Integer.MIN_VALUE) {
239                    value = -value;
240                } else {
241                    buf.append("" + -(long)Integer.MIN_VALUE);
242                    return;
243                }
244            }
245            if (value < 10) {
246                buf.append((char)(value + '0'));
247            } else if (value < 100) {
248                // Calculate value div/mod by 10 without using two expensive
249                // division operations. (2 ^ 27) / 10 = 13421772. Add one to
250                // value to correct rounding error.
251                int d = ((value + 1) * 13421772) >> 27;
252                buf.append((char) (d + '0'));
253                // Append remainder by calculating (value - d * 10).
254                buf.append((char) (value - (d << 3) - (d << 1) + '0'));
255            } else {
256                buf.append(Integer.toString(value));
257            }
258        }
259    
260        /**
261         * Converts an integer to a string, and appends it to the given buffer.
262         *
263         * <p>This method is optimized for converting small values to strings.
264         *
265         * @param buf receives integer converted to a string
266         * @param value value to convert to a string
267         */
268        public static void appendUnpaddedInteger(StringBuffer buf, long value) {
269            int intValue = (int)value;
270            if (intValue == value) {
271                appendUnpaddedInteger(buf, intValue);
272            } else {
273                buf.append(Long.toString(value));
274            }
275        }
276    
277        /**
278         * Converts an integer to a string, and writes it to the given writer.
279         *
280         * <p>This method is optimized for converting small values to strings.
281         *
282         * @param out receives integer converted to a string
283         * @param value value to convert to a string
284         */
285        public static void writeUnpaddedInteger(Writer out, int value)
286            throws IOException
287        {
288            if (value < 0) {
289                out.write('-');
290                if (value != Integer.MIN_VALUE) {
291                    value = -value;
292                } else {
293                    out.write("" + -(long)Integer.MIN_VALUE);
294                    return;
295                }
296            }
297            if (value < 10) {
298                out.write(value + '0');
299            } else if (value < 100) {
300                // Calculate value div/mod by 10 without using two expensive
301                // division operations. (2 ^ 27) / 10 = 13421772. Add one to
302                // value to correct rounding error.
303                int d = ((value + 1) * 13421772) >> 27;
304                out.write(d + '0');
305                // Append remainder by calculating (value - d * 10).
306                out.write(value - (d << 3) - (d << 1) + '0');
307            } else {
308                out.write(Integer.toString(value));
309            }
310        }
311    
312        /**
313         * Converts an integer to a string, and writes it to the given writer.
314         *
315         * <p>This method is optimized for converting small values to strings.
316         *
317         * @param out receives integer converted to a string
318         * @param value value to convert to a string
319         */
320        public static void writeUnpaddedInteger(Writer out, long value)
321            throws IOException
322        {
323            int intValue = (int)value;
324            if (intValue == value) {
325                writeUnpaddedInteger(out, intValue);
326            } else {
327                out.write(Long.toString(value));
328            }
329        }
330    
331        /**
332         * Calculates the number of decimal digits for the given value,
333         * including the sign.
334         */
335        public static int calculateDigitCount(long value) {
336            if (value < 0) {
337                if (value != Long.MIN_VALUE) {
338                    return calculateDigitCount(-value) + 1;
339                } else {
340                    return 20;
341                }
342            }
343            return 
344                (value < 10 ? 1 :
345                 (value < 100 ? 2 :
346                  (value < 1000 ? 3 :
347                   (value < 10000 ? 4 :
348                    ((int)(Math.log(value) / LOG_10) + 1)))));
349        }
350    
351        static int parseTwoDigits(String text, int position) {
352            int value = text.charAt(position) - '0';
353            return ((value << 3) + (value << 1)) + text.charAt(position + 1) - '0';
354        }
355    
356        static String createErrorMessage(final String text, final int errorPos) {
357            int sampleLen = errorPos + 32;
358            String sampleText;
359            if (text.length() <= sampleLen + 3) {
360                sampleText = text;
361            } else {
362                sampleText = text.substring(0, sampleLen).concat("...");
363            }
364            
365            if (errorPos <= 0) {
366                return "Invalid format: \"" + sampleText + '"';
367            }
368            
369            if (errorPos >= text.length()) {
370                return "Invalid format: \"" + sampleText + "\" is too short";
371            }
372            
373            return "Invalid format: \"" + sampleText + "\" is malformed at \"" +
374                sampleText.substring(errorPos) + '"';
375        }
376    
377    }