001: package org.bouncycastle.i18n;
002:
003: import org.bouncycastle.i18n.filter.Filter;
004: import org.bouncycastle.i18n.filter.TrustedInput;
005: import org.bouncycastle.i18n.filter.UntrustedInput;
006: import org.bouncycastle.i18n.filter.UntrustedUrlInput;
007:
008: import java.io.UnsupportedEncodingException;
009: import java.nio.charset.Charset;
010: import java.text.DateFormat;
011: import java.text.Format;
012: import java.text.MessageFormat;
013: import java.util.Locale;
014: import java.util.MissingResourceException;
015: import java.util.ResourceBundle;
016: import java.util.TimeZone;
017:
018: public class LocalizedMessage {
019:
020: protected final String id;
021: protected final String resource;
022:
023: // ISO-8859-1 is the default encoding
024: public static final String DEFAULT_ENCODING = "ISO-8859-1";
025: protected String encoding = DEFAULT_ENCODING;
026:
027: protected FilteredArguments arguments;
028: protected FilteredArguments extraArgs = null;
029:
030: protected Filter filter = null;
031:
032: protected ClassLoader loader = null;
033:
034: /**
035: * Constructs a new LocalizedMessage using <code>resource</code> as the base name for the
036: * RessourceBundle and <code>id</code> as the message bundle id the resource file.
037: * @param resource base name of the resource file
038: * @param id the id of the corresponding bundle in the resource file
039: * @throws NullPointerException if <code>resource</code> or <code>id</code> is <code>null</code>
040: */
041: public LocalizedMessage(String resource, String id)
042: throws NullPointerException {
043: if (resource == null || id == null) {
044: throw new NullPointerException();
045: }
046: this .id = id;
047: this .resource = resource;
048: arguments = new FilteredArguments();
049: }
050:
051: /**
052: * Constructs a new LocalizedMessage using <code>resource</code> as the base name for the
053: * RessourceBundle and <code>id</code> as the message bundle id the resource file.
054: * @param resource base name of the resource file
055: * @param id the id of the corresponding bundle in the resource file
056: * @param encoding the encoding of the resource file
057: * @throws NullPointerException if <code>resource</code> or <code>id</code> is <code>null</code>
058: * @throws UnsupportedEncodingException if the encoding is not supported
059: */
060: public LocalizedMessage(String resource, String id, String encoding)
061: throws NullPointerException, UnsupportedEncodingException {
062: if (resource == null || id == null) {
063: throw new NullPointerException();
064: }
065: this .id = id;
066: this .resource = resource;
067: arguments = new FilteredArguments();
068: if (!Charset.isSupported(encoding)) {
069: throw new UnsupportedEncodingException("The encoding \""
070: + encoding + "\" is not supported.");
071: }
072: this .encoding = encoding;
073: }
074:
075: /**
076: * Constructs a new LocalizedMessage using <code>resource</code> as the base name for the
077: * RessourceBundle and <code>id</code> as the message bundle id the resource file.
078: * @param resource base name of the resource file
079: * @param id the id of the corresponding bundle in the resource file
080: * @param arguments an array containing the arguments for the message
081: * @throws NullPointerException if <code>resource</code> or <code>id</code> is <code>null</code>
082: */
083: public LocalizedMessage(String resource, String id,
084: Object[] arguments) throws NullPointerException {
085: if (resource == null || id == null || arguments == null) {
086: throw new NullPointerException();
087: }
088: this .id = id;
089: this .resource = resource;
090: this .arguments = new FilteredArguments(arguments);
091: }
092:
093: /**
094: * Constructs a new LocalizedMessage using <code>resource</code> as the base name for the
095: * RessourceBundle and <code>id</code> as the message bundle id the resource file.
096: * @param resource base name of the resource file
097: * @param id the id of the corresponding bundle in the resource file
098: * @param encoding the encoding of the resource file
099: * @param arguments an array containing the arguments for the message
100: * @throws NullPointerException if <code>resource</code> or <code>id</code> is <code>null</code>
101: * @throws UnsupportedEncodingException if the encoding is not supported
102: */
103: public LocalizedMessage(String resource, String id,
104: String encoding, Object[] arguments)
105: throws NullPointerException, UnsupportedEncodingException {
106: if (resource == null || id == null || arguments == null) {
107: throw new NullPointerException();
108: }
109: this .id = id;
110: this .resource = resource;
111: this .arguments = new FilteredArguments(arguments);
112: if (!Charset.isSupported(encoding)) {
113: throw new UnsupportedEncodingException("The encoding \""
114: + encoding + "\" is not supported.");
115: }
116: this .encoding = encoding;
117: }
118:
119: /**
120: * Reads the entry <code>id + "." + key</code> from the resource file and returns a
121: * formated message for the given Locale and TimeZone.
122: * @param key second part of the entry id
123: * @param loc the used {@link Locale}
124: * @param timezone the used {@link TimeZone}
125: * @return a Strng containing the localized message
126: * @throws MissingEntryException if the resource file is not available or the entry does not exist.
127: */
128: public String getEntry(String key, Locale loc, TimeZone timezone)
129: throws MissingEntryException {
130: String entry = id;
131: if (key != null) {
132: entry += "." + key;
133: }
134:
135: try {
136: ResourceBundle bundle;
137: if (loader == null) {
138: bundle = ResourceBundle.getBundle(resource, loc);
139: } else {
140: bundle = ResourceBundle
141: .getBundle(resource, loc, loader);
142: }
143: String result = bundle.getString(entry);
144: if (!encoding.equals(DEFAULT_ENCODING)) {
145: result = new String(result.getBytes(DEFAULT_ENCODING),
146: encoding);
147: }
148: if (!arguments.isEmpty()) {
149: result = formatWithTimeZone(result, arguments
150: .getFilteredArgs(loc), loc, timezone);
151: }
152: result = addExtraArgs(result, loc);
153: return result;
154: } catch (MissingResourceException mre) {
155: throw new MissingEntryException("Can't find entry " + entry
156: + " in resource file " + resource + ".", resource,
157: entry, loc, loader != null ? loader : this
158: .getClassLoader());
159: } catch (UnsupportedEncodingException use) {
160: // should never occur - cause we already test this in the constructor
161: throw new RuntimeException(use);
162: }
163: }
164:
165: protected String formatWithTimeZone(String template,
166: Object[] arguments, Locale locale, TimeZone timezone) {
167: MessageFormat mf = new MessageFormat(" ");
168: mf.setLocale(locale);
169: mf.applyPattern(template);
170: if (!timezone.equals(TimeZone.getDefault())) {
171: Format[] formats = mf.getFormats();
172: for (int i = 0; i < formats.length; i++) {
173: if (formats[i] instanceof DateFormat) {
174: DateFormat temp = (DateFormat) formats[i];
175: temp.setTimeZone(timezone);
176: mf.setFormat(i, temp);
177: }
178: }
179: }
180: return mf.format(arguments);
181: }
182:
183: protected String addExtraArgs(String msg, Locale locale) {
184: if (extraArgs != null) {
185: StringBuffer sb = new StringBuffer(msg);
186: Object[] filteredArgs = extraArgs.getFilteredArgs(locale);
187: for (int i = 0; i < filteredArgs.length; i++) {
188: sb.append(filteredArgs[i]);
189: }
190: msg = sb.toString();
191: }
192: return msg;
193: }
194:
195: /**
196: * Sets the {@link Filter} that is used to filter the arguments of this message
197: * @param filter the {@link Filter} to use. <code>null</code> to disable filtering.
198: */
199: public void setFilter(Filter filter) {
200: arguments.setFilter(filter);
201: if (extraArgs != null) {
202: extraArgs.setFilter(filter);
203: }
204: this .filter = filter;
205: }
206:
207: /**
208: * Returns the current filter.
209: * @return the current filter
210: */
211: public Filter getFilter() {
212: return filter;
213: }
214:
215: /**
216: * Set the {@link ClassLoader} which loads the resource files. If it is set to <code>null</code>
217: * then the default {@link ClassLoader} is used.
218: * @param loader the {@link ClassLoader} which loads the resource files
219: */
220: public void setClassLoader(ClassLoader loader) {
221: this .loader = loader;
222: }
223:
224: /**
225: * Returns the {@link ClassLoader} which loads the resource files or <code>null</code>
226: * if the default ClassLoader is used.
227: * @return the {@link ClassLoader} which loads the resource files
228: */
229: public ClassLoader getClassLoader() {
230: return loader;
231: }
232:
233: /**
234: * Returns the id of the message in the resource bundle.
235: * @return the id of the message
236: */
237: public String getId() {
238: return id;
239: }
240:
241: /**
242: * Returns the name of the resource bundle for this message
243: * @return name of the resource file
244: */
245: public String getResource() {
246: return resource;
247: }
248:
249: /**
250: * Returns an <code>Object[]</code> containing the message arguments.
251: * @return the message arguments
252: */
253: public Object[] getArguments() {
254: return arguments.getArguments();
255: }
256:
257: /**
258: *
259: * @param extraArg
260: */
261: public void setExtraArgument(Object extraArg) {
262: setExtraArguments(new Object[] { extraArg });
263: }
264:
265: /**
266: *
267: * @param extraArgs
268: */
269: public void setExtraArguments(Object[] extraArgs) {
270: if (extraArgs != null) {
271: this .extraArgs = new FilteredArguments(extraArgs);
272: this .extraArgs.setFilter(filter);
273: } else {
274: this .extraArgs = null;
275: }
276: }
277:
278: /**
279: *
280: * @return
281: */
282: public Object[] getExtraArgs() {
283: return (extraArgs == null) ? null : extraArgs.getArguments();
284: }
285:
286: protected class FilteredArguments {
287: protected static final int NO_FILTER = 0;
288: protected static final int FILTER = 1;
289: protected static final int FILTER_URL = 2;
290:
291: protected Filter filter = null;
292:
293: protected boolean[] isLocaleSpecific;
294: protected int[] argFilterType;
295: protected Object[] arguments;
296: protected Object[] unpackedArgs;
297: protected Object[] filteredArgs;
298:
299: FilteredArguments() {
300: this (new Object[0]);
301: }
302:
303: FilteredArguments(Object[] args) {
304: this .arguments = args;
305: this .unpackedArgs = new Object[args.length];
306: this .filteredArgs = new Object[args.length];
307: this .isLocaleSpecific = new boolean[args.length];
308: this .argFilterType = new int[args.length];
309: for (int i = 0; i < args.length; i++) {
310: if (args[i] instanceof TrustedInput) {
311: this .unpackedArgs[i] = ((TrustedInput) args[i])
312: .getInput();
313: argFilterType[i] = NO_FILTER;
314: } else if (args[i] instanceof UntrustedInput) {
315: this .unpackedArgs[i] = ((UntrustedInput) args[i])
316: .getInput();
317: if (args[i] instanceof UntrustedUrlInput) {
318: argFilterType[i] = FILTER_URL;
319: } else {
320: argFilterType[i] = FILTER;
321: }
322: } else {
323: this .unpackedArgs[i] = args[i];
324: argFilterType[i] = FILTER;
325: }
326:
327: // locale specific
328: this .isLocaleSpecific[i] = (this .unpackedArgs[i] instanceof LocaleString);
329: }
330: }
331:
332: public boolean isEmpty() {
333: return unpackedArgs.length == 0;
334: }
335:
336: public Object[] getArguments() {
337: return arguments;
338: }
339:
340: public Object[] getFilteredArgs(Locale locale) {
341: Object[] result = new Object[unpackedArgs.length];
342: for (int i = 0; i < unpackedArgs.length; i++) {
343: Object arg;
344: if (filteredArgs[i] != null) {
345: arg = filteredArgs[i];
346: } else {
347: arg = unpackedArgs[i];
348: if (isLocaleSpecific[i]) {
349: // get locale
350: arg = ((LocaleString) arg)
351: .getLocaleString(locale);
352: arg = filter(argFilterType[i], arg);
353: } else {
354: arg = filter(argFilterType[i], arg);
355: filteredArgs[i] = arg;
356: }
357: }
358: result[i] = arg;
359: }
360: return result;
361: }
362:
363: private Object filter(int type, Object obj) {
364: if (filter != null) {
365: Object o = (null == obj) ? "null" : obj;
366: switch (type) {
367: case NO_FILTER:
368: return o;
369: case FILTER:
370: return filter.doFilter(o.toString());
371: case FILTER_URL:
372: return filter.doFilterUrl(o.toString());
373: default:
374: return null;
375: }
376: } else {
377: return obj;
378: }
379: }
380:
381: public Filter getFilter() {
382: return filter;
383: }
384:
385: public void setFilter(Filter filter) {
386: if (filter != this .filter) {
387: for (int i = 0; i < unpackedArgs.length; i++) {
388: filteredArgs[i] = null;
389: }
390: }
391: this .filter = filter;
392: }
393:
394: }
395:
396: public String toString() {
397: StringBuffer sb = new StringBuffer();
398: sb.append("Resource: \"").append(resource);
399: sb.append("\" Id: \"").append(id).append("\"");
400: sb.append(" Arguments: ").append(
401: arguments.getArguments().length).append(" normal, ")
402: .append(extraArgs.getArguments().length).append(
403: " extra");
404: sb.append(" Encoding: ").append(encoding);
405: sb.append(" ClassLoader: ").append(loader);
406: return sb.toString();
407: }
408:
409: }
|