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