All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vesalainen.bean.BeanHelper Maven / Gradle / Ivy

The newest version!
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.vesalainen.bean;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import java.util.Spliterator;
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.xml.bind.annotation.XmlAccessOrder;
import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlType;
import org.vesalainen.util.ArrayBasedComparator;
import org.vesalainen.util.ArrayIterator;
import org.vesalainen.util.CharSequences;
import org.vesalainen.util.ConvertUtility;
import org.vesalainen.util.LinkedSet;
import org.vesalainen.util.function.IndexFunction;

/**
 * A helper class for handling beans.
 * 
 * 

Bean object are accessed by strings. * *

Examples: *

prop - getProp / setProp *

list.1 - list.get(1) *

list.0.p1 - list.get(0).getP1() * @author tkv */ public class BeanHelper { public static final char Lim = '.'; public static final String RegexDel = "\\"+Lim; public static final char Add = '+'; public static final char Rem = '#'; public static final String Remove = Rem+""; public static final char Assign = '='; private static final Pattern Index = Pattern.compile("[0-9]+"); public static final String dump(Object bean) { StringBuilder sb = new StringBuilder(); BeanHelper.stream(bean).forEach((s)->sb.append(String.format("%s = %s\n", s, BeanHelper.getValue(bean, s)))); return sb.toString(); } private static final Object resolvType(Object bean, Object object, Function defaultFunc, BiFunction fieldFunc, BiFunction methodFunc) { if (object instanceof Field) { Field field = (Field) object; return fieldFunc.apply(bean, field); } if (object instanceof Method) { Method method = (Method) object; return methodFunc.apply(bean, method); } return defaultFunc.apply(object); } /** * Executes consumer for property. * @param * @param bean * @param property * @param consumer */ public static final void doFor(Object bean, String property, Consumer consumer) { T t = (T) getValue(bean, property); consumer.accept(t); } /** * Return propertys value. * @param bean * @param property * @return */ public static final Object getValue(Object bean, String property) { return doFor(bean, property, null, BeanHelper::getValue, BeanHelper::getValue, BeanHelper::getFieldValue, BeanHelper::getMethodValue); } private static Object getFieldValue(Object base, Class cls, String property) throws NoSuchFieldException { try { return cls.getField(property).get(base); } catch (IllegalArgumentException | IllegalAccessException ex) { throw new IllegalArgumentException(ex); } } private static Object getMethodValue(Object base, Method method) { try { return method.invoke(base); } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) { throw new IllegalArgumentException(ex); } } private static Object getValue(Object arr, int index) { return Array.get(arr, index); } private static Object getValue(List list, int index) { return list.get(index); } private static Object doFor(Object bean, String property, Class type, IndexFunction arrayFunc, IndexFunction listFunc, FieldFunction fieldFunc, BiFunction methodFunc) { String[] parts = property.split(RegexDel); int len = parts.length - 1; for (int ii = 0; ii < len; ii++) { bean = doIt(bean, parts[ii], null, BeanHelper::getValue, BeanHelper::getValue, BeanHelper::getFieldValue, BeanHelper::getMethodValue); } int idx = property.lastIndexOf(Lim); if (idx != -1) { property = property.substring(idx + 1); } return doIt(bean, property, type, arrayFunc, listFunc, fieldFunc, methodFunc); } private static Object doIt(Object bean, String property, Class argType, IndexFunction arrayFunc, IndexFunction listFunc, FieldFunction fieldFunc, BiFunction methodFunc) { if (Index.matcher(property).matches()) { int index = Integer.parseInt(property); if (bean.getClass().isArray()) { return arrayFunc.apply(bean, index); } if (bean instanceof List) { List list = (List) bean; return listFunc.apply(list, index); } throw new IllegalArgumentException(bean + " not list type"); } Class type = bean.getClass(); try { try { return fieldFunc.apply(bean, type, property); } catch (NoSuchFieldException exx) { if (argType != null) { try { return methodFunc.apply(bean, type.getMethod(property, argType)); } catch (NoSuchMethodException ex) { String methodName = BeanHelper.setter(property); return methodFunc.apply(bean, type.getMethod(methodName, argType)); } } else { try { return methodFunc.apply(bean, type.getMethod(property)); } catch (NoSuchMethodException ex) { String methodName = BeanHelper.getter(property); try { return methodFunc.apply(bean, type.getMethod(methodName)); } catch (NoSuchMethodException ex2) { methodName = BeanHelper.isser(property); return methodFunc.apply(bean, type.getMethod(methodName)); } } } } } catch (IllegalArgumentException | NoSuchMethodException ex) { throw new BeanHelperException(property + " not found in " + bean.getClass().getName(), ex); } } /** * Returns actual parameter types for property * @param bean * @param property * @return */ public static final Class[] getParameterTypes(Object bean, String property) { Type type = (Type) doFor( bean, property, null, (Object a, int i)->{Object o = Array.get(a, i);return o!=null?o.getClass().getComponentType():null;}, (List l, int i)->{return l.get(i).getClass().getGenericSuperclass();}, (Object o, Class c, String p)->{return getField(c, p).getGenericType();}, (Object o, Method m)->{return m.getGenericReturnType();}); if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; Type[] ata = pt.getActualTypeArguments(); if (ata.length > 0) { Class[] ca = new Class[ata.length]; for (int ii=0;ii * @param bean * @param property * @param annotationClass * @return */ public static final T getAnnotation(Object bean, String property, Class annotationClass) { return (T) doFor( bean, property, null, (Object a, int i)->{Object o=Array.get(a, i);return o!=null?o.getClass().getAnnotation(annotationClass):null;}, (List l, int i)->{return l.get(i).getClass().getAnnotation(annotationClass);}, (Object b, Class c, String p)->{return getField(c, p).getAnnotation(annotationClass);}, (Object b, Method m)->{return m.getAnnotation(annotationClass);}); } /** * Return true if property exists. * @param bean * @param property * @return */ public static final boolean hasProperty(Object bean, String property) { try { return (boolean) doFor( bean, property, null, (Object a, int i)->{return true;}, (List l, int i)->{return true;}, (Object b, Class c, String p)->{getField(c, p);return true;}, (Object b, Method m)->{return true;}); } catch (BeanHelperException ex) { return false; } } /** * Return propertys annotations * @param * @param bean * @param property * @return */ public static final T[] getAnnotations(Object bean, String property) { return (T[]) doFor( bean, property, null, (Object a, int i)->{Object o=Array.get(a, i);return o!=null?o.getClass().getAnnotations():null;}, (List l, int i)->{return l.get(i).getClass().getAnnotations();}, (Object b, Class c, String p)->{return getField(c, p).getAnnotations();}, (Object b, Method m)->{return m.getAnnotations();}); } /** * Returns AnnotatedElement for property * @param bean * @param property * @return */ public static final AnnotatedElement getAnnotatedElement(Object bean, String property) { return (AnnotatedElement) doFor( bean, property, null, (Object a, int i)->{Object o=Array.get(a, i);return o!=null?o.getClass():null;}, (List l, int i)->{Object o = l.get(i);return o!=null?o.getClass():null;}, (Object b, Class c, String p)->{return getField(c, p);}, (Object b, Method m)->{return m;}); } /** * Default object factory that calls newInstance method. Checked exceptions * are wrapped in IllegalArgumentException. * @param * @param cls Target class * @param hint Hint for factory * @return */ public static final T defaultFactory(Class cls, String hint) { try { return cls.newInstance(); } catch (InstantiationException | IllegalAccessException ex) { throw new IllegalArgumentException(ex); } } /** * Applies bean action by using default factory. * *

Bean actions are: *

List item property remove by adding '#' to the end of pattern. *

E.g. list.3- same as list.remove(3) *

List item property assign by adding '=' to the end of pattern *

E.g. list.3=hint same as list.set(3, factory.get(cls, hint)) *

List item creation to the end of the list. *

E.g. list+ same as add(factory.get(cls, null)) *

E.g. list+hint same as add(factory.get(cls, hint)) * @param * @param base * @param fieldname * @return true if pattern was applied */ public static final T applyList(Object base, String fieldname) { return applyList(base, fieldname, BeanHelper::defaultFactory); } /** * Applies bean action by using given factory *

Bean actions are: *

List item property remove by adding '#' to the end of pattern. *

E.g. list.3- - list.remove(3) *

List item creation to the end of the list. *

E.g. list+ - add(factory.get(cls, null)) *

E.g. list+hint - add(factory.get(cls, hint)) * @param * @param bean * @param property * @param factory * @return true if pattern was applied */ public static final T applyList(Object bean, String property, BiFunction,String,T> factory) { int addIdx = property.lastIndexOf(Add); if (addIdx != -1) { String hint = property.substring(addIdx+1); return addList(bean, property.substring(0, addIdx), hint, factory); } else { int assignIdx = property.lastIndexOf(Assign); if (assignIdx != -1) { String hint = property.substring(assignIdx+1); return assignList(bean, property.substring(0, assignIdx), hint, factory); } else { if (property.endsWith(Remove)) { return (T)removeList(bean, property.substring(0, property.length()-1)); } else { return null; } } } } /** * Return prefix of apply pattern or pattern if not apply-pattern. * @param property * @return */ public static final String applyPrefix(String property) { int addIdx = property.lastIndexOf(Add); if (addIdx != -1) { return property.substring(0, addIdx); } else { int assignIdx = property.lastIndexOf(Assign); if (assignIdx != -1) { return property.substring(0, assignIdx); } else { if (property.endsWith(Remove)) { return property.substring(0, property.length()-1); } else { return property; } } } } /** * Return true if pattern is add, assign or remove * @param pattern * @return */ public static final boolean isApplyPattern(String pattern) { return isAdd(pattern) || isAssign(pattern) || isRemove(pattern); } /** * Return true for add action * @param property * @return */ public static final boolean isAdd(String property) { return property.lastIndexOf(Add) != -1; } /** * Return true for assign action * @param property * @return */ public static final boolean isAssign(String property) { return property.lastIndexOf(Assign) != -1; } /** * Return true for remove action * @param property * @return */ public static final boolean isRemove(String property) { return property.lastIndexOf(Remove) != -1; } /** * Removes pattern item from list * @param bean * @param property */ public static final Object removeList(Object bean, String property) { return doFor( bean, property, null, (Object a, int i)->{throw new UnsupportedOperationException("not supported");}, (List l, int i)->{return l.remove(i);}, (Object b, Class c, String p)->{throw new UnsupportedOperationException("not supported");}, (Object b, Method m)->{throw new UnsupportedOperationException("not supported");} ); } /** * Adds pattern item to end of list * @param * @param bean * @param property * @param value */ public static final void addList(Object bean, String property, T value) { addList(bean, property, null, (Class c, String h)->{return value;}); } /** * Adds pattern item to end of list using given factory and hint * @param * @param bean * @param property * @param hint * @param factory */ public static final T addList(Object bean, String property, String hint, BiFunction,String,T> factory) { Object fieldValue = getValue(bean, property); if (fieldValue instanceof List) { List list = (List) fieldValue; Class[] pt = getParameterTypes(bean, property); T value = (T)factory.apply(pt[0], hint); if (value != null && !pt[0].isAssignableFrom(value.getClass())) { throw new IllegalArgumentException(pt[0]+" not assignable from "+value); } list.add(value); return value; } else { throw new IllegalArgumentException(fieldValue+" not List"); } } /** * Assign pattern item a new value using given factory (and hint) * @param * @param bean * @param property * @param hint * @param factory */ public static final T assignList(Object bean, String property, String hint, BiFunction,String,T> factory) { Class[] pt = getParameterTypes(bean, prefix(property)); Class type = pt != null && pt.length > 0 ? pt[0] : null; T value = (T) factory.apply(type, hint); doFor( bean, property, null, (Object a, int i)->{Array.set(a, i, value);return null;}, (List l, int i)->{l.set(i, value);return null;}, (Object b, Class c, String p)->{throw new UnsupportedOperationException("not supported");}, (Object b, Method m)->{throw new UnsupportedOperationException("not supported");} ); return value; } /** * Set value * @param bean * @param property * @param v */ public static final void setValue(Object bean, String property, Object v) { Object vv = BeanHelper.getValue(bean, property); if (v != null && v.getClass().isArray() && vv != null && (vv instanceof Collection)) { // array assignment to Collection Collection col = (Collection) vv; Class[] pt = BeanHelper.getParameterTypes(bean, property); col.clear(); int len = Array.getLength(v); for (int ii=0;ii type = BeanHelper.getType(bean, property); if (v != null) { v = ConvertUtility.convert(type, v); } Object value = v; doFor( bean, property, type, (Object a, int i)->{Array.set(a, i, value);return null;}, (List l, int i)->{l.set(i, value);return null;}, (Object b, Class c, String p)->{try { c.getField(p).set(b, value);return null; } catch (IllegalArgumentException | IllegalAccessException ex) { throw new IllegalArgumentException(ex); } }, (Object b, Method m)->{try { m.invoke(b, value);return null; } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) { throw new IllegalArgumentException(ex); } }); } } /** * property -> getProperty * * @param field * @param fieldName * @return */ public static final String getter(Field field, String fieldName) { Class type = field.getType(); if (type.isPrimitive() && "boolean".equals(type.getName())) { return isser(fieldName); } else { return getter(fieldName); } } public static final String getter(String fieldName) { return "get" + upper(fieldName); } public static final String isser(String fieldName) { return "is" + upper(fieldName); } /** * property -> setField * * @param property * @return */ public static final String setter(String property) { return "set" + upper(property); } /** *

* (g/s)etField -> property

* Field -> property * * @param etter * @return */ public static final String property(String etter) { if (etter.startsWith("get") || etter.startsWith("set")) { etter = etter.substring(3); } if (etter.startsWith("is")) { etter = etter.substring(2); } return lower(etter); } private static String lower(String str) { if (str.length() > 0) { return str.substring(0, 1).toLowerCase() + str.substring(1); } else { return str; } } private static String upper(String str) { if (str.length() > 0) { return str.substring(0, 1).toUpperCase() + str.substring(1); } else { return str; } } /** * Returns fieldname for method. getX -> 'x' * * @param method * @return */ public static final String getProperty(Method method) { return property(method.getName()); } private static final int ModifierMask = Modifier.ABSTRACT | Modifier.INTERFACE | Modifier.NATIVE | Modifier.STATIC; /** * Returns Set of classes patterns. *

Annotations XmlAccessorOrder and XmlType are checked for correct ordering. * @param cls * @return * @see javax.xml.bind.annotation.XmlAccessorOrder * @see javax.xml.bind.annotation.XmlType */ public static final Set getProperties(Class cls) { if (cls.isPrimitive() || cls.isAnnotation() || cls.isEnum() || String.class.equals(cls)) { return Collections.EMPTY_SET; } NavigableSet set = setFor(cls); Method[] methods = cls.getMethods(); for (Method method : methods) { if ((method.getModifiers() & ModifierMask) == 0) { String name = method.getName(); if ((name.startsWith("get") || name.startsWith("is")) && method.getParameterCount() == 0) { String property = getProperty(method); Class returnType = method.getReturnType(); if ( returnType.isArray() || List.class.isAssignableFrom(returnType) ) { set.add(property); } else { if (hasMethod(methods, setter(property))) { set.add(property); } } } } } for (Field field : cls.getFields()) { if ((field.getModifiers() & ModifierMask) == 0) { set.add(field.getName()); } } return set; } private static boolean hasMethod(Method[] methods, String setter) { for (Method method : methods) { if (setter.equals(method.getName()) && method.getParameterCount() == 1) { return true; } } return false; } private static NavigableSet setFor(Class cls) { XmlType xmlType = cls.getAnnotation(XmlType.class); if (xmlType != null) { String[] propOrder = xmlType.propOrder(); if (propOrder != null && propOrder.length > 0) { return new TreeSet<>(new ArrayBasedComparator<>(propOrder)); } } XmlAccessorOrder xmlAccessorOrder = cls.getAnnotation(XmlAccessorOrder.class); if (xmlAccessorOrder == null) { xmlAccessorOrder = cls.getPackage().getAnnotation(XmlAccessorOrder.class); } if (xmlAccessorOrder != null) { XmlAccessOrder xmlAccessOrder = xmlAccessorOrder.value(); if (xmlAccessOrder != null && xmlAccessOrder.equals(XmlAccessOrder.ALPHABETICAL)) { return new TreeSet<>(); } } return new LinkedSet<>(); } private static boolean separator(int cc) { switch (cc) { case Lim: case Add: case Rem: case Assign: return true; default: return false; } } /** * Return string before last reparator * @param pattern * @return */ public static final String prefix(String pattern) { int idx = CharSequences.lastIndexOf(pattern, BeanHelper::separator); if (idx != -1) { return pattern.substring(0, idx); } else { return ""; } } /** * Return true if string after last separator is numeric * @param pattern * @return */ public static final boolean isListItem(String pattern) { int idx = pattern.lastIndexOf(Lim); if (idx != -1) { try { Integer.parseInt(pattern.substring(idx+1)); return true; } catch (NumberFormatException ex) { return false; } } else { return false; } } /** * Return string after last '.'/'+'/'#' * @param pattern * @return */ public static final String suffix(String pattern) { int idx = CharSequences.lastIndexOf(pattern, BeanHelper::separator); if (idx != -1) { return pattern.substring(idx+1); } else { return pattern; } } /** * Return set of objects patterns * @param bean * @return */ public static final Set getProperties(Object bean) { return stream(bean).collect(Collectors.toSet()); } /** * Returns targets pattern or null if target not found. * @param bean * @param target * @return */ public static String getPattern(Object bean, Object target) { return stream(bean).filter((s)->{return target.equals(getValue(bean, s));}).findFirst().orElse(null); } /** * Return stream of bean patterns * @param bean * @return */ public static final Stream stream(Object bean) { return StreamSupport.stream(spliterator(bean), false); } /** * Return spliterator of bean patterns *

Note! tryAdvance method is not implemented. * @param bean * @return */ public static final Spliterator spliterator(Object bean) { return new SpliteratorImpl(bean); } private static class SpliteratorImpl implements Spliterator { private Deque stack; private Object bean; public SpliteratorImpl(Object bean) { this.bean = bean; } @Override public void forEachRemaining(Consumer action) { walk(bean, action); } private void walk(Object bean, Consumer consumer) { walk("", bean, consumer); } private void walk(String prefix, Object bean, Consumer consumer) { if (bean != null) { for (String fld : getProperties(bean.getClass())) { String name = prefix + fld; consumer.accept(name); Object value = getValue(bean, fld); if (value != null) { if (value.getClass().isArray()) { int len = Array.getLength(value); for (int index = 0;index action) { if (stack == null) { stack = new ArrayDeque<>(); stack.push(new Ctx("", bean)); } while (!stack.isEmpty()) { Ctx c = stack.peek(); if (c.oit != null && c.oit.hasNext()) { action.accept(c.name + Lim + c.idx); Object next = c.oit.next(); stack.push(new Ctx(c.name+Lim+c.idx+Lim, next)); c.idx++; return true; } if (c.fit == null && c.ob != null) { c.fit = getProperties(c.ob.getClass()).iterator(); } if (c.fit != null && c.fit.hasNext()) { String fld = c.fit.next(); c.name = c.prefix + fld; action.accept(c.name); Object value = getValue(c.ob, fld); if (value != null) { if (value.getClass().isArray()) { c.oit = new ArrayIterator<>(value); c.idx = 0; } else { if (value instanceof List) { List list = (List) value; c.oit = list.iterator(); c.idx = 0; } else { stack.push(new Ctx(c.name + Lim, value)); } } } return true; } stack.pop(); } return false; } @Override public Spliterator trySplit() { return null; } @Override public long estimateSize() { return bean.getClass().getDeclaredFields().length*2; } @Override public int characteristics() { return 0; } } private static class Ctx { private String prefix; private Object ob; private Iterator fit; private Iterator oit; private int idx; private String name; public Ctx(String p, Object o) { this.prefix = p; this.ob = o; } } }