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 */
025 package org.eigenbase.resgen;
026
027 import java.text.MessageFormat;
028 import java.text.Format;
029 import java.text.NumberFormat;
030 import java.text.DateFormat;
031 import java.util.ResourceBundle;
032 import java.util.Properties;
033 import java.lang.reflect.Method;
034 import 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 */
050 public 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