/**
* 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.ebus.internal.parser;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.openhab.binding.ebus.internal.configuration.TelegramConfiguration;
import org.openhab.binding.ebus.internal.configuration.TelegramValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The configuration provider reads the vendors specific ebus protocol
* information from the json configuration files. All placeholders (regex)
* and javascript snippets will be compiled after loading to improve
* runtime performance.
*
* @author Christian Sowada
* @since 1.7.0
*/
public class EBusConfigurationProvider {
private static final Logger logger = LoggerFactory.getLogger(EBusConfigurationProvider.class);
// filter: ??
private static Pattern P_PLACEHOLDER = Pattern.compile("\\?\\?");
// filter: (00)
private static Pattern P_BRACKETS_VALS = Pattern.compile("(\\([0-9A-Z]{2}\\))");
// filter: (|)
private static Pattern P_BRACKETS_CLEAN = Pattern.compile("(\\(|\\))");
// The registry with all loaded configuration entries
private ArrayList<TelegramConfiguration> telegramRegistry = new ArrayList<TelegramConfiguration>();
private Map<String, String> loadedFilters = new HashMap<String, String>();
// The script engine if available
private Compilable compEngine;
/**
* Return if the provider is empty.
*
* @return
*/
public boolean isEmpty() {
return telegramRegistry.isEmpty();
}
/**
* Constructor
*/
public EBusConfigurationProvider() {
final ScriptEngineManager mgr = new ScriptEngineManager();
// load script engine if available
if (mgr != null) {
final ScriptEngine engine = mgr.getEngineByName("JavaScript");
if (engine == null) {
logger.warn("Unable to load \"JavaScript\" engine! Skip every eBus value calculated by JavaScript.");
} else if (engine instanceof Compilable) {
compEngine = (Compilable) engine;
}
}
}
/**
* Clears all loaded configurations
*/
public void clear() {
if (telegramRegistry != null) {
telegramRegistry.clear();
}
}
/**
* Loads a JSON configuration file by url
*
* @param url The url to a configuration file
* @throws IOException Unable to read configuration file
* @throws ParseException A invalid json file
*/
public void loadConfigurationFile(URL url) throws IOException {
final ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
final InputStream inputStream = url.openConnection().getInputStream();
final List<TelegramConfiguration> loadedTelegramRegistry = mapper.readValue(inputStream,
new TypeReference<List<TelegramConfiguration>>() {
});
for (Iterator<TelegramConfiguration> iterator = loadedTelegramRegistry.iterator(); iterator.hasNext();) {
TelegramConfiguration object = iterator.next();
transformDataTypes(object);
// check if this filter pattern is already loaded
String filter = object.getFilterPattern().toString();
String fileComment = StringUtils.substringAfterLast(url.getFile(), "/") + " >>> " + object.getComment();
if (loadedFilters.containsKey(filter)) {
logger.info("Identical filter already loaded ... {} AND {}", loadedFilters.get(filter), fileComment);
} else {
loadedFilters.put(filter, fileComment);
}
}
if (loadedTelegramRegistry != null && !loadedTelegramRegistry.isEmpty()) {
telegramRegistry.addAll(loadedTelegramRegistry);
}
}
/**
* @param configurationEntry
*/
protected void transformDataTypes(TelegramConfiguration configurationEntry) {
// Use filter property if set
if (StringUtils.isNotEmpty(configurationEntry.getFilter())) {
String filter = configurationEntry.getFilter();
filter = P_PLACEHOLDER.matcher(filter).replaceAll("[0-9A-Z]{2}");
logger.trace("Compile RegEx filter: {}", filter);
configurationEntry.setFilterPattern(Pattern.compile(filter));
} else {
// Build filter string
// Always ignore first two hex bytes
String filter = "[0-9A-Z]{2} [0-9A-Z]{2}";
// Add command to filter string
if (StringUtils.isNotEmpty(configurationEntry.getCommand())) {
filter += " " + configurationEntry.getCommand();
filter += " [0-9A-Z]{2}";
}
// Add data to filter string
if (StringUtils.isNotEmpty(configurationEntry.getData())) {
Matcher matcher = P_BRACKETS_VALS.matcher(configurationEntry.getData());
filter += " " + matcher.replaceAll("[0-9A-Z]{2}");
}
// Finally add .* to end with everything
filter += " .*";
logger.trace("Compile RegEx filter: {}", filter);
configurationEntry.setFilterPattern(Pattern.compile(filter));
}
// remove brackets if used
if (StringUtils.isNotEmpty(configurationEntry.getData())) {
Matcher matcher = P_BRACKETS_CLEAN.matcher(configurationEntry.getData());
configurationEntry.setData(matcher.replaceAll(""));
}
// compile scipt's if available also once
if (configurationEntry.getValues() != null && !configurationEntry.getValues().isEmpty()) {
Map<String, TelegramValue> values = configurationEntry.getValues();
for (Entry<String, TelegramValue> entry : values.entrySet()) {
if (StringUtils.isNotEmpty(entry.getValue().getScript())) {
String script = entry.getValue().getScript();
// check if engine is available
if (StringUtils.isNotEmpty(script) && compEngine != null) {
try {
CompiledScript compile = compEngine.compile(script);
entry.getValue().setCsript(compile);
} catch (ScriptException e) {
logger.error("Error while compiling JavaScript!", e);
}
}
}
}
}
// compile scipt's if available
if (configurationEntry.getComputedValues() != null && !configurationEntry.getComputedValues().isEmpty()) {
Map<String, TelegramValue> cvalues = configurationEntry.getComputedValues();
for (Entry<String, TelegramValue> entry : cvalues.entrySet()) {
if (StringUtils.isNotEmpty(entry.getValue().getScript())) {
String script = entry.getValue().getScript();
// check if engine is available
if (StringUtils.isNotEmpty(script) && compEngine != null) {
try {
CompiledScript compile = compEngine.compile(script);
entry.getValue().setCsript(compile);
} catch (ScriptException e) {
logger.error("Error while compiling JavaScript!", e);
}
}
}
}
}
}
/**
* Return all configuration which filter match the bufferString paramter
*
* @param bufferString The byte string to check against all loaded filters
* @return All configurations with matching filter
*/
public List<TelegramConfiguration> getCommandsByFilter(String bufferString) {
final List<TelegramConfiguration> matchedTelegramRegistry = new ArrayList<TelegramConfiguration>();
/** select matching telegram registry entries */
for (TelegramConfiguration registryEntry : telegramRegistry) {
Pattern pattern = registryEntry.getFilterPattern();
Matcher matcher = pattern.matcher(bufferString);
if (matcher.matches()) {
matchedTelegramRegistry.add(registryEntry);
}
}
return matchedTelegramRegistry;
}
/**
* Return all configurations by command id and class
*
* @param commandId The command id
* @return All matching configurations
*/
public TelegramConfiguration getCommandById(String commandId) {
String[] idElements = StringUtils.split(commandId, ".");
String commandClass = null;
commandId = null;
if (idElements.length > 1) {
commandClass = idElements[0];
commandId = idElements[1];
}
for (TelegramConfiguration entry : telegramRegistry) {
if (StringUtils.equals(entry.getId(), commandId) && StringUtils.equals(entry.getClazz(), commandClass)) {
return entry;
}
}
return null;
}
}