de.tsl2.nano.bean.def.ValueExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.descriptor Show documentation
Show all versions of tsl2.nano.descriptor Show documentation
TSL2 Framework Descriptor (currency-handling, generic formatter, descriptors for beans, collections, actions and values)
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Thomas Schneider
* created on: Jun 2, 2013
*
* Copyright: (c) Thomas Schneider 2013, all rights reserved
*/
package de.tsl2.nano.bean.def;
import java.io.IOException;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Formatter;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.core.Commit;
import de.tsl2.nano.bean.BeanContainer;
import de.tsl2.nano.bean.BeanProxy;
import de.tsl2.nano.bean.BeanUtil;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.cls.BeanAttribute;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.cls.PrimitiveUtil;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.ByteUtil;
import de.tsl2.nano.core.util.FormatUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.format.GenericTypeMatcher;
import de.tsl2.nano.util.operation.IConverter;
/**
* Is able to parse a string to create an object. the string will be split into one or more attribute values to be found
* by example through the {@link BeanContainer}. Please see {@link #ValueExpression(String)} for more informations about
* the {@link #expression}.
*
* TODO: implement and test printf format
*
* @author Thomas Schneider
* @version $Revision$
*/
public class ValueExpression implements
IValueExpression,
IConverter,
IInputAssist,
Serializable {
/** serialVersionUID */
private static final long serialVersionUID = 9157362251663475852L;
/** optional expression prefix to define the kind of expression */
private static final String PREFIX_PRINTF_FORMAT = "printf:";
private static final Log LOG = LogFactory.getLog(ValueExpression.class);
/**
* the expression to get the attribute names from - to be used as 'toString' output and to create an object from a
* unique string. the expression is a message expression of type {@link MessageFormat}. If you give the prefix
* {@link #PREFIX_PRINTF_FORMAT}, the expression has to be of style {@link Formatter}. In both cases the argument
* variables have to be replaced by attribute names!
*
* Example using {@link MessageFormat}:
*
*
* {birthday}, {date}, {medium}
*
*
* Example using {@link Formatter}:
*
*
* %birthday$TD
*
*/
@Attribute
String expression;
/** format, the transformed expression */
transient String format;
/** only used to convert/parse from string to object */
@Attribute(required=false)
Class type;
/** attributes, extracted from expression */
transient String[] attributes;
/** separation characters between attributes - extracted from expression */
transient String[] attributeSplitters;
/**
* true, if expression is a standard message format of {@link MessageFormat}, false , if it starts with
* {@link #PREFIX_PRINTF_FORMAT}
*/
transient boolean isMessageFormat;
/** true, if at least one attribute were found in expression */
transient boolean hasArguments;
/** should be true, if {@link #type} is persistable (see {@link BeanContainer#isPersistable(Class)} */
transient boolean isPersistable = false;
transient Comparator comparator;
/**
* constructor to be serializable
*/
protected ValueExpression() {
super();
}
public ValueExpression(String expression) {
this(expression, null);
}
/**
* constructor
*
* @param expression please see {@link #expression} for more informations.
*/
public ValueExpression(String expression, Class type) {
super();
init(expression, type);
}
@Commit
private void initDeserialization() {
init(expression, type);
}
/**
* init
*
* @param expression see {@link #expression}
* @param type
*/
private void init(String expression, Class type) {
this.expression = expression;
isPersistable = type != null && BeanContainer.isInitialized() && BeanContainer.instance().isPersistable(type);
isMessageFormat = !expression.startsWith(PREFIX_PRINTF_FORMAT);
if (isMessageFormat()) {
//ok, thats not 'hinreichend'
hasArguments = expression.contains("{") && expression.contains("}");
attributes = extractAttributeNamesMF(expression);
if (type != null) {
attributeSplitters = extractAttributeSplittersMF(expression);
this.type = type;
}
} else {
//ok, thats not 'hinreichend'
hasArguments = expression.contains("%") && expression.contains("$");
expression = expression.substring(PREFIX_PRINTF_FORMAT.length());
attributes = extractAttributeNames(expression);
if (type != null) {
attributeSplitters = extractAttributeSplitters(expression);
this.type = type;
}
}
LOG.debug("new ValueExpression for type " + type + ": " + expression);
}
/**
* Extension for {@link Serializable}
*/
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
init(expression, type);
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return expression;
}
/**
* {@inheritDoc}
*/
@Override
public TYPE from(String toValue) {
if (type == null) {
throw ManagedException
.implementationError(
"The conversion from string to object is only available, if the ValueExpression was created with a class type argument!",
"type of value-expression '" + toString() + "' is null");
}
if (Util.isEmpty(toValue)) {
return null;
}
//if type is object we try to get a type through string patterns
if (type.isAssignableFrom(Object.class) || type.isAssignableFrom(Serializable.class)) {
return (TYPE) ENV.get(GenericTypeMatcher.class).materialize(toValue);
}
TYPE exampleBean = createExampleBean(toValue);
if (isPersistable) {//check for unique!
Collection beansByExample = BeanContainer.instance().getBeansByExample(exampleBean);
if (beansByExample.size() > 1) {
LOG.warn("string-to-object-parser: found more than one object:\n"
+ StringUtil.toFormattedString(beansByExample, 100, true) + "\n... --> using a simpel transient one!");
//on creating search beans, multiple items should not be a problem
// throw new ManagedException("tsl2nano.multiple.items", new Object[] { toValue, type, type });
return exampleBean;
} else if (beansByExample.size() == 0) {
LOG.error("string-to-object-parser: found no object:\n"
+ StringUtil.toFormattedString(BeanUtil.toValueMap(exampleBean), 100, true));
}
return beansByExample.size() > 0 ? beansByExample.iterator().next() : null;
} else {
return exampleBean;
}
}
public TYPE createExampleBean(String toValue) {
return createExampleBean(toValue, false);
}
/**
* createExampleBean
*
* @param toValue
* @param addSearchPostfix if true, an '*' will be added on attributes of type {@link CharSequence}.
* @return example bean holding attributes given by toValue
*/
public TYPE createExampleBean(String toValue, boolean addSearchPostfix) {
TYPE exampleBean = createInstance(toValue);
if (String.class.isAssignableFrom(getType())) // the following Bean.getBean() doesn't know, that the string is an instance and not a name!
return exampleBean;
//TODO: how-to extract the attribute-name information from expression?
Bean b = (Bean) Bean.getBean(exampleBean);
String[] attributeValues = getAttributeValues(toValue);
for (int i = 0; i < attributes.length; i++) {
IValueDefinition> attr = b.getAttribute(attributes[i]);
//if attribute is a relation, we resolve it
if (BeanContainer.isInitialized() && BeanContainer.instance().isPersistable(attr
.getType())) {
Object v =
BeanDefinition.getBeanDefinition(attr.getType()).getValueExpression().from(attributeValues[i]);
b.setValue(attributes[i], v);
} else {//here we are able to directly parse the string to a value
b.setParsedValue(
attributes[i],
attributeValues[i]
+ (addSearchPostfix
&& (attr.getType() == null || CharSequence.class.isAssignableFrom(attr.getType())) ? "*"
: ""));
}
}
return b.instance;
}
/**
* implements the interface method {@link #getValue(Object)} by {@link IValueExpression} and delegates directly to
* {@link #from(String)} using object instance {@link #toString()} as value. thrown.
*/
@Override
public TYPE getValue(Object instance) {
return from(Util.asString(instance));
}
/**
* creates a new instance from 'toValue' (user-input).
*
* @param toValue
* @return
*/
protected TYPE createInstance(String toValue) {
TYPE instance;
if (type.isInterface()) {
instance = BeanProxy.createBeanImplementation(type, null, null, Thread.currentThread().getContextClassLoader());
} else if (BeanUtil.isStandardType(type)) {
instance = PrimitiveUtil.create(type, toValue);
} else if (BeanClass.hasStringConstructor(type)) {
instance = BeanClass.createInstance(type, toValue);
} else if (ByteUtil.isByteStream(type)) {
instance = (TYPE) toValue.getBytes();
} else {
instance = BeanClass.createInstance(type);
}
return instance;
}
/**
* {@inheritDoc}
*/
@Override
public String to(TYPE fromValue) {
if (fromValue == null) {
return "";
}
if (hasArguments) {
Map valueMap = BeanUtil.toValueMap(fromValue, false, false, true, attributes);
if (valueMap.size() == 0) {
return "?" + Util.asString(fromValue) + "?";
}
Object[] args = mapToAttributeOrder(valueMap);
StringUtil.replaceNulls(args, false);
//check for entity beans to resolve their format recursive through it's valueexpression
preformatBeans(args);
//if object 'fromValue' is empty or not filled, we fill questionmarks to the formatted text
if (args.length < attributes.length) {
Object[] arr = new Object[attributes.length];
Arrays.fill(arr, "?");
args = arr;
}
String txt = isMessageFormat() ? MessageFormat.format(format, args) : String.format(format, args);
/*
* workaround for new instances of composite beans, having a value expression of its id attribute.
* this would result in an empty string on new instances.
*/
return txt.isEmpty() ? "[new: " + fromValue + "]" : txt;
} else {
//lazy workaround...
return getWorkaroundFormat(fromValue);
}
}
private String getWorkaroundFormat(TYPE fromValue) {
return !Util.isEmpty(format) && !format.equals("Object") ? format : FormatUtil.getDefaultFormat(fromValue,
false).format(fromValue);//Util.asString(fromValue);
}
private Object[] mapToAttributeOrder(Map valueMap) {
Object[] mapped = new Object[attributes.length];
for (int i = 0; i < attributes.length; i++) {
mapped[i] = valueMap.get(attributes[i]);
}
return mapped;
}
protected void preformatBeans(Object[] args) {
if (!BeanContainer.isInitialized()) {
return;
}
for (int i = 0; i < args.length; i++) {
if (args[i] != null) {
if (BeanContainer.instance().isPersistable(args[i].getClass())) {
args[i] = Bean.getBean((Serializable) args[i]).toString();
} else if (args[i] instanceof Collection) {
Collection> c = (Collection>) args[i];
if (c.size() > 0 && BeanContainer.instance().isPersistable(c.iterator().next().getClass())) {
StringBuilder buf = new StringBuilder();
for (Object obj : c) {
buf.append(Bean.getBean((Serializable) obj).toString() + ";");
}
args[i] = buf.toString();
}
}
}
}
}
private final boolean isMessageFormat() {
return isMessageFormat;
}
/**
* extractAttributeNames
*
* @param expression to extract bean attribute names from
* @return attribute names, contained in expression
*/
protected String[] extractAttributeNames(String expression) {
Collection attributes = new ArrayList();
int i = 1;
String attrName;
StringBuilder expr = new StringBuilder(expression);
while ((attrName = StringUtil.extract(expr, "%" + BeanAttribute.REGEXP_ATTR_NAME + "\\$", "%" + i + "$"))
.length() > 0) {
attributes.add(attrName.substring(1, attrName.length() - 1));
i++;
}
this.format = expr.toString();
return attributes.toArray(new String[0]);
}
/**
* extractAttributeSplitters
*
* @param expression extracts the placeholders between the attribute names
* @return array of placeholders
*/
protected String[] extractAttributeSplitters(String expression) {
Collection splitters = new ArrayList();
int i = 0;
String splitter;
StringBuilder expr = new StringBuilder(expression);
while (i != -1 && (splitter = StringUtil.substring(expr, "$", "%", i)).length() > 0) {
splitters.add(splitter);
if (i == 0) {
i = expr.indexOf("$");
}
i = expr.indexOf("$", i + 1);
}
return splitters.toArray(new String[0]);
}
/**
* extractAttributeNames for {@link MessageFormat}
*
* @param expression to extract bean attribute names from
* @return attribute names, contained in expression
*/
protected String[] extractAttributeNamesMF(String expression) {
Collection attributes = new ArrayList();
int i = 0;
String attrName;
StringBuilder expr = new StringBuilder(expression);
while ((attrName = StringUtil.extract(expr, "\\{" + BeanAttribute.REGEXP_ATTR_NAME + "\\}", "{" + i + "}"))
.length() > 0) {
attributes.add(attrName.substring(1, attrName.length() - 1));
i++;
}
this.format = expr.toString();
return attributes.toArray(new String[0]);
}
/**
* extractAttributeSplitters for {@link MessageFormat}
*
* @param expression extracts the placeholders between the attribute names
* @return array of placeholders
*/
protected String[] extractAttributeSplittersMF(String expression) {
Collection splitters = new ArrayList();
int i = 0;
String splitter;
StringBuilder expr = new StringBuilder(expression);
while (i != -1 && (splitter = StringUtil.substring(expr, "}", "{", i)).length() > 0) {
splitters.add(splitter);
if (i == 0) {
i = expr.indexOf("}");
}
i = expr.indexOf("}", i + 1);
}
return splitters.toArray(new String[0]);
}
/**
* splits the given value into values for all attributes in expression.
*
* @param toValue value to split
* @return attribute values
*/
protected String[] getAttributeValues(String toValue) {
if (attributeSplitters.length == 0) {
return new String[] { toValue };
}
String splittedValues[] = new String[attributeSplitters.length + 1];
String from = null;
String to = null;
int j = 0;
for (int i = 0; i <= attributeSplitters.length; i++) {
to = i < attributeSplitters.length ? attributeSplitters[i] : null;
splittedValues[i] = StringUtil.substring(toValue, from, to, j);
if (i == attributeSplitters.length || toValue.indexOf(to, j) == -1) {
break;
}
from = to;
j = toValue.indexOf(to) + to.length();
}
return splittedValues;
}
@Override
public Class getType() {
return type;
}
/**
* @return Returns the expression.
*/
@Override
public String getExpression() {
return expression;
}
/**
* @param expression The expression to set.
*/
@Override
public void setExpression(String expression) {
this.expression = expression;
init(expression, type);
}
/**
* isExpressionPart
*
* @param attribute attribute to check
* @return true, if given attribute is part of value expression
*/
public boolean isExpressionPart(String attribute) {
return Arrays.asList(attributes).contains(attribute);
}
@Override
public String toString() {
return Util.toString(getClass(), expression);
}
@Override
public String getExpressionPattern() {
throw new UnsupportedOperationException();
}
@Override
public Collection matchingObjects(Object prefix) {
String input = Util.asString(prefix).trim();
TYPE exampleBean = createExampleBean(input, true);
Collection values =
BeanContainer.instance().getBeansByExample(exampleBean, true, 0,
ENV.get("websocket.intputassist.maxitemcount", 20));
return values;
}
@Override
public Collection availableValues(Object prefix) {
Collection values = matchingObjects(prefix);
Collection result = new ArrayList(values.size());
if (values.size() > 0) {
for (TYPE t : values) {
result.add(to(t));
}
}
return result;
}
/**
* getComparator
*
* @return comparator through value expression strings
*/
public Comparator getComparator() {
if (comparator == null) {
comparator = new Comparator() {
@Override
public int compare(TYPE o1, TYPE o2) {
return to(o1).compareTo(to(o2));
}
};
}
return comparator;
}
}