| /** |
| * |
| * Copyright (c) 2011, 2016 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany) |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Florian Pirchner - Initial implementation |
| * |
| */ |
| package org.eclipse.osbp.ecview.extension.presentation.vaadin.converter; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.text.DecimalFormat; |
| import java.text.DecimalFormatSymbols; |
| import java.text.ParseException; |
| import java.util.Collections; |
| import java.util.Locale; |
| |
| import org.apache.commons.beanutils.PropertyUtils; |
| import org.apache.commons.lang.StringEscapeUtils; |
| import org.eclipse.core.databinding.beans.BeanProperties; |
| import org.eclipse.core.databinding.beans.IBeanValueProperty; |
| import org.eclipse.osbp.ecview.core.common.context.IViewContext; |
| import org.eclipse.osbp.ecview.extension.model.converter.YQuantityToStringConverter; |
| import org.eclipse.osbp.runtime.common.types.ITypeProviderService; |
| import org.eclipse.osbp.runtime.web.vaadin.common.data.IBeanSearchService; |
| import org.eclipse.osbp.runtime.web.vaadin.common.data.IBeanSearchServiceFactory; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.vaadin.data.Container.Filter; |
| import com.vaadin.data.util.converter.Converter; |
| import com.vaadin.data.util.filter.Compare; |
| |
| /** |
| * This converter converters values from a "quantity object" (model) to a String |
| * (presentation) and back to a quantity object. |
| * <p> |
| * Therefore it uses following logic:<br> |
| * The <code>model value</code> is an object containing the value as a numeric |
| * value and the uom (UnitOfMeasure). Value and uom may be nested properties. |
| * <h3>Access path</h3> |
| * The YQuantityToStringConverter contains three "access path". An "access path" |
| * is a property path with dot-notation like "uom.isoCode". To access the values |
| * inside the given object, reflection needs to be used. |
| * <p> |
| * Sample with mappings to the YQuantityToStringConverter: |
| * |
| * <pre> |
| * /** |
| * /** |
| * /** |
| * } |
| * |
| * class Uom { |
| * /** |
| * String name; |
| * ... |
| * } |
| * </pre> |
| * |
| * The Quantity bean is the model value. To access the values following |
| * accessPath in the YQuantityToStringConverter needs to be used:<br> |
| * -> for value: <code>value</code> --> converter#amountPropertyPath<br> |
| * -> for uom-Object: <code>uom</code> --> converter#uomPropertyPath<br> |
| * -> for uomCode: <code>uom.iso3Code</code> --> converter#uomPropertyPath + "." |
| * + converter#uomCodeRelativePropertyPath<br> |
| * By reflection the values can become accessed. |
| * |
| * <h3>Convert To Presentation</h3> For the convert to presentation |
| * implementation, a number format pattern is used. The uom-String-value is |
| * added to the number format pattern as a constant. <br> |
| * <br> |
| * The result may look like: <code>1.200,23 kg</code> |
| * |
| * <h3>Convert To Model</h3> For the convert to model implementation, the given |
| * presentation-String needs to become parsed. And a new Object of type |
| * "model value" needs to be created. The type of the instance, is also |
| * contained in the YQuantityToStringConverter object. See |
| * {@link YQuantityToStringConverter#getQuantityTypeQualifiedName()}. To get a |
| * class based on the FQN use the {@link ITypeProviderService}.<br> |
| * The UOM needs to be converted to a valid UOM-object. Therefore use the |
| * {@link IBeanSearchService}. |
| * |
| * <h3>Unsupported UOMs</h3> To ensure, that only valid UOMs are being entered |
| * in the UI, an additional Validator needs to be implemented. It validates, |
| * that the entered UOM is valid. To query about valid UOMs, the |
| * BeanSearchService is available. See {@link IBeanSearchService}. |
| * |
| * <h3>Usage</h3> You may use this converter, to bind a Quantity-Dto to a |
| * TextField. |
| * |
| */ |
| @SuppressWarnings("serial") |
| public class QuantityToStringConverter implements Converter<String, Object> { |
| |
| /** The Constant LOGGER. */ |
| private static final Logger LOGGER = LoggerFactory |
| .getLogger(QuantityToStringConverter.class); |
| |
| /** The Constant NUMBER_FORMAT_PATTERN. */ |
| private static final String NUMBER_FORMAT_PATTERN = "##,##0.00 {$uom}"; |
| |
| /** The cx converter. */ |
| private final YQuantityToStringConverter cxConverter; |
| |
| /** The bean search service. */ |
| private IBeanSearchService<Object> beanSearchService; |
| |
| /** The quantity type. */ |
| private final Class<?> quantityType; |
| |
| /** |
| * Instantiates a new quantity to string converter. |
| * |
| * @param context |
| * the context |
| * @param cxConverter |
| * the cx converter |
| */ |
| @SuppressWarnings("unchecked") |
| public QuantityToStringConverter(IViewContext context, |
| YQuantityToStringConverter cxConverter) { |
| super(); |
| this.cxConverter = cxConverter; |
| |
| ITypeProviderService typeService = context |
| .getService(ITypeProviderService.class.getName()); |
| quantityType = typeService.forName(null, |
| cxConverter.getQuantityTypeQualifiedName()); |
| |
| // use the BeanProperties to find the UnitOfMeasure by its code |
| IBeanValueProperty prop = BeanProperties.value(quantityType, |
| cxConverter.getUomPropertyPath()); |
| IBeanSearchServiceFactory factory = context |
| .getService(IBeanSearchServiceFactory.class.getName()); |
| if (factory != null) { |
| beanSearchService = factory.createService((Class<Object>) prop |
| .getValueType()); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, |
| * java.lang.Class, java.util.Locale) |
| */ |
| @Override |
| public Object convertToModel(String valueString, |
| Class<? extends Object> targetType, Locale locale) |
| throws com.vaadin.data.util.converter.Converter.ConversionException { |
| |
| try { |
| // parse amount and uom from the given user input |
| Number amount = parseAmount(valueString); |
| Object uom = parseUom(valueString); |
| |
| // set amount and uom to the new quantity object |
| Object quantity = quantityType.newInstance(); |
| applyAmount(quantity, amount); |
| applyUom(quantity, uom); |
| |
| return quantity; |
| } catch (InstantiationException | IllegalAccessException |
| | InvocationTargetException | ParseException |
| | NoSuchMethodException e) { |
| LOGGER.error("{}", e); |
| return null; |
| } |
| } |
| |
| /** |
| * Sets the uom into the quantity object. |
| * |
| * @param quantity |
| * the quantity |
| * @param uom |
| * the uom |
| * @throws IllegalAccessException |
| * the illegal access exception |
| * @throws InvocationTargetException |
| * the invocation target exception |
| * @throws NoSuchMethodException |
| * the no such method exception |
| */ |
| private void applyUom(Object quantity, Object uom) |
| throws IllegalAccessException, InvocationTargetException, |
| NoSuchMethodException { |
| PropertyUtils.setNestedProperty(quantity, |
| cxConverter.getUomPropertyPath(), uom); |
| } |
| |
| /** |
| * Sets the amount into the quantity object. |
| * |
| * @param quantity |
| * the quantity |
| * @param amount |
| * the amount |
| * @throws IllegalAccessException |
| * the illegal access exception |
| * @throws InvocationTargetException |
| * the invocation target exception |
| * @throws NoSuchMethodException |
| * the no such method exception |
| */ |
| private void applyAmount(Object quantity, Number amount) |
| throws IllegalAccessException, InvocationTargetException, |
| NoSuchMethodException { |
| PropertyUtils.setNestedProperty(quantity, |
| cxConverter.getAmountPropertyPath(), amount); |
| } |
| |
| /** |
| * Parses the amount part from the given value String.<br> |
| * "1200.98 kg" -> "1200.98" |
| * |
| * @param valueString |
| * the value string |
| * @return the number |
| * @throws ParseException |
| * the parse exception |
| */ |
| private Number parseAmount(String valueString) throws ParseException { |
| // TODO DOM - enhance with a better split criteria |
| String[] tokens = valueString.trim().split(" "); |
| String amountString = tokens.length == 2 ? tokens[0] : ""; |
| return new DecimalFormat().parse(amountString); |
| } |
| |
| /** |
| * Parses the uom part from the given value String and creates a UOM Object.<br> |
| * "1200.98 kg" -> "kg" -> Object{kg} |
| * |
| * @param valueString |
| * the value string |
| * @return the object |
| */ |
| private Object parseUom(String valueString) { |
| // TODO DOM - enhance with a better split criteria |
| String[] tokens = valueString.trim().split(" "); |
| String uomCode = tokens.length == 2 ? tokens[1] : ""; |
| return getUOMForCode(uomCode); |
| } |
| |
| /** |
| * Returns the unit of measure for the given uomCode or <code>null</code> if |
| * no uom could be found. |
| * |
| * @param uomCode |
| * the uom code |
| * @return the UOM for code |
| */ |
| private Object getUOMForCode(String uomCode) { |
| // create a filter based on the relative property path that should be |
| // used to show the value |
| Filter filter = new Compare.Equal( |
| cxConverter.getUomCodeRelativePropertyPath(), uomCode); |
| return beanSearchService.getFirstBean( |
| Collections.singletonList(filter), null); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang |
| * .Object, java.lang.Class, java.util.Locale) |
| */ |
| @Override |
| public String convertToPresentation(Object value, |
| Class<? extends String> targetType, Locale locale) |
| throws com.vaadin.data.util.converter.Converter.ConversionException { |
| |
| try { |
| String pattern = NUMBER_FORMAT_PATTERN; |
| pattern = pattern.replace("{$uom}", getUomCode(value)); |
| try { |
| DecimalFormat format = new DecimalFormat(StringEscapeUtils.unescapeHtml(pattern), new DecimalFormatSymbols(locale)); |
| return format.format(getAmount(value)); |
| } catch (IllegalArgumentException e) { |
| String msg = String.format( |
| "formatter %s is invalid for decimal numbers: %s", |
| pattern, e.getLocalizedMessage()); |
| throw new ConversionException(msg); |
| } |
| } catch (IllegalAccessException | InvocationTargetException |
| | NoSuchMethodException e) { |
| LOGGER.error("{}", e); |
| return "Error"; |
| } |
| } |
| |
| /** |
| * Returns the amount value based on the AmountPropertyPath. |
| * |
| * @param value |
| * the value |
| * @return the amount |
| * @throws IllegalAccessException |
| * the illegal access exception |
| * @throws InvocationTargetException |
| * the invocation target exception |
| * @throws NoSuchMethodException |
| * the no such method exception |
| */ |
| private Object getAmount(Object value) throws IllegalAccessException, |
| InvocationTargetException, NoSuchMethodException { |
| return PropertyUtils.getNestedProperty(value, |
| cxConverter.getAmountPropertyPath()); |
| } |
| |
| /** |
| * Returns the amount value based on the UomPropertyPath. |
| * |
| * @param value |
| * the value |
| * @return the uom code |
| * @throws IllegalAccessException |
| * the illegal access exception |
| * @throws InvocationTargetException |
| * the invocation target exception |
| * @throws NoSuchMethodException |
| * the no such method exception |
| */ |
| private String getUomCode(Object value) throws IllegalAccessException, |
| InvocationTargetException, NoSuchMethodException { |
| // concat the uomPath and the uomCodePath to directly access the uom |
| // code. |
| return (String) PropertyUtils.getNestedProperty( |
| value, |
| cxConverter.getUomPropertyPath() + "." |
| + cxConverter.getUomCodeRelativePropertyPath()); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.vaadin.data.util.converter.Converter#getModelType() |
| */ |
| @Override |
| public Class<Object> getModelType() { |
| return Object.class; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see com.vaadin.data.util.converter.Converter#getPresentationType() |
| */ |
| @Override |
| public Class<String> getPresentationType() { |
| return String.class; |
| } |
| |
| } |