View Javadoc
1   /*
2    * CSVeed (https://github.com/42BV/CSVeed)
3    *
4    * Copyright 2013-2023 CSVeed.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of The Apache Software License,
8    * Version 2.0 which accompanies this distribution, and is available at
9    * https://www.apache.org/licenses/LICENSE-2.0.txt
10   */
11  package org.csveed.bean;
12  
13  import java.beans.BeanInfo;
14  import java.beans.IntrospectionException;
15  import java.beans.Introspector;
16  import java.beans.PropertyDescriptor;
17  import java.lang.reflect.Field;
18  import java.text.NumberFormat;
19  import java.util.ArrayList;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Locale;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.TreeMap;
26  
27  import org.csveed.bean.conversion.Converter;
28  import org.csveed.bean.conversion.CustomNumberConverter;
29  import org.csveed.bean.conversion.DateConverter;
30  import org.csveed.bean.conversion.EnumConverter;
31  import org.csveed.common.Column;
32  import org.csveed.report.CsvException;
33  import org.csveed.report.GeneralError;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * The Class BeanProperties.
39   */
40  public class BeanProperties implements Iterable<BeanProperty> {
41  
42      /** The Constant LOG. */
43      private static final Logger LOG = LoggerFactory.getLogger(BeanProperties.class);
44  
45      /** The properties. */
46      private List<BeanProperty> properties = new ArrayList<>();
47  
48      /** The index to property. */
49      private Map<Column, BeanProperty> indexToProperty = new TreeMap<>();
50  
51      /** The name to property. */
52      private Map<Column, BeanProperty> nameToProperty = new TreeMap<>();
53  
54      /** The bean class. */
55      private Class beanClass;
56  
57      /** The header value property. */
58      private BeanProperty headerValueProperty;
59  
60      /** The header name property. */
61      private BeanProperty headerNameProperty;
62  
63      /**
64       * Instantiates a new bean properties.
65       *
66       * @param beanClass
67       *            the bean class
68       */
69      public BeanProperties(Class beanClass) {
70          this.beanClass = beanClass;
71          parseBean(beanClass);
72      }
73  
74      /**
75       * Parses the bean.
76       *
77       * @param beanClass
78       *            the bean class
79       */
80      private void parseBean(Class beanClass) {
81          final BeanInfo beanInfo;
82          try {
83              beanInfo = Introspector.getBeanInfo(beanClass);
84          } catch (IntrospectionException err) {
85              throw new RuntimeException(err);
86          }
87  
88          PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
89  
90          // Note that we use getDeclaredFields here instead of the PropertyDescriptor order. The order we now
91          // use is guaranteed to be the declaration order from JDK 6+, which is exactly what we need.
92          for (Field field : beanClass.getDeclaredFields()) {
93              PropertyDescriptor propertyDescriptor = getPropertyDescriptor(propertyDescriptors, field);
94              if (propertyDescriptor == null || propertyDescriptor.getWriteMethod() == null) {
95                  LOG.info("Skipping {}.{}", beanClass.getName(), field.getName());
96                  continue;
97              }
98              addProperty(propertyDescriptor, field);
99          }
100 
101         if (beanClass.getSuperclass() != null) {
102             parseBean(beanClass.getSuperclass());
103         }
104     }
105 
106     /**
107      * Adds the property.
108      *
109      * @param propertyDescriptor
110      *            the property descriptor
111      * @param field
112      *            the field
113      */
114     @SuppressWarnings("unchecked")
115     private void addProperty(PropertyDescriptor propertyDescriptor, Field field) {
116         BeanProperty beanProperty = new BeanProperty();
117         beanProperty.setPropertyDescriptor(propertyDescriptor);
118         beanProperty.setField(field);
119         if (Enum.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
120             beanProperty.setConverter(new EnumConverter(propertyDescriptor.getPropertyType()));
121         }
122         this.properties.add(beanProperty);
123     }
124 
125     /**
126      * Gets the property descriptor.
127      *
128      * @param propertyDescriptors
129      *            the property descriptors
130      * @param field
131      *            the field
132      *
133      * @return the property descriptor
134      */
135     private PropertyDescriptor getPropertyDescriptor(PropertyDescriptor[] propertyDescriptors, Field field) {
136         for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
137             if (field.getName().equals(propertyDescriptor.getName())) {
138                 return propertyDescriptor;
139             }
140         }
141         return null;
142     }
143 
144     /**
145      * Sets the required.
146      *
147      * @param propertyName
148      *            the property name
149      * @param required
150      *            the required
151      */
152     public void setRequired(String propertyName, boolean required) {
153         get(propertyName).setRequired(required);
154     }
155 
156     /**
157      * Sets the date.
158      *
159      * @param propertyName
160      *            the property name
161      * @param formatText
162      *            the format text
163      */
164     public void setDate(String propertyName, String formatText) {
165         setConverter(propertyName, new DateConverter(formatText, true));
166     }
167 
168     /**
169      * Sets the localized number.
170      *
171      * @param propertyName
172      *            the property name
173      * @param locale
174      *            the locale
175      */
176     public void setLocalizedNumber(String propertyName, Locale locale) {
177         Class<? extends Number> numberClass = get(propertyName).getNumberClass();
178         if (numberClass == null) {
179             throw new CsvException(new GeneralError(
180                     "Property " + beanClass.getName() + "." + propertyName + " is not a java.lang.Number"));
181         }
182         CustomNumberConverter converter = new CustomNumberConverter(numberClass, NumberFormat.getNumberInstance(locale),
183                 true);
184         setConverter(propertyName, converter);
185     }
186 
187     /**
188      * Sets the converter.
189      *
190      * @param propertyName
191      *            the property name
192      * @param converter
193      *            the converter
194      */
195     public void setConverter(String propertyName, Converter converter) {
196         get(propertyName).setConverter(converter);
197     }
198 
199     /**
200      * Removes the from column index.
201      *
202      * @param property
203      *            the property
204      */
205     protected void removeFromColumnIndex(BeanProperty property) {
206         while (indexToProperty.values().remove(property)) {
207 
208         }
209     }
210 
211     /**
212      * Removes the from column name.
213      *
214      * @param property
215      *            the property
216      */
217     protected void removeFromColumnName(BeanProperty property) {
218         while (nameToProperty.values().remove(property)) {
219 
220         }
221     }
222 
223     /**
224      * Ignore property.
225      *
226      * @param propertyName
227      *            the property name
228      */
229     public void ignoreProperty(String propertyName) {
230         BeanProperty property = get(propertyName);
231         properties.remove(property);
232         removeFromColumnIndex(property);
233         removeFromColumnName(property);
234     }
235 
236     /**
237      * Sets the header value property.
238      *
239      * @param propertyName
240      *            the new header value property
241      */
242     public void setHeaderValueProperty(String propertyName) {
243         this.headerValueProperty = get(propertyName);
244         this.headerValueProperty.setDynamicColumnProperty(true);
245     }
246 
247     /**
248      * Sets the header name property.
249      *
250      * @param propertyName
251      *            the new header name property
252      */
253     public void setHeaderNameProperty(String propertyName) {
254         this.headerNameProperty = get(propertyName);
255         this.headerNameProperty.setDynamicColumnProperty(true);
256     }
257 
258     /**
259      * Gets the header value property.
260      *
261      * @return the header value property
262      */
263     public BeanProperty getHeaderValueProperty() {
264         return this.headerValueProperty;
265     }
266 
267     /**
268      * Gets the header name property.
269      *
270      * @return the header name property
271      */
272     public BeanProperty getHeaderNameProperty() {
273         return this.headerNameProperty;
274     }
275 
276     /**
277      * Map index to property.
278      *
279      * @param columnIndex
280      *            the column index
281      * @param propertyName
282      *            the property name
283      */
284     public void mapIndexToProperty(int columnIndex, String propertyName) {
285         BeanProperty property = get(propertyName);
286         removeFromColumnIndex(property);
287         property.setColumnIndex(columnIndex);
288         indexToProperty.put(new Column(columnIndex), property);
289     }
290 
291     /**
292      * Map name to property.
293      *
294      * @param columnName
295      *            the column name
296      * @param propertyName
297      *            the property name
298      */
299     public void mapNameToProperty(String columnName, String propertyName) {
300         BeanProperty property = get(propertyName);
301         removeFromColumnName(property);
302         property.setColumnName(columnName);
303         nameToProperty.put(new Column(columnName.toLowerCase(Locale.getDefault())), property);
304     }
305 
306     /**
307      * Gets the.
308      *
309      * @param propertyName
310      *            the property name
311      *
312      * @return the bean property
313      */
314     protected BeanProperty get(String propertyName) {
315         for (BeanProperty beanProperty : properties) {
316             if (beanProperty.getPropertyName().equals(propertyName)) {
317                 return beanProperty;
318             }
319         }
320         throw new CsvException(
321                 new GeneralError("Property does not exist: " + beanClass.getName() + "." + propertyName));
322     }
323 
324     /**
325      * From index.
326      *
327      * @param column
328      *            the column
329      *
330      * @return the bean property
331      */
332     public BeanProperty fromIndex(Column column) {
333         return indexToProperty.get(column);
334     }
335 
336     /**
337      * From name.
338      *
339      * @param column
340      *            the column
341      *
342      * @return the bean property
343      */
344     public BeanProperty fromName(Column column) {
345         return nameToProperty.get(column);
346     }
347 
348     @SuppressWarnings("unchecked")
349     @Override
350     public Iterator<BeanProperty> iterator() {
351         return ((List<BeanProperty>) ((ArrayList<BeanProperty>) this.properties).clone()).iterator();
352     }
353 
354     /**
355      * Column index keys.
356      *
357      * @return the sets the
358      */
359     public Set<Column> columnIndexKeys() {
360         return this.indexToProperty.keySet();
361     }
362 
363     /**
364      * Column name keys.
365      *
366      * @return the sets the
367      */
368     public Set<Column> columnNameKeys() {
369         return this.nameToProperty.keySet();
370     }
371 
372 }