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

de.tsl2.nano.bean.def.ValueExpression Maven / Gradle / Ivy

Go to download

TSL2 Framework Descriptor (currency-handling, generic formatter, descriptors for beans, collections, actions and values)

There is a newer version: 2.5.1
Show newest version
/*
 * 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); //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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy