/** * Copyright (c) 2010-2016 by the respective copyright holders. * * 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 */ package org.openhab.binding.weather.internal.bus; import java.math.RoundingMode; import org.apache.commons.lang.StringUtils; import org.openhab.binding.weather.internal.common.Unit; import org.openhab.binding.weather.internal.common.binding.ForecastBindingConfig; import org.openhab.binding.weather.internal.common.binding.WeatherBindingConfig; import org.openhab.binding.weather.internal.model.Weather; import org.openhab.binding.weather.internal.utils.PropertyUtils; import org.openhab.core.items.Item; import org.openhab.core.library.types.DecimalType; import org.openhab.model.item.binding.BindingConfigParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class to parse the key - value base config for an Weather item. * <p> * Example: * </p> * * <pre> * Number Temperature "Temperature [%.2f °C]" {weather="locationId=home, type=temperature, property=current"} * Number Temperature_F "Temperature [%.2f °F]" {weather="locationId=home, type=temperature, property=current, unit=fahrenheit"} * Number Humidity "Humidity [%.0f %]" {weather="locationId=home, type=atmosphere, property=humidity"} * Number Rain "Rain [%.2f mm]" {weather="locationId=home, type=precipitation, property=rain"} * * Number Temperature "Temperature [%.0f °C]" {weather="locationId=home, type=precipitation, property=rain, roundingMode=ceiling, scale=0"} * * </pre> * * @author Gerhard Riegler * @since 1.6.0 */ public class BindingConfigParser { private static final Logger logger = LoggerFactory.getLogger(BindingConfigParser.class); /** * Parses the bindingConfig of an item and returns a WeatherBindingConfig. */ public WeatherBindingConfig parse(Item item, String bindingConfig) throws BindingConfigParseException { bindingConfig = StringUtils.trimToEmpty(bindingConfig); bindingConfig = StringUtils.removeStart(bindingConfig, "{"); bindingConfig = StringUtils.removeEnd(bindingConfig, "}"); String[] entries = bindingConfig.split("[,]"); WeatherBindingConfigHelper helper = new WeatherBindingConfigHelper(); for (String entry : entries) { String[] entryParts = StringUtils.trimToEmpty(entry).split("[=]"); if (entryParts.length != 2) { throw new BindingConfigParseException("A bindingConfig must have a key and a value"); } String key = StringUtils.trim(entryParts[0]); String value = StringUtils.trim(entryParts[1]); value = StringUtils.removeStart(value, "\""); value = StringUtils.removeEnd(value, "\""); try { helper.getClass().getDeclaredField(key).set(helper, value); } catch (Exception e) { throw new BindingConfigParseException("Could not set value " + value + " for attribute " + key); } } if (!helper.isValid()) { throw new BindingConfigParseException("Invalid binding: " + bindingConfig); } helper.type = StringUtils.replace(helper.type, "athmosphere", "atmosphere"); WeatherBindingConfig weatherConfig = null; if (helper.isForecast()) { Integer forecast = parseInteger(helper.forecast, bindingConfig); if (forecast < 0) { throw new BindingConfigParseException("Invalid binding, forecast must be >= 0: " + bindingConfig); } weatherConfig = new ForecastBindingConfig(helper.locationId, forecast, helper.type, helper.property); } else { weatherConfig = new WeatherBindingConfig(helper.locationId, helper.type, helper.property); } Weather validationInstance = new Weather(null); String property = weatherConfig.getWeatherProperty(); if (!Weather.isVirtualProperty(property) && !PropertyUtils.hasProperty(validationInstance, property)) { throw new BindingConfigParseException("Invalid binding, unknown type or property: " + bindingConfig); } boolean isDecimalTypeItem = item.getAcceptedDataTypes().contains(DecimalType.class); if (isDecimalTypeItem || Weather.isVirtualProperty(property)) { RoundingMode roundingMode = RoundingMode.HALF_UP; if (helper.roundingMode != null) { try { roundingMode = RoundingMode.valueOf(StringUtils.upperCase(helper.roundingMode)); } catch (IllegalArgumentException ex) { throw new BindingConfigParseException("Invalid binding, unknown roundingMode: " + bindingConfig); } } Integer scale = 2; if (helper.scale != null) { scale = parseInteger(helper.scale, bindingConfig); if (scale < 0) { throw new BindingConfigParseException("Invalid binding, scale must be >= 0: " + bindingConfig); } } weatherConfig.setScale(roundingMode, scale); } weatherConfig.setUnit(Unit.parse(helper.unit)); if (StringUtils.isNotBlank(helper.unit) && weatherConfig.getUnit() == null) { throw new BindingConfigParseException("Invalid binding, unknown unit: " + bindingConfig); } try { if (!Weather.isVirtualProperty(property) && weatherConfig.hasUnit()) { String doubleTypeName = Double.class.getName(); String propertyTypeName = PropertyUtils.getPropertyTypeName(validationInstance, property); if (!StringUtils.equals(doubleTypeName, propertyTypeName)) { throw new BindingConfigParseException( "Invalid binding, unit specified but property is not a double type: " + bindingConfig); } } } catch (IllegalAccessException ex) { logger.error(ex.getMessage(), ex); throw new BindingConfigParseException(ex.getMessage()); } return weatherConfig; } /** * Parse a string to a integer value. */ private Integer parseInteger(String valueString, String bindingConfig) throws BindingConfigParseException { try { return Integer.parseInt(valueString); } catch (Exception ex) { throw new BindingConfigParseException( "Invalid binding, value " + valueString + " is not a number: " + bindingConfig); } } /** * Helper class for parsing the bindingConfig. */ private class WeatherBindingConfigHelper { public String locationId; public String type; public String property; public String forecast; public String roundingMode; public String scale; public String unit; protected boolean isValid() { return StringUtils.isNotBlank(locationId) && StringUtils.isNotBlank(type) && StringUtils.isNotBlank(property); } protected boolean isForecast() { return StringUtils.isNotBlank(forecast); } } }