You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Formatter.java 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * Copyright (c) 2006-2011 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. package com.dmdirc.ui.messages;
  23. import com.dmdirc.Precondition;
  24. import com.dmdirc.config.ConfigManager;
  25. import java.util.HashMap;
  26. import java.util.IllegalFormatConversionException;
  27. import java.util.Map;
  28. import java.util.MissingFormatArgumentException;
  29. import java.util.UnknownFormatConversionException;
  30. /**
  31. * The Formatter provides a standard way to format messages for display.
  32. */
  33. public final class Formatter {
  34. /**
  35. * A cache of types needed by the various formatters.
  36. */
  37. private static final Map<String, Character[]> TYPE_CACHE
  38. = new HashMap<String, Character[]>();
  39. /**
  40. * Creates a new instance of Formatter.
  41. */
  42. private Formatter() {
  43. // Shouldn't be used
  44. }
  45. /**
  46. * Inserts the supplied arguments into a format string for the specified
  47. * message type.
  48. *
  49. * @param messageType The message type that the arguments should be formatted as
  50. * @param config The config manager to use to format the message
  51. * @param arguments The arguments to this message type
  52. * @return A formatted string
  53. */
  54. @Precondition("The specified message type is not null")
  55. public static String formatMessage(final ConfigManager config, final String messageType,
  56. final Object... arguments) {
  57. assert messageType != null;
  58. final String res = config.hasOptionString("formatter", messageType) ?
  59. config.getOption("formatter", messageType).replace("%-1$", "%"
  60. + arguments.length + "$"): null;
  61. if (res == null) {
  62. return "<No format string for message type " + messageType + ">";
  63. } else {
  64. try {
  65. final Object[] newArgs = castArguments(res, arguments);
  66. return String.format(res.replaceAll("(%[0-9]+\\$)u", "$1s"), newArgs);
  67. } catch (IllegalFormatConversionException ex) {
  68. return "<Invalid format string for message type " + messageType
  69. + "; Error: Illegal format conversion: " + ex.getMessage() + ">";
  70. } catch (UnknownFormatConversionException ex) {
  71. return "<Invalid format string for message type " + messageType
  72. + "; Error: Unknown format conversion: " + ex.getMessage() + ">";
  73. } catch (MissingFormatArgumentException ex) {
  74. return "<Invalid format string for message type " + messageType
  75. + "; Error: Missing format argument: " + ex.getMessage() + ">";
  76. } catch (NumberFormatException ex) {
  77. return "<Invalid format string for message type " + messageType
  78. + "; Error: Invalid number conversion: " + ex.getMessage() + ">";
  79. }
  80. }
  81. }
  82. /**
  83. * Casts the specified arguments to the relevant classes, based on the
  84. * format type cache.
  85. *
  86. * @param format The format to be used
  87. * @param args The arguments to be casted
  88. * @return A new set of arguments of appropriate types
  89. */
  90. @Precondition("The specified format is not null")
  91. private static Object[] castArguments(final String format, final Object[] args) {
  92. assert format != null;
  93. if (!TYPE_CACHE.containsKey(format)) {
  94. analyseFormat(format, args);
  95. }
  96. final Object[] res = new Object[args.length];
  97. int i = 0;
  98. for (Character chr : TYPE_CACHE.get(format)) {
  99. if (i >= args.length) {
  100. break;
  101. }
  102. switch (chr) {
  103. case 'b': case 'B': case 'h': case 'H': case 's': case 'S':
  104. // General (strings)
  105. res[i] = String.valueOf(args[i]);
  106. break;
  107. case 'c': case 'C':
  108. // Character
  109. res[i] = String.valueOf(args[i]).charAt(0);
  110. break;
  111. case 'd': case 'o': case 'x': case 'X':
  112. // Integers
  113. res[i] = Integer.valueOf((String) args[i]);
  114. break;
  115. case 'e': case 'E': case 'f': case 'g': case 'G': case 'a': case 'A':
  116. // Floating point
  117. res[i] = Float.valueOf((String) args[i]);
  118. break;
  119. case 't': case 'T':
  120. // Date
  121. if (args[i] instanceof String) {
  122. // Assume it's a timestamp(?)
  123. res[i] = Long.valueOf(1000 * Long.valueOf((String) args[i]));
  124. } else {
  125. res[i] = args[i];
  126. }
  127. break;
  128. case 'u':
  129. // Duration hacks
  130. res[i] = formatDuration(Integer.valueOf(String.valueOf(args[i].toString())));
  131. break;
  132. default:
  133. res[i] = args[i];
  134. }
  135. i++;
  136. }
  137. return res;
  138. }
  139. /**
  140. * Tests for and adds one component of the duration format.
  141. *
  142. * @param builder The string builder to append text to
  143. * @param current The number of seconds in the duration
  144. * @param duration The number of seconds in this component
  145. * @param name The name of this component
  146. * @return The number of seconds used by this component
  147. */
  148. private static int doDuration(final StringBuilder builder, final int current,
  149. final int duration, final String name) {
  150. int res = 0;
  151. if (current >= duration) {
  152. final int units = current / duration;
  153. res = units * duration;
  154. if (builder.length() > 0) {
  155. builder.append(", ");
  156. }
  157. builder.append(units);
  158. builder.append(' ');
  159. builder.append(name + (units != 1 ? 's' : ""));
  160. }
  161. return res;
  162. }
  163. /**
  164. * Formats the specified number of seconds as a string containing the
  165. * number of days, hours, minutes and seconds.
  166. *
  167. * @param duration The duration in seconds to be formatted
  168. * @return A textual version of the duration
  169. */
  170. public static String formatDuration(final int duration) {
  171. final StringBuilder buff = new StringBuilder();
  172. int seconds = duration;
  173. seconds -= doDuration(buff, seconds, 60*60*24, "day");
  174. seconds -= doDuration(buff, seconds, 60*60, "hour");
  175. seconds -= doDuration(buff, seconds, 60, "minute");
  176. seconds -= doDuration(buff, seconds, 1, "second");
  177. return buff.length() == 0 ? "0 seconds" : buff.toString();
  178. }
  179. /**
  180. * Analyses the specified format string and fills in the format type cache.
  181. *
  182. * @param format The format to analyse
  183. * @param args The raw arguments
  184. */
  185. private static void analyseFormat(final String format, final Object[] args) {
  186. final Character[] types = new Character[args.length];
  187. for (int i = 0; i < args.length; i++) {
  188. final int index = format.indexOf("%" + (i + 1) + "$");
  189. if (index > -1) {
  190. types[i] = format.charAt(index + 3);
  191. } else {
  192. types[i] = 's';
  193. }
  194. }
  195. TYPE_CACHE.put(format, types);
  196. }
  197. }