
de.tsl2.nano.bean.BeanUtil Maven / Gradle / Ivy
/*
* Copyright © 2002-2009 Thomas Schneider
* Alle Rechte vorbehalten.
* Weiterverbreitung, Benutzung, Vervielfältigung oder Offenlegung,
* auch auszugsweise, nur mit Genehmigung.
*
* $Id$
*/
package de.tsl2.nano.bean;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StreamTokenizer;
import java.text.Format;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.logging.Log;
import de.tsl2.nano.bean.def.Bean;
import de.tsl2.nano.bean.def.BeanCollector;
import de.tsl2.nano.bean.def.BeanDefinition;
import de.tsl2.nano.bean.def.IAttributeDefinition;
import de.tsl2.nano.bean.def.NamedValue;
import de.tsl2.nano.collection.CollectionUtil;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.Messages;
import de.tsl2.nano.core.cls.BeanAttribute;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.cls.IAttribute;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.DefaultFormat;
import de.tsl2.nano.core.util.FormatUtil;
import de.tsl2.nano.core.util.ListSet;
import de.tsl2.nano.core.util.NumberUtil;
import de.tsl2.nano.core.util.ObjectUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.format.RegExpFormat;
import de.tsl2.nano.util.operation.IConverter;
/**
* A Utility-Class for beans
*
* @author ts 05.03.2009
* @version $Revision$
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class BeanUtil extends ObjectUtil {
private static final Log LOG = LogFactory.getLog(BeanUtil.class);
/**
* as BeanUtil.copyProperties(..) does only a shallow copy, this method does a deep recursive copy.
*
* uses lamdas of jdk 1.8. handles: iterable, enum, number - not handled yet: map, array
*/
/* @SuppressWarnings({ "rawtypes", "unchecked" })
private static Object deepCopy(Object src, Object dest) throws Exception {
BeanUtils.copyProperties(src, dest);
PropertyDescriptor[] pdSrc = BeanUtils.getPropertyDescriptors(src.getClass());
PropertyDescriptor pdDest;
String name;
Object vold, vnew;
Class type;
for (int i = 0; i < pdSrc.length; i++) {
name = pdSrc[i].getName();
vold = null;
vnew = null;
pdDest = BeanUtils.getPropertyDescriptor(dest.getClass(), name);
if (pdDest != null && pdSrc[i].getReadMethod() != null && (vold = pdSrc[i].getReadMethod().invoke(src)) != null
&& (pdDest.getReadMethod() == null || (vnew = pdDest.getReadMethod().invoke(dest)) == null)) {
type = pdDest.getReadMethod().getReturnType();
//create instance of enum, number or entity
vnew = type.isEnum() ? Enum.valueOf((Class) type, ((Enum) vold).name())
: Number.class.isAssignableFrom(type) ? type.getConstructor(String.class).newInstance(vold.toString()) : type.newInstance();
//set the new value and go into recursion
pdDest.getWriteMethod().invoke(dest, deepCopy(vold, vnew));
} else if (vold != null && Iterable.class.isAssignableFrom(type = pdDest.getReadMethod().getReturnType())) {
//handle collections with recursion
Collection it = (Collection) vold;
Collection cnew = (Collection) vnew;
Object genType = pdDest.getReadMethod().getGenericReturnType();
if (genType instanceof ParameterizedType) {
genType = ((ParameterizedType) genType).getActualTypeArguments()[0];
}
if (genType instanceof Class) {
Class gtype = (Class) genType;
boolean javaType = gtype.getPackage().getName().startsWith("java");
it.forEach((Object o) -> {
try {
cnew.add(javaType ? o : deepCopy(o, gtype.newInstance()));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
}
return dest;
}
*/
/**
* creates new objects, cloned from defaultClone and with increasing attribute value - evaluated by first+count+step.
* @param defaultClone
* @param attributeName
* @param first first attribute value - defaultClone can contain the value or this must not be null
* @param count number of objects to create
* @param step step of increase
* @return list of new created objects
*/
public static List create(S defaultClone, String attributeName, Object first, int count, double step) {
Object clone, value;
ArrayList result = new ArrayList();
if (first == null) {
first = Bean.getBean(defaultClone).getAttribute(attributeName).getValue();
}
long start = NumberUtil.toNumber(first);
long end = (long) (start + count * step);
for (long i = start; i < end; i+=step) {
clone = clone(defaultClone);
value = NumberUtil.fromNumber(i, first.getClass());
Bean.getBean(clone).getAttribute(attributeName).setValue(value);
result.add(clone);
}
return result;
}
/**
* wraps all attributes having a collection as value into a new {@link ListSet} instance to unbind a
* {@link #clone()} instance.
*
* @param src instance to wrap the attribute values for
* @return the instance itself
*/
public static S createOwnCollectionInstances(S src) {
BeanClass bc = (BeanClass) BeanClass.getBeanClass(src.getClass());
List attributes = bc.getAttributes();
for (IAttribute a : attributes) {
if (Collection.class.isAssignableFrom(a.getType())) {
Collection v = (Collection) a.getValue(src);
if (v != null) {
LOG.debug("creating own collection instance for " + a.getName() + " with" + v.size() + " elements");
a.setValue(src, new ListSet(v));
}
}
}
return src;
}
/**
* calls a method through reflection
*
* @param clazz class to load
* @param method method to call
* @param args optional arguments
* @return result of method calling
*/
public static Object call(String clazz, String method, Object... args) {
return BeanClass.createBeanClass(clazz).callMethod(null, method, null, args);
}
/**
* calls {@link #call(String, String, Object...)} to start any java method
*
* @param args
*/
public static final void main(String args[]) {
System.out.println("calling BeanUtil.main");
String clazz = args[0];
String method = args[1];
Object[] objs = CollectionUtil.copyOfRange(args, 2, args.length, Object[].class);
call(clazz, method, objs);
}
/**
* fills a map with all bean-attribute-names and their values
*
* @param o bean
* @return see {@link Bean#toValueMap()}
*/
public static Map toValueMap(Object o) {
return toValueMap(o, false, false, false);
}
/**
* fills a map with all bean-attribute-names and their values
*
* @param o bean
* @param useClassPrefix if true, the class-name will be used as prefix for the key
* @param onlySingleValues if true, collections will be ignored
* @param onlyFilterAttributes if true, all other than filterAttributes will be ignored
* @param filterAttributes attributes to be filtered (ignored, if onlyFilterAttributes)
* @return see {@link Bean#toValueMap()}
*/
public static Map toValueMap(Object o,
boolean useClassPrefix,
boolean onlySingleValues,
boolean onlyFilterAttributes,
String... filterAttributes) {
BeanDefinition beandef;
// if (onlyFilterAttributes && filterAttributes.length > 0) {
// //attributes will be changed - so we have to use an own instance
// beandef = new BeanDefinition(BeanClass.getDefiningClass(o.getClass()));
// beandef.setAttributeFilter(filterAttributes);
// } else {
beandef = BeanDefinition.getBeanDefinition(BeanClass.getDefiningClass(o.getClass()));
// }
return beandef.toValueMap(o, useClassPrefix, onlySingleValues, onlyFilterAttributes, filterAttributes);
}
/**
* delegates to {@link #toValueMap(Object, String, boolean, boolean, String...)} with onlyFilteredAttributes = true.
*/
public static Map toValueMap(Object o,
String keyPrefix,
boolean onlySingleValues,
String... filterAttributes) {
return toValueMap(o, keyPrefix, onlySingleValues, true, filterAttributes);
}
/**
* fills a map with all bean-attribute-names and their values
*
* @param o java instance
* @param keyPrefix key prefix to be used for each attribute name. must not be null - use an empty string instead!
* @param onlySingleValues if true, collections will be ignored
* @param onlyFilteredAttributes if true, only the filterAttributes will be filled - otherwise, all others will be
* filled.
* @param filterAttributes attributes to be filtered
* @return see {@link Bean#toValueMap()}
*/
public static Map toValueMap(Object o,
String keyPrefix,
boolean onlySingleValues,
boolean onlyFilteredAttributes,
String... filterAttributes) {
BeanDefinition beandef = BeanDefinition.getBeanDefinition(BeanClass.getDefiningClass(o.getClass()));
// if (onlyFilteredAttributes && filterAttributes.length > 0) {
// //attributes will be changed - so we have to use an own instance
// beandef = new BeanDefinition(BeanClass.getDefiningClass(o.getClass()));
// beandef.setAttributeFilter(filterAttributes);
// } else {
// beandef = BeanDefinition.getBeanDefinition(BeanClass.getDefiningClass(o.getClass()));
// }
return beandef.toValueMap(o, keyPrefix, onlySingleValues, onlyFilteredAttributes, filterAttributes);
}
/**
* delegates to {@link #toFormattedMap(Object, String, boolean)}.
*/
public static Map toFormattedMap(Object bean) {
return toFormattedMap(bean, null, true, new DefaultFormat());
}
/**
* provides a map containing all formatted single value attributes of the given bean
*
* @param bean bean to evaluate
* @param keyPrefix (optional) key name prefix (normally ending with a dot)
* @param translateKeys if true, all keys will be translated
* @param filterAttributes attributes to be filtered (ignored)
* @param format formatter (use {@link DefaultFormat} if unknown)
* @return map containing formatted values
*/
public static Map toFormattedMap(Object bean,
String keyPrefix,
boolean translateKeys,
Format format,
String... filterAttributes) {
final Map valueMap = keyPrefix != null ? BeanUtil.toValueMap(bean,
keyPrefix,
true,
filterAttributes) : BeanUtil.toValueMap(bean, true, true, false, filterAttributes);
final Set keySet = valueMap.keySet();
final Map formattedMap = new LinkedHashMap();
for (final String k : keySet) {
final String key = translateKeys ? Messages.getString(k) : k;
formattedMap.put(key, format.format(valueMap.get(k)));
}
return formattedMap;
}
/**
* convenience for {@link #fromFlatFile(Reader, Class, String...)}.
*/
public static Collection fromFlatFile(String fileName, Class rootType, String... attributeNames) {
try {
return fromFlatFile(new BufferedReader(new FileReader(new File(fileName))), rootType, null, attributeNames);
} catch (final FileNotFoundException e) {
ManagedException.forward(e);
return null;
}
}
/**
* convenience for {@link #fromFlatFile(Reader, Class, String...)}.
*/
public static Collection fromFlatFile(String fileName,
String separation,
Class rootType,
String... attributeNames) {
try {
return fromFlatFile(new BufferedReader(new FileReader(new File(fileName))),
separation,
rootType,
null,
attributeNames);
} catch (final FileNotFoundException e) {
ManagedException.forward(e);
return null;
}
}
/**
* reads a flat (like csv) file and tries to put the values to the given bean type. the given bean type must have a
* default constructor.
*
* @param
* @param r normally a buffered reader.
* @param rootType root bean type to be instantiated and filled to the result collection
* @param attributeNames (optional) simple attribute names or point-separated relation expressions. use null to
* ignore the token
* @return filled collection of beans
*/
public static Collection fromFlatFile(Reader r,
Class rootType,
Map formats,
String... attributeNames) {
return fromFlatFile(r, null, Bean.newBean(rootType), formats, attributeNames);
}
/**
* reads a flat (like csv) file and tries to put the values to the given bean type. the given bean type must have a
* default constructor.
*
* @param
* @param r normally a buffered reader.
* @param separation separation character. if this is null, the attributeNames must contain at least one
* column-index.
* @param rootType root bean type to be instantiated and filled to the result collection
* @param attributeNames (optional) simple attribute names or point-separated relation expressions. use null to
* ignore the token
* @return filled collection of beans
*/
public static Collection fromFlatFile(Reader r,
String separation,
Class rootType,
Map formats,
String... attributeNames) {
return fromFlatFile(r, separation, Bean.newBean(rootType), formats, attributeNames);
}
/**
* reads a flat file (like csv) and tries to put the values to the given bean type. the given bean type should have
* a default constructor. the given bean holds an example instance of your root-type. it is possible to provide an
* overridden bean, to implement the method {@link Bean#newInstance(Object...)} to initialize your desired instance.
* a new instance will be created on each new line.
*
* there are two possibilities to use this method:
* - with a field separator (like comma or semicolon)
* - with line-column definitions (like '1-10:myBeanAttributeName)
*
*
* with the first alternative, you give a separator (not null) and the pure attribute names of the given rootType
* (included in your bean). it is possible to give attribute-relations like 'myAttr1.myAttr2.myAttr3'. to ignore
* fields, use null
as beanattribute-name.
*
*
* the second alternative needs all beanattribute names with a column-prefix like 'begin-end:attributename'. for
* example: 1-11:date. it is possible to use bean relations as in the first alternative, too.
*
* Please notice, that the column-indexes are one-based - the first column is 1 - and the end-index will not be
* included like in String.substring(begin, end). e.g. to read '01.01.2001' you need 1-11. the indexes are
* equivalent to standard texteditors like notepad++.
*
* @param root type
* @param r normally a buffered reader.
* @param separation separation character. if this is null, the attributeNames must contain at least one
* column-index.
* @param bean root bean type to be instantiated and filled to the result collection
* @param attributeFormats (optional) some format-instances to parse to the right object. used if found, otherwise
* standard formatters will be used.
* @param attributeNames simple attribute names or point-separated relation expressions. use null to ignore the
* token
* @return filled collection of beans
*/
public static Collection fromFlatFile(Reader r,
String separation,
Bean bean,
Map attributeFormats,
String... attributeNames) {
/*
* do some validation checks
*/
if (attributeNames.length == 0) {
throw ManagedException.implementationError("give at least one attribute-name to be filled!", null);
}
if (separation == null) {
boolean hasColumnIndexes = false;
for (String n : attributeNames) {
if (n != null && n.contains(":")) {
hasColumnIndexes = true;
break;
}
}
if (!hasColumnIndexes) {
throw ManagedException
.implementationError(
"if you don't give a separation-character, you should give at least one column-index in your attribute-names",
null);
}
}
final Collection result = new LinkedList();
final StreamTokenizer st = new StreamTokenizer(r);
/*
* to remove parsing of numbers by the tokenizer we have to reset all!
* ugly jdk implementation - perhaps we should use Scanner.
*/
st.resetSyntax();
st.wordChars(0x00, 0xFF);
// st.quoteChar('\"');
st.whitespaceChars('\r', '\r');
st.whitespaceChars('\n', '\n');
st.eolIsSignificant(true);
st.commentChar('#');
// st.slashSlashComments(true);
// st.slashStarComments(true);
int ttype = 0;
final Class rootType = bean.getClazz();
// bean.newInstance();
final Map errors = new Hashtable();
final String rootInfo = rootType.getSimpleName() + ".";
/*
* prepare the format cache to parse strings with performance
*/
final Map formatCache = new HashMap();
if (attributeFormats != null) {
formatCache.putAll(attributeFormats);
}
/*
* prepared fixed columns
*/
int begin, end;
String attrName;
final Map attributeColumns = new LinkedHashMap(attributeNames.length);
for (int i = 0; i < attributeNames.length; i++) {
if (separation != null || attributeNames[i] == null) {
attributeColumns.put((attributeNames[i] != null ? attributeNames[i] : "null:" + String.valueOf(i)),
null);
} else {
begin = Integer.valueOf(StringUtil.substring(attributeNames[i], null, "-"));
end = Integer.valueOf(StringUtil.substring(attributeNames[i], "-", ":"));
if (end <= begin || begin < 0 || end < 1) {
throw new IllegalArgumentException("The given range " + attributeNames[i] + " is illegal!");
}
attrName = StringUtil.substring(attributeNames[i], ":", null);
//store one-based indexes
attributeColumns.put(attrName, new Point(begin - 1, end - 1));
}
}
final Set cols = attributeColumns.keySet();
boolean filled = false;
/*
* do the reading, collecting all errors to throw only one exception at the end
*/
try {
String t;
while ((ttype = st.nextToken()) != StreamTokenizer.TT_EOF) {
if (ttype != StreamTokenizer.TT_EOL && st.sval.trim().length() > 0) {
bean.newInstance();
int lastSep = 0;
for (final String attr : cols) {
final Point c = attributeColumns.get(attr);
if (c != null) {
if (c.x >= st.sval.length() || c.y > st.sval.length()) {
throw new StringIndexOutOfBoundsException("The range " + c.x
+ "-"
+ c.y
+ " is not available on line "
+ st.lineno()
+ " with length "
+ st.sval.length()
+ ":"
+ st.sval);
}
t = st.sval.substring(c.x, c.y);
} else {
t = StringUtil.substring(st.sval, null, separation, lastSep);
}
lastSep += t.length() + (c != null ? 0 : separation.length());
//at line end, no separation char will occur
if (st.sval.length() < lastSep) {
lastSep = st.sval.length();
}
if (attr == null || attr.startsWith("null:")) {
LOG.info("ignoring line " + st.lineno()
+ ", token '"
+ t
+ "' at column "
+ (lastSep - t.length()));
continue;
}
t = StringUtil.trim(t, "\"");
final String info = "reading line " + st.lineno() + ":'" + t + "' into " + rootInfo + attr;
try {
Object newValue = null;
if (!Util.isEmpty(t)) {
final BeanAttribute beanAttribute = BeanAttribute.getBeanAttribute(rootType, attr);
Format parser = formatCache.get(attr);
if (parser == null) {
parser = FormatUtil.getDefaultFormat(beanAttribute.getType(), true);
formatCache.put(attr, parser);
}
newValue = parser.parseObject(t);
bean.setValue(beanAttribute.getName(), newValue);
}
LOG.info(info + "(" + newValue + ")");
filled = true;
} catch (final Exception e) {
LOG.info("problem on " + info);
LOG.error(e.toString());
errors.put(info, e);
}
}
if (filled) {
result.add(bean.getInstance());
}
}
}
} catch (final Exception e) {
ManagedException.forward(e);
}
if (errors.size() > 0) {
throw new ManagedException(StringUtil.toFormattedString(errors, 80, true));
}
LOG.info("import finished - imported items: " + result.size() + " of type " + rootType.getSimpleName());
return result;
}
/**
* delegates to {@link #present(BeanCollector, String, String, String, String, String, String, String, String)}.
*/
public static String presentAsCSV(BeanCollector collector) {
return present(collector, "", "", "", "\n", "", ",", null, null);
}
/**
* delegates to {@link #present(BeanCollector, String, String, String, String, String, String, String, String)}.
*/
public static String presentAsTabSheet(BeanCollector collector) {
return present(collector, "", "", "", "\n", "", "\t", null, null);
}
/**
* creates a simple html-table as presentation for the given collector.
*
* @param collector
* @return see {@link #present(BeanCollector, String, String, String, String, String, String, String, String)}
*/
public static String presentAsHtmlTable(BeanCollector collector) {
return present(collector, "\n", "
", "", " \n", "", "\" ", "", ": \"");
}
/**
* creates a string representing all items with all attributes of the given beancollector (holding a collection of
* items).
*
* All parameters without nameBegin and nameEnd must not be null!
*
* @param collector holding a list - defining the attribute presentation.
* @param header text header
* @param footer text footer
* @param rowBegin text on a new line
* @param rowEnd text on line end
* @param colBegin text on new column
* @param colEnd text on column end
* @param nameBegin (optional) if not null, starting text of a fields name. if null, no field name will be presented
* @param nameEnd (optional) if not null, ending text of a fields name. if null, no field name will be presented
* @return string presentation of given collector
*/
public static String present(BeanCollector collector,
String header,
String footer,
String rowBegin,
String rowEnd,
String colBegin,
String colEnd,
String nameBegin,
String nameEnd) {
Collection c = collector.getCurrentData();
List attributes = collector.getBeanAttributes();
StringBuilder buf = new StringBuilder(c.size() * attributes.size() * 30 + 100);
buf.append(header);
for (Object o : c) {
buf.append(rowBegin);
for (IAttributeDefinition a : attributes) {
buf.append(colBegin + (nameBegin != null && nameEnd != null ? nameBegin + a.getName() + nameEnd : "")
+ collector.getColumnText(o, a) + colEnd);
}
buf.append(rowEnd);
}
buf.append(footer);
return buf.toString();
}
/**
* simple delegation to {@link #valueOf(Object, Object)}.
*/
public static final T defaultValue(T value, T defaultIfNull) {
return valueOf(value, defaultIfNull);
}
/**
* returns the value itself - or if null the defaultIfNull
*
* @param
* @param value value or null
* @param defaultIfNull default value if value is null
* @return value of defaultIfNull
*/
public static final T valueOf(T value, T defaultIfNull) {
return value != null ? value : defaultIfNull;
}
/**
* createUUID (see {@link UUID#randomUUID()}
*
* @return random uuid string (128-bit value)
*/
public static final String createUUID() {
return UUID.randomUUID().toString();
}
/**
* creates a hashcode through all single-value attibutes of given bean instance
*
* @param bean instance to evaluate the hashcode for
* @param attributes (optional) attributes to be used for hashcode
* @return new hashcode for given bean instance
*/
public static int hashCodeReflect(Object bean, String... attributes) {
final int prime = 31;
int result = 1;
BeanClass bc = BeanClass.getBeanClass(bean.getClass());
if (attributes == null) {
attributes = bc.getAttributeNames();
}
Object v;
for (int i = 0; i < attributes.length; i++) {
v = BeanClass.getValue(bean, attributes[i]);
result = prime * result + (v == null ? 0 : v.hashCode());
}
return result;
}
/**
* if not already a bean or beancollector, the given object will wrapped into a bean or beancollector. if it is not
* serializable, a map of values will be packed into a beancollector.
*
* @param obj
* @return
*/
public static BeanDefinition> getBean(Object obj) {
return (BeanDefinition>) (obj instanceof BeanDefinition> ? obj
: (Util.isContainer(obj)
? BeanCollector.getBeanCollector(CollectionUtil.getContainer(obj), 0)
: obj instanceof Serializable ? Bean.getBean((Serializable) obj)
: BeanCollector.getBeanCollector(CollectionUtil.getContainer(BeanUtil.toValueMap(obj)), 0)));
}
public static Collection asNamedCollection(Map, T> m) {
LinkedList list = new LinkedList();
NamedValue.putAll(m, list);
return list;
}
/**
* delegates to {@link #getParser(Class, String, String, String, IConverter, boolean)} using @id attribute and
* cache.
*/
public static RegExpFormat getParser(final Class type,
String pattern,
final IConverter converter) {
//workaround to have a simple instance for calling getIdAttribute(). poor performance - but works
TYPE instance = BeanClass.createInstance(type);
return RegExpFormat.getParser(type, BeanContainer.getIdAttribute(instance).getName(), pattern, null, converter, true);
}
}
/**
* To avoid using package awt, we can't use java.awt.Point - but we need a simple Point.
*/
class Point {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy