Skip to content

Commit

Permalink
Fix UI defined sitemaps (#3850)
Browse files Browse the repository at this point in the history
* fix UI defined sitemaps
* allow AND conditions
* label not required

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
  • Loading branch information
mherwege authored Oct 26, 2023
1 parent f2c20af commit d75d6df
Showing 1 changed file with 97 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
package org.openhab.core.ui.internal.components;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
Expand Down Expand Up @@ -82,6 +84,7 @@
* @author Laurent Garnier - icon color support for all widgets
* @author Laurent Garnier - Added support for new element Buttongrid
* @author Laurent Garnier - Added icon field for mappings
* @author Mark Herwege - Make UI provided sitemaps compatible with enhanced syntax in conditions
*/
@NonNullByDefault
@Component(service = SitemapProvider.class)
Expand All @@ -93,11 +96,8 @@ public class UIComponentSitemapProvider implements SitemapProvider, RegistryChan
private static final String SITEMAP_PREFIX = "uicomponents_";
private static final String SITEMAP_SUFFIX = ".sitemap";

private static final Pattern VISIBILITY_PATTERN = Pattern
.compile("(?<item>[A-Za-z]\\w*)\\s*(?<condition>==|!=|<=|>=|<|>)\\s*(?<sign>\\+|-)?(?<state>.+)");
private static final Pattern COLOR_PATTERN = Pattern.compile(
"((?<item>[A-Za-z]\\w*)?\\s*((?<condition>==|!=|<=|>=|<|>)\\s*(?<sign>\\+|-)?(?<state>[^=]*[^= ]+))?\\s*=)?\\s*(?<arg>\\S+)");
private static final Pattern ICON_PATTERN = COLOR_PATTERN;
private static final Pattern CONDITION_PATTERN = Pattern
.compile("(?<item>[A-Za-z]\\w*)?\\s*(?<condition>==|!=|<=|>=|<|>)?\\s*(?<sign>\\+|-)?(?<state>.+)");

private Map<String, Sitemap> sitemaps = new HashMap<>();
private @Nullable UIComponentRegistryFactory componentRegistryFactory;
Expand Down Expand Up @@ -160,7 +160,10 @@ protected Sitemap buildSitemap(RootUIComponent rootComponent) {

SitemapImpl sitemap = (SitemapImpl) SitemapFactory.eINSTANCE.createSitemap();
sitemap.setName(SITEMAP_PREFIX + rootComponent.getUID());
sitemap.setLabel(rootComponent.getConfig().get("label").toString());
Object label = rootComponent.getConfig().get("label");
if (label != null) {
sitemap.setLabel(label.toString());
}

if (rootComponent.getSlots() != null && rootComponent.getSlots().containsKey("widgets")) {
for (UIComponent component : rootComponent.getSlot("widgets")) {
Expand Down Expand Up @@ -280,8 +283,7 @@ protected Sitemap buildSitemap(RootUIComponent rootComponent) {

if (widget != null) {
setWidgetPropertyFromComponentConfig(widget, component, "label", SitemapPackage.WIDGET__LABEL);
setWidgetPropertyFromComponentConfig(widget, component, "icon", SitemapPackage.WIDGET__ICON);
setWidgetPropertyFromComponentConfig(widget, component, "staticIcon", SitemapPackage.WIDGET__STATIC_ICON);
setWidgetIconPropertyFromComponentConfig(widget, component);
setWidgetPropertyFromComponentConfig(widget, component, "item", SitemapPackage.WIDGET__ITEM);

if (widget instanceof LinkableWidget linkableWidget) {
Expand Down Expand Up @@ -331,10 +333,28 @@ private void setWidgetPropertyFromComponentConfig(Widget widget, @Nullable UICom
}
}

private void setWidgetIconPropertyFromComponentConfig(Widget widget, @Nullable UIComponent component) {
if (component == null || component.getConfig() == null) {
return;
}
Object staticIcon = component.getConfig().get("staticIcon");
if (staticIcon != null && Boolean.valueOf(ConfigUtil.normalizeType(staticIcon).toString())) {
setWidgetPropertyFromComponentConfig(widget, component, "icon", SitemapPackage.WIDGET__STATIC_ICON);
return;
}

Object icon = component.getConfig().get("icon");
if (icon == null) {
return;
}
setWidgetPropertyFromComponentConfig(widget, component, "icon", SitemapPackage.WIDGET__ICON);
}

private void addWidgetMappings(EList<Mapping> mappings, UIComponent component) {
if (component.getConfig() != null && component.getConfig().containsKey("mappings")) {
if (component.getConfig().get("mappings") instanceof Collection<?>) {
for (Object sourceMapping : (Collection<?>) component.getConfig().get("mappings")) {
Object sourceMappings = component.getConfig().get("mappings");
if (sourceMappings instanceof Collection<?>) {
for (Object sourceMapping : (Collection<?>) sourceMappings) {
if (sourceMapping instanceof String) {
String[] splitMapping = sourceMapping.toString().split("=");
String cmd = splitMapping[0].trim();
Expand All @@ -353,8 +373,9 @@ private void addWidgetMappings(EList<Mapping> mappings, UIComponent component) {

private void addWidgetButtons(EList<Button> buttons, UIComponent component) {
if (component.getConfig() != null && component.getConfig().containsKey("buttons")) {
if (component.getConfig().get("buttons") instanceof Collection<?>) {
for (Object sourceButton : (Collection<?>) component.getConfig().get("buttons")) {
Object sourceButtons = component.getConfig().get("buttons");
if (sourceButtons instanceof Collection<?>) {
for (Object sourceButton : (Collection<?>) sourceButtons) {
if (sourceButton instanceof String) {
String[] splitted1 = sourceButton.toString().split(":");
int idx = Integer.parseInt(splitted1[0].trim());
Expand All @@ -376,22 +397,16 @@ private void addWidgetButtons(EList<Button> buttons, UIComponent component) {

private void addWidgetVisibility(EList<VisibilityRule> visibility, UIComponent component) {
if (component.getConfig() != null && component.getConfig().containsKey("visibility")) {
for (Object sourceVisibility : (Collection<?>) component.getConfig().get("visibility")) {
if (sourceVisibility instanceof String) {
Matcher matcher = VISIBILITY_PATTERN.matcher(sourceVisibility.toString());
if (matcher.matches()) {
Object sourceVisibilities = component.getConfig().get("visibility");
if (sourceVisibilities instanceof Collection<?>) {
for (Object sourceVisibility : (Collection<?>) sourceVisibilities) {
if (sourceVisibility instanceof String) {
List<String> conditionsString = getRuleConditions(sourceVisibility.toString(), null);
VisibilityRuleImpl visibilityRule = (VisibilityRuleImpl) SitemapFactory.eINSTANCE
.createVisibilityRule();
ConditionImpl condition = (ConditionImpl) SitemapFactory.eINSTANCE.createCondition();
condition.setItem(matcher.group("item"));
condition.setCondition(matcher.group("condition"));
condition.setSign(matcher.group("sign"));
condition.setState(matcher.group("state"));
visibilityRule.eSet(SitemapPackage.VISIBILITY_RULE__CONDITIONS, condition);
List<ConditionImpl> conditions = getConditions(conditionsString, component, "visibility");
visibilityRule.eSet(SitemapPackage.VISIBILITY_RULE__CONDITIONS, conditions);
visibility.add(visibilityRule);
} else {
logger.warn("Syntax error in visibility rule '{}' for widget {}", sourceVisibility,
component.getType());
}
}
}
Expand All @@ -412,50 +427,78 @@ private void addIconColor(EList<ColorArray> iconColor, UIComponent component) {

private void addColor(EList<ColorArray> color, UIComponent component, String key) {
if (component.getConfig() != null && component.getConfig().containsKey(key)) {
for (Object sourceColor : (Collection<?>) component.getConfig().get(key)) {
if (sourceColor instanceof String) {
Matcher matcher = COLOR_PATTERN.matcher(sourceColor.toString());
if (matcher.matches()) {
Object sourceColors = component.getConfig().get(key);
if (sourceColors instanceof Collection<?>) {
for (Object sourceColor : (Collection<?>) sourceColors) {
if (sourceColor instanceof String) {
String argument = getRuleArgument(sourceColor.toString());
List<String> conditionsString = getRuleConditions(sourceColor.toString(), argument);
ColorArrayImpl colorArray = (ColorArrayImpl) SitemapFactory.eINSTANCE.createColorArray();
ConditionImpl condition = (ConditionImpl) SitemapFactory.eINSTANCE.createCondition();
condition.setItem(matcher.group("item"));
condition.setCondition(matcher.group("condition"));
condition.setSign(matcher.group("sign"));
condition.setState(matcher.group("state"));
colorArray.eSet(SitemapPackage.COLOR_ARRAY__CONDITIONS, condition);
colorArray.setArg(argument);
List<ConditionImpl> conditions = getConditions(conditionsString, component, key);
colorArray.eSet(SitemapPackage.COLOR_ARRAY__CONDITIONS, conditions);
color.add(colorArray);
} else {
logger.warn("Syntax error in {} rule '{}' for widget {}", key, sourceColor,
component.getType());
}
}
}
}
}

private void addIconRules(EList<IconRule> iconDef, UIComponent component) {
if (component.getConfig() != null && component.getConfig().containsKey("icon")) {
for (Object sourceIcon : (Collection<?>) component.getConfig().get("icon")) {
if (sourceIcon instanceof String) {
Matcher matcher = ICON_PATTERN.matcher(sourceIcon.toString());
if (matcher.matches()) {
private void addIconRules(EList<IconRule> icon, UIComponent component) {
if (component.getConfig() != null && component.getConfig().containsKey("iconrules")) {
Object sourceIcons = component.getConfig().get("iconrules");
if (sourceIcons instanceof Collection<?>) {
for (Object sourceIcon : (Collection<?>) sourceIcons) {
if (sourceIcon instanceof String) {
String argument = getRuleArgument(sourceIcon.toString());
List<String> conditionsString = getRuleConditions(sourceIcon.toString(), argument);
IconRuleImpl iconRule = (IconRuleImpl) SitemapFactory.eINSTANCE.createIconRule();
ConditionImpl condition = (ConditionImpl) SitemapFactory.eINSTANCE.createCondition();
condition.setItem(matcher.group("item"));
condition.setCondition(matcher.group("condition"));
condition.setSign(matcher.group("sign"));
condition.setState(matcher.group("state"));
iconRule.eSet(SitemapPackage.ICON_RULE__CONDITIONS, condition);
iconRule.setArg(matcher.group("arg"));
iconDef.add(iconRule);
} else {
logger.warn("Syntax error in icon rule '{}' for widget {}", sourceIcon, component.getType());
iconRule.setArg(argument);
List<ConditionImpl> conditions = getConditions(conditionsString, component, "iconrules");
iconRule.eSet(SitemapPackage.ICON_RULE__CONDITIONS, conditions);
icon.add(iconRule);
}
}
}
}
}

private List<ConditionImpl> getConditions(List<String> conditionsString, UIComponent component, String key) {
List<ConditionImpl> conditions = new ArrayList<>();
for (String conditionString : conditionsString) {
Matcher matcher = CONDITION_PATTERN.matcher(conditionString);
if (matcher.matches()) {
ConditionImpl condition = (ConditionImpl) SitemapFactory.eINSTANCE.createCondition();
condition.setItem(matcher.group("item"));
condition.setCondition(matcher.group("condition"));
condition.setSign(matcher.group("sign"));
condition.setState(matcher.group("state"));
conditions.add(condition);
} else {
logger.warn("Syntax error in {} rule condition '{}' for widget {}", key, conditionString,
component.getType());
}
}
return conditions;
}

private String getRuleArgument(String rule) {
int argIndex = rule.lastIndexOf("=") + 1;
return rule.substring(argIndex).trim();
}

private List<String> getRuleConditions(String rule, @Nullable String argument) {
String conditions = rule;
if (argument != null) {
conditions = rule.substring(0, rule.lastIndexOf(argument)).trim();
if (conditions.endsWith("=")) {
conditions = conditions.substring(0, conditions.length() - 1);
}
}
List<String> conditionsList = List.of(conditions.split(" AND "));
return conditionsList.stream().map(String::trim).toList();
}

@Override
public void addModelChangeListener(ModelRepositoryChangeListener listener) {
modelChangeListeners.add(listener);
Expand Down

0 comments on commit d75d6df

Please sign in to comment.