package org.openhab.domain;
import org.openhab.domain.command.WidgetPhraseMatchResult;
import org.openhab.domain.model.GraphicUnit;
import org.openhab.domain.model.OpenHABItem;
import org.openhab.domain.model.OpenHABItemType;
import org.openhab.domain.model.OpenHABWidget;
import org.openhab.domain.model.OpenHABWidgetDataSource;
import org.openhab.domain.model.OpenHABWidgetEvent;
import org.openhab.domain.model.OpenHABWidgetType;
import org.openhab.domain.model.OpenHABWidgetTypeSet;
import org.openhab.domain.model.Room;
import org.openhab.domain.model.SitemapUpdateEvent;
import org.openhab.domain.rule.UnitEntityDataType;
import org.openhab.domain.util.ILogger;
import org.openhab.domain.util.IRegularExpression;
import org.openhab.domain.util.RegExAccuracyResult;
import org.openhab.domain.util.StringHandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Created by Tony Alpskog in 2014.
*/
@Singleton
public class OpenHABWidgetProvider implements IOpenHABWidgetProvider {
private static final String TAG = "OpenHABWidgetProvider";
private final IRegularExpression mRegularExpression;
private final ILogger mLogger;
private final IEventBus mEventBus;
private Map<String, OpenHABWidget> mOpenHABWidgetIdMap;
private Map<String, OpenHABWidget> mOpenHABItemNameMap;
private Map<OpenHABWidgetType, List<String>> mOpenHABWidgetTypeMap;
private Map<OpenHABItemType, List<String>> mOpenHABItemTypeMap;
private UUID mUpdateSetUUID;
private IPopularNameProvider mPopularNameProvider;
private Map<String, List<UnitEntityDataType>> mOpenHABItemListenerMap;
private List<UnitEntityDataType> mRecalculationListenerList;
@Inject
public OpenHABWidgetProvider(IRegularExpression regularExpression,
ILogger logger,
IPopularNameProvider popularNameProvider,
IEventBus eventBus) {
if(regularExpression == null) throw new IllegalArgumentException("regularExpression is null");
if(logger == null) throw new IllegalArgumentException("logger is null");
if(eventBus == null) throw new IllegalArgumentException("eventBus is null");
mEventBus = eventBus;
mRegularExpression = regularExpression;
mLogger = logger;
mPopularNameProvider = popularNameProvider;
mOpenHABWidgetTypeMap = new HashMap<OpenHABWidgetType, List<String>>();
mOpenHABItemTypeMap = new HashMap<OpenHABItemType, List<String>>();
mOpenHABWidgetIdMap = new HashMap<String, OpenHABWidget>();
mOpenHABItemNameMap = new HashMap<String, OpenHABWidget>();
mOpenHABItemListenerMap = new HashMap<String, List<UnitEntityDataType>>();
mRecalculationListenerList = new ArrayList<UnitEntityDataType>();
}
//Long polling method..?
public void requestWidgetUpdate() {
//TODO - Implement this method.
}
public Map<OpenHABWidgetType, List<OpenHABWidget>> getOpenHABWidgets() {
Map<OpenHABWidgetType, List<OpenHABWidget>> resultingMap = new HashMap<OpenHABWidgetType, List<OpenHABWidget>>();
for (OpenHABWidgetType type : mOpenHABWidgetTypeMap.keySet()) {
resultingMap.put(type, getWidgetList(type));
}
return resultingMap;
}
public void setOpenHABWidgets(Map<OpenHABWidgetType, List<OpenHABWidget>> openHABWidgets) {
clearData();
for (OpenHABWidgetType type : openHABWidgets.keySet()) {
List<OpenHABWidget> widgetList = openHABWidgets.get(type);
List<String> stringList = new ArrayList<String>(widgetList.size());
for (OpenHABWidget widget : widgetList) {
mOpenHABWidgetIdMap.put(widget.getId(), widget);
if (widget.hasItem())
mOpenHABItemNameMap.put(widget.getItem().getName(), widget);
stringList.add(widget.getItem().getName());
}
mOpenHABWidgetTypeMap.put(type, stringList);
//TODO - TA: implement population of mOpenHABItemTypeMap.put(...)
}
}
public void setOpenHABItem(OpenHABItem item) {
mOpenHABItemNameMap.get(item.getName()).setItem(item);
}
@Override
public void setOpenHABWidgets(OpenHABWidgetDataSource openHABWidgetDataSource) {
if(openHABWidgetDataSource.getRootWidget() == null)
return;
clearData();
addOpenHABWidget(openHABWidgetDataSource.getRootWidget(), true);
}
private void clearData() {
// mOpenHABWidgetIdMap.clear();
// mOpenHABWidgetTypeMap.clear();
//
mUpdateSetUUID = UUID.randomUUID();
}
@Override
public UUID getUpdateUUID() {
return mUpdateSetUUID;
}
private void addOpenHABWidget(OpenHABWidget widget, boolean isStartOfBatch) {
if(widget == null)
return;
if(widget.getType() == null)
mLogger.d(TAG, String.format("[SITEMAP] typeless widget found: label = '%s' link = '%s'", widget.getLabel(), widget.getLinkedPage()));
if(widget.getId() != null && widget.getId().equalsIgnoreCase("0001_3"))
mLogger.d(TAG, String.format("[SITEMAP] widget '%s' was found. Label = '%s'", widget.getId(), widget.getLabel()));
widget.setUpdateUUID(mUpdateSetUUID);
boolean widgetExists = mOpenHABWidgetIdMap.containsKey(widget.getId());
if(widgetExists && widget.hasItem()) {
mEventBus.postSticky(new OpenHABWidgetEvent(widget));
updateListeners(widget.getItem());
}
if(!widgetExists && widget.getType() != null) {
//Overwrite / Update widget if it already exists.
mOpenHABWidgetIdMap.put(widget.getId(), widget);
if(widget.hasItem())
mOpenHABItemNameMap.put(widget.getItem().getName(), widget);
if(widget.getType() == OpenHABWidgetType.Group || widget.getType() == OpenHABWidgetType.SitemapText) {
String widgetName = (widget.hasLinkedPage()? widget.getLinkedPage().getTitle() : widget.getId());
mLogger.d(TAG, String.format("Setting data for group widget '%s' of type '%s'", widgetName, widget.getType().name()));
}
mLogger.d(TAG, String.format("Setting data for widget '%s' of type '%s'", widget.getId(), widget.getType()));
if(!widgetExists) { //Don't add existing widgets to the type<->name_list mapping.
//Add widget to widget type mapping
if(widget.getType() != null && !mOpenHABWidgetTypeMap.containsKey(widget.getType()))
mOpenHABWidgetTypeMap.put(widget.getType(), new ArrayList<String>());
List<String> widgetList = mOpenHABWidgetTypeMap.get(widget.getType());
widgetList.add(widget.getId());
if(widget.hasItem()) {
//Add item to widget type mapping
if (widget.getItem().getType() != null && !mOpenHABItemTypeMap.containsKey(widget.getItem().getType()))
mOpenHABItemTypeMap.put(widget.getItem().getType(), new ArrayList<String>());
List<String> itemList = mOpenHABItemTypeMap.get(widget.getItem().getType());
if(itemList == null)
mLogger.e("OpenHABWidgetProvider", String.format("Cannot find openHABWidget item type. Item name = '%s'. Widget ID = %s. Item was not added in mOpenHABItemTypeMap.", widget.getItem().getName(), widget.getId()));
else
itemList.add(widget.getItemName());
}
}
}
if(widget.hasChildren()) {
for (OpenHABWidget widget1 : widget.getChildren()) {
addOpenHABWidget(widget1, false);//TODO - Don't add as parent if parent type = null
}
}
if(isStartOfBatch) {
mEventBus.post(new SitemapUpdateEvent(true));
recalculateUnits();
}
}
private void recalculateUnits() {
for(UnitEntityDataType unit : mRecalculationListenerList)
unit.resumeOnValueChangedEvent();
}
public Map<OpenHABWidgetType, List<OpenHABWidget>> getWidgetMap(Set<OpenHABWidgetType> category) {
Map<OpenHABWidgetType, List<OpenHABWidget>> resultMap = new HashMap<OpenHABWidgetType, List<OpenHABWidget>>();
for (OpenHABWidgetType key : category) {
resultMap.put(key, getWidgetList(key));
}
return resultMap;
}
@Override
public List<OpenHABWidget> getWidgetList(Set<OpenHABWidgetType> category) {
ArrayList<OpenHABWidget> resultList = new ArrayList<OpenHABWidget>();
if(category == null) {
resultList.addAll(mOpenHABWidgetIdMap.values());
return resultList;
}
for (OpenHABWidgetType aCategory : category) {
resultList.addAll(getWidgetList(aCategory));
}
return resultList;
}
@Override
public List<OpenHABWidget> getWidgetList(OpenHABWidgetType type) {
List<String> idList;
List<OpenHABWidget> resultList = new ArrayList<OpenHABWidget>();
if(mOpenHABWidgetTypeMap.containsKey(type)) {
idList = mOpenHABWidgetTypeMap.get(type);
for (String anIdList : idList) {
resultList.add(mOpenHABWidgetIdMap.get(anIdList));
}
}
return resultList;
}
@Override
public List<String> getItemNamesByType(OpenHABItemType type) {
return mOpenHABItemTypeMap.get(type);
}
private static final double APPROVED_UNIT_ACCURACY_VALUE = 0.75;
private static final double DENIED_UNIT_ACCURACY_VALUE = 0.4;
private static final double APPROVED_PARENT_ACCURACY_VALUE = 0.6;
private static final double COMBINED_ACCURACY_FACTOR = 1.6;
@Override
public List<WidgetPhraseMatchResult> getWidgetByLabel(String searchLabel) {
String[] splittedSearchLabel = searchLabel.split(" ");
List<String> sourceWordsList = new ArrayList<String>();
for(String sourceWord : splittedSearchLabel)
sourceWordsList.add(sourceWord.toUpperCase());
List<WidgetPhraseMatchResult> resultList = new ArrayList<WidgetPhraseMatchResult>();
for(OpenHABWidget widget : mOpenHABWidgetIdMap.values().toArray(new OpenHABWidget[0])) {
RegExAccuracyResult regExResult = mRegularExpression.getStringMatchAccuracy(sourceWordsList, mPopularNameProvider.getPopularNameFromWidgetLabel(widget.getLabel()));
double accuracy = regExResult.getAccuracy();
if(accuracy < APPROVED_UNIT_ACCURACY_VALUE && accuracy > DENIED_UNIT_ACCURACY_VALUE) {
List<String> sitemapGroupWordList = StringHandler.getStringListDiff(sourceWordsList, regExResult.getMatchingWords());
double parentAccuracy = getHighestWidgetParentMatch(widget, sitemapGroupWordList);
if(parentAccuracy > APPROVED_PARENT_ACCURACY_VALUE) {
Double combinedAccuracyPercent = Double.valueOf(((accuracy + parentAccuracy) / COMBINED_ACCURACY_FACTOR) * 100);
if(combinedAccuracyPercent > 100)
combinedAccuracyPercent = 100d;
resultList = addWidgetPhraseMatchResultItemToSortedList(resultList, new WidgetPhraseMatchResult(combinedAccuracyPercent.intValue(), widget));
}
} else if(accuracy >= APPROVED_UNIT_ACCURACY_VALUE) resultList = addWidgetPhraseMatchResultItemToSortedList(resultList, new WidgetPhraseMatchResult(Double.valueOf(accuracy * 100).intValue(), widget));
}
return resultList;
}
private List<WidgetPhraseMatchResult> addWidgetPhraseMatchResultItemToSortedList(List<WidgetPhraseMatchResult> list, WidgetPhraseMatchResult itemToAdd) {
if(list.size() == 0)
list.add(itemToAdd);
else {
boolean isAdded = false;
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getMatchPercent() < itemToAdd.getMatchPercent()) {
list.add(i, itemToAdd);
isAdded = true;
break;
}
}
if(!isAdded)
list.add(list.size(), itemToAdd);
}
return list;
}
public double getHighestWidgetParentMatch(OpenHABWidget unit, List<String> sourceWordsList) {
double maxResult = 0;
while(unit.hasParent()) {
unit = unit.getParent();
if(!unit.hasLinkedPage())
continue;
String linkTitle = unit.getLinkedPage().getTitle();
double result = mRegularExpression.getStringMatchAccuracy(sourceWordsList, linkTitle).getAccuracy();
if (result > maxResult) {
maxResult = result;
}
}
return maxResult;
}
@Override
public OpenHABWidget getWidgetByID(String widgetID) {
OpenHABWidget widget = mOpenHABWidgetIdMap.get(widgetID);
if(widget == null)
mLogger.w(TAG, String.format("Widget ID '%s' doesn't exist i current widget mapping", widgetID));
return widget;
}
public boolean hasWidgetID(String widgetId) {
if(widgetId == null)
return false;
boolean result = mOpenHABWidgetIdMap.containsKey(widgetId);
if(!result)
mLogger.w(TAG, String.format("Widget ID '%s' doesn't exist i current widget mapping", widgetId));
return result;
}
@Override
public OpenHABWidget getWidgetByItemName(String openHabItemName) {
OpenHABWidget widget = mOpenHABItemNameMap.get(openHabItemName);
if(widget == null)
mLogger.w(TAG, String.format("Item name '%s' doesn't exist i current widget mapping", openHabItemName));
return widget;
}
public boolean hasItemName(String openHabItemName) {
if(openHabItemName == null)
return false;
boolean result = mOpenHABItemNameMap.containsKey(openHabItemName);
if(!result)
mLogger.w(TAG, String.format("Item name '%s' doesn't exist i current widget mapping", openHabItemName));
return result;
}
public List<String> getItemNameList() {
List<String> list = new ArrayList<String>();
list.addAll(mOpenHABItemNameMap.keySet());
return list;
}
@Override
public List<String> getItemNameListByWidgetType(Set<OpenHABWidgetType> widgetTypes) {
List<String> itemNameList = new ArrayList<String>();
List<OpenHABWidget> widgetList = getWidgetList(widgetTypes);
for(OpenHABWidget widget : widgetList) {
if(widget.hasItem())
itemNameList.add(widget.getItemName());
}
return itemNameList;
}
@Override
public List<OpenHABWidget> getListOfWidgetsFromListOfRooms(List<Room> listOfRooms) {
//Get widgets from room list
if(listOfRooms != null && !listOfRooms.isEmpty()){
final List<OpenHABWidget> widgetList = new ArrayList<OpenHABWidget>();
for (Room nextRoom : listOfRooms) {
for(GraphicUnit gu : nextRoom.getUnits())
widgetList.add(gu.getOpenHABWidget());
}
return widgetList;
}
//Get all unit widgets
return getWidgetList(OpenHABWidgetTypeSet.UnitItem);
}
@Override
public void addItemListener(UnitEntityDataType listener) {
if(!mOpenHABItemListenerMap.containsKey(listener.getDataSourceId()))
mOpenHABItemListenerMap.put(listener.getDataSourceId(), new ArrayList<UnitEntityDataType>());
if(!mOpenHABItemListenerMap.get(listener.getDataSourceId()).contains(listener))
mOpenHABItemListenerMap.get(listener.getDataSourceId()).add(listener);
}
@Override
public void removeItemListener(UnitEntityDataType listener) {
if(!mOpenHABItemListenerMap.containsKey(listener.getDataSourceId()))
return;
List<UnitEntityDataType> listenerList = mOpenHABItemListenerMap.get(listener.getDataSourceId());
if(listenerList.contains(listener))
listenerList.remove(listener);
}
private void updateListeners(OpenHABItem item) {
if(mOpenHABItemListenerMap.containsKey(item.getName()))
for(UnitEntityDataType listener : mOpenHABItemListenerMap.get(item.getName()))
if(!listener.valueOf(item.getState()).equals(listener.getFormattedString()/*getValue()*/)) {
listener.setValue(listener.valueOf(item.getState()), false);
mRecalculationListenerList.add(listener);
}
}
}