View Javadoc

1   package org.apache.velocity.tools.generic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.text.MessageFormat;
23  import java.util.ArrayList;
24  import java.util.Enumeration;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.ResourceBundle;
28  import org.apache.velocity.tools.ConversionUtils;
29  import org.apache.velocity.tools.config.DefaultKey;
30  
31  /**
32   * <p>Tool for accessing ResourceBundles and formatting messages therein.</p>
33   * <p><pre>
34   * Template example(s):
35   *   $text.foo                      ->  bar
36   *   $text.hello.world              ->  Hello World!
37   *   $text.keys                     ->  [foo, hello.world, world]
38   *   #set( $otherText = $text.bundle('otherBundle') )
39   *   $otherText.foo                 ->  woogie
40   *   $otherText.bar                 ->  The args are {0} and {1}.
41   *   $otherText.bar.insert(4)       ->  The args are 4 and {1}.
42   *   $otherText.bar.insert(4,true)  ->  The args are 4 and true.
43   *
44   * Toolbox configuration example:
45   * &lt;tools&gt;
46   *   &lt;toolbox scope="request"&gt;
47   *     &lt;tool class="org.apache.velocity.tools.generic.ResourceTool"
48   *              bundles="resources,com.foo.moreResources"
49   *              locale="en_US"/&gt;
50   *   &lt;/toolbox&gt;
51   * &lt;/tools&gt;
52   * </pre></p>
53   *
54   * <p>This comes in very handy when internationalizing templates.
55   *    Note that the default resource bundle baseName is "resources", and
56   *    the default locale is either:
57   *    <ul>
58   *      <li>the result of HttpServletRequest.getLocale() (if used in request scope
59   *          of a VelocityView app)</li>
60   *      <li>the configured locale for this tool (as shown above)<li>
61   *      <li>the configured locale for the toolbox this tool is in<li>
62   *      <li>the configured locale for the toolbox factory managing this tool</li>
63   *      <li>the system locale, if none of the above</li>
64   *    </ul>.
65   * </p>
66   * <p>Also, be aware that very few performance considerations have been made
67   *    in this initial version.  It should do fine, but if you have performance
68   *    issues, please report them to dev@velocity.apache.org, so we can make
69   *    improvements.
70   * </p>
71   *
72   * @author Nathan Bubna
73   * @version $Revision: 722509 $ $Date: 2006-11-27 10:49:37 -0800 (Mon, 27 Nov 2006) $
74   * @since VelocityTools 1.3
75   */
76  @DefaultKey("text")
77  public class ResourceTool extends LocaleConfig
78  {
79      public static final String BUNDLES_KEY = "bundles";
80  
81      private String[] bundles = new String[] { "resources" };
82      private boolean deprecationSupportMode = false;
83  
84  
85      protected final void setDefaultBundle(String bundle)
86      {
87          if (bundle == null)
88          {
89              throw new NullPointerException("Default bundle cannot be null");
90          }
91          this.bundles = new String[] { bundle };
92      }
93  
94      protected final String getDefaultBundle()
95      {
96          return this.bundles[0];
97      }
98  
99      @Deprecated
100     protected final void setDefaultLocale(Locale locale)
101     {
102         if (locale == null)
103         {
104             throw new NullPointerException("Default locale cannot be null");
105         }
106         super.setLocale(locale);
107     }
108 
109     @Deprecated
110     protected final Locale getDefaultLocale()
111     {
112         return super.getLocale();
113     }
114 
115     @Deprecated
116     public void setDeprecationSupportMode(boolean depMode)
117     {
118         this.deprecationSupportMode = depMode;
119     }
120 
121 
122     protected void configure(ValueParser parser)
123     {
124         String[] bundles = parser.getStrings(BUNDLES_KEY);
125         if (bundles != null)
126         {
127             this.bundles = bundles;
128         }
129 
130         super.configure(parser);
131     }
132 
133 
134     /**
135      * Accepts objects and uses their string value as the key.
136      */
137     public Key get(Object k)
138     {
139         String key = k == null ? null : String.valueOf(k);
140         return get(key);
141     }
142 
143     public Key get(String key)
144     {
145         return new Key(key, this.bundles, getLocale(), null);
146     }
147 
148     public List<String> getKeys()
149     {
150         return getKeys(null, this.bundles, getLocale());
151     }
152 
153     public Key bundle(String bundle)
154     {
155         return new Key(null, new String[] { bundle }, getLocale(), null);
156     }
157 
158     public Key locale(Object locale)
159     {
160         return new Key(null, this.bundles, locale, null);
161     }
162 
163     public Key insert(Object[] args)
164     {
165         return new Key(null, this.bundles, getLocale(), args);
166     }
167 
168     public Key insert(List args)
169     {
170         return insert(args.toArray());
171     }
172 
173     public Key insert(Object arg)
174     {
175         return insert(new Object[] { arg });
176     }
177 
178     public Key insert(Object arg0, Object arg1)
179     {
180         return insert(new Object[] { arg0, arg1 });
181     }
182 
183 
184     /**
185      * Retrieves the {@link ResourceBundle} for the specified baseName
186      * and locale, if such exists.  If the baseName or locale is null
187      * or if the locale argument cannot be converted to a {@link Locale},
188      * then this will return null.
189      */
190     protected ResourceBundle getBundle(String baseName, Object loc)
191     {
192         Locale locale = (loc == null) ? getLocale() : toLocale(loc);
193         if (baseName == null || locale == null)
194         {
195             return null;
196         }
197         return ResourceBundle.getBundle(baseName, locale);
198     }
199 
200     /**
201      * Returns the value for the specified key in the ResourceBundle for
202      * the specified basename and locale.  If no such resource can be
203      * found, no errors are thrown and {@code null} is returned.
204      *
205      * @param key the key for the requested resource
206      * @param baseName the base name of the resource bundle to search
207      * @param loc the locale to use
208      */
209     public Object get(Object key, String baseName, Object loc)
210     {
211         ResourceBundle bundle = getBundle(baseName, loc);
212         if (key == null || bundle == null)
213         {
214             return null;
215         }
216         try
217         {
218             return bundle.getObject(String.valueOf(key));
219         }
220         catch (Exception e)
221         {
222             return null;
223         }
224     }
225 
226     /**
227      * Retrieve a resource for the specified key from the first of the
228      * specified bundles in which a matching resource is found.
229      * If no resource is found, no exception will be thrown and {@code null}
230      * will be returned.
231      *
232      * @param k the key for the requested resource
233      * @param bundles the resource bundles to search
234      * @param l the locale to use
235      */
236     public Object get(Object k, String[] bundles, Object l)
237     {
238         String key = k == null ? null : String.valueOf(k);
239         for (int i=0; i < bundles.length; i++)
240         {
241             Object resource = get(key, bundles[i], l);
242             if (resource != null)
243             {
244                 return resource;
245             }
246         }
247         return null;
248     }
249 
250     /**
251      * Returns a {@link List} of the key strings in the ResourceBundle
252      * with the specified baseName and locale.  If the specified prefix
253      * is not null, then this will skip any keys that do not begin with
254      * that prefix and trim the prefix and any subsequent '.' off of the
255      * remaining ones.  If the prefix is null, then no filtering or trimming
256      * will be done.
257      *
258      * @param prefix the prefix for the requested keys
259      * @param bundles the resource bundles to search
260      * @param loc the locale to use
261      */
262     public List<String> getKeys(String prefix, String baseName, Object loc)
263     {
264         ResourceBundle bundle = getBundle(baseName, loc);
265         if (bundle == null)
266         {
267             return null;
268         }
269         Enumeration<String> keys = bundle.getKeys();
270         if (keys == null)
271         {
272             return null;
273         }
274         ArrayList<String> list = new ArrayList<String>();
275         while (keys.hasMoreElements())
276         {
277             String key = keys.nextElement();
278             if (prefix == null)
279             {
280                 list.add(key);
281             }
282             else if (key.startsWith(prefix))
283             {
284                 key = key.substring(prefix.length(), key.length());
285                 if (key.charAt(0) == '.')
286                 {
287                     key = key.substring(1, key.length());
288                 }
289                 list.add(key);
290             }
291         }
292         return list;
293     }
294 
295     /**
296      * Returns a {@link List} of the key strings in the specified
297      * ResourceBundles.  If the specified prefix
298      * is not null, then this will skip any keys that do not begin with
299      * that prefix and trim the prefix and any subsequent '.' off of the
300      * remaining ones.  If the prefix is null, then no filtering or trimming
301      * will be done.
302      *
303      * @param prefix the prefix for the requested keys
304      * @param bundles the resource bundles to search
305      * @param loc the locale to use
306      * @see #getKeys(String,String,Object)
307      */
308     public List<String> getKeys(String prefix, String[] bundles, Object loc)
309     {
310         Locale locale = (loc == null) ? getLocale() : toLocale(loc);
311         if (locale == null || bundles == null || bundles.length == 0)
312         {
313             return null;
314         }
315 
316         List<String> master = new ArrayList<String>();
317         for (String bundle : bundles)
318         {
319             List<String> sub = getKeys(prefix, bundle, locale);
320             if (sub != null)
321             {
322                 master.addAll(sub);
323             }
324         }
325         return master;
326     }
327 
328     private Locale toLocale(Object obj)
329     {
330         if (obj == null)
331         {
332             return null;
333         }
334         if (obj instanceof Locale)
335         {
336             return (Locale)obj;
337         }
338         String s = String.valueOf(obj);
339         return ConversionUtils.toLocale(s);
340     }
341 
342     /**
343      * Renders the specified resource value and arguments as a String.
344      * The resource is treated as a {@link MessageFormat} pattern which
345      * is used for formatting along with any specified argument values.
346      * If <code>deprecationSupportMode</code> is set to true, then this
347      * will return the resource directly when there are no args (as it
348      * did in 1.x versions).
349      */
350     public String render(Object resource, Object[] args)
351     {
352         String value = String.valueOf(resource);
353         if (deprecationSupportMode && args == null)
354         {
355             return value;
356         }
357         return MessageFormat.format(value, args);
358     }
359 
360 
361 
362     /**
363      * Internal class used to enable an elegant syntax for accessing
364      * resources.
365      */
366     public final class Key
367     {
368         // these are copied and/or altered when a mutator is called
369         private final String[] bundles;
370         private final String key;
371         private final Object locale;
372         private final Object[] args;
373 
374         // these are not copied when a mutator is called
375         private boolean cached = false;
376         private Object rawValue;
377 
378         public Key(String key, String[] bundles, Object locale, Object[] args)
379         {
380             this.key = key;
381             this.bundles = bundles;
382             this.locale = locale;
383             this.args = args;
384         }
385 
386         // ----- mutators (these return an altered duplicate) ---
387 
388         public Key get(Object k)
389         {
390             return get(String.valueOf(k));
391         }
392 
393         public Key get(String key)
394         {
395             String newKey;
396             if (this.key == null)
397             {
398                 newKey = key;
399             }
400             else
401             {
402                 newKey = this.key + '.' + key;
403             }
404             return new Key(newKey, this.bundles, this.locale, this.args);
405         }
406 
407         public Key bundle(String bundle)
408         {
409             String[] newBundles = new String[] { bundle };
410             return new Key(this.key, newBundles, this.locale, this.args);
411         }
412 
413         public Key locale(Object locale)
414         {
415             return new Key(this.key, this.bundles, locale, this.args);
416         }
417 
418         public Key insert(Object[] args)
419         {
420             Object[] newargs;
421             if (this.args == null)
422             {
423                 // we can just use the new ones
424                 newargs = args;
425             }
426             else
427             {
428                 // create a new array to hold both the new and old args
429                 newargs = new Object[this.args.length + args.length];
430                 // copy the old args into the newargs array
431                 System.arraycopy(this.args, 0, newargs, 0, this.args.length);
432                 // copy the args to be inserted into the newargs array
433                 System.arraycopy(args, 0, newargs, this.args.length, args.length);
434             }
435             return new Key(this.key, this.bundles, this.locale, newargs);
436         }
437 
438         public Key insert(List args)
439         {
440             return insert(args.toArray());
441         }
442 
443         public Key insert(Object arg)
444         {
445             return insert(new Object[] { arg });
446         }
447 
448         public Key insert(Object arg0, Object arg1)
449         {
450             return insert(new Object[] { arg0, arg1 });
451         }
452 
453         // ---  accessors (these do not return a new Key) ---
454 
455         public boolean getExists()
456         {
457             return (getRaw() != null);
458         }
459 
460         public Object getRaw()
461         {
462             if (!this.cached)
463             {
464                 this.rawValue =
465                     ResourceTool.this.get(this.key, this.bundles, this.locale);
466                 this.cached = true;
467             }
468             return this.rawValue;
469         }
470 
471         public List<String> getKeys()
472         {
473             return ResourceTool.this.getKeys(this.key, this.bundles, this.locale);
474         }
475 
476         public String toString()
477         {
478             if (this.key == null)
479             {
480                 return "";
481             }
482             if (!getExists())
483             {
484                 return "???"+this.key+"???";
485             }
486             return ResourceTool.this.render(this.rawValue, this.args);
487         }
488     }
489 
490 }