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.io.Serializable;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Enumeration;
33 import java.util.HashMap;
34 import java.util.LinkedHashSet;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Properties;
38
39 import net.sf.jasperreports.engine.design.events.JRPropertyChangeSupport;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43
44 /**
45  * Properties map of an JR element.
46  * <p/>
47  * The order of the properties (obtained by {@link #getPropertyNames() getPropertyNames()}
48  * is the same as the order in which the properties were added.
49  * 
50  * @author Lucian Chirita (lucianc@users.sourceforge.net)
51  */

52 public class JRPropertiesMap implements Serializable, Cloneable
53 {
54     private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
55     
56     private static final Log log = LogFactory.getLog(JRPropertiesMap.class);
57     
58     /**
59      * @deprecated no longer used, {@link #setProperty(String, String)} now uses 
60      * the actual property name for the change event
61      */

62     @Deprecated
63     public static final String PROPERTY_VALUE = "value";
64     
65     private Map<String, String> propertiesMap;
66     private List<String> propertiesList;
67     
68     private JRPropertiesMap base;
69     
70     /**
71      * Creates a properties map.
72      */

73     public JRPropertiesMap()
74     {
75     }
76     
77     /**
78      * Clones a properties map.
79      * 
80      * @param propertiesMap the original properties map
81      */

82     public JRPropertiesMap(JRPropertiesMap propertiesMap)
83     {
84         this();
85         
86         this.base = propertiesMap.base;
87         
88         //this copies all properties from base to this instance
89         //FIXME in some cases we might want to keep the properties in base
90         String[] propertyNames = propertiesMap.getPropertyNames();
91         if (propertyNames != null && propertyNames.length > 0)
92         {
93             for(int i = 0; i < propertyNames.length; i++)
94             {
95                 setProperty(propertyNames[i], propertiesMap.getProperty(propertyNames[i]));
96             }
97         }
98     }
99
100     protected synchronized void ensureInit()
101     {
102         if (propertiesMap == null)
103         {
104             init();
105         }
106     }
107
108     private void init()
109     {
110         // start with small collections
111         propertiesMap = new HashMap<String, String>(4, 0.75f);
112         propertiesList = new ArrayList<String>(2);
113     }
114
115     
116     /**
117      * Returns the names of the properties.
118      *  
119      * @return the names of the properties
120      */

121     public String[] getPropertyNames()
122     {
123         String[] names;
124         if (hasOwnProperties())
125         {
126             if (base == null)
127             {
128                 names = propertiesList.toArray(new String[propertiesList.size()]);
129             }
130             else
131             {
132                 LinkedHashSet<String> namesSet = new LinkedHashSet<String>();
133                 collectPropertyNames(namesSet);
134                 names = namesSet.toArray(new String[namesSet.size()]);
135             }
136         }
137         else if (base != null)
138         {
139             names = base.getPropertyNames();
140         }
141         else
142         {
143             names = new String[0];
144         }
145         return names;
146     }
147     
148     public String[] getOwnPropertyNames()
149     {
150         String[] names;
151         if (hasOwnProperties())
152         {
153             names = propertiesList.toArray(new String[propertiesList.size()]);
154         }
155         else
156         {
157             names = new String[0];
158         }
159         return names;
160     }
161
162     
163     protected void collectPropertyNames(Collection<String> names)
164     {
165         if (base != null)
166         {
167             base.collectPropertyNames(names);
168         }
169         
170         if (propertiesList != null)
171         {
172             names.addAll(propertiesList);
173         }
174     }
175
176
177     /**
178      * Returns the value of a property.
179      * 
180      * @param propName the name of the property
181      * @return the value
182      */

183     public String getProperty(String propName)
184     {
185         String val;
186         if (hasOwnProperty(propName))
187         {
188             val = getOwnProperty(propName);
189         }
190         else if (base != null)
191         {
192             val = base.getProperty(propName);
193         }
194         else
195         {
196             val = null;
197         }
198         return val;
199     }
200     
201     
202     /**
203      * Decides whether the map contains a specified property.
204      * 
205      * The method returns true even if the property value is null.
206      * 
207      * @param propName the property name
208      * @return <code>true</code> if and only if the map contains the property
209      */

210     public boolean containsProperty(String propName)
211     {
212         return hasOwnProperty(propName) 
213                 || base != null && base.containsProperty(propName);
214     }
215
216
217     protected boolean hasOwnProperty(String propName)
218     {
219         return propertiesMap != null && propertiesMap.containsKey(propName);
220     }
221
222
223     protected String getOwnProperty(String propName)
224     {
225         return propertiesMap != null ? (String) propertiesMap.get(propName) : null;
226     }
227
228     
229     /**
230      * Adds/sets a property value.
231      * 
232      * @param propName the name of the property
233      * @param value the value of the property
234      */

235     public void setProperty(String propName, String value)
236     {
237         Object old = getOwnProperty(propName);
238         
239         ensureInit();
240         
241         if (!hasOwnProperty(propName))
242         {
243             propertiesList.add(propName);
244         }
245         propertiesMap.put(propName, value);
246
247         if (hasEventSupport())
248         {
249             getEventSupport().firePropertyChange(propName, old, value);
250         }
251     }
252     
253     
254     /**
255      * Removes a property.
256      * 
257      * @param propName the property name
258      */
    
259     public void removeProperty(String propName)
260     {
261         //FIXME base properties?
262         if (hasOwnProperty(propName))
263         {
264             String old = getOwnProperty(propName);
265             propertiesList.remove(propName);
266             propertiesMap.remove(propName);
267
268             if (hasEventSupport())
269             {
270                 getEventSupport().firePropertyRemove(propName, old);
271             }
272         }
273     }
274     
275     
276     /**
277      * Clones this property map.
278      * 
279      * @return a clone of this property map
280      */

281     public JRPropertiesMap cloneProperties()
282     {
283         return new JRPropertiesMap(this);
284     }
285     
286     
287     @Override
288     public Object clone()
289     {
290         return this.cloneProperties();
291     }
292     
293     
294     @Override
295     public String toString()
296     {
297         return propertiesMap == null ? "" : propertiesMap.toString();
298     }
299     
300     
301     private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
302     {
303         in.defaultReadObject();
304         
305         if (propertiesList == null && propertiesMap != null)// an instance from an old version has been deserialized
306         {
307             //recreate the properties list and map
308             propertiesList = new ArrayList<String>(propertiesMap.keySet());
309             propertiesMap = new HashMap<String, String>(propertiesMap);
310         }
311     }
312     
313     
314     /**
315      * Checks whether there are any properties.
316      * 
317      * @return whether there are any properties
318      */

319     public boolean hasProperties()
320     {
321         return hasOwnProperties()
322                 || base != null && base.hasProperties();
323     }
324
325     public boolean isEmpty()
326     {
327         // only checking base for null and not whether base has any properties
328         return !hasOwnProperties() && base == null;
329     }
330
331     /**
332      * Checks whether this object has properties of its own
333      * (i.e. not inherited from the base properties).
334      * 
335      * @return whether this object has properties of its own
336      * @see #setBaseProperties(JRPropertiesMap)
337      */

338     public boolean hasOwnProperties()
339     {
340         return propertiesList != null && !propertiesList.isEmpty();
341     }
342     
343     
344     /**
345      * Clones the properties map of a properties holder.
346      * If the holder does not have any properties, null is returned.
347      * 
348      * @param propertiesHolder the properties holder
349      * @return a clone of the holder's properties map, or <code>null</code>
350      * if the holder does not have any properties
351      */

352     public static JRPropertiesMap getPropertiesClone(JRPropertiesHolder propertiesHolder)
353     {
354         JRPropertiesMap clone;
355         if (propertiesHolder.hasProperties())
356         {
357             clone = propertiesHolder.getPropertiesMap().cloneProperties();
358         }
359         else
360         {
361             clone = null;
362         }
363         return clone;
364     }
365
366
367     /**
368      * Returns the base properties map, if any.
369      * 
370      * @return the base properties map
371      * @see #setBaseProperties(JRPropertiesMap)
372      */

373     public JRPropertiesMap getBaseProperties()
374     {
375         return base;
376     }
377
378
379     /**
380      * Sets the base properties map.
381      * 
382      * <p>
383      * The base properties map are used as base/default properties for this
384      * instance.  All of the {@link #containsProperty(String)}, 
385      * {@link #getProperty(String)}, {@link #getPropertyNames()} and 
386      * {@link #hasProperties()} methods include base properties as well.
387      * </p>
388      * 
389      * @param base the base properties map
390      */

391     public void setBaseProperties(JRPropertiesMap base)
392     {
393         this.base = base;
394     }
395     
396     /**
397      * Loads a properties file from a location.
398      * 
399      * @param location the properties file URL
400      * @return the properties file loaded as a in-memory properties map
401      */

402     public static JRPropertiesMap loadProperties(URL location)
403     {
404         boolean close = true;
405         InputStream stream = null;
406         try
407         {
408             stream = location.openStream();
409             
410             Properties props = new Properties();
411             props.load(stream);
412             
413             close = false;
414             stream.close();
415             
416             JRPropertiesMap properties = new JRPropertiesMap();
417             for (Enumeration<?> names = props.propertyNames(); names.hasMoreElements(); )
418             {
419                 String name = (String) names.nextElement();
420                 String value = props.getProperty(name);
421                 properties.setProperty(name, value);
422             }
423             return properties;
424         }
425         catch (IOException e)
426         {
427             throw new JRRuntimeException(e);
428         }
429         finally
430         {
431             if (close && stream != null)
432             {
433                 try
434                 {
435                     stream.close();
436                 }
437                 catch (IOException e)
438                 {
439                     if (log.isWarnEnabled())
440                     {
441                         log.warn("Error closing stream for " + location, e);
442                     }
443                 }
444             }
445         }
446     }
447
448     
449     private transient JRPropertyChangeSupport eventSupport;
450     
451     protected boolean hasEventSupport()
452     {
453         return eventSupport != null;
454     }
455     
456     public JRPropertyChangeSupport getEventSupport()
457     {
458         synchronized (this)
459         {
460             if (eventSupport == null)
461             {
462                 eventSupport = new JRPropertyChangeSupport(this);
463             }
464         }
465         
466         return eventSupport;
467     }
468 }
469