001/* 002// $Id: //open/util/resgen/src/org/eigenbase/resgen/ResourceDefinition.java#4 $ 003// Package org.eigenbase.resgen is an i18n resource generator. 004// Copyright (C) 2005-2005 The Eigenbase Project 005// Copyright (C) 2005-2005 Disruptive Tech 006// Copyright (C) 2005-2005 LucidEra, Inc. 007// Portions Copyright (C) 2002-2005 Kana Software, Inc. and others. 008// 009// This library is free software; you can redistribute it and/or modify it 010// under the terms of the GNU Lesser General Public License as published by the 011// Free Software Foundation; either version 2 of the License, or (at your 012// option) any later version approved by The Eigenbase Project. 013// 014// This library is distributed in the hope that it will be useful, 015// but WITHOUT ANY WARRANTY; without even the implied warranty of 016// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017// GNU Lesser General Public License for more details. 018// 019// You should have received a copy of the GNU Lesser General Public License 020// along with this library; if not, write to the Free Software 021// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 022// 023// jhyde, 19 September, 2002 024*/ 025package org.eigenbase.resgen; 026 027import java.text.MessageFormat; 028import java.text.Format; 029import java.text.NumberFormat; 030import java.text.DateFormat; 031import java.util.ResourceBundle; 032import java.util.Properties; 033import java.lang.reflect.Method; 034import java.lang.reflect.InvocationTargetException; 035 036/** 037 * Definition of a resource such as a parameterized message or exception. 038 * 039 * <p>A resource is identified within a {@link ResourceBundle} by a text 040 * <em>key</em>, and has a <em>message</em> in its base locale (which is 041 * usually US-English (en_US)). It may also have a set of properties, which are 042 * represented as name-value pairs. 043 * 044 * <p>A resource definition is immutable. 045 * 046 * @author jhyde 047 * @since 19 September, 2005 048 * @version $Id: //open/util/resgen/src/org/eigenbase/resgen/ResourceDefinition.java#4 $ 049 */ 050public class ResourceDefinition 051{ 052 public final String key; 053 public final String baseMessage; 054 private final String[] props; 055 056 private static final String[] EmptyStringArray = new String[0]; 057 058 public static final int TYPE_UNKNOWN = -1; 059 public static final int TYPE_STRING = 0; 060 public static final int TYPE_NUMBER = 1; 061 public static final int TYPE_DATE = 2; 062 public static final int TYPE_TIME = 3; 063 private static final String[] TypeNames = 064 {"string", "number", "date", "time"}; 065 066 /** 067 * Creates a resource definition with no properties. 068 * 069 * @param key Unique name for this resource definition. 070 * @param baseMessage Message for this resource definition in the base 071 * locale. 072 */ 073 public ResourceDefinition(String key, String baseMessage) 074 { 075 this(key, baseMessage, null); 076 } 077 078 /** 079 * Creates a resource definition. 080 * 081 * @param key Unique name for this resource definition. 082 * @param baseMessage Message for this resource definition in the base 083 * locale. 084 * @param props Array of property name/value pairs. 085 * <code>null</code> means the same as an empty array. 086 */ 087 public ResourceDefinition(String key, String baseMessage, String[] props) 088 { 089 this.key = key; 090 this.baseMessage = baseMessage; 091 if (props == null) { 092 props = EmptyStringArray; 093 } 094 assert props.length % 2 == 0 : 095 "Must have even number of property names/values"; 096 this.props = props; 097 } 098 099 /** 100 * Returns this resource definition's key. 101 */ 102 public String getKey() 103 { 104 return key; 105 } 106 107 /** 108 * Returns this resource definition's message in the base locale. 109 * (To find the message in another locale, you will need to load a 110 * resource bundle for that locale.) 111 */ 112 public String getBaseMessage() 113 { 114 return baseMessage; 115 } 116 117 /** 118 * Returns the properties of this resource definition. 119 */ 120 public Properties getProperties() 121 { 122 final Properties properties = new Properties(); 123 for (int i = 0; i < props.length; i++) { 124 String prop = props[i]; 125 String value = props[++i]; 126 properties.setProperty(prop, value); 127 } 128 return properties; 129 } 130 131 /** 132 * Returns the types of arguments. 133 */ 134 public String[] getArgTypes() 135 { 136 return getArgTypes(baseMessage, TypeNames); 137 } 138 139 /** 140 * Creates an instance of this definition with a set of parameters. 141 * This is a factory method, which may be overridden by a derived class. 142 * 143 * @param bundle Resource bundle the resource instance will belong to 144 * (This contains the locale, among other things.) 145 * @param args Arguments to populate the message's parameters. 146 * The arguments must be consistent in number and type with the results 147 * of {@link #getArgTypes}. 148 */ 149 public ResourceInstance instantiate(ResourceBundle bundle, Object[] args) 150 { 151 return new Instance(bundle, this, args); 152 } 153 154 /** 155 * Parses a message for the arguments inside it, and 156 * returns an array with the types of those arguments. 157 * 158 * <p>For example, <code>getArgTypes("I bought {0,number} {2}s", 159 * new String[] {"string", "number", "date", "time"})</code> 160 * yields {"number", null, "string"}. 161 * Note the null corresponding to missing message #1. 162 * 163 * @param message Message to be parsed. 164 * @param typeNames Strings to return for types. 165 * @return Array of type names 166 */ 167 protected static String[] getArgTypes(String message, String[] typeNames) 168 { 169 assert typeNames.length == 4; 170 Format[] argFormats; 171 try { 172 // We'd like to do 173 // argFormats = format.getFormatsByArgumentIndex() 174 // but it doesn't exist until JDK 1.4, and we'd like this code 175 // to work earlier. 176 Method method = MessageFormat.class.getMethod( 177 "getFormatsByArgumentIndex", (Class[]) null); 178 try { 179 MessageFormat format = new MessageFormat(message); 180 argFormats = (Format[]) method.invoke(format, (Object[]) null); 181 String[] argTypes = new String[argFormats.length]; 182 for (int i = 0; i < argFormats.length; i++) { 183 int x = formatToType(argFormats[i]); 184 argTypes[i] = typeNames[x]; 185 } 186 return argTypes; 187 } catch (IllegalAccessException e) { 188 throw new RuntimeException(e.toString()); 189 } catch (IllegalArgumentException e) { 190 throw new RuntimeException(e.toString()); 191 } catch (InvocationTargetException e) { 192 throw new RuntimeException(e.toString()); 193 } 194 } catch (NoSuchMethodException e) { 195 // Fallback pre JDK 1.4 196 return getArgTypesByHand(message, typeNames); 197 } catch (SecurityException e) { 198 throw new RuntimeException(e.toString()); 199 } 200 } 201 202 protected static String [] getArgTypesByHand( 203 String message, 204 String[] typeNames) 205 { 206 assert typeNames.length == 4; 207 String[] argTypes = new String[10]; 208 int length = 0; 209 for (int i = 0; i < 10; i++) { 210 final int type = getArgType(i, message); 211 if (type != TYPE_UNKNOWN) { 212 length = i + 1; 213 argTypes[i] = typeNames[type]; 214 } 215 } 216 // Created a truncated copy (but keep intervening nulls). 217 String[] argTypes2 = new String[length]; 218 System.arraycopy(argTypes, 0, argTypes2, 0, length); 219 return argTypes2; 220 } 221 222 /** 223 * Returns the type of the <code>i</code>th argument inside a message, 224 * or {@link #TYPE_UNKNOWN} if not found. 225 * 226 * @param i Ordinal of argument 227 * @param message Message to parse 228 * @return Type code ({@link #TYPE_STRING} etc.) 229 */ 230 protected static int getArgType(int i, String message) { 231 String arg = "{" + Integer.toString(i); // e.g. "{1" 232 int index = message.lastIndexOf(arg); 233 if (index < 0) { 234 return TYPE_UNKNOWN; 235 } 236 index += arg.length(); 237 int end = message.length(); 238 while (index < end && message.charAt(index) == ' ') { 239 index++; 240 } 241 if (index < end && message.charAt(index) == ',') { 242 index++; 243 while (index < end && message.charAt(index) == ' ') { 244 index++; 245 } 246 if (index < end) { 247 String sub = message.substring(index); 248 if (sub.startsWith("number")) { 249 return TYPE_NUMBER; 250 } else if (sub.startsWith("date")) { 251 return TYPE_DATE; 252 } else if (sub.startsWith("time")) { 253 return TYPE_TIME; 254 } else if (sub.startsWith("choice")) { 255 return TYPE_UNKNOWN; 256 } 257 } 258 } 259 return TYPE_STRING; 260 } 261 262 263 /** 264 * Converts a {@link Format} to a type code ({@link #TYPE_STRING} etc.) 265 */ 266 private static int formatToType(Format format) { 267 if (format == null) { 268 return TYPE_STRING; 269 } else if (format instanceof NumberFormat) { 270 return TYPE_NUMBER; 271 } else if (format instanceof DateFormat) { 272 // might be date or time, but assume it's date 273 return TYPE_DATE; 274 } else { 275 return TYPE_STRING; 276 } 277 } 278 279 /** 280 * Default implementation of {@link ResourceInstance}. 281 */ 282 private static class Instance implements ResourceInstance { 283 ResourceDefinition definition; 284 ResourceBundle bundle; 285 Object[] args; 286 287 public Instance( 288 ResourceBundle bundle, 289 ResourceDefinition definition, 290 Object[] args) 291 { 292 this.definition = definition; 293 this.bundle = bundle; 294 this.args = args; 295 } 296 297 public String toString() 298 { 299 String message = bundle.getString(definition.key); 300 MessageFormat format = new MessageFormat(message); 301 format.setLocale(bundle.getLocale()); 302 String formattedMessage = format.format(args); 303 return formattedMessage; 304 } 305 } 306} 307 308// End ResourceDefinition.java