1 /*
2  * JasperReports - Free Java Reporting Library.
3  * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4  * http://www.jaspersoft.com
5  *
6  * Unless you have purchased a commercial license agreement from Jaspersoft,
7  * the following license terms apply:
8  *
9  * This program is part of JasperReports.
10  *
11  * JasperReports is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * JasperReports is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23  */

24 package net.sf.jasperreports.engine;
25
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Properties;
35 import java.util.ResourceBundle.Control;
36 import java.util.Set;
37
38 import net.sf.jasperreports.engine.util.JRLoader;
39
40 /**
41  * Class that provides static methods for loading, getting and setting properties.
42  * <p>
43  * The following actions are performed:
44  * <ul>
45  * <li>The default (hardcoded) properties are set.</li>
46  * <li>If the system property "net.sf.jasperreports.properties" has been set 
47  * then the specified proprties file is loaded.</li>
48  * <li>Otherwise "jasperreports.properties" is loaded if found in the classpath.</li>
49  * <li>For backward compatibility, system properties like "jasper.reports.compile.xml.validation"
50  * are checked and their values are used.  This way of specifying properties is deprecated.</li>
51  * </ul>
52  * </p> 
53  * 
54  * @author Lucian Chirita (lucianc@users.sourceforge.net)
55  */

56 public final class JRPropertiesUtil
57 {
58     /**
59      * The prefix used by all properties.
60      */

61     public static final String PROPERTY_PREFIX = "net.sf.jasperreports.";
62     public static final String EXCEPTION_MESSAGE_KEY_LOAD_PROPERTIES_FILE_FAILURE = "engine.load.properties.file.failure";
63     public static final String EXCEPTION_MESSAGE_KEY_LOAD_PROPERTIES_FAILURE = "engine.load.properties.failure";
64
65     private JasperReportsContext jasperReportsContext;
66
67
68     /**
69      *
70      */

71     private JRPropertiesUtil(JasperReportsContext jasperReportsContext)
72     {
73         this.jasperReportsContext = jasperReportsContext;
74     }
75     
76     
77     /**
78      *
79      */

80     public static JRPropertiesUtil getInstance(JasperReportsContext jasperReportsContext)
81     {
82         return new JRPropertiesUtil(jasperReportsContext);
83     }
84     
85     
86     /**
87      * Loads a properties file from the classpath.
88      * 
89      * @param name the resource name
90      * @param defaults the default properties
91      * @return the loaded properties if the resource is found, <code>null</code> otherwise
92      * @throws JRException 
93      */

94     public static Properties loadProperties (String name, Properties defaults) throws JRException
95     {
96         Properties properties = null;
97         
98         InputStream is = null;
99         
100         try
101         {
102             is = JRLoader.getLocationInputStream(name);
103         }
104         catch (SecurityException e)
105         {
106             // This could fail if we are in the applet viewer or some other 
107             // restrictive environment, but most of the time it should be safe to ignore.
108             // We cannot log this properly using a logging API, 
109             // as we want to keep applet JAR dependencies to a minimum.
110         }
111         
112         if (is != null)
113         {
114             properties = new Properties(defaults);
115             try
116             {
117                 properties.load(is);
118             }
119             catch (IOException e)
120             {
121                 throw 
122                     new JRException(
123                         EXCEPTION_MESSAGE_KEY_LOAD_PROPERTIES_FILE_FAILURE, 
124                         new Object[]{name}, 
125                         e);
126             }
127             finally
128             {
129                 try
130                 {
131                     is.close();
132                 }
133                 catch (IOException e)
134                 {
135                 }
136             }
137         }
138         
139         return properties;
140     }
141     
142     /**
143      * Returns the value of the property.
144      * 
145      * @param key the key
146      * @return the property value
147      */

148     public String getProperty(String key)
149     {
150         return jasperReportsContext.getProperty(key);
151     }
152     
153     /**
154      * 
155      */

156     public void setProperty(String key, String value)
157     {
158         jasperReportsContext.setProperty(key, value);
159     }
160     
161     /**
162      * 
163      */

164     public void removeProperty(String key)
165     {
166         jasperReportsContext.removeProperty(key);
167     }
168     
169     /**
170      * Returns a property as a boolean value.
171      * 
172      * @param key the key
173      * @return the property value as a boolean
174      */

175     public boolean getBooleanProperty(String key)
176     {
177         return asBoolean(getProperty(key));
178     }
179     
180     /**
181      * Returns a property as a boolean value.
182      * 
183      * @param key the key
184      * @param defaultValue the default value
185      * @return the property value as a boolean
186      */

187     public boolean getBooleanProperty(String key, boolean defaultValue)
188     {
189         return asBoolean(getProperty(key), defaultValue);
190     }
191     
192     /**
193      * Returns a property as an integer value.
194      * 
195      * @param key the key
196      * @return the property value as an integer
197      */

198     public int getIntegerProperty (String key)
199     {
200         return asInteger(getProperty(key));
201     }
202
203     /**
204      * Returns a property as a float value.
205      * 
206      * @param key the key
207      * @return the property value as a float
208      */

209     public float getFloatProperty (String key)
210     {
211         return asFloat(getProperty(key));
212     }
213
214     /**
215      * Converts a <code>String</code> value into a <code>boolean</code>.
216      * 
217      * @param value the value
218      * @return the value as a <code>boolean</code>
219      */

220     public static boolean asBoolean(String value)
221     {
222         return Boolean.valueOf(value == null ? value : value.trim());
223     }
224
225     public static boolean asBoolean(String value, boolean defaultValue)
226     {
227         return value == null ? defaultValue : Boolean.valueOf(value.trim());
228     }
229
230     /**
231      * Converts a <code>String</code> value into a <code>int</code>.
232      * 
233      * @param value the value
234      * @return the value as a <code>int</code>
235      */

236     public static int asInteger(String value)
237     {
238         return Integer.parseInt(value == null ? value : value.trim());
239     }
240     
241     /**
242      * Converts a <code>String</code> value into a <code>float</code>.
243      * 
244      * @param value the value
245      * @return the value as a <code>float</code>
246      */

247     public static float asFloat(String value)
248     {
249         return Float.parseFloat(value == null ? value : value.trim());
250     }
251     
252     /**
253      * Converts a <code>String</code> value into a <code>double</code>.
254      * 
255      * @param value the value
256      * @return the value as a <code>double</code>
257      */

258     public static double asDouble(String value)
259     {
260         return Double.parseDouble(value == null ? value : value.trim());
261     }
262     
263     /**
264      * Class used by {@link JRPropertiesUtil#getProperties(String)}.
265      * 
266      * @author Lucian Chirita
267      */

268     public static class PropertySuffix
269     {
270         protected final String key;
271         protected final String suffix;
272         protected final String value;
273         
274         public PropertySuffix (String key, String suffix, String value)
275         {
276             this.key = key;
277             this.suffix = suffix;
278             this.value = value;
279         }
280         
281         public String getKey()
282         {
283             return key;
284         }
285         
286         public String getSuffix ()
287         {
288             return suffix;
289         }
290         
291         public String getValue ()
292         {
293             return value;
294         }
295     }
296     
297     /**
298      * Returns the list of all properties for a key prefix.
299      * 
300      * @param prefix the key prefix
301      * @return a list of {@link PropertySuffix PropertySuffix} objects containing the suffix of the key and the value
302      */

303     public List<PropertySuffix> getProperties (String prefix)
304     {
305         Map<String, String> properties = jasperReportsContext.getProperties();
306         
307         int prefixLength = prefix.length();
308         List<PropertySuffix> values = new ArrayList<PropertySuffix>();
309         for (Map.Entry<String, String> entry : properties.entrySet())
310         {
311             String name = entry.getKey();
312             if (name.startsWith(prefix))
313             {
314                 String suffix = name.substring(prefixLength);
315                 String value = entry.getValue();
316                 values.add(new PropertySuffix(name, suffix, value));
317             }
318         }
319         return values;
320     }
321
322     /**
323      * Returns the list of all properties for a key prefix.
324      * 
325      * Only this holder's own properties are considered, and not global
326      * properties.
327      * 
328      * @param propertiesHolder the properties holder
329      * @param prefix the key prefix
330      * @return a list of {@link PropertySuffix PropertySuffix} objects containing the suffix of the key and the value
331      * @see #getAllProperties(JRPropertiesHolder, String)
332      */

333     public static List<PropertySuffix> getProperties(JRPropertiesHolder propertiesHolder, String prefix)
334     {
335         return getProperties(getOwnProperties(propertiesHolder), prefix);
336     }
337
338     /**
339      * Returns the list of all properties for a key prefix, including global
340      * properties.
341      * 
342      * @param propertiesHolder the properties holder
343      * @param prefix the key prefix
344      * @return a list of {@link PropertySuffix PropertySuffix} objects containing the suffix of the key and the value
345      * @see #getProperties(JRPropertiesHolder, String)
346      */

347     public List<PropertySuffix> getAllProperties(JRPropertiesHolder propertiesHolder, String prefix)
348     {
349         return getAllProperties(getOwnProperties(propertiesHolder), prefix);
350     }
351     
352     /**
353      * Returns the list of all properties for a key prefix.
354      * 
355      * Only properties from <code>propertiesMap</code> are considered, and
356      * not global properties. 
357      * 
358      * @param propertiesMap the properties map
359      * @param prefix the key prefix
360      * @return a list of {@link PropertySuffix PropertySuffix} objects containing the suffix of the key and the value
361      * @see #getAllProperties(JRPropertiesMap, String)
362      */

363     public static List<PropertySuffix> getProperties(JRPropertiesMap propertiesMap, String prefix)
364     {
365         int prefixLength = prefix.length();
366         List<PropertySuffix> values = new ArrayList<PropertySuffix>();
367         if (propertiesMap != null)
368         {
369             String[] propertyNames = propertiesMap.getPropertyNames();
370             for (int i = 0; i < propertyNames.length; i++)
371             {
372                 String name = propertyNames[i];
373                 if (name.startsWith(prefix))
374                 {
375                     String suffix = name.substring(prefixLength);
376                     String value = propertiesMap.getProperty(name);
377                     values.add(new PropertySuffix(name, suffix, value));
378                 }
379             }
380         }
381         return values;
382     }
383     
384     /**
385      * Returns the list of all properties for a key prefix, including global
386      * properties.
387      * 
388      * @param propertiesMap the properties map
389      * @param prefix the key prefix
390      * @return a list of {@link PropertySuffix PropertySuffix} objects containing the suffix of the key and the value
391      * @see #getProperties(JRPropertiesMap, String)
392      */

393     public List<PropertySuffix> getAllProperties(JRPropertiesMap propertiesMap, String prefix)
394     {
395         List<PropertySuffix> own = getProperties(propertiesMap, prefix);
396         List<PropertySuffix> global = getProperties(prefix);
397         List<PropertySuffix> collected;
398         if (own.isEmpty())
399         {
400             collected = global;
401         }
402         else
403         {
404             if (!global.isEmpty())
405             {
406                 Set<String> ownSuffixes = new HashSet<String>();
407                 for (Iterator<PropertySuffix> it = own.iterator(); it.hasNext();)
408                 {
409                     PropertySuffix prop = it.next();
410                     ownSuffixes.add(prop.getSuffix());
411                 }
412                 
413                 for (Iterator<PropertySuffix> it = global.iterator(); it.hasNext();)
414                 {
415                     PropertySuffix prop = it.next();
416                     if (!ownSuffixes.contains(prop.getSuffix()))
417                     {
418                         own.add(prop);
419                     }
420                 }
421             }
422             
423             collected = own;
424         }
425         return collected;
426     }
427
428     /**
429      * Returns the value of a property, looking first in the supplied properties holder
430      * and then in the system properties.
431      * 
432      * @param propertiesHolder the properties holder
433      * @param key the key
434      * @return the property value
435      */

436     public String getProperty(JRPropertiesHolder propertiesHolder, String key)
437     {
438         String value = null;
439         while (propertiesHolder != null && value == null)
440         {
441             if (propertiesHolder.hasProperties())
442             {
443                 value = propertiesHolder.getPropertiesMap().getProperty(key);
444             }
445             propertiesHolder = propertiesHolder.getParentProperties();
446         }
447         
448         if (value == null)
449         {
450             value = getProperty(key);
451         }
452         
453         return value;
454     }
455
456     /**
457      * Returns the value of a property, looking first in the supplied properties holder
458      * and then in the system properties, using a default value if the property is not found.
459      * 
460      * @param propertiesHolder the properties holder
461      * @param key the key
462      * @param defaultValue the value to return if no property is found
463      * @return the property value
464      */

465     public String getProperty(JRPropertiesHolder propertiesHolder, String key, String defaultValue)
466     {
467         String value = getProperty(propertiesHolder, key);
468         if (value == null)
469         {
470             value = defaultValue;
471         }
472         return value;
473     }
474
475     /**
476      * Returns the value of a property, looking for it in several properties holders
477      * and then in the system properties.
478      * 
479      * @param key the key
480      * @param propertiesHolders the properties holders
481      * @return the property value
482      */

483     public String getProperty(String key, JRPropertiesHolder ... propertiesHolders)
484     {
485         String value = null;
486         main: for (JRPropertiesHolder propertiesHolder : propertiesHolders)
487         {
488             while (propertiesHolder != null)
489             {
490                 if (propertiesHolder.hasProperties())
491                 {
492                     String prop = propertiesHolder.getPropertiesMap().getProperty(key);
493                     if (prop != null)
494                     {
495                         value = prop;
496                         break main;
497                     }
498                 }
499                 propertiesHolder = propertiesHolder.getParentProperties();
500             }
501         }
502         
503         if (value == null)
504         {
505             value = getProperty(key);
506         }
507         
508         return value;
509     }
510     
511     /**
512      * Returns the value of a property, looking first in the supplied properties map
513      * and then in the system properties.
514      * 
515      * @param propertiesMap the properties map
516      * @param key the key
517      * @return the property value
518      */

519     public String getProperty(JRPropertiesMap propertiesMap, String key)
520     {
521         String value = null;
522         if (propertiesMap != null)
523         {
524             value = propertiesMap.getProperty(key);
525         }
526         
527         if (value == null)
528         {
529             value = getProperty(key);
530         }
531         
532         return value;
533     }
534
535     /**
536      * Returns the value of a property as a boolean, looking first in the supplied properties holder
537      * and then in the system properties.
538      * 
539      * @param propertiesHolder the properties holder
540      * @param key the key
541      * @param defaultValue the default value used if the property is not found
542      * @return the property value
543      */

544     public boolean getBooleanProperty(JRPropertiesHolder propertiesHolder, String key, boolean defaultValue)
545     {
546         String value = getProperty(propertiesHolder, key);
547         
548         return value == null ? defaultValue : asBoolean(value);
549     }
550
551     /**
552      * Returns the value of a property as a boolean, looking first in several properties holders
553      * and then in the system properties.
554      * 
555      * @param key the key
556      * @param defaultValue the default value used if the property is not found
557      * @param propertiesHolders the properties holders
558      * @return the property value
559      */

560     public boolean getBooleanProperty(String key, boolean defaultValue, JRPropertiesHolder ... propertiesHolders)
561     {
562         String value = getProperty(key, propertiesHolders);
563         
564         return value == null ? defaultValue : asBoolean(value);
565     }
566
567     /**
568      * Returns the value of a property as a boolean, looking first in the supplied properties map
569      * and then in the system properties.
570      * 
571      * @param propertiesMap the properties map
572      * @param key the key
573      * @param defaultValue the default value used if the property is not found
574      * @return the property value
575      */

576     public boolean getBooleanProperty(JRPropertiesMap propertiesMap, String key, boolean defaultValue)
577     {
578         String value = getProperty(propertiesMap, key);
579         
580         return value == null ? defaultValue : asBoolean(value);
581     }
582
583     /**
584      * Returns the value of a property as a boolean, looking first in the supplied properties map
585      * and then in the system properties.
586      * 
587      * @param propertiesMap the properties map
588      * @param key the key
589      * @return the property value
590      */

591     public Boolean getBooleanProperty(JRPropertiesMap propertiesMap, String key)
592     {
593         String value = getProperty(propertiesMap, key);
594         
595         return value == null ? null : asBoolean(value);
596     }
597
598     /**
599      * Returns the value of a property as a Boolean, looking first in the supplied properties holder
600      * and then in the system properties.
601      * 
602      * @param propertiesHolder the properties holder
603      * @param key the key
604      * @return the property value
605      */

606     public Boolean getBooleanProperty(JRPropertiesHolder propertiesHolder, String key)
607     {
608         String value = getProperty(propertiesHolder, key);
609         
610         return value == null ? null : asBoolean(value);
611     }
612     
613     /**
614      * Returns the value of a property as an Integer, looking first in the supplied properties holder
615      * and then in the system properties.
616      * 
617      * @param propertiesHolder the properties holder
618      * @param key the key
619      * @return the property value
620      */

621     public Integer getIntegerProperty(JRPropertiesHolder propertiesHolder, String key)
622     {
623         String value = getProperty(propertiesHolder, key);
624         
625         return value == null || value.trim().length() == 0 ? null : asInteger(value);
626     }
627     
628     /**
629      * Returns the value of a property as an integer, looking first in the supplied properties holder
630      * and then in the system properties.
631      * 
632      * @param propertiesHolder the properties holder
633      * @param key the key
634      * @param defaultValue the default value used if the property is not found
635      * @return the property value
636      */

637     public int getIntegerProperty(JRPropertiesHolder propertiesHolder, String key, int defaultValue)
638     {
639         String value = getProperty(propertiesHolder, key);
640         
641         return value == null || value.trim().length() == 0 ? defaultValue : asInteger(value);
642     }
643     
644     /**
645      * Returns the value of a property as an integer, looking first in the supplied properties map
646      * and then in the system properties.
647      * 
648      * @param propertiesMap the properties map
649      * @param key the key
650      * @param defaultValue the default value used if the property is not found
651      * @return the property value
652      */

653     public int getIntegerProperty(JRPropertiesMap propertiesMap, String key, int defaultValue)
654     {
655         String value = getProperty(propertiesMap, key);
656         
657         return value == null || value.trim().length() == 0 ? defaultValue : asInteger(value);
658     }
659
660     /**
661      * Returns the value of a property as an integer.
662      * 
663      * @param key the key
664      * @param defaultValue the default value used if the property is not found
665      * @return the property value
666      */

667     public int getIntegerProperty(String key, int defaultValue)
668     {
669         String value = getProperty(key);
670         
671         return value == null || value.trim().length() == 0 ? defaultValue : asInteger(value);
672     }
673
674     /**
675      * Returns the value of a property as a Float, looking first in the supplied properties holder
676      * and then in the system properties.
677      * 
678      * @param propertiesHolder the properties holder
679      * @param key the key
680      * @return the property value
681      */

682     public Float getFloatProperty(JRPropertiesHolder propertiesHolder, String key)
683     {
684         String value = getProperty(propertiesHolder, key);
685         
686         return value == null || value.trim().length() == 0 ? null : asFloat(value);
687     }
688     
689     /**
690      * Returns the value of a property as a float, looking first in the supplied properties holder
691      * and then in the system properties.
692      * 
693      * @param propertiesHolder the properties holder
694      * @param key the key
695      * @param defaultValue the default value used if the property is not found
696      * @return the property value
697      */

698     public float getFloatProperty(JRPropertiesHolder propertiesHolder, String key, float defaultValue)
699     {
700         String value = getProperty(propertiesHolder, key);
701         
702         return value == null || value.trim().length() == 0 ? defaultValue : asFloat(value);
703     }
704     
705     /**
706      * Returns the value of a property as a float, looking first in several properties holders
707      * and then in the system properties.
708      * 
709      * @param key the key
710      * @param defaultValue the default value used if the property is not found
711      * @param propertiesHolders the properties holders
712      * @return the property value
713      */

714     public float getFloatProperty(String key, float defaultValue, JRPropertiesHolder ... propertiesHolders)
715     {
716         String value = getProperty(key, propertiesHolders);
717         
718         return value == null || value.trim().length() == 0 ? defaultValue : asFloat(value);
719     }
720
721     /**
722      * Returns the value of a property as a float, looking first in the supplied properties map
723      * and then in the system properties.
724      * 
725      * @param propertiesMap the properties map
726      * @param key the key
727      * @param defaultValue the default value used if the property is not found
728      * @return the property value
729      */

730     public float getFloatProperty(JRPropertiesMap propertiesMap, String key, float defaultValue)
731     {
732         String value = getProperty(propertiesMap, key);
733         
734         return value == null || value.trim().length() == 0 ? defaultValue : asFloat(value);
735     }
736
737     /**
738      * Returns the value of a property as a float.
739      * 
740      * @param key the key
741      * @param defaultValue the default value used if the property is not found
742      * @return the property value
743      */

744     public float getFloatProperty(String key, float defaultValue)
745     {
746         String value = getProperty(key);
747         
748         return value == null || value.trim().length() == 0 ? defaultValue : asFloat(value);
749     }
750
751     /**
752      * Converts a <code>String</code> value into a <code>long</code>.
753      * 
754      * @param value the value
755      * @return the value as a <code>long</code>
756      */

757     public static long asLong(String value)
758     {
759         return Long.parseLong(value == null ? value : value.trim());
760     }
761     
762     /**
763      * Returns a property as a long value.
764      * 
765      * @param key the key
766      * @return the property value as a long
767      */

768     public long getLongProperty(String key)
769     {
770         return asLong(getProperty(key));
771     }
772     
773     /**
774      * Returns a property as a long value.
775      * 
776      * @param key the key
777      * @param defaultValue the default value
778      * @return the property value as a long
779      */

780     public long getLongProperty(String key, long defaultValue)
781     {
782         String property = getProperty(key);
783         return property == null || property.trim().isEmpty() ? defaultValue : asLong(property);
784     }
785
786     /**
787      * Returns the value of a property as a long, looking first in the supplied properties map
788      * and then in the system properties.
789      * 
790      * @param propertiesMap the properties map
791      * @param key the key
792      * @param defaultValue the default value used if the property is not found
793      * @return the property value
794      */

795     public long getLongProperty(JRPropertiesMap propertiesMap, String key, long defaultValue)
796     {
797         String value = getProperty(propertiesMap, key);
798         
799         return value == null || value.trim().length() == 0 ? defaultValue : asLong(value);
800     }
801     
802     /**
803      * Returns the value of a property as a long, looking first in the supplied properties holder
804      * and then in the system properties.
805      * 
806      * @param propertiesHolder the properties holder
807      * @param key the key
808      * @param defaultValue the default value used if the property is not found
809      * @return the property value
810      */

811     public long getLongProperty(JRPropertiesHolder propertiesHolder, String key, long defaultValue)
812     {
813         String value = getProperty(propertiesHolder, key);
814         
815         return value == null || value.trim().length() == 0 ? defaultValue : asLong(value);
816     }
817
818     /**
819      * Returns the value of a property as a Double, looking first in the supplied properties holder
820      * and then in the system properties.
821      * 
822      * @param propertiesHolder the properties holder
823      * @param key the key
824      * @return the property value
825      */

826     public Double getDoubleProperty(JRPropertiesHolder propertiesHolder, String key)
827     {
828         String value = getProperty(propertiesHolder, key);
829         
830         return value == null || value.trim().length() == 0 ? null : asDouble(value);
831     }
832     
833     protected static JRPropertiesMap getOwnProperties(JRPropertiesHolder propertiesHolder)
834     {
835         return propertiesHolder.hasProperties() ? propertiesHolder.getPropertiesMap() : null;
836     }
837     
838     /**
839      * Copies properties from one object to another.
840      * 
841      * <p>
842      * The properties to be copied are determined by one or more JasperReports
843      * properties having a specified prefix.  The values of these properties
844      * are interpreted as prefixes of properties to copy.
845      * </p>
846      * 
847      * @param source the source properties holder
848      * @param destination the destination properties holder 
849      * @param tranferPropertiesPrefix the prefix of the JasperReports properties
850      * that specify the object properties to copy 
851      */

852     public void transferProperties(JRPropertiesHolder source,
853             JRPropertiesHolder destination, String tranferPropertiesPrefix)
854     {
855         if (!source.hasProperties())
856         {
857             return;
858         }
859
860         transfer(source.getPropertiesMap(), destination, tranferPropertiesPrefix);
861     }
862
863     /**
864      * Copies properties from one object to another.
865      * 
866      * @param source the source properties
867      * @param destination the destination properties holder 
868      * @param tranferPropertiesPrefix the prefix of the JasperReports properties
869      * that specify the object properties to copy 
870      * @see #transferProperties(JRPropertiesHolder, JRPropertiesHolder, String)
871      */

872     public void transferProperties(JRPropertiesMap source,
873             JRPropertiesHolder destination, String tranferPropertiesPrefix)
874     {
875         if (source == null || !source.hasProperties())
876         {
877             return;
878         }
879
880         transfer(source, destination, tranferPropertiesPrefix);
881     }
882     
883     public void transferProperties(JRPropertiesMap source,
884             JRPropertiesHolder destination, List<String> propertyNames)
885     {
886         if (source == null || !source.hasProperties()
887                 || propertyNames == null || propertyNames.isEmpty())
888         {
889             return;
890         }
891         
892         JRPropertiesMap destinationProperties = destination.getPropertiesMap();
893         for (String property : propertyNames)
894         {
895             String value = source.getProperty(property);
896             destinationProperties.setProperty(property, value);
897         }
898     }
899
900     protected void transfer(JRPropertiesMap source,
901             JRPropertiesHolder destination, String tranferPropertiesPrefix)
902     {
903         List<PropertySuffix> transferPrefixProps = getProperties(tranferPropertiesPrefix);//FIXME cache this
904         for (Iterator<PropertySuffix> prefixIt = transferPrefixProps.iterator(); prefixIt.hasNext();)
905         {
906             JRPropertiesUtil.PropertySuffix transferPrefixProp = prefixIt.next();
907             String transferPrefix = transferPrefixProp.getValue();
908             if (transferPrefix != null && transferPrefix.length() > 0)
909             {
910                 List<PropertySuffix> transferProps = getProperties(source, transferPrefix);
911                 for (Iterator<PropertySuffix> propIt = transferProps.iterator(); propIt.hasNext();)
912                 {
913                     JRPropertiesUtil.PropertySuffix property = propIt.next();
914                     String value = property.getValue();
915                     destination.getPropertiesMap().setProperty(property.getKey(), value);
916                 }
917             }
918         }
919     }
920     
921     /**
922      * Returns a property as a <code>Character</code> value.
923      * 
924      * @param key the key
925      * @return the property value as a <code>Character</code>
926      * @see #asCharacter(String)
927      */

928     public Character getCharacterProperty(String key)
929     {
930         return asCharacter(getProperty(key));
931     }
932
933     /**
934      * Returns the value of a property as a <code>Character</code> value, 
935      * looking first in the supplied properties holder and then in the
936      * system properties.
937      * 
938      * @param propertiesHolder the properties holder
939      * @param key the key
940      * @return the property value as a <code>Character</code>
941      */

942     public Character getCharacterProperty(JRPropertiesHolder propertiesHolder, String key)
943     {
944         String value = getProperty(propertiesHolder, key);
945         return asCharacter(value);
946     }
947     
948     /**
949      * Returns the value of a property as a <code>Character</code> value, 
950      * looking first in the supplied properties map
951      * and then in the system properties.
952      * 
953      * @param propertiesMap the properties map
954      * @param key the key
955      * @return the property value as a <code>Character</code>
956      */

957     public Character getCharacterProperty(JRPropertiesMap propertiesMap, String key)
958     {
959         String value = getProperty(propertiesMap, key);
960         return asCharacter(value);
961     }
962     
963     /**
964      * Converts a <code>String</code> into a <code>Character</code> value.
965      * 
966      * <p>
967      * If the <code>String</code> value is null or the empty string, 
968      * <code>null</code> is returned.  Otherwise, the method returns
969      * the first character in the string.
970      * 
971      * @param value the <code>String</code> value
972      * @return the value converted to <code>Character</code>
973      */

974     public static Character asCharacter(String value)
975     {
976         return value == null || value.length() == 0 ? null 
977                 : value.charAt(0);
978     }
979
980     public static String getOwnProperty(JRPropertiesHolder propertiesHolder, String key)
981     {
982         String value = null;
983         if (propertiesHolder.hasProperties())
984         {
985             value = propertiesHolder.getPropertiesMap().getProperty(key);
986         }
987         return value;
988     }
989
990
991     public String getLocalizedProperty(String property, Locale locale)
992     {
993         Control control = Control.getControl(Control.FORMAT_PROPERTIES);
994         String value = null;
995         
996         // we're not looking at the fallback locale to be consistent with JRResourcesUtil.loadResourceBundle
997         List<Locale> locales = control.getCandidateLocales(property, locale);
998         for (Locale candidate : locales)
999         {
1000             String candidateString = candidate.toString();
1001             String candidateProperty = candidateString.isEmpty() ? property : (property + "_" + candidateString);
1002             String candidateValue = getProperty(candidateProperty);
1003             if (candidateValue != null)// test for empty?
1004             {
1005                 value = candidateValue;
1006                 break;
1007             }
1008         }
1009         return value;
1010     }
1011 }
1012