BeanProperties.java
/*
* CSVeed (https://github.com/42BV/CSVeed)
*
* Copyright 2013-2023 CSVeed.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of The Apache Software License,
* Version 2.0 which accompanies this distribution, and is available at
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
package org.csveed.bean;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.csveed.bean.conversion.Converter;
import org.csveed.bean.conversion.CustomNumberConverter;
import org.csveed.bean.conversion.DateConverter;
import org.csveed.bean.conversion.EnumConverter;
import org.csveed.common.Column;
import org.csveed.report.CsvException;
import org.csveed.report.GeneralError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Class BeanProperties.
*/
public class BeanProperties implements Iterable<BeanProperty> {
/** The Constant LOG. */
private static final Logger LOG = LoggerFactory.getLogger(BeanProperties.class);
/** The properties. */
private List<BeanProperty> properties = new ArrayList<>();
/** The index to property. */
private Map<Column, BeanProperty> indexToProperty = new TreeMap<>();
/** The name to property. */
private Map<Column, BeanProperty> nameToProperty = new TreeMap<>();
/** The bean class. */
private Class beanClass;
/** The header value property. */
private BeanProperty headerValueProperty;
/** The header name property. */
private BeanProperty headerNameProperty;
/**
* Instantiates a new bean properties.
*
* @param beanClass
* the bean class
*/
public BeanProperties(Class beanClass) {
this.beanClass = beanClass;
parseBean(beanClass);
}
/**
* Parses the bean.
*
* @param beanClass
* the bean class
*/
private void parseBean(Class beanClass) {
final BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(beanClass);
} catch (IntrospectionException err) {
throw new RuntimeException(err);
}
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
// Note that we use getDeclaredFields here instead of the PropertyDescriptor order. The order we now
// use is guaranteed to be the declaration order from JDK 6+, which is exactly what we need.
for (Field field : beanClass.getDeclaredFields()) {
PropertyDescriptor propertyDescriptor = getPropertyDescriptor(propertyDescriptors, field);
if (propertyDescriptor == null || propertyDescriptor.getWriteMethod() == null) {
LOG.info("Skipping {}.{}", beanClass.getName(), field.getName());
continue;
}
addProperty(propertyDescriptor, field);
}
if (beanClass.getSuperclass() != null) {
parseBean(beanClass.getSuperclass());
}
}
/**
* Adds the property.
*
* @param propertyDescriptor
* the property descriptor
* @param field
* the field
*/
@SuppressWarnings("unchecked")
private void addProperty(PropertyDescriptor propertyDescriptor, Field field) {
BeanProperty beanProperty = new BeanProperty();
beanProperty.setPropertyDescriptor(propertyDescriptor);
beanProperty.setField(field);
if (Enum.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
beanProperty.setConverter(new EnumConverter(propertyDescriptor.getPropertyType()));
}
this.properties.add(beanProperty);
}
/**
* Gets the property descriptor.
*
* @param propertyDescriptors
* the property descriptors
* @param field
* the field
*
* @return the property descriptor
*/
private PropertyDescriptor getPropertyDescriptor(PropertyDescriptor[] propertyDescriptors, Field field) {
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
if (field.getName().equals(propertyDescriptor.getName())) {
return propertyDescriptor;
}
}
return null;
}
/**
* Sets the required.
*
* @param propertyName
* the property name
* @param required
* the required
*/
public void setRequired(String propertyName, boolean required) {
get(propertyName).setRequired(required);
}
/**
* Sets the date.
*
* @param propertyName
* the property name
* @param formatText
* the format text
*/
public void setDate(String propertyName, String formatText) {
setConverter(propertyName, new DateConverter(formatText, true));
}
/**
* Sets the localized number.
*
* @param propertyName
* the property name
* @param locale
* the locale
*/
public void setLocalizedNumber(String propertyName, Locale locale) {
Class<? extends Number> numberClass = get(propertyName).getNumberClass();
if (numberClass == null) {
throw new CsvException(new GeneralError(
"Property " + beanClass.getName() + "." + propertyName + " is not a java.lang.Number"));
}
CustomNumberConverter converter = new CustomNumberConverter(numberClass, NumberFormat.getNumberInstance(locale),
true);
setConverter(propertyName, converter);
}
/**
* Sets the converter.
*
* @param propertyName
* the property name
* @param converter
* the converter
*/
public void setConverter(String propertyName, Converter converter) {
get(propertyName).setConverter(converter);
}
/**
* Removes the from column index.
*
* @param property
* the property
*/
protected void removeFromColumnIndex(BeanProperty property) {
while (indexToProperty.values().remove(property)) {
}
}
/**
* Removes the from column name.
*
* @param property
* the property
*/
protected void removeFromColumnName(BeanProperty property) {
while (nameToProperty.values().remove(property)) {
}
}
/**
* Ignore property.
*
* @param propertyName
* the property name
*/
public void ignoreProperty(String propertyName) {
BeanProperty property = get(propertyName);
properties.remove(property);
removeFromColumnIndex(property);
removeFromColumnName(property);
}
/**
* Sets the header value property.
*
* @param propertyName
* the new header value property
*/
public void setHeaderValueProperty(String propertyName) {
this.headerValueProperty = get(propertyName);
this.headerValueProperty.setDynamicColumnProperty(true);
}
/**
* Sets the header name property.
*
* @param propertyName
* the new header name property
*/
public void setHeaderNameProperty(String propertyName) {
this.headerNameProperty = get(propertyName);
this.headerNameProperty.setDynamicColumnProperty(true);
}
/**
* Gets the header value property.
*
* @return the header value property
*/
public BeanProperty getHeaderValueProperty() {
return this.headerValueProperty;
}
/**
* Gets the header name property.
*
* @return the header name property
*/
public BeanProperty getHeaderNameProperty() {
return this.headerNameProperty;
}
/**
* Map index to property.
*
* @param columnIndex
* the column index
* @param propertyName
* the property name
*/
public void mapIndexToProperty(int columnIndex, String propertyName) {
BeanProperty property = get(propertyName);
removeFromColumnIndex(property);
property.setColumnIndex(columnIndex);
indexToProperty.put(new Column(columnIndex), property);
}
/**
* Map name to property.
*
* @param columnName
* the column name
* @param propertyName
* the property name
*/
public void mapNameToProperty(String columnName, String propertyName) {
BeanProperty property = get(propertyName);
removeFromColumnName(property);
property.setColumnName(columnName);
nameToProperty.put(new Column(columnName.toLowerCase(Locale.getDefault())), property);
}
/**
* Gets the.
*
* @param propertyName
* the property name
*
* @return the bean property
*/
protected BeanProperty get(String propertyName) {
for (BeanProperty beanProperty : properties) {
if (beanProperty.getPropertyName().equals(propertyName)) {
return beanProperty;
}
}
throw new CsvException(
new GeneralError("Property does not exist: " + beanClass.getName() + "." + propertyName));
}
/**
* From index.
*
* @param column
* the column
*
* @return the bean property
*/
public BeanProperty fromIndex(Column column) {
return indexToProperty.get(column);
}
/**
* From name.
*
* @param column
* the column
*
* @return the bean property
*/
public BeanProperty fromName(Column column) {
return nameToProperty.get(column);
}
@SuppressWarnings("unchecked")
@Override
public Iterator<BeanProperty> iterator() {
return ((List<BeanProperty>) ((ArrayList<BeanProperty>) this.properties).clone()).iterator();
}
/**
* Column index keys.
*
* @return the sets the
*/
public Set<Column> columnIndexKeys() {
return this.indexToProperty.keySet();
}
/**
* Column name keys.
*
* @return the sets the
*/
public Set<Column> columnNameKeys() {
return this.nameToProperty.keySet();
}
}