File Source: Formatter.java
1 /*
2 * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 package com.dmdirc.ui.messages;
24
25 import com.dmdirc.Precondition;
26 import com.dmdirc.config.ConfigManager;
27
28 import java.util.HashMap;
29 import java.util.IllegalFormatConversionException;
30 import java.util.Map;
31 import java.util.MissingFormatArgumentException;
32 import java.util.UnknownFormatConversionException;
33
34 /**
35 * The Formatter provides a standard way to format messages for display.
36 */
/*
P/P * Method: com.dmdirc.ui.messages.Formatter__static_init
*
* Postconditions:
* typeCache == &new HashMap(Formatter__static_init#1)
* new HashMap(Formatter__static_init#1) num objects == 1
*/
37 public final class Formatter {
38
39 /**
40 * A cache of types needed by the various formatters.
41 */
42 private static final Map<String, Character[]> typeCache
43 = new HashMap<String, Character[]>();
44
45 /**
46 * Creates a new instance of Formatter.
47 */
/*
P/P * Method: void com.dmdirc.ui.messages.Formatter()
*/
48 private Formatter() {
49 // Shouldn't be used
50 }
51
52 /**
53 * Inserts the supplied arguments into a format string for the specified
54 * message type.
55 *
56 * @param messageType The message type that the arguments should be formatted as
57 * @param config The config manager to use to format the message
58 * @param arguments The arguments to this message type
59 * @return A formatted string
60 */
61 @Precondition("The specified message type is not null")
62 public static String formatMessage(final ConfigManager config, final String messageType,
63 final Object... arguments) {
/*
P/P * Method: String formatMessage(ConfigManager, String, Object[])
*
* Preconditions:
* config != null
* messageType != null
* (soft) arguments != null
* (soft) arguments.length <= 232-1
* (soft) arguments[...] != null
*
* Presumptions:
* com.dmdirc.config.ConfigManager:getOption(...)@66 != null
*
* Postconditions:
* init'ed(java.lang.StringBuilder:toString(...)._tainted)
* init'ed(return_value)
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:hasOptionString(...)@66: {1}, {0}
*/
64 assert(messageType != null);
65
66 final String res = config.hasOptionString("formatter", messageType) ?
67 config.getOption("formatter", messageType).replace("%-1$", "%"
68 + arguments.length + "$"): null;
69
70 if (res == null) {
71 return "<No format string for message type " + messageType + ">";
72 } else {
73 try {
74 final Object[] newArgs = castArguments(res, arguments);
75 return String.format(res.replaceAll("(%[0-9]+\\$)u", "$1s"), newArgs);
76 } catch (IllegalFormatConversionException ex) {
77 return "<Invalid format string for message type " + messageType
78 + "; Error: Illegal format conversion: " + ex.getMessage() + ">";
79 } catch (UnknownFormatConversionException ex) {
80 return "<Invalid format string for message type " + messageType
81 + "; Error: Unknown format conversion: " + ex.getMessage() + ">";
82 } catch (MissingFormatArgumentException ex) {
83 return "<Invalid format string for message type " + messageType
84 + "; Error: Missing format argument: " + ex.getMessage() + ">";
85 }
86 }
87 }
88
89 /**
90 * Casts the specified arguments to the relevant classes, based on the
91 * format type cache.
92 *
93 * @param format The format to be used
94 * @param args The arguments to be casted
95 * @return A new set of arguments of appropriate types
96 */
97 @Precondition("The specified format is not null")
98 private static Object[] castArguments(final String format, final Object[] args) {
/*
P/P * Method: Object[] castArguments(String, Object[])
*
* Preconditions:
* args != null
* format != null
* (soft) args.length <= 232-1
* (soft) args[...] != null
*
* Presumptions:
* arr$.length@108 <= 232-1
* arr$.length@108 - args.length in {-232+1..0}
* arr$[i$]@108 != null
* java.lang.Integer:valueOf(...)@141 != null
* java.lang.Long:longValue(...)@134 in {-9_223_372_036_854_775..18_446_744_073_709_551}
* ...
*
* Postconditions:
* init'ed(java.lang.String:valueOf(...)._tainted)
* init'ed(java.lang.StringBuilder:toString(...)._tainted)
* return_value == &new Object[](castArguments#1)
* new Object[](castArguments#1) num objects == 1
* return_value.length == args.length
* return_value.length <= 232-1
* possibly_updated(return_value[...])
*
* Test Vectors:
* java.lang.Character:charValue(...)@113: {65, 69, 71, 97, 101..103}, {66, 72, 83, 98, 104, 115}, {67, 99}, {84, 116}, {88, 100, 111, 120}, {117}, {0..64, 68, 70, 73..82, 85..87, 89..96, 105..110, 112..114, 118,119, 121..216-1}
* java.lang.String:instanceof(...)@132: {0}, {1}
* java.util.Map:containsKey(...)@101: {1}, {0}
*/
99 assert(format != null);
100
101 if (!typeCache.containsKey(format)) {
102 analyseFormat(format, args);
103 }
104
105 final Object[] res = new Object[args.length];
106
107 int i = 0;
108 for (Character chr : typeCache.get(format)) {
109 if (i >= args.length) {
110 break;
111 }
112
113 switch (chr) {
114 case 'b': case 'B': case 'h': case 'H': case 's': case 'S':
115 // General (strings)
116 res[i] = String.valueOf(args[i]);
117 break;
118 case 'c': case 'C':
119 // Character
120 res[i] = String.valueOf(args[i]).charAt(0);
121 break;
122 case 'd': case 'o': case 'x': case 'X':
123 // Integers
124 res[i] = Integer.valueOf((String) args[i]);
125 break;
126 case 'e': case 'E': case 'f': case 'g': case 'G': case 'a': case 'A':
127 // Floating point
128 res[i] = Float.valueOf((String) args[i]);
129 break;
130 case 't': case 'T':
131 // Date
132 if (args[i] instanceof String) {
133 // Assume it's a timestamp(?)
134 res[i] = Long.valueOf(1000 * Long.valueOf((String) args[i]));
135 } else {
136 res[i] = args[i];
137 }
138 break;
139 case 'u':
140 // Duration hacks
141 res[i] = formatDuration(Integer.valueOf(String.valueOf(args[i].toString())));
142 break;
143 default:
144 res[i] = args[i];
145 }
146
147 i++;
148 }
149
150 return res;
151 }
152
153 /**
154 * Tests for and adds one component of the duration format.
155 *
156 * @param builder The string builder to append text to
157 * @param current The number of seconds in the duration
158 * @param duration The number of seconds in this component
159 * @param name The name of this component
160 * @return The number of seconds used by this component
161 */
162 private static int doDuration(final StringBuilder builder, final int current,
163 final int duration, final String name) {
/*
P/P * Method: int doDuration(StringBuilder, int, int, String)
*
* Preconditions:
* (soft) builder != null
* (soft) current/duration in {-231..232-1}
* (soft) duration != 0
* (soft) duration*(current/duration) in {-231..232-1}
*
* Postconditions:
* init'ed(builder._tainted)
* return_value == One-of{0, duration*(current/duration)}
* init'ed(return_value)
*
* Test Vectors:
* duration - current: {1..6_442_450_943}, {-6_442_450_943..0}
* java.lang.StringBuilder:length(...)@170: {-231..0}, {1..232-1}
*/
164 int res = 0;
165
166 if (current >= duration) {
167 final int units = current / duration;
168 res = units * duration;
169
170 if (builder.length() > 0) {
171 builder.append(", ");
172 }
173
174 builder.append(units);
175 builder.append(' ');
176 builder.append(name + (units != 1 ? 's' : ""));
177 }
178
179 return res;
180 }
181
182 /**
183 * Formats the specified number of seconds as a string containing the
184 * number of days, hours, minutes and seconds.
185 *
186 * @param duration The duration in seconds to be formatted
187 * @return A textual version of the duration
188 */
189 public static String formatDuration(final int duration) {
/*
P/P * Method: String formatDuration(int)
*
* Postconditions:
* init'ed(java.lang.StringBuilder:toString(...)._tainted)
* return_value == &java.lang.StringBuilder:toString(...)
*/
190 final StringBuilder buff = new StringBuilder();
191
192 int seconds = duration;
193
194 seconds -= doDuration(buff, seconds, 60*60*24, "day");
195 seconds -= doDuration(buff, seconds, 60*60, "hour");
196 seconds -= doDuration(buff, seconds, 60, "minute");
197 seconds -= doDuration(buff, seconds, 1, "second");
198
199 return buff.toString();
200 }
201
202 /**
203 * Analyses the specified format string and fills in the format type cache.
204 *
205 * @param format The format to analyse
206 * @param args The raw arguments
207 */
208 private static void analyseFormat(final String format, final Object[] args) {
/*
P/P * Method: void analyseFormat(String, Object[])
*
* Preconditions:
* args != null
* args.length <= 232-1
* (soft) format != null
*
* Presumptions:
* java.lang.String:indexOf(...)@212 <= 232-4
*
* Test Vectors:
* java.lang.String:indexOf(...)@212: {-231..-1}, {0..232-4}
*/
209 final Character[] types = new Character[args.length];
210
211 for (int i = 0; i < args.length; i++) {
212 final int index = format.indexOf("%" + (i + 1) + "$");
213
214 if (index > -1) {
215 types[i] = format.charAt(index + 3);
216 } else {
217 types[i] = 's';
218 }
219 }
220
221 typeCache.put(format, types);
222 }
223
224 }
SofCheck Inspector Build Version : 2.17854
| Formatter.java |
2009-Jun-25 01:54:24 |
| Formatter.class |
2009-Sep-02 17:04:17 |