blob: 8019c8aff3663828a120a99e2248b462a84b3714 [file] [log] [blame]
/**
*
* 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christophe Loetz (Loetz GmbH&Co.KG) - initial implementation
*
*/
package org.eclipse.osbp.ecview.extension.presentation.converter;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Locale;
import org.apache.commons.beanutils.PropertyUtils;
import org.eclipse.osbp.ecview.core.common.context.IViewContext;
import org.eclipse.osbp.runtime.common.types.ITypeProviderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vaadin.data.util.converter.Converter;
import org.eclipse.osbp.ecview.extension.api.IUomService;
import org.eclipse.osbp.ecview.extension.model.converter.CxQuantityToStringConverter;
/**
* 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 CxQuantityToStringConverter contains two "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>
* Example:
*
* <pre>
* class Quantity {
* double value;
* Uom uom;
* }
*
* class Uom {
* String iso3Code;
* String name;
* ...
* }
* </pre>
*
* The Quantity bean is the model value. To access the values following
* accessPath in the CxQuantityToStringConverter needs to be used:<br>
* -> for value: <code>value</code><br>
* -> for currency: <code>uom.iso3Code</code><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 CxQuantityToStringConverter object. See
* {@link CxQuantityToStringConverter#getTypeQualifiedName()}. 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 IUomService}.
*
* <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 UOM-Service is
* available. See {@link IUomService}.
*/
@SuppressWarnings("serial")
public class QuantityToStringConverter implements Converter<String, Object> {
private static final Logger LOGGER = LoggerFactory
.getLogger(QuantityToStringConverter.class);
private static final String NUMBER_FORMAT_PATTERN = "#,###,###,##0.00 {$uom}";
private final CxQuantityToStringConverter cxConverter;
private final IUomService uomService;
private final Class<?> quantityType;
public QuantityToStringConverter(IViewContext context,
CxQuantityToStringConverter cxConverter) {
super();
this.cxConverter = cxConverter;
ITypeProviderService typeService = context
.getService(ITypeProviderService.class.getName());
quantityType = typeService.forName(null,
cxConverter.getTypeQualifiedName());
uomService = context.getService(IUomService.class.getName());
}
@Override
public Object convertToModel(String valueString,
Class<? extends Object> targetType, Locale locale)
throws com.vaadin.data.util.converter.Converter.ConversionException {
try {
Object quantity = quantityType.newInstance();
Number amount = parseAmount(valueString);
Object uom = parseUom(valueString);
applyAmount(quantity, amount);
applyUom(quantity, uom);
return quantity;
} catch (InstantiationException | IllegalAccessException
| InvocationTargetException | ParseException
| NoSuchMethodException e) {
LOGGER.error("{}", e);
return null;
}
}
private void applyUom(Object quantity, Object uom)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
PropertyUtils.setNestedProperty(quantity,
cxConverter.getUomPropertyPath(), uom);
}
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
* @return
* @throws ParseException
*/
private Number parseAmount(String valueString) throws ParseException {
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
* @return
*/
private Object parseUom(String valueString) {
String[] tokens = valueString.trim().split(" ");
String uomCode = tokens.length == 2 ? tokens[1] : "";
return uomService.getUOMForCode(uomCode);
}
@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));
DecimalFormat format = new DecimalFormat(pattern);
return format.format(getAmount(value));
} catch (IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
LOGGER.error("{}", e);
return "Error";
}
}
/**
* Returns the amount value based on the AmountPropertyPath.
*
* @param value
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
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
* @return
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
private String getUomCode(Object value) throws IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
return (String) PropertyUtils.getNestedProperty(value,
cxConverter.getUomPropertyPath());
}
@Override
public Class<Object> getModelType() {
return Object.class;
}
@Override
public Class<String> getPresentationType() {
return String.class;
}
}