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.util.Map;
23  import java.util.Locale;
24  import java.util.Set;
25  import java.util.HashMap;
26  import java.util.Collection;
27  
28  import org.apache.velocity.tools.config.DefaultKey;
29  
30  /**
31   * <p>Utility class for easy parsing of String values held in a Map.</p>
32   *
33   * <p>This comes in very handy when parsing parameters.</p>
34   *
35   * <p>When subkeys are allowed, getValue("foo") will also search for all keys
36   * of the form "foo.bar" and return a ValueParser of the type "bar" -> value for all found values.</p>
37   *
38   * TODO: someone doing java configuration ought to be able to put a source Map
39   *       in the tool properties, allowing this to be used like other tools
40   *
41   * @author Nathan Bubna
42   * @version $Revision: 933536 $ $Date: 2010-04-13 03:09:52 -0700 (Tue, 13 Apr 2010) $
43   * @since VelocityTools 1.2
44   */
45  @DefaultKey("parser")
46  public class ValueParser extends ConversionTool implements Map<String,Object>
47  {
48      private Map<String,Object> source = null;
49  
50      private boolean allowSubkeys = true; /* default to whatever, should be overridden by deprecationSupportMode default value anyway */
51  
52      /* when using subkeys, cache at least the presence of any subkey,
53      so that the rendering of templates not using subkeys will only
54      look once for subkeys
55       */
56      private Boolean hasSubkeys = null;
57  
58      /* whether the wrapped map should be read-only or not */
59      private boolean readOnly = true;
60  
61      /**
62       * The key used for specifying whether to support subkeys
63       */
64      public static final String ALLOWSUBKEYS_KEY = "allowSubkeys";
65  
66      /**
67       * The key used for specifying whether to be read-only
68       */
69      public static final String READONLY_KEY = "readOnly";
70  
71      public ValueParser() {}
72  
73      public ValueParser(Map<String,Object> source)
74      {
75          setSource(source);
76      }
77  
78      protected void setSource(Map<String,Object> source)
79      {
80          this.source = source;
81      }
82  
83      protected Map<String,Object> getSource()
84      {
85          return this.source;
86      }
87  
88      /**
89       * Are subkeys allowed ?
90       * @return yes/no
91       */
92      protected boolean getAllowSubkeys()
93      {
94          return allowSubkeys;
95      }
96  
97      /**
98       * allow or disallow subkeys
99       * @param allow
100      */
101     protected void setAllowSubkeys(boolean allow)
102     {
103         allowSubkeys = allow;
104     }
105 
106     /**
107      * Is the Map read-only?
108      * @return yes/no
109      */
110     protected boolean getReadOnly()
111     {
112         return readOnly;
113     }
114 
115     /**
116      * Set or unset read-only behaviour
117      * @param ro
118      */
119     protected void setReadOnly(boolean ro)
120     {
121         readOnly = ro;
122     }
123 
124     /**
125      * Does the actual configuration. This is protected, so
126      * subclasses may share the same ValueParser and call configure
127      * at any time, while preventing templates from doing so when 
128      * configure(Map) is locked.
129      */
130     @Override
131     protected void configure(ValueParser values)
132     {
133         super.configure(values);
134 
135         // if we're supporting 1.x behavior
136         Boolean depMode = values.getBoolean("deprecationSupportMode");
137         if (depMode != null && depMode.booleanValue())
138         {
139             // then don't allow subkeys
140             setAllowSubkeys(false);
141         }
142 
143         // except if explicitely asked for
144         Boolean allow = values.getBoolean(ALLOWSUBKEYS_KEY);
145         if(allow != null)
146         {
147             setAllowSubkeys(allow);
148         }
149 
150         Boolean ro = values.getBoolean(READONLY_KEY);
151         if(ro != null)
152         {
153             setReadOnly(ro);
154         }
155     }
156 
157     // ----------------- public parsing methods --------------------------
158 
159     /**
160      * Convenience method for checking whether a certain parameter exists.
161      *
162      * @param key the parameter's key
163      * @return <code>true</code> if a parameter exists for the specified
164      *         key; otherwise, returns <code>false</code>.
165      */
166     public boolean exists(String key)
167     {
168         return (getValue(key) != null);
169     }
170 
171     /**
172      * Convenience method for use in Velocity templates.
173      * This allows for easy "dot" access to parameters.
174      *
175      * e.g. $params.foo instead of $params.getString('foo')
176      *
177      * @param key the parameter's key
178      * @return parameter matching the specified key or
179      *         <code>null</code> if there is no matching
180      *         parameter
181      */
182     public Object get(String key)
183     {
184         Object value = getValue(key);
185         if (value == null && getSource() != null && getAllowSubkeys())
186         {
187             value = getSubkey(key);
188         }
189         return value;
190     }
191 
192     /**
193      * Returns the value mapped to the specified key
194      * in the {@link Map} returned by {@link #getSource()}. If there is
195      * no source, then this will always return {@code null}.
196      */
197     public Object getValue(String key)
198     {
199         if (getSource() == null)
200         {
201             return null;
202         }
203         return getSource().get(key);
204     }
205 
206     /**
207      * @param key the desired parameter's key
208      * @param alternate The alternate value
209      * @return parameter matching the specified key or the
210      *         specified alternate Object if there is no matching
211      *         parameter
212      */
213     public Object getValue(String key, Object alternate)
214     {
215         Object value = getValue(key);
216         if (value == null)
217         {
218             return alternate;
219         }
220         return value;
221     }
222 
223     public Object[] getValues(String key)
224     {
225         Object value = getValue(key);
226         if (value == null)
227         {
228             return null;
229         }
230         if (value instanceof String)
231         {
232             return parseStringList((String)value);
233         }
234         if (value instanceof Object[])
235         {
236             return (Object[])value;
237         }
238         return new Object[] { value };
239     }
240 
241     /**
242      * @param key the parameter's key
243      * @return parameter matching the specified key or
244      *         <code>null</code> if there is no matching
245      *         parameter
246      */
247     public String getString(String key)
248     {
249         return toString(getValue(key));
250     }
251 
252     /**
253      * @param key the desired parameter's key
254      * @param alternate The alternate value
255      * @return parameter matching the specified key or the
256      *         specified alternate String if there is no matching
257      *         parameter
258      */
259     public String getString(String key, String alternate)
260     {
261         String s = getString(key);
262         return (s != null) ? s : alternate;
263     }
264 
265     /**
266      * @param key the desired parameter's key
267      * @return a {@link Boolean} object for the specified key or
268      *         <code>null</code> if no matching parameter is found
269      */
270     public Boolean getBoolean(String key)
271     {
272         return toBoolean(getValue(key));
273     }
274 
275     /**
276      * @param key the desired parameter's key
277      * @param alternate The alternate boolean value
278      * @return boolean value for the specified key or the
279      *         alternate boolean is no value is found
280      */
281     public boolean getBoolean(String key, boolean alternate)
282     {
283         Boolean bool = getBoolean(key);
284         return (bool != null) ? bool.booleanValue() : alternate;
285     }
286 
287     /**
288      * @param key the desired parameter's key
289      * @param alternate the alternate {@link Boolean}
290      * @return a {@link Boolean} for the specified key or the specified
291      *         alternate if no matching parameter is found
292      */
293     public Boolean getBoolean(String key, Boolean alternate)
294     {
295         Boolean bool = getBoolean(key);
296         return (bool != null) ? bool : alternate;
297     }
298 
299     /**
300      * @param key the desired parameter's key
301      * @return a {@link Integer} for the specified key or
302      *         <code>null</code> if no matching parameter is found
303      */
304     public Integer getInteger(String key)
305     {
306         return toInteger(getValue(key));
307     }
308 
309     /**
310      * @param key the desired parameter's key
311      * @param alternate The alternate Integer
312      * @return an Integer for the specified key or the specified
313      *         alternate if no matching parameter is found
314      */
315     public Integer getInteger(String key, Integer alternate)
316     {
317         Integer num = getInteger(key);
318         if (num == null)
319         {
320             return alternate;
321         }
322         return num;
323     }
324 
325     /**
326      * @param key the desired parameter's key
327      * @return a {@link Double} for the specified key or
328      *         <code>null</code> if no matching parameter is found
329      */
330     public Double getDouble(String key)
331     {
332         return toDouble(getValue(key));
333     }
334 
335     /**
336      * @param key the desired parameter's key
337      * @param alternate The alternate Double
338      * @return an Double for the specified key or the specified
339      *         alternate if no matching parameter is found
340      */
341     public Double getDouble(String key, Double alternate)
342     {
343         Double num = getDouble(key);
344         if (num == null)
345         {
346             return alternate;
347         }
348         return num;
349     }
350 
351     /**
352      * @param key the desired parameter's key
353      * @return a {@link Number} for the specified key or
354      *         <code>null</code> if no matching parameter is found
355      */
356     public Number getNumber(String key)
357     {
358         return toNumber(getValue(key));
359     }
360 
361     /**
362      * @param key the desired parameter's key
363      * @return a {@link Locale} for the specified key or
364      *         <code>null</code> if no matching parameter is found
365      */
366     public Locale getLocale(String key)
367     {
368         return toLocale(getValue(key));
369     }
370 
371     /**
372      * @param key the desired parameter's key
373      * @param alternate The alternate Number
374      * @return a Number for the specified key or the specified
375      *         alternate if no matching parameter is found
376      */
377     public Number getNumber(String key, Number alternate)
378     {
379         Number n = getNumber(key);
380         return (n != null) ? n : alternate;
381     }
382 
383     /**
384      * @param key the desired parameter's key
385      * @param alternate The alternate int value
386      * @return the int value for the specified key or the specified
387      *         alternate value if no matching parameter is found
388      */
389     public int getInt(String key, int alternate)
390     {
391         Number n = getNumber(key);
392         return (n != null) ? n.intValue() : alternate;
393     }
394 
395     /**
396      * @param key the desired parameter's key
397      * @param alternate The alternate double value
398      * @return the double value for the specified key or the specified
399      *         alternate value if no matching parameter is found
400      */
401     public double getDouble(String key, double alternate)
402     {
403         Number n = getNumber(key);
404         return (n != null) ? n.doubleValue() : alternate;
405     }
406 
407     /**
408      * @param key the desired parameter's key
409      * @param alternate The alternate Locale
410      * @return a Locale for the specified key or the specified
411      *         alternate if no matching parameter is found
412      */
413     public Locale getLocale(String key, Locale alternate)
414     {
415         Locale l = getLocale(key);
416         return (l != null) ? l : alternate;
417     }
418 
419 
420     /**
421      * @param key the key for the desired parameter
422      * @return an array of String objects containing all of the values
423      *         associated with the given key, or <code>null</code>
424      *         if the no values are associated with the given key
425      */
426     public String[] getStrings(String key)
427     {
428         return toStrings(getValues(key));
429     }
430 
431 
432     /**
433      * @param key the key for the desired parameter
434      * @return an array of Boolean objects associated with the given key.
435      */
436     public Boolean[] getBooleans(String key)
437     {
438         return toBooleans(getValues(key));
439     }
440 
441     /**
442      * @param key the key for the desired parameter
443      * @return an array of Number objects associated with the given key,
444      *         or <code>null</code> if Numbers are not associated with it.
445      */
446     public Number[] getNumbers(String key)
447     {
448         return toNumbers(getValues(key));
449     }
450 
451     /**
452      * @param key the key for the desired parameter
453      * @return an array of int values associated with the given key,
454      *         or <code>null</code> if numbers are not associated with it.
455      */
456     public int[] getInts(String key)
457     {
458         return toInts(getValues(key));
459     }
460 
461     /**
462      * @param key the key for the desired parameter
463      * @return an array of double values associated with the given key,
464      *         or <code>null</code> if numbers are not associated with it.
465      */
466     public double[] getDoubles(String key)
467     {
468         return toDoubles(getValues(key));
469     }
470 
471     /**
472      * @param key the key for the desired parameter
473      * @return an array of Locale objects associated with the given key,
474      *         or <code>null</code> if Locales are not associated with it.
475      */
476     public Locale[] getLocales(String key)
477     {
478         return toLocales(getValues(key));
479     }
480 
481     /**
482      * Determines whether there are subkeys available in the source map.
483      */
484     public boolean hasSubkeys()
485     {
486         if (getSource() == null)
487         {
488             return false;
489         }
490 
491         if (hasSubkeys == null)
492         {
493             for (String key : getSource().keySet())
494             {
495                 int dot = key.indexOf('.');
496                 if (dot > 0 && dot < key.length())
497                 {
498                     hasSubkeys = Boolean.TRUE;
499                     break;
500                 }
501             }
502             if (hasSubkeys == null)
503             {
504                 hasSubkeys = Boolean.FALSE;
505             }
506         }
507         return hasSubkeys;
508     }
509 
510     /**
511      * subkey getter that returns a map <subkey#2> -> value
512      * for every "subkey.subkey2" found entry
513      *
514      * @param subkey subkey to search for
515      * @return the map of found values
516      */
517     protected ValueParser getSubkey(String subkey)
518     {
519         if (!hasSubkeys() || subkey == null || subkey.length() == 0)
520         {
521             return null;
522         }
523 
524         Map<String,Object> values = null;
525         subkey = subkey.concat(".");
526         for (Map.Entry<String,Object> entry : getSource().entrySet())
527         {
528             if (entry.getKey().startsWith(subkey) &&
529                 entry.getKey().length() > subkey.length())
530             {
531                 if(values == null)
532                 {
533                     values = new HashMap<String,Object>();
534                 }
535 
536                 values.put(entry.getKey().substring(subkey.length()),entry.getValue());
537             }
538         }
539         if (values == null)
540         {
541             return null;
542         }
543         else
544         {
545             return new ValueParser(values);
546         }
547     }
548 
549     public int size()
550     {
551         return getSource().size();
552     }
553 
554     public boolean isEmpty()
555     {
556         return getSource().isEmpty();
557     }
558 
559     public boolean containsKey(Object key)
560     {
561         return getSource().containsKey(key);
562     }
563 
564     public boolean containsValue(Object value)
565     {
566         return getSource().containsValue(value);
567     }
568 
569     public Object get(Object key)
570     {
571         return get(String.valueOf(key));
572     }
573 
574     public Object put(String key, Object value)
575     {
576         if(readOnly)
577         {
578             throw new UnsupportedOperationException("Cannot put("+key+","+value+"); "+getClass().getName()+" is read-only");
579         }
580         if(hasSubkeys != null && hasSubkeys.equals(Boolean.FALSE) && key.indexOf('.') != -1)
581         {
582             hasSubkeys = Boolean.TRUE;
583         }
584         return source.put(key,value); // TODO this tool should be made thread-safe (the request-scoped ParameterTool doesn't need it, but other uses could...)
585     }
586 
587     public Object remove(Object key)
588     {
589         if(readOnly)
590         {
591             throw new UnsupportedOperationException("Cannot remove("+key+"); "+getClass().getName()+" is read-only");
592         }
593         if(hasSubkeys != null && hasSubkeys.equals(Boolean.TRUE) && ((String)key).indexOf('.') != -1)
594         {
595             hasSubkeys = null;
596         }
597         return source.remove(key);
598     }
599 
600     public void putAll(Map<? extends String,? extends Object> m) {
601         if(readOnly)
602         {
603             throw new UnsupportedOperationException("Cannot putAll("+m+"); "+getClass().getName()+" is read-only");
604         }
605         hasSubkeys = null;
606         source.putAll(m);
607     }
608 
609     public void clear() {
610         if(readOnly)
611         {
612             throw new UnsupportedOperationException("Cannot clear(); "+getClass().getName()+" is read-only");
613         }
614         hasSubkeys = Boolean.FALSE;
615         source.clear();
616     }
617 
618     public Set<String> keySet() {
619         return getSource().keySet();
620     }
621 
622     public Collection values() {
623         return getSource().values();
624     }
625 
626     public Set<Map.Entry<String,Object>> entrySet() {
627         return getSource().entrySet();
628     }
629 
630     public String toString() {
631         StringBuilder builder = new StringBuilder();
632         builder.append('{');
633         boolean empty = true;
634         for(Map.Entry<String,Object> entry:entrySet())
635         {
636             if(!empty)
637             {
638                 builder.append(", ");
639             }
640             empty = false;
641             builder.append(entry.getKey());
642             builder.append('=');
643             builder.append(String.valueOf(entry.getValue()));
644         }
645         builder.append('}');
646         return builder.toString();
647     }
648 }