/**
* 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.mios.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.mios.MiosBindingProvider;
import org.openhab.binding.mios.internal.config.DeviceBindingConfig;
import org.openhab.binding.mios.internal.config.MiosBindingConfig;
import org.openhab.binding.mios.internal.config.RoomBindingConfig;
import org.openhab.binding.mios.internal.config.SceneBindingConfig;
import org.openhab.binding.mios.internal.config.SystemBindingConfig;
import org.openhab.core.binding.BindingConfig;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemRegistry;
import org.openhab.model.item.binding.AbstractGenericBindingProvider;
import org.openhab.model.item.binding.BindingConfigParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is responsible for parsing the binding configuration.
*
* Each MiOS Binding declaration consists of a comma-separated list of elements, of the form <name>:<value>, that are
* expressed in a specific order.
* <p>
*
* The order is:
* <ul>
* <li>{@code unit:<name>} - the name of the MiOS Unit, declared in the openHAB configuration. The value is a
* case-sensitive MiOS Unit name [alphaNumeric] String.
*
* <li>{@code <type>:<id>} - the type of entity bound at the MiOS Unit. The value is a MiOS-specific identifier Integer.
* Type names include " {@code device}", " {@code scene}", and "{@code system}" and the corresponding {@code id} used
* within the MiOS Unit.
*
* <li>{@code command:<transform>} - a Transformation expression to map openHAB Command data to MiOS UPnP calls.
*
* <li>{@code in:<transform>} - a Transformation expression for inbound data from the MiOS Unit for openHAB.
*
* <li>{@code out:<transform>} - a Transformation expression for outbound data from openHAB for the MiOS Unit.
* </ul>
* <p>
* Example MiOS binding expressions look like the following:
* <ul>
* <li>
* A read-only, binding expression with no mappings applied<br>
* {@code mios="unit:house,device:228/service/AlarmPartition2/DetailedArmMode"}
* <li>
* A read-write, binding expression with input & output value and command transformations<br>
* {@code mios="unit:house,device:6/service/SwitchPower1/Status,command:ON|OFF,in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"}
* </ul>
*
* @author Mark Clark
* @since 1.6.0
*/
public class MiosBindingProviderImpl extends AbstractGenericBindingProvider implements MiosBindingProvider {
// TODO: Fix parsing for system to be tighter. We "opened" the parsing for
// the others to permit no "id" field, but that's not valid
private static final Pattern BINDING_PATTERN = Pattern
.compile("(unit:(?<unit>[a-zA-Z]+[a-zA-Z0-9]*),)" + "(?<inThing>[^,]+)" + "(,command:(?<command>[^,]*))?"
+ "(,in:(?<inTransform>[a-zA-Z]+[a-zA-Z0-9]*\\([^,]*\\)))?"
+ "(,out:(?<outTransform>[a-zA-Z]+[a-zA-Z0-9]*\\([^,]*\\)))?");
private static final Pattern IN_CONFIG_PATTERN = Pattern
.compile("((?<inType>device|scene|system|room):" + "(?<id>[0-9]*))" + "(/(?<inStuff>.+))?");
private static final Logger logger = LoggerFactory.getLogger(MiosBindingProviderImpl.class);
// Injected by the OSGi Container through the setItemRegistry and
// unsetItemRegistry methods.
private ItemRegistry itemRegistry;
/**
* Invoked by the OSGi Framework.
*
* This method is invoked by OSGi during the initialization of the MiOSBinding, so we have subsequent access to the
* ItemRegistry (needed to get values from Items in openHAB)
*/
public void setItemRegistry(ItemRegistry itemRegistry) {
logger.debug("setItemRegistry: called");
this.itemRegistry = itemRegistry;
}
/**
* Invoked by the OSGi Framework.
*
* This method is invoked by OSGi during the initialization of the MiOSBinding, so we have subsequent access to the
* ItemRegistry (needed to get values from Items in openHAB)
*/
public void unsetItemRegistry(ItemRegistry itemRegistry) {
logger.debug("unsetItemRegistry: called");
this.itemRegistry = null;
}
/**
* {@inheritDoc}
*/
@Override
public ItemRegistry getItemRegistry() {
return this.itemRegistry;
}
@Override
public String getBindingType() {
return "mios";
}
/**
* {@inheritDoc}
*/
@Override
public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException {
// Validation is done at the BindingConfig level, just after parsing the
// bindingConfig String inside processBindingConfiguration.
}
/**
* {@inheritDoc}
*/
@Override
public void processBindingConfiguration(String context, Item item, String bindingConfig)
throws BindingConfigParseException {
super.processBindingConfiguration(context, item, bindingConfig);
try {
MiosBindingConfig config = parseBindingConfig(context, item, bindingConfig);
config.validateItemType(item);
logger.debug("processBindingConfiguration: Adding Item '{}' Binding '{}', from '{}'",
new Object[] { item.getName(), config, context });
addBindingConfig(item, config);
} catch (BindingConfigParseException bcpe) {
logger.debug(String.format(
"processBindingConfiguration: Exception parsing/validating context '%s', item'%s', bindingConfig '%s'. Exception is %s.",
context, item, bindingConfig, bcpe.getMessage()));
throw bcpe;
}
}
private MiosBindingConfig parseBindingConfig(String context, Item item, String bindingConfig)
throws BindingConfigParseException {
Matcher matcher;
matcher = BINDING_PATTERN.matcher(bindingConfig);
if (!matcher.matches()) {
throw new BindingConfigParseException(
String.format("Config for item '%s' could not be parsed. Bad general format '%s'", item.getName(),
bindingConfig.toString()));
}
String unitName = matcher.group("unit");
String inThing = matcher.group("inThing");
String commandThing = matcher.group("command");
String inTrans = matcher.group("inTransform");
String outTrans = matcher.group("outTransform");
logger.trace("parseBindingConfig: unit '{}' thing '{}' inTrans '{}' outTrans '{}'",
new Object[] { unitName, inThing, inTrans, outTrans });
// The inbound pattern is mandatory, and enforced by the
// pattern-matcher.
matcher = IN_CONFIG_PATTERN.matcher(inThing);
if (!matcher.matches()) {
throw new BindingConfigParseException(String.format(
"Config for item '%s' could not be parsed. Bad thing format '%s'", item.getName(), bindingConfig));
}
String inType = matcher.group("inType");
String inId = matcher.group("id");
String inStuff = matcher.group("inStuff");
logger.trace("parseBindingConfig: in: (Type '{}' id '{}' Stuff '{}'), command: ('{}')",
new Object[] { inType, inId, inStuff, commandThing });
// FIXME: Inline a factory for now...
if (inType.equals("device")) {
return DeviceBindingConfig.create(context, item.getName(), unitName, Integer.parseInt(inId), inStuff,
item.getClass(), commandThing, inTrans, outTrans);
} else if (inType.equals("scene")) {
return SceneBindingConfig.create(context, item.getName(), unitName, Integer.parseInt(inId), inStuff,
item.getClass(), commandThing, inTrans, outTrans);
} else if (inType.equals("system")) {
return SystemBindingConfig.create(context, item.getName(), unitName, inStuff, item.getClass(), inTrans,
outTrans);
} else if (inType.equals("room")) {
return RoomBindingConfig.create(context, item.getName(), unitName, Integer.parseInt(inId), inStuff,
item.getClass(), inTrans, outTrans);
} else {
throw new BindingConfigParseException(
String.format("Invalid binding type received for Item %s, bad type (%s)", item.getName(), inType));
}
}
public MiosBindingConfig getMiosBindingConfig(String itemName) {
return (MiosBindingConfig) bindingConfigs.get(itemName);
}
/**
* {@inheritDoc}
*/
@Override
public String getMiosUnitName(String itemName) {
MiosBindingConfig config = getMiosBindingConfig(itemName);
return (config == null) ? null : config.getUnitName();
}
/**
* {@inheritDoc}
*/
@Override
public String getProperty(String itemName) {
MiosBindingConfig config = getMiosBindingConfig(itemName);
return (config == null) ? null : config.toProperty();
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getItemNamesForProperty(String property) {
ArrayList<String> result = new ArrayList<String>();
// TODO: Make a reverse map somewhere, and keep it around as this is
// going to get a lot of calls, and a lot of garbage along the way!!
for (Entry<String, BindingConfig> bindingConfigEntry : bindingConfigs.entrySet()) {
if (bindingConfigEntry.getValue() instanceof MiosBindingConfig) {
String p = ((MiosBindingConfig) bindingConfigEntry.getValue()).toProperty();
if (p.equals(property)) {
logger.trace("getItemNamesForProperty: MATCH property '{}' against BindingConfig.toProperty '{}'",
property, p);
String itemName = bindingConfigEntry.getKey();
result.add(itemName);
}
}
}
return result;
}
}