diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml
index 2beba0b65d6..c426c70ccdb 100644
--- a/bom/openhab-core/pom.xml
+++ b/bom/openhab-core/pom.xml
@@ -88,6 +88,12 @@
${project.version}
compile
+
+ org.openhab.core.bundles
+ org.openhab.core.sitemap
+ ${project.version}
+ compile
+
org.openhab.core.bundles
org.openhab.core.transform
diff --git a/bundles/org.openhab.core.io.rest.sitemap/pom.xml b/bundles/org.openhab.core.io.rest.sitemap/pom.xml
index 4ea6496f1f1..17fb86a1186 100644
--- a/bundles/org.openhab.core.io.rest.sitemap/pom.xml
+++ b/bundles/org.openhab.core.io.rest.sitemap/pom.xml
@@ -25,11 +25,6 @@
org.openhab.core.io.rest
${project.version}
-
- org.openhab.core.bundles
- org.openhab.core.model.sitemap
- ${project.version}
-
org.openhab.core.bundles
org.openhab.core.io.rest.core
@@ -37,7 +32,7 @@
org.openhab.core.bundles
- org.openhab.core.model.core
+ org.openhab.core.sitemap
${project.version}
@@ -56,11 +51,6 @@
org.openhab.core.thing
${project.version}
-
- org.openhab.core.bom
- org.openhab.core.bom.compile-model
- pom
-
diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java
index 5e047750485..abbd7067e55 100644
--- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java
+++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/SitemapSubscriptionService.java
@@ -24,10 +24,9 @@
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
-import org.eclipse.emf.common.util.BasicEList;
-import org.eclipse.emf.common.util.EList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.common.registry.RegistryChangeListener;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventSubscriber;
import org.openhab.core.i18n.TimeZoneProvider;
@@ -36,12 +35,10 @@
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.items.events.ItemStatePredictedEvent;
-import org.openhab.core.model.core.EventType;
-import org.openhab.core.model.core.ModelRepositoryChangeListener;
-import org.openhab.core.model.sitemap.SitemapProvider;
-import org.openhab.core.model.sitemap.sitemap.LinkableWidget;
-import org.openhab.core.model.sitemap.sitemap.Sitemap;
-import org.openhab.core.model.sitemap.sitemap.Widget;
+import org.openhab.core.sitemap.LinkableWidget;
+import org.openhab.core.sitemap.Sitemap;
+import org.openhab.core.sitemap.Widget;
+import org.openhab.core.sitemap.registry.SitemapRegistry;
import org.openhab.core.thing.events.ChannelDescriptionChangedEvent;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.osgi.framework.BundleContext;
@@ -51,8 +48,6 @@
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.annotations.ReferenceCardinality;
-import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,11 +61,12 @@
* Subscribing to whole sitemaps is discouraged, since a large number of item updates may result in a high SSE traffic.
*
* @author Kai Kreuzer - Initial contribution
+ * @author Mark Herwege - Implement sitemap registry
*/
@Component(service = { SitemapSubscriptionService.class,
EventSubscriber.class }, configurationPid = "org.openhab.sitemapsubscription")
@NonNullByDefault
-public class SitemapSubscriptionService implements ModelRepositoryChangeListener, EventSubscriber {
+public class SitemapSubscriptionService implements RegistryChangeListener, EventSubscriber {
private static final String SITEMAP_PAGE_SEPARATOR = "#";
private static final String SITEMAP_SUFFIX = ".sitemap";
@@ -88,10 +84,9 @@ public interface SitemapSubscriptionCallback {
}
private final ItemUIRegistry itemUIRegistry;
+ private final SitemapRegistry sitemapRegistry;
private final TimeZoneProvider timeZoneProvider;
- private final List sitemapProviders = new ArrayList<>();
-
/* subscription id -> sitemap+page */
private final Map scopeOfSubscription = new ConcurrentHashMap<>();
@@ -109,15 +104,19 @@ public interface SitemapSubscriptionCallback {
@Activate
public SitemapSubscriptionService(Map config, final @Reference ItemUIRegistry itemUIRegistry,
- final @Reference TimeZoneProvider timeZoneProvider, BundleContext bundleContext) {
+ final @Reference SitemapRegistry sitemapRegistry, final @Reference TimeZoneProvider timeZoneProvider,
+ BundleContext bundleContext) {
this.itemUIRegistry = itemUIRegistry;
+ this.sitemapRegistry = sitemapRegistry;
this.timeZoneProvider = timeZoneProvider;
this.bundleContext = bundleContext;
applyConfig(config);
+ sitemapRegistry.addRegistryChangeListener(this);
}
@Deactivate
protected void deactivate() {
+ sitemapRegistry.removeRegistryChangeListener(this);
scopeOfSubscription.clear();
callbacks.clear();
creationInstants.clear();
@@ -144,17 +143,6 @@ private void applyConfig(Map config) {
}
}
- @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
- public void addSitemapProvider(SitemapProvider provider) {
- sitemapProviders.add(provider);
- provider.addModelChangeListener(this);
- }
-
- public void removeSitemapProvider(SitemapProvider provider) {
- sitemapProviders.remove(provider);
- provider.removeModelChangeListener(this);
- }
-
/**
* Creates a new subscription with the given id.
*
@@ -275,8 +263,8 @@ private void addCallbackToListener(String sitemapName, @Nullable String pageId,
listener.widgetsChangeListener().addCallback(callback);
}
- public EList collectWidgets(String sitemapName, @Nullable String pageId) {
- EList widgets = new BasicEList<>();
+ public List collectWidgets(String sitemapName, @Nullable String pageId) {
+ List widgets = new ArrayList<>();
Sitemap sitemap = getSitemap(sitemapName);
if (sitemap == null) {
@@ -329,28 +317,28 @@ private String getScopeIdentifier(String sitemapName, @Nullable String pageId) {
}
private @Nullable Sitemap getSitemap(String sitemapName) {
- for (SitemapProvider provider : sitemapProviders) {
- Sitemap sitemap = provider.getSitemap(sitemapName);
- if (sitemap != null) {
- return sitemap;
- }
- }
- return null;
+ return sitemapRegistry.get(sitemapName);
}
@Override
- public void modelChanged(String modelName, EventType type) {
- if (type != EventType.MODIFIED || !modelName.endsWith(SITEMAP_SUFFIX)) {
- return; // we process only sitemap modifications here
- }
+ public void added(Sitemap element) {
+ // Nothing to do
+ }
+
+ @Override
+ public void removed(Sitemap element) {
+ // Nothing to do
+ }
- String changedSitemapName = modelName.substring(0, modelName.length() - SITEMAP_SUFFIX.length());
+ @Override
+ public void updated(Sitemap oldElement, Sitemap element) {
+ String changedSitemapName = oldElement.getName();
for (Entry listenerEntry : pageChangeListeners.entrySet()) {
String sitemapWithPage = listenerEntry.getKey();
String sitemapName = extractSitemapName(sitemapWithPage);
- EList widgets;
+ List widgets;
if (sitemapName.equals(changedSitemapName)) {
if (isPageListener(sitemapWithPage)) {
String pageId = extractPageId(sitemapWithPage);
diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java
index d9ad389c8d2..4a3926c5e06 100644
--- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java
+++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java
@@ -12,8 +12,9 @@
*/
package org.openhab.core.io.rest.sitemap.internal;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
import java.net.URI;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
@@ -23,6 +24,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -53,9 +55,6 @@
import javax.ws.rs.sse.Sse;
import javax.ws.rs.sse.SseEventSink;
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.auth.Role;
@@ -79,30 +78,29 @@
import org.openhab.core.items.events.ItemStateChangedEvent;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.library.types.HSBType;
-import org.openhab.core.model.sitemap.SitemapProvider;
-import org.openhab.core.model.sitemap.sitemap.Button;
-import org.openhab.core.model.sitemap.sitemap.ButtonDefinition;
-import org.openhab.core.model.sitemap.sitemap.Buttongrid;
-import org.openhab.core.model.sitemap.sitemap.Chart;
-import org.openhab.core.model.sitemap.sitemap.ColorArray;
-import org.openhab.core.model.sitemap.sitemap.Colortemperaturepicker;
-import org.openhab.core.model.sitemap.sitemap.Condition;
-import org.openhab.core.model.sitemap.sitemap.Frame;
-import org.openhab.core.model.sitemap.sitemap.IconRule;
-import org.openhab.core.model.sitemap.sitemap.Image;
-import org.openhab.core.model.sitemap.sitemap.Input;
-import org.openhab.core.model.sitemap.sitemap.LinkableWidget;
-import org.openhab.core.model.sitemap.sitemap.Mapping;
-import org.openhab.core.model.sitemap.sitemap.Mapview;
-import org.openhab.core.model.sitemap.sitemap.Selection;
-import org.openhab.core.model.sitemap.sitemap.Setpoint;
-import org.openhab.core.model.sitemap.sitemap.Sitemap;
-import org.openhab.core.model.sitemap.sitemap.Slider;
-import org.openhab.core.model.sitemap.sitemap.Switch;
-import org.openhab.core.model.sitemap.sitemap.Video;
-import org.openhab.core.model.sitemap.sitemap.VisibilityRule;
-import org.openhab.core.model.sitemap.sitemap.Webview;
-import org.openhab.core.model.sitemap.sitemap.Widget;
+import org.openhab.core.sitemap.Button;
+import org.openhab.core.sitemap.ButtonDefinition;
+import org.openhab.core.sitemap.Buttongrid;
+import org.openhab.core.sitemap.Chart;
+import org.openhab.core.sitemap.Colortemperaturepicker;
+import org.openhab.core.sitemap.Condition;
+import org.openhab.core.sitemap.Frame;
+import org.openhab.core.sitemap.Image;
+import org.openhab.core.sitemap.Input;
+import org.openhab.core.sitemap.LinkableWidget;
+import org.openhab.core.sitemap.Mapping;
+import org.openhab.core.sitemap.Mapview;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Rule;
+import org.openhab.core.sitemap.Selection;
+import org.openhab.core.sitemap.Setpoint;
+import org.openhab.core.sitemap.Sitemap;
+import org.openhab.core.sitemap.Slider;
+import org.openhab.core.sitemap.Switch;
+import org.openhab.core.sitemap.Video;
+import org.openhab.core.sitemap.Webview;
+import org.openhab.core.sitemap.Widget;
+import org.openhab.core.sitemap.registry.SitemapRegistry;
import org.openhab.core.types.State;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.openhab.core.ui.items.ItemUIRegistry.WidgetLabelSource;
@@ -111,8 +109,6 @@
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.annotations.ReferenceCardinality;
-import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
@@ -121,8 +117,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.collect.MapMaker;
-
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
@@ -150,6 +144,7 @@
* @author Laurent Garnier - Added releaseCmd field for mappings used for switch element
* @author Laurent Garnier - Added support for Buttongrid as container for Button elements
* @author Laurent Garnier - Added support for new sitemap element Colortemperaturepicker
+ * @author Mark Herwege - Implement sitemap registry, remove Guava dependency
*/
@Component(service = { RESTResource.class, EventSubscriber.class })
@JaxrsResource
@@ -171,7 +166,7 @@ public class SitemapResource
private static final long TIMEOUT_IN_MS = 30000;
- private SseBroadcaster<@NonNull SseSinkInfo> broadcaster;
+ private SseBroadcaster broadcaster;
@Context
@NonNullByDefault({})
@@ -190,13 +185,12 @@ public class SitemapResource
Sse sse;
private final ItemUIRegistry itemUIRegistry;
+ private final SitemapRegistry sitemapRegistry;
private final SitemapSubscriptionService subscriptions;
private final LocaleService localeService;
private final TimeZoneProvider timeZoneProvider;
- private final java.util.List sitemapProviders = new ArrayList<>();
-
- private final Map knownSubscriptions = new MapMaker().weakValues().makeMap();
+ private final WeakValueConcurrentHashMap knownSubscriptions = new WeakValueConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = ThreadPoolManager
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
@@ -207,10 +201,12 @@ public class SitemapResource
@Activate
public SitemapResource( //
final @Reference ItemUIRegistry itemUIRegistry, //
+ final @Reference SitemapRegistry sitemapRegistry, //
final @Reference LocaleService localeService, //
final @Reference TimeZoneProvider timeZoneProvider, //
final @Reference SitemapSubscriptionService subscriptions) {
this.itemUIRegistry = itemUIRegistry;
+ this.sitemapRegistry = sitemapRegistry;
this.localeService = localeService;
this.timeZoneProvider = timeZoneProvider;
this.subscriptions = subscriptions;
@@ -242,15 +238,6 @@ protected void deactivate() {
broadcaster.close();
}
- @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
- public void addSitemapProvider(SitemapProvider provider) {
- sitemapProviders.add(provider);
- }
-
- public void removeSitemapProvider(SitemapProvider provider) {
- sitemapProviders.remove(provider);
- }
-
@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(operationId = "getSitemaps", summary = "Get all available sitemaps.", responses = {
@@ -429,6 +416,7 @@ public void getSitemapEvents(@Context final SseEventSink sseEventSink, @Context
private void getSitemapEvents(SseEventSink sseEventSink, HttpServletResponse response, String subscriptionId,
@Nullable String sitemapname, @Nullable String pageId, boolean subscribeToWholeSitemap) {
+ // Clean up stale subscriptions
final SseSinkInfo sinkInfo = knownSubscriptions.get(subscriptionId);
if (sinkInfo == null) {
logger.debug("Subscription id {} does not exist.", subscriptionId);
@@ -460,19 +448,19 @@ private PageDTO getPageBean(String sitemapName, String pageId, URI uri, Locale l
Sitemap sitemap = getSitemap(sitemapName);
if (sitemap != null) {
if (pageId.equals(sitemap.getName())) {
- EList children = itemUIRegistry.getChildren(sitemap);
+ List children = itemUIRegistry.getChildren(sitemap);
return createPageBean(sitemapName, sitemap.getLabel(), sitemap.getIcon(), sitemap.getName(), children,
false, isLeaf(children), uri, locale, timeout, includeHidden);
} else {
Widget pageWidget = itemUIRegistry.getWidget(sitemap, pageId);
if (pageWidget instanceof LinkableWidget widget) {
- EList children = itemUIRegistry.getChildren(widget);
+ List children = itemUIRegistry.getChildren(widget);
PageDTO pageBean = createPageBean(sitemapName, itemUIRegistry.getLabel(pageWidget),
itemUIRegistry.getCategory(pageWidget), pageId, children, false, isLeaf(children), uri,
locale, timeout, includeHidden);
- EObject parentPage = pageWidget.eContainer();
- while (parentPage instanceof Frame) {
- parentPage = parentPage.eContainer();
+ Parent parentPage = widget.getParent();
+ while (parentPage instanceof Frame frameParent) {
+ parentPage = frameParent.getParent();
}
if (parentPage instanceof Widget parentPageWidget) {
String parentId = itemUIRegistry.getWidgetId(parentPageWidget);
@@ -507,28 +495,16 @@ private PageDTO getPageBean(String sitemapName, String pageId, URI uri, Locale l
public Collection getSitemapBeans(URI uri) {
Collection beans = new LinkedList<>();
- Set names = new HashSet<>();
logger.debug("Received HTTP GET request at '{}'.", UriBuilder.fromUri(uri).build().toASCIIString());
- for (SitemapProvider provider : sitemapProviders) {
- for (String modelName : provider.getSitemapNames()) {
- Sitemap sitemap = provider.getSitemap(modelName);
- if (sitemap != null) {
- if (!names.contains(modelName)) {
- names.add(modelName);
- SitemapDTO bean = new SitemapDTO();
- bean.name = modelName;
- bean.icon = sitemap.getIcon();
- bean.label = sitemap.getLabel();
- bean.link = UriBuilder.fromUri(uri).path(bean.name).build().toASCIIString();
- bean.homepage = new PageDTO();
- bean.homepage.link = bean.link + "/" + sitemap.getName();
- beans.add(bean);
- } else {
- logger.warn("Found duplicate sitemap name '{}' - ignoring it. Please check your configuration.",
- modelName);
- }
- }
- }
+ for (Sitemap sitemap : sitemapRegistry.getAll()) {
+ SitemapDTO bean = new SitemapDTO();
+ bean.name = sitemap.getName();
+ bean.icon = sitemap.getIcon();
+ bean.label = sitemap.getLabel();
+ bean.link = UriBuilder.fromUri(uri).path(bean.name).build().toASCIIString();
+ bean.homepage = new PageDTO();
+ bean.homepage.link = bean.link + "/" + sitemap.getName();
+ beans.add(bean);
}
return beans;
}
@@ -560,8 +536,8 @@ private SitemapDTO createSitemapBean(String sitemapName, Sitemap sitemap, URI ur
}
private PageDTO createPageBean(String sitemapName, @Nullable String title, @Nullable String icon, String pageId,
- @Nullable EList children, boolean drillDown, boolean isLeaf, URI uri, Locale locale,
- boolean timeout, boolean includeHiddenWidgets) {
+ @Nullable List children, boolean drillDown, boolean isLeaf, URI uri, Locale locale, boolean timeout,
+ boolean includeHiddenWidgets) {
PageDTO bean = new PageDTO();
bean.timeout = timeout;
bean.id = pageId;
@@ -593,17 +569,18 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
WidgetDTO bean = new WidgetDTO();
State itemState = null;
- if (widget.getItem() != null) {
+ String itemName = widget.getItem();
+ if (itemName != null) {
try {
- Item item = itemUIRegistry.getItem(widget.getItem());
+ Item item = itemUIRegistry.getItem(itemName);
itemState = item.getState();
- String widgetTypeName = widget.eClass().getInstanceTypeName()
- .substring(widget.eClass().getInstanceTypeName().lastIndexOf(".") + 1);
+ String widgetTypeName = widget.getWidgetType();
boolean isMapview = "mapview".equalsIgnoreCase(widgetTypeName);
Predicate- itemFilter = (i -> CoreItemFactory.LOCATION.equals(i.getType()));
bean.item = EnrichedItemDTOMapper.map(item, isMapview, itemFilter,
UriBuilder.fromUri(uri).path("items/{itemName}"), locale, timeZoneProvider.getTimeZone());
- bean.state = itemUIRegistry.getState(widget).toFullString();
+ State widgetState = itemUIRegistry.getState(widget);
+ bean.state = widgetState != null ? widgetState.toFullString() : null;
// In case the widget state is identical to the item state, its value is set to null.
if (bean.state != null && bean.state.equals(bean.item.state)) {
bean.state = null;
@@ -614,7 +591,7 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
}
bean.widgetId = widgetId;
bean.icon = itemUIRegistry.getCategory(widget);
- bean.staticIcon = widget.getStaticIcon() != null || !widget.getIconRules().isEmpty();
+ bean.staticIcon = widget.isStaticIcon() || !widget.getIconRules().isEmpty();
bean.labelcolor = convertItemValueColor(itemUIRegistry.getLabelColor(widget), itemState);
bean.valuecolor = convertItemValueColor(itemUIRegistry.getValueColor(widget), itemState);
bean.iconcolor = convertItemValueColor(itemUIRegistry.getIconColor(widget), itemState);
@@ -622,10 +599,10 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
bean.labelSource = itemUIRegistry.getLabelSource(widget).toString();
bean.pattern = itemUIRegistry.getFormatPattern(widget);
bean.unit = itemUIRegistry.getUnitForWidget(widget);
- bean.type = widget.eClass().getName();
+ bean.type = widget.getWidgetType();
bean.visibility = itemUIRegistry.getVisiblity(widget);
if (widget instanceof LinkableWidget linkableWidget) {
- EList children = itemUIRegistry.getChildren(linkableWidget);
+ List children = itemUIRegistry.getChildren(linkableWidget);
if (widget instanceof Frame || widget instanceof Buttongrid) {
for (Widget child : children) {
String wID = itemUIRegistry.getWidgetId(child);
@@ -680,7 +657,8 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
if (videoWidget.getEncoding() != null) {
bean.encoding = videoWidget.getEncoding();
}
- if (videoWidget.getEncoding() != null && videoWidget.getEncoding().toLowerCase().contains("hls")) {
+ String encoding = videoWidget.getEncoding();
+ if (encoding != null && encoding.toLowerCase().contains("hls")) {
bean.url = videoWidget.getUrl();
} else {
bean.url = buildProxyUrl(sitemapName, videoWidget, uri);
@@ -696,8 +674,8 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
if (widget instanceof Chart chartWidget) {
bean.service = chartWidget.getService();
bean.period = chartWidget.getPeriod();
- bean.legend = chartWidget.getLegend();
- bean.forceAsItem = chartWidget.getForceAsItem();
+ bean.legend = chartWidget.hasLegend();
+ bean.forceAsItem = chartWidget.forceAsItem();
bean.yAxisDecimalPattern = chartWidget.getYAxisDecimalPattern();
bean.interpolation = chartWidget.getInterpolation();
if (chartWidget.getRefresh() > 0) {
@@ -726,7 +704,7 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
}
if (widget instanceof Button buttonWidget) {
// Get the icon from the widget only
- if (widget.getIcon() == null && widget.getStaticIcon() == null && widget.getIconRules().isEmpty()) {
+ if (widget.getIcon() == null && widget.getIconRules().isEmpty()) {
bean.icon = null;
bean.staticIcon = null;
}
@@ -765,14 +743,14 @@ private String buildProxyUrl(String sitemapName, Widget widget, URI uri) {
return sb.toString();
}
- private boolean isLeaf(EList children) {
+ private boolean isLeaf(List children) {
for (Widget w : children) {
if (w instanceof Frame frame) {
- if (isLeaf(frame.getChildren())) {
+ if (isLeaf(frame.getWidgets())) {
return false;
}
} else if (w instanceof Buttongrid grid) {
- if (isLeaf(grid.getChildren())) {
+ if (isLeaf(grid.getWidgets())) {
return false;
}
} else if (w instanceof LinkableWidget linkableWidget) {
@@ -785,18 +763,11 @@ private boolean isLeaf(EList children) {
}
private @Nullable Sitemap getSitemap(String sitemapname) {
- for (SitemapProvider provider : sitemapProviders) {
- Sitemap sitemap = provider.getSitemap(sitemapname);
- if (sitemap != null) {
- return sitemap;
- }
- }
-
- return null;
+ return sitemapRegistry.get(sitemapname);
}
private boolean blockUntilChangeOccurs(String sitemapname, @Nullable String pageId) {
- EList widgets = subscriptions.collectWidgets(sitemapname, pageId);
+ List widgets = subscriptions.collectWidgets(sitemapname, pageId);
if (widgets.isEmpty()) {
return false;
}
@@ -862,46 +833,30 @@ private Set getAllItems(List widgets) {
}
// Consider all items inside the frame
if (widget instanceof Frame frame) {
- items.addAll(getAllItems(frame.getChildren()));
+ items.addAll(getAllItems(frame.getWidgets()));
} else if (widget instanceof Buttongrid grid) {
- items.addAll(getAllItems(grid.getChildren()));
+ items.addAll(getAllItems(grid.getWidgets()));
}
// Consider items involved in any icon condition
- items.addAll(getItemsInIconCond(widget.getIconRules()));
+ items.addAll(getItemsInRuleConditions(widget.getIconRules()));
// Consider items involved in any visibility, labelcolor, valuecolor and iconcolor condition
- items.addAll(getItemsInVisibilityCond(widget.getVisibility()));
- items.addAll(getItemsInColorCond(widget.getLabelColor()));
- items.addAll(getItemsInColorCond(widget.getValueColor()));
- items.addAll(getItemsInColorCond(widget.getIconColor()));
- }
- return items;
- }
-
- private Set getItemsInVisibilityCond(EList ruleList) {
- Set items = new HashSet<>();
- for (VisibilityRule rule : ruleList) {
- getItemsInConditions(rule.getConditions(), items);
- }
- return items;
- }
-
- private Set getItemsInColorCond(EList colorList) {
- Set items = new HashSet<>();
- for (ColorArray rule : colorList) {
- getItemsInConditions(rule.getConditions(), items);
+ items.addAll(getItemsInRuleConditions(widget.getVisibility()));
+ items.addAll(getItemsInRuleConditions(widget.getLabelColor()));
+ items.addAll(getItemsInRuleConditions(widget.getValueColor()));
+ items.addAll(getItemsInRuleConditions(widget.getIconColor()));
}
return items;
}
- private Set getItemsInIconCond(EList ruleList) {
+ private Set getItemsInRuleConditions(List ruleList) {
Set items = new HashSet<>();
- for (IconRule rule : ruleList) {
+ for (Rule rule : ruleList) {
getItemsInConditions(rule.getConditions(), items);
}
return items;
}
- private void getItemsInConditions(@Nullable EList conditions, Set items) {
+ private void getItemsInConditions(@Nullable List conditions, Set items) {
if (conditions != null) {
for (Condition condition : conditions) {
String itemName = condition.getItem();
@@ -975,6 +930,52 @@ public void sseEventSinkRemoved(SseEventSink sink, SseSinkInfo info) {
knownSubscriptions.remove(info.subscriptionId);
}
+ /**
+ * This is a replacement implementation for Google Guava
new MapMaker().weakValues().makeMap(), to
+ * avoid pulling in a Guava dependency in this class.
+ *
+ * @param key
+ * @param value
+ */
+ private class WeakValueConcurrentHashMap {
+ // Map from key → WeakReference to value
+ private final Map> backingMap = new ConcurrentHashMap<>();
+ private final ReferenceQueue refQueue = new ReferenceQueue<>();
+
+ // Custom WeakReference that remembers its key
+ private static class WeakValueRef extends WeakReference {
+ final K key;
+
+ WeakValueRef(K key, V value, ReferenceQueue queue) {
+ super(value, queue);
+ this.key = key;
+ }
+ }
+
+ public void put(K key, V value) {
+ processQueue();
+ backingMap.put(key, new WeakValueRef<>(key, value, refQueue));
+ }
+
+ public @Nullable V get(K key) {
+ processQueue();
+ WeakValueRef ref = backingMap.get(key);
+ return ref != null ? ref.get() : null;
+ }
+
+ public void remove(K key) {
+ processQueue();
+ backingMap.remove(key);
+ }
+
+ private void processQueue() {
+ WeakValueRef ref;
+ while ((ref = (WeakValueRef) refQueue.poll()) != null) {
+ backingMap.remove(ref.key, ref); // remove only if still mapped
+ }
+ }
+ }
+
private static class BlockingStateChangeListener {
private final Set items;
private boolean changed = false;
diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetsChangeListener.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetsChangeListener.java
index 6bbb3173ffd..cf83cfeb6b8 100644
--- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetsChangeListener.java
+++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/WidgetsChangeListener.java
@@ -22,7 +22,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
-import org.eclipse.emf.common.util.EList;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.events.Event;
@@ -36,15 +36,13 @@
import org.openhab.core.items.events.ItemEvent;
import org.openhab.core.items.events.ItemStateChangedEvent;
import org.openhab.core.library.CoreItemFactory;
-import org.openhab.core.model.sitemap.sitemap.Button;
-import org.openhab.core.model.sitemap.sitemap.Buttongrid;
-import org.openhab.core.model.sitemap.sitemap.Chart;
-import org.openhab.core.model.sitemap.sitemap.ColorArray;
-import org.openhab.core.model.sitemap.sitemap.Condition;
-import org.openhab.core.model.sitemap.sitemap.Frame;
-import org.openhab.core.model.sitemap.sitemap.IconRule;
-import org.openhab.core.model.sitemap.sitemap.VisibilityRule;
-import org.openhab.core.model.sitemap.sitemap.Widget;
+import org.openhab.core.sitemap.Button;
+import org.openhab.core.sitemap.Buttongrid;
+import org.openhab.core.sitemap.Chart;
+import org.openhab.core.sitemap.Condition;
+import org.openhab.core.sitemap.Frame;
+import org.openhab.core.sitemap.Rule;
+import org.openhab.core.sitemap.Widget;
import org.openhab.core.types.State;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.openhab.core.ui.items.ItemUIRegistry.WidgetLabelSource;
@@ -57,6 +55,7 @@
* @author Laurent Garnier - Support added for multiple AND conditions in labelcolor/valuecolor/visibility
* @author Laurent Garnier - New widget icon parameter based on conditional rules
* @author Laurent Garnier - Buttongrid as container for Button elements
+ * @author Mark Herwege - Implement sitemap registry
*/
public class WidgetsChangeListener implements EventSubscriber {
@@ -67,7 +66,7 @@ public class WidgetsChangeListener implements EventSubscriber {
private final String pageId;
private final ItemUIRegistry itemUIRegistry;
private final TimeZoneProvider timeZoneProvider;
- private EList widgets;
+ private List widgets;
private Set- items;
private final HashSet filterItems = new HashSet<>();
private final List callbacks = Collections.synchronizedList(new ArrayList<>());
@@ -82,7 +81,7 @@ public class WidgetsChangeListener implements EventSubscriber {
* @param widgets the list of widgets that are part of the page.
*/
public WidgetsChangeListener(String sitemapName, String pageId, final ItemUIRegistry itemUIRegistry,
- final TimeZoneProvider timeZoneProvider, EList widgets) {
+ final TimeZoneProvider timeZoneProvider, List widgets) {
this.sitemapName = sitemapName;
this.pageId = pageId;
this.itemUIRegistry = itemUIRegistry;
@@ -91,7 +90,7 @@ public WidgetsChangeListener(String sitemapName, String pageId, final ItemUIRegi
updateItemsAndWidgets(widgets);
}
- private void updateItemsAndWidgets(EList widgets) {
+ private void updateItemsAndWidgets(List widgets) {
this.widgets = widgets;
items = getAllItems(widgets);
filterItems.clear();
@@ -124,34 +123,34 @@ public void removeCallback(SitemapSubscriptionCallback callback) {
* the widget list to get the items for added to all bundles containing REST resources
* @return all items that are represented by the list of widgets
*/
- private Set
- getAllItems(EList widgets) {
+ private Set
- getAllItems(List widgets) {
Set
- items = new HashSet<>();
if (itemUIRegistry != null) {
for (Widget widget : widgets) {
addItemWithName(items, widget.getItem());
if (widget instanceof Frame frame) {
- items.addAll(getAllItems(frame.getChildren()));
+ items.addAll(getAllItems(frame.getWidgets()));
} else if (widget instanceof Buttongrid grid) {
- items.addAll(getAllItems(grid.getChildren()));
+ items.addAll(getAllItems(grid.getWidgets()));
}
// now scan icon rules
- for (IconRule rule : widget.getIconRules()) {
+ for (Rule rule : widget.getIconRules()) {
addItemsFromConditions(items, rule.getConditions());
}
// now scan visibility rules
- for (VisibilityRule rule : widget.getVisibility()) {
+ for (Rule rule : widget.getVisibility()) {
addItemsFromConditions(items, rule.getConditions());
}
// now scan label color rules
- for (ColorArray rule : widget.getLabelColor()) {
+ for (Rule rule : widget.getLabelColor()) {
addItemsFromConditions(items, rule.getConditions());
}
// now scan value color rules
- for (ColorArray rule : widget.getValueColor()) {
+ for (Rule rule : widget.getValueColor()) {
addItemsFromConditions(items, rule.getConditions());
}
// now scan icon color rules
- for (ColorArray rule : widget.getIconColor()) {
+ for (Rule rule : widget.getIconColor()) {
addItemsFromConditions(items, rule.getConditions());
}
}
@@ -159,7 +158,7 @@ private Set
- getAllItems(EList widgets) {
return items;
}
- private void addItemsFromConditions(Set
- items, @Nullable EList conditions) {
+ private void addItemsFromConditions(Set
- items, @Nullable List conditions) {
if (conditions != null) {
for (Condition condition : conditions) {
addItemWithName(items, condition.getItem());
@@ -206,11 +205,12 @@ private Set constructSitemapEvents(Item item, State state, List 0;
+ Integer refresh = chartWidget.getRefresh();
+ skipWidget = refresh != null && refresh > 0;
}
if (!skipWidget || definesVisibilityOrColorOrIcon(w, item.getName())) {
SitemapWidgetEvent event = constructSitemapEventForWidget(item, state, w);
@@ -228,10 +228,10 @@ private SitemapWidgetEvent constructSitemapEventForWidget(Item item, State state
event.labelSource = itemUIRegistry.getLabelSource(widget).toString();
event.widgetId = itemUIRegistry.getWidgetId(widget);
event.icon = itemUIRegistry.getCategory(widget);
- event.reloadIcon = widget.getStaticIcon() == null;
+ event.reloadIcon = !widget.isStaticIcon();
if (widget instanceof Button buttonWidget) {
// Get the icon from the widget only
- if (widget.getIcon() == null && widget.getStaticIcon() == null && widget.getIconRules().isEmpty()) {
+ if (widget.getIcon() == null && widget.getIconRules().isEmpty()) {
event.icon = null;
event.reloadIcon = false;
}
@@ -243,12 +243,11 @@ private SitemapWidgetEvent constructSitemapEventForWidget(Item item, State state
event.descriptionChanged = false;
// event.item contains the (potentially changed) data of the item belonging to
// the widget including its state (in event.item.state)
- boolean itemBelongsToWidget = widget.getItem() != null && widget.getItem().equals(item.getName());
+ boolean itemBelongsToWidget = widget.getItem() != null && item.getName().equals(widget.getItem());
final Item itemToBeSent = itemBelongsToWidget ? item : getItemForWidget(widget);
State stateToBeSent = null;
if (itemToBeSent != null) {
- String widgetTypeName = widget.eClass().getInstanceTypeName()
- .substring(widget.eClass().getInstanceTypeName().lastIndexOf(".") + 1);
+ String widgetTypeName = widget.getWidgetType();
boolean drillDown = "mapview".equalsIgnoreCase(widgetTypeName);
Predicate
- itemFilter = (i -> CoreItemFactory.LOCATION.equals(i.getType()));
event.item = EnrichedItemDTOMapper.map(itemToBeSent, drillDown, itemFilter, null, null,
@@ -256,7 +255,8 @@ private SitemapWidgetEvent constructSitemapEventForWidget(Item item, State state
// event.state is an adjustment of the item state to the widget type.
stateToBeSent = itemBelongsToWidget ? state : itemToBeSent.getState();
- event.state = itemUIRegistry.convertState(widget, itemToBeSent, stateToBeSent).toFullString();
+ State convertedState = itemUIRegistry.convertState(widget, itemToBeSent, stateToBeSent);
+ event.state = convertedState == null ? null : convertedState.toFullString();
// In case this state is identical to the item state, its value is set to null.
if (event.state != null && event.state.equals(event.item.state)) {
event.state = null;
@@ -288,12 +288,12 @@ private boolean definesVisibilityOrColorOrIcon(Widget w, String name) {
|| w.getIconRules().stream().anyMatch(r -> conditionsDependsOnItem(r.getConditions(), name));
}
- private boolean conditionsDependsOnItem(@Nullable EList conditions, String name) {
+ private boolean conditionsDependsOnItem(@Nullable List conditions, String name) {
return conditions != null && conditions.stream().anyMatch(c -> name.equals(c.getItem()));
}
- public void sitemapContentChanged(EList widgets) {
- updateItemsAndWidgets(widgets);
+ public void sitemapContentChanged(List<@NonNull Widget> widgets2) {
+ updateItemsAndWidgets(widgets2);
SitemapChangedEvent changeEvent = new SitemapChangedEvent();
changeEvent.pageId = pageId;
diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java b/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java
index 9c7413f1287..d6edd82e98c 100644
--- a/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java
+++ b/bundles/org.openhab.core.io.rest.sitemap/src/test/java/org/openhab/core/io/rest/sitemap/internal/SitemapResourceTest.java
@@ -15,10 +15,9 @@
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import static org.hamcrest.collection.IsEmptyCollection.empty;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -31,9 +30,6 @@
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
-import org.eclipse.emf.common.util.BasicEList;
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.ecore.EClass;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -51,14 +47,12 @@
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
-import org.openhab.core.model.sitemap.SitemapProvider;
-import org.openhab.core.model.sitemap.sitemap.ColorArray;
-import org.openhab.core.model.sitemap.sitemap.Condition;
-import org.openhab.core.model.sitemap.sitemap.Group;
-import org.openhab.core.model.sitemap.sitemap.IconRule;
-import org.openhab.core.model.sitemap.sitemap.Sitemap;
-import org.openhab.core.model.sitemap.sitemap.VisibilityRule;
-import org.openhab.core.model.sitemap.sitemap.Widget;
+import org.openhab.core.sitemap.Condition;
+import org.openhab.core.sitemap.Group;
+import org.openhab.core.sitemap.Rule;
+import org.openhab.core.sitemap.Sitemap;
+import org.openhab.core.sitemap.Widget;
+import org.openhab.core.sitemap.registry.SitemapRegistry;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
@@ -71,6 +65,7 @@
*
* @author Henning Treu - Initial contribution
* @author Laurent Garnier - Extended tests for static icon and icon based on conditional rules
+ * @author Mark Herwege - Implement sitemap registry
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -81,10 +76,8 @@ public class SitemapResourceTest extends JavaTest {
private static final String HTTP_HEADER_X_ATMOSPHERE_TRANSPORT = "X-Atmosphere-Transport";
private static final String ITEM_NAME = "itemName";
- private static final String SUBPAGE_ITEM_NAME = "subpabeItemName";
private static final String ITEM_LABEL = "item label";
private static final String SITEMAP_PATH = "/sitemaps";
- private static final String SITEMAP_MODEL_NAME = "sitemapModel";
private static final String SITEMAP_NAME = "defaultSitemap";
private static final String SITEMAP_TITLE = "Default Sitemap";
private static final String VISIBILITY_RULE_ITEM_NAME = "visibilityRuleItem";
@@ -123,20 +116,19 @@ public class SitemapResourceTest extends JavaTest {
private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock;
private @Mock @NonNullByDefault({}) LocaleService localeServiceMock;
private @Mock @NonNullByDefault({}) HttpServletRequest requestMock;
- private @Mock @NonNullByDefault({}) SitemapProvider sitemapProviderMock;
+ private @Mock @NonNullByDefault({}) SitemapRegistry sitemapRegistryMock;
private @Mock @NonNullByDefault({}) UriInfo uriInfoMock;
private @Mock @NonNullByDefault({}) BundleContext bundleContextMock;
- private EList widgets = new BasicEList<>();
+ private List widgets = new ArrayList<>();
@BeforeEach
public void setup() throws Exception {
- subscriptions = new SitemapSubscriptionService(Collections.emptyMap(), itemUIRegistryMock, timeZoneProviderMock,
- bundleContextMock);
- subscriptions.addSitemapProvider(sitemapProviderMock);
+ subscriptions = new SitemapSubscriptionService(Collections.emptyMap(), itemUIRegistryMock, sitemapRegistryMock,
+ timeZoneProviderMock, bundleContextMock);
- sitemapResource = new SitemapResource(itemUIRegistryMock, localeServiceMock, timeZoneProviderMock,
- subscriptions);
+ sitemapResource = new SitemapResource(itemUIRegistryMock, sitemapRegistryMock, localeServiceMock,
+ timeZoneProviderMock, subscriptions);
when(uriInfoMock.getAbsolutePathBuilder()).thenReturn(UriBuilder.fromPath(SITEMAP_PATH));
when(uriInfoMock.getBaseUriBuilder()).thenReturn(UriBuilder.fromPath(SITEMAP_PATH));
@@ -154,9 +146,8 @@ public void setup() throws Exception {
when(localeServiceMock.getLocale(null)).thenReturn(Locale.US);
- configureSitemapProviderMock();
+ configureSitemapRegistryMock();
configureSitemapMock();
- sitemapResource.addSitemapProvider(sitemapProviderMock);
widgets = initSitemapWidgetsWithSubpages();
configureItemUIRegistry(PercentType.HUNDRED, OnOffType.ON);
@@ -165,15 +156,6 @@ public void setup() throws Exception {
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(null);
}
- @Test
- public void whenNoSitemapProvidersAreSetShouldReturnEmptyList() {
- sitemapResource.removeSitemapProvider(sitemapProviderMock);
- Response sitemaps = sitemapResource.getSitemaps();
-
- assertThat(sitemaps.getEntity(), instanceOf(Collection.class));
- assertThat((Collection>) sitemaps.getEntity(), is(empty()));
- }
-
@Test
public void whenSitemapsAreProvidedShouldReturnSitemapBeans() {
Response sitemaps = sitemapResource.getSitemaps();
@@ -182,8 +164,8 @@ public void whenSitemapsAreProvidedShouldReturnSitemapBeans() {
@SuppressWarnings("unchecked")
SitemapDTO dto = ((Collection) sitemaps.getEntity()).iterator().next();
- assertThat(dto.name, is(SITEMAP_MODEL_NAME));
- assertThat(dto.link, is(SITEMAP_PATH + "/" + SITEMAP_MODEL_NAME));
+ assertThat(dto.name, is(SITEMAP_NAME));
+ assertThat(dto.link, is(SITEMAP_PATH + "/" + SITEMAP_NAME));
}
@Test
@@ -197,7 +179,7 @@ public void whenLongPollingWholeSitemapShouldObserveAllItems() throws ItemNotFou
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getSitemapData(headersMock, null, SITEMAP_MODEL_NAME, null, false);
+ Response response = sitemapResource.getSitemapData(headersMock, null, SITEMAP_NAME, null, false);
SitemapDTO sitemapDTO = (SitemapDTO) response.getEntity();
// assert that the item state change did trigger the blocking method to return
@@ -217,8 +199,7 @@ public void whenLongPollingSpecificPageMustNotObserveAllItems() throws ItemNotFo
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
// assert that the item state change did trigger the blocking method to return
@@ -234,8 +215,7 @@ public void whenLongPollingShouldObserveItems() {
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
// assert that the item state change did trigger the blocking method to return
@@ -243,7 +223,7 @@ public void whenLongPollingShouldObserveItems() {
}
@Test
- public void whenLongPollingShouldObserveItemsFromVisibilityRules() {
+ public void whenLongPollingShouldObserveItemsFromRules() {
ItemEvent itemEvent = mock(ItemEvent.class);
when(itemEvent.getItemName()).thenReturn(visibilityRuleItem.getName());
executeWithDelay(() -> sitemapResource.receive(itemEvent));
@@ -251,8 +231,7 @@ public void whenLongPollingShouldObserveItemsFromVisibilityRules() {
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
// assert that the item state change did trigger the blocking method to return
@@ -268,8 +247,7 @@ public void whenLongPollingShouldObserveItemsFromLabelColorConditions() {
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
// assert that the item state change did trigger the blocking method to return
@@ -285,8 +263,7 @@ public void whenLongPollingShouldObserveItemsFromValueColorConditions() {
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
assertThat(pageDTO.timeout, is(false)); // assert that the item state change did trigger the blocking method to
@@ -302,8 +279,7 @@ public void whenLongPollingShouldObserveItemsFromIconColorConditions() {
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
assertThat(pageDTO.timeout, is(false)); // assert that the item state change did trigger the blocking method to
@@ -319,8 +295,7 @@ public void whenLongPollingShouldObserveItemsFromIconConditions() {
// non-null is sufficient here.
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(List.of());
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
// assert that the item state change did trigger the blocking method to return
@@ -346,8 +321,7 @@ public void whenGetPageDataShouldReturnPageBean() throws ItemNotFoundException {
// Disable long polling
when(headersMock.getRequestHeader(HTTP_HEADER_X_ATMOSPHERE_TRANSPORT)).thenReturn(null);
- Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_MODEL_NAME, SITEMAP_NAME, null,
- false);
+ Response response = sitemapResource.getPageData(headersMock, null, SITEMAP_NAME, SITEMAP_NAME, null, false);
PageDTO pageDTO = (PageDTO) response.getEntity();
assertThat(pageDTO.id, is(SITEMAP_NAME));
@@ -402,9 +376,9 @@ private void configureItemUIRegistryWithSubpages(State state1, State state2, Sta
Group group1 = (Group) widgets.getFirst();
Group group2 = (Group) widgets.get(4);
- when(itemUIRegistryMock.getChildren(defaultSitemapMock)).thenReturn(new BasicEList<>(List.of(group1, group2)));
+ when(itemUIRegistryMock.getChildren(defaultSitemapMock)).thenReturn(List.of(group1, group2));
configureCommonUIRegistryMockMethods();
- EList subpage1Widgets = new BasicEList<>(group1.getChildren());
+ List subpage1Widgets = group1.getWidgets();
configureItemUIRegistryForWidget(group1, FRAME_ID, GROUP_ICON, GROUP_LABEL, WidgetLabelSource.SITEMAP_WIDGET,
true, null, null, null, null);
@@ -412,7 +386,7 @@ private void configureItemUIRegistryWithSubpages(State state1, State state2, Sta
configureWidgetStatesPage1(state1, state2);
- EList subpage2Widgets = new BasicEList<>(group2.getChildren());
+ List subpage2Widgets = group2.getWidgets();
configureItemUIRegistryForWidget(group2, FRAME_ID, GROUP_ICON, GROUP_LABEL, WidgetLabelSource.SITEMAP_WIDGET,
true, null, null, null, null);
@@ -422,7 +396,7 @@ private void configureItemUIRegistryWithSubpages(State state1, State state2, Sta
}
private void configureItemUIRegistry(State state1, State state2) throws ItemNotFoundException {
- EList mainpageWidgets = new BasicEList<>(widgets.subList(1, 4));
+ List mainpageWidgets = widgets.subList(1, 4);
when(itemUIRegistryMock.getChildren(defaultSitemapMock)).thenReturn(mainpageWidgets);
configureCommonUIRegistryMockMethods();
@@ -440,7 +414,7 @@ private void configureCommonUIRegistryMockMethods() throws ItemNotFoundException
}
private void configureWidgetStatesPage1(State state1, State state2) {
- EList mainpageWidgets = new BasicEList<>(widgets.subList(1, 4));
+ List mainpageWidgets = widgets.subList(1, 4);
Widget w1 = mainpageWidgets.getFirst();
configureItemUIRegistryForWidget(w1, WIDGET1_ID, WIDGET1_ICON, WIDGET1_LABEL, WidgetLabelSource.SITEMAP_WIDGET,
true, "GREEN", "BLUE", "ORANGE", state1);
@@ -474,112 +448,112 @@ private void configureItemUIRegistryForWidget(Widget w, String widgetId, String
when(itemUIRegistryMock.getState(w)).thenReturn(state);
}
- private EList initSitemapWidgets() {
+ private List initSitemapWidgets() {
// Initialize a sitemap containing 2 widgets linked to the same number item,
// one slider and one switch,
// which has one subpage
// add icon rules to the mock widget:
- Class classToMock = IconRule.class;
- IconRule iconRule = mock(classToMock);
+ Class classToMock = Rule.class;
+ Rule iconRule = mock(classToMock);
Condition conditon0 = mock(Condition.class);
when(conditon0.getItem()).thenReturn(ICON_ITEM_NAME);
- EList conditions0 = new BasicEList<>();
+ List conditions0 = new ArrayList<>();
conditions0.add(conditon0);
when(iconRule.getConditions()).thenReturn(conditions0);
- EList iconRulesW1 = new BasicEList<>();
+ List iconRulesW1 = new ArrayList<>();
iconRulesW1.add(iconRule);
// add visibility rules to the mock widget:
- VisibilityRule visibilityRule = mock(VisibilityRule.class);
+ Rule visibilityRule = mock(Rule.class);
Condition conditon = mock(Condition.class);
when(conditon.getItem()).thenReturn(VISIBILITY_RULE_ITEM_NAME);
- EList conditions = new BasicEList<>();
+ List conditions = new ArrayList<>();
conditions.add(conditon);
when(visibilityRule.getConditions()).thenReturn(conditions);
- EList visibilityRulesW1 = new BasicEList<>(1);
+ List visibilityRulesW1 = new ArrayList<>(1);
visibilityRulesW1.add(visibilityRule);
// add label color conditions to the item:
- ColorArray labelColor = mock(ColorArray.class);
+ Rule labelColor = mock(Rule.class);
Condition conditon1 = mock(Condition.class);
when(conditon1.getItem()).thenReturn(LABEL_COLOR_ITEM_NAME);
- EList conditions1 = new BasicEList<>();
+ List conditions1 = new ArrayList<>();
conditions1.add(conditon1);
when(labelColor.getConditions()).thenReturn(conditions1);
- EList labelColorsW1 = new BasicEList<>();
+ List labelColorsW1 = new ArrayList<>();
labelColorsW1.add(labelColor);
// add value color conditions to the item:
- ColorArray valueColor = mock(ColorArray.class);
+ Rule valueColor = mock(Rule.class);
Condition conditon2 = mock(Condition.class);
when(conditon2.getItem()).thenReturn(VALUE_COLOR_ITEM_NAME);
- EList conditions2 = new BasicEList<>();
+ List conditions2 = new ArrayList<>();
conditions2.add(conditon2);
when(valueColor.getConditions()).thenReturn(conditions2);
- EList valueColorsW1 = new BasicEList<>();
+ List valueColorsW1 = new ArrayList<>();
valueColorsW1.add(valueColor);
// add icon color conditions to the item:
- ColorArray iconColor = mock(ColorArray.class);
+ Rule iconColor = mock(Rule.class);
Condition conditon3 = mock(Condition.class);
when(conditon3.getItem()).thenReturn(ICON_COLOR_ITEM_NAME);
- EList conditions3 = new BasicEList<>();
+ List conditions3 = new ArrayList<>();
conditions3.add(conditon3);
when(iconColor.getConditions()).thenReturn(conditions3);
- EList iconColorsW1 = new BasicEList<>();
+ List iconColorsW1 = new ArrayList<>();
iconColorsW1.add(iconColor);
- EClass sliderEClass = mockEClass("slider", "org.openhab.core.model.sitemap.Slider");
+ String sliderType = "Slider";
- Widget w1 = mockWidget(iconRulesW1, visibilityRulesW1, labelColorsW1, valueColorsW1, iconColorsW1, sliderEClass,
- WIDGET1_LABEL, null, null);
+ Widget w1 = mockWidget(iconRulesW1, visibilityRulesW1, labelColorsW1, valueColorsW1, iconColorsW1, sliderType,
+ WIDGET1_LABEL, null, false);
- EList iconRules = new BasicEList<>();
- EList visibilityRules = new BasicEList<>();
- EList labelColors = new BasicEList<>();
- EList valueColors = new BasicEList<>();
- EList iconColors = new BasicEList<>();
+ List iconRules = new ArrayList<>();
+ List visibilityRules = new ArrayList<>();
+ List labelColors = new ArrayList<>();
+ List valueColors = new ArrayList<>();
+ List iconColors = new ArrayList<>();
- EClass switchEClass = mockEClass("switch", "org.openhab.core.model.sitemap.Switch");
+ String switchType = "Switch";
- Widget w2 = mockWidget(iconRules, visibilityRules, labelColors, valueColors, iconColors, switchEClass, null,
- WIDGET2_ICON, null);
+ Widget w2 = mockWidget(iconRules, visibilityRules, labelColors, valueColors, iconColors, switchType, null,
+ WIDGET2_ICON, false);
mock(Widget.class);
- Widget w3 = mockWidget(iconRules, visibilityRules, labelColors, valueColors, iconColors, switchEClass,
- WIDGET3_LABEL, null, WIDGET3_ICON);
+ Widget w3 = mockWidget(iconRules, visibilityRules, labelColors, valueColors, iconColors, switchType,
+ WIDGET3_LABEL, WIDGET3_ICON, true);
- EList widgets = new BasicEList<>(3);
+ List widgets = new ArrayList<>(3);
widgets.add(w1);
widgets.add(w2);
widgets.add(w3);
return widgets;
}
- private EList initSitemapWidgetsWithSubpages() {
- EList baseWidgets = initSitemapWidgets();
+ private List initSitemapWidgetsWithSubpages() {
+ List baseWidgets = initSitemapWidgets();
- EClass groupEClass = mockEClass("group", "org.openhab.core.model.sitemap.Group");
+ String groupType = "Group";
- EList iconRules = new BasicEList<>();
- EList visibilityRules = new BasicEList<>();
- EList labelColors = new BasicEList<>();
- EList valueColors = new BasicEList<>();
- EList iconColors = new BasicEList<>();
+ List iconRules = new ArrayList<>();
+ List visibilityRules = new ArrayList<>();
+ List labelColors = new ArrayList<>();
+ List valueColors = new ArrayList<>();
+ List iconColors = new ArrayList<>();
- Widget group1 = mockGroup(iconRules, visibilityRules, labelColors, valueColors, iconColors, groupEClass,
- GROUP_LABEL, null, GROUP_ICON, baseWidgets);
+ Widget group1 = mockGroup(iconRules, visibilityRules, labelColors, valueColors, iconColors, groupType,
+ GROUP_LABEL, GROUP_ICON, true, baseWidgets);
- EClass switchEClass = mockEClass("switch", "org.openhab.core.model.sitemap.Switch");
+ String switchType = "Switch";
- Widget w4 = mockWidget(iconRules, visibilityRules, labelColors, valueColors, iconColors, switchEClass,
- WIDGET4_LABEL, null, WIDGET4_ICON);
+ Widget w4 = mockWidget(iconRules, visibilityRules, labelColors, valueColors, iconColors, switchType,
+ WIDGET4_LABEL, WIDGET4_ICON, true);
- Widget group2 = mockGroup(iconRules, visibilityRules, labelColors, valueColors, iconColors, groupEClass,
- GROUP_LABEL, null, GROUP_ICON, new BasicEList<>(List.of(w4)));
+ Widget group2 = mockGroup(iconRules, visibilityRules, labelColors, valueColors, iconColors, groupType,
+ GROUP_LABEL, GROUP_ICON, true, new ArrayList<>(List.of(w4)));
- EList allWidgets = new BasicEList<>();
+ List allWidgets = new ArrayList<>();
allWidgets.add(group1);
allWidgets.addAll(baseWidgets);
allWidgets.add(group2);
@@ -587,40 +561,33 @@ private EList initSitemapWidgetsWithSubpages() {
return allWidgets;
}
- private static EClass mockEClass(String eClassName, String eClassInstanceName) {
- EClass sliderEClass = mock(EClass.class);
- when(sliderEClass.getName()).thenReturn(eClassName);
- when(sliderEClass.getInstanceTypeName()).thenReturn(eClassInstanceName);
- return sliderEClass;
- }
-
- private static Widget mockWidget(EList iconRules1, EList visibilityRules1,
- EList labelColors1, EList valueColors1, EList iconColors1,
- EClass eClass, String widgetLabel, String widgetIcon, String widgetStaticIcon) {
+ private static Widget mockWidget(List iconRules1, List visibilityRules1, List labelColors1,
+ List valueColors1, List iconColors1, String widgetType, String widgetLabel, String widgetIcon,
+ boolean widgetStaticIcon) {
Widget w = mock(Widget.class);
- mockWidgetMethods(iconRules1, visibilityRules1, labelColors1, valueColors1, iconColors1, eClass, widgetLabel,
- widgetIcon, widgetStaticIcon, w);
+ mockWidgetMethods(iconRules1, visibilityRules1, labelColors1, valueColors1, iconColors1, widgetType,
+ widgetLabel, widgetIcon, widgetStaticIcon, w);
when(w.getItem()).thenReturn(ITEM_NAME);
return w;
}
- private static Group mockGroup(EList iconRules1, EList visibilityRules1,
- EList labelColors1, EList valueColors1, EList iconColors1,
- EClass eClass, String widgetLabel, String widgetIcon, String widgetStaticIcon, EList children) {
+ private static Group mockGroup(List iconRules1, List visibilityRules1, List labelColors1,
+ List valueColors1, List iconColors1, String widgetType, String widgetLabel, String widgetIcon,
+ boolean widgetStaticIcon, List children) {
Group w = mock(Group.class);
- mockWidgetMethods(iconRules1, visibilityRules1, labelColors1, valueColors1, iconColors1, eClass, widgetLabel,
- widgetIcon, widgetStaticIcon, w);
- when(w.getChildren()).thenReturn(children);
+ mockWidgetMethods(iconRules1, visibilityRules1, labelColors1, valueColors1, iconColors1, widgetType,
+ widgetLabel, widgetIcon, widgetStaticIcon, w);
+ when(w.getWidgets()).thenReturn(children);
return w;
}
- private static void mockWidgetMethods(EList iconRules1, EList visibilityRules1,
- EList labelColors1, EList valueColors1, EList iconColors1,
- EClass eClass, String widgetLabel, String widgetIcon, String widgetStaticIcon, Widget w) {
- when(w.eClass()).thenReturn(eClass);
+ private static void mockWidgetMethods(List iconRules1, List visibilityRules1, List labelColors1,
+ List valueColors1, List iconColors1, String widgetType, String widgetLabel, String widgetIcon,
+ boolean widgetStaticIcon, Widget w) {
+ when(w.getWidgetType()).thenReturn(widgetType);
when(w.getLabel()).thenReturn(widgetLabel);
when(w.getIcon()).thenReturn(widgetIcon);
- when(w.getStaticIcon()).thenReturn(widgetStaticIcon);
+ when(w.isStaticIcon()).thenReturn(widgetStaticIcon);
when(w.getIconRules()).thenReturn(iconRules1);
when(w.getVisibility()).thenReturn(visibilityRules1);
when(w.getLabelColor()).thenReturn(labelColors1);
@@ -634,9 +601,9 @@ private void configureSitemapMock() {
when(defaultSitemapMock.getIcon()).thenReturn("");
}
- private void configureSitemapProviderMock() {
- when(sitemapProviderMock.getSitemapNames()).thenReturn(Set.of(SITEMAP_MODEL_NAME));
- when(sitemapProviderMock.getSitemap(SITEMAP_MODEL_NAME)).thenReturn(defaultSitemapMock);
+ private void configureSitemapRegistryMock() {
+ when(sitemapRegistryMock.getAll()).thenReturn(Set.of(defaultSitemapMock));
+ when(sitemapRegistryMock.get(SITEMAP_NAME)).thenReturn(defaultSitemapMock);
}
private static class TestItem extends GenericItem {
diff --git a/bundles/org.openhab.core.model.sitemap/bnd.bnd b/bundles/org.openhab.core.model.sitemap/bnd.bnd
index 08b21881f9e..d285ed9b799 100644
--- a/bundles/org.openhab.core.model.sitemap/bnd.bnd
+++ b/bundles/org.openhab.core.model.sitemap/bnd.bnd
@@ -13,8 +13,11 @@ Export-Package: org.openhab.core.model.sitemap,\
org.openhab.core.model.sitemap.validation
Import-Package: org.apache.log4j,\
org.eclipse.jdt.annotation;resolution:=optional,\
+ org.openhab.core.common.registry, \
org.openhab.core.items.dto,\
org.openhab.core.model.core,\
+ org.openhab.core.sitemap, \
+ org.openhab.core.sitemap.registry, \
org.eclipse.xtext.xbase.lib,\
org.osgi.framework,\
org.osgi.service.cm,\
diff --git a/bundles/org.openhab.core.model.sitemap/pom.xml b/bundles/org.openhab.core.model.sitemap/pom.xml
index 89dd2addc88..26fe88b22ea 100644
--- a/bundles/org.openhab.core.model.sitemap/pom.xml
+++ b/bundles/org.openhab.core.model.sitemap/pom.xml
@@ -20,6 +20,11 @@
org.openhab.core.model.core
${project.version}
+
+ org.openhab.core.bundles
+ org.openhab.core.sitemap
+ ${project.version}
+
diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext
index a89e623c9df..a7a03e23b1d 100644
--- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext
+++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/Sitemap.xtext
@@ -4,232 +4,225 @@ import "http://www.eclipse.org/emf/2002/Ecore" as ecore
generate sitemap "https://openhab.org/model/Sitemap"
SitemapModel:
- 'sitemap' Sitemap;
+ 'sitemap' ModelSitemap;
-Sitemap:
+ModelSitemap:
name=ID ('label=' label=STRING)? ('icon=' icon=STRING)? '{'
- (children+=Widget)+
+ (children+=ModelWidget)+
'}';
-Widget:
- (LinkableWidget | NonLinkableWidget);
+ModelWidget:
+ (ModelLinkableWidget | ModelNonLinkableWidget);
-NonLinkableWidget:
- Switch | Selection | Slider | Setpoint | Video | Chart | Webview | Colorpicker | Colortemperaturepicker | Mapview | Input | Button | Default;
+ModelNonLinkableWidget:
+ ModelSwitch | ModelSelection | ModelSlider | ModelSetpoint | ModelVideo | ModelChart | ModelWebview | ModelColorpicker | ModelColortemperaturepicker | ModelMapview | ModelInput | ModelButton | ModelDefault;
-LinkableWidget:
- (Text | Group | Image | Frame | Buttongrid)
+ModelLinkableWidget:
+ (ModelText | ModelGroup | ModelImage | ModelFrame | ModelButtongrid)
('{'
- (children+=Widget)+
+ (children+=ModelWidget)+
'}')?;
-Frame:
- {Frame} 'Frame' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Text:
- {Text} 'Text' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Group:
- 'Group' (('item=' item=GroupItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Image:
- 'Image' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('url=' url=STRING)? & ('refresh=' refresh=INT)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Video:
- 'Video' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('url=' url=STRING) & ('encoding=' encoding=STRING)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Chart:
- 'Chart' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('service=' service=STRING)? & ('refresh=' refresh=INT)? & ('period=' period=Period) &
- ('legend=' legend=BOOLEAN_OBJECT)? & ('forceasitem=' forceAsItem=BOOLEAN_OBJECT)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')? &
- ('yAxisDecimalPattern=' yAxisDecimalPattern=(STRING))? &
- ('interpolation=' interpolation=(STRING))?);
-
-Webview:
- 'Webview' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('height=' height=INT)? & ('url=' url=STRING) &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Switch:
- 'Switch' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('mappings=[' mappings+=Mapping (',' mappings+=Mapping)* ']')? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Mapview:
- 'Mapview' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('height=' height=INT)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Slider:
- 'Slider' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- (switchEnabled?='switchSupport')? & (releaseOnly?='releaseOnly')? &
- ('minValue=' minValue=Number)? & ('maxValue=' maxValue=Number)? & ('step=' step=Number)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Selection:
- 'Selection' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('mappings=[' mappings+=Mapping (',' mappings+=Mapping)* ']')? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Setpoint:
- 'Setpoint' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('minValue=' minValue=Number)? & ('maxValue=' maxValue=Number)? & ('step=' step=Number)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Colorpicker:
- 'Colorpicker' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Colortemperaturepicker:
- 'Colortemperaturepicker' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('minValue=' minValue=Number)? & ('maxValue=' maxValue=Number)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Input:
- 'Input' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('inputHint=' inputHint=STRING)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Buttongrid:
- {Buttongrid} 'Buttongrid' (('item=' item=ItemRef)? & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('buttons=[' buttons+=ButtonDefinition (',' buttons+=ButtonDefinition)* ']')? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Button:
- 'Button' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('row=' row=INT) & ('column=' column=INT) & (stateless?='stateless')? &
- ('click=' cmd=Command) & ('release=' releaseCmd=Command)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-Default:
- 'Default' (('item=' item=ItemRef) & ('label=' label=(ID | STRING))? &
- (('icon=' icon=Icon) |
- ('icon=[' (IconRules+=IconRule (',' IconRules+=IconRule)*) ']') |
- ('staticIcon=' staticIcon=Icon))? &
- ('height=' height=INT)? &
- ('labelcolor=[' (LabelColor+=ColorArray (',' LabelColor+=ColorArray)*) ']')? &
- ('valuecolor=[' (ValueColor+=ColorArray (',' ValueColor+=ColorArray)*) ']')? &
- ('iconcolor=[' (IconColor+=ColorArray (',' IconColor+=ColorArray)*) ']')? &
- ('visibility=[' (Visibility+=VisibilityRule (',' Visibility+=VisibilityRule)*) ']')?);
-
-ButtonDefinition:
+ModelFrame:
+ {ModelFrame} 'Frame' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelText:
+ {ModelText} 'Text' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelGroup:
+ 'Group' (('item=' item=GroupItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelImage:
+ 'Image' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('url=' url=STRING) | ('refresh=' refresh=INT) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelVideo:
+ 'Video' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('url=' url=STRING) | ('encoding=' encoding=STRING) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelChart:
+ 'Chart' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('service=' service=STRING) | ('refresh=' refresh=INT) | ('period=' period=Period) |
+ ('legend=' legend=BOOLEAN_OBJECT) | ('forceasitem=' forceAsItem=BOOLEAN_OBJECT) |
+ ('yAxisDecimalPattern=' yAxisDecimalPattern=(STRING)) |
+ ('interpolation=' interpolation=(STRING)) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelWebview:
+ 'Webview' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('height=' height=INT) | ('url=' url=STRING) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelSwitch:
+ 'Switch' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('mappings=' mappings=ModelMappingList) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelMapview:
+ 'Mapview' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('height=' height=INT) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelSlider:
+ 'Slider' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ (switchEnabled?='switchSupport') | (releaseOnly?='releaseOnly') |
+ ('minValue=' minValue=Number) | ('maxValue=' maxValue=Number) | ('step=' step=Number) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelSelection:
+ 'Selection' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('mappings=' mappings=ModelMappingList) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelSetpoint:
+ 'Setpoint' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('minValue=' minValue=Number) | ('maxValue=' maxValue=Number) | ('step=' step=Number) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelColorpicker:
+ 'Colorpicker' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelColortemperaturepicker:
+ 'Colortemperaturepicker' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('minValue=' minValue=Number) | ('maxValue=' maxValue=Number) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelInput:
+ 'Input' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('inputHint=' inputHint=STRING) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelButtongrid:
+ {ModelButtongrid} 'Buttongrid' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('buttons=' buttons=ModelButtonDefinitionList) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelButton:
+ 'Button' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('row=' row=INT) | ('column=' column=INT) | (stateless?='stateless') |
+ ('click=' cmd=Command) | ('release=' releaseCmd=Command) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelDefault:
+ 'Default' (('item=' item=ItemRef) | ('label=' label=(ID | STRING)) |
+ ('icon=' icon=Icon) | ('icon=' IconRules=ModelIconRuleList) | ('staticIcon=' staticIcon=Icon) |
+ ('height=' height=INT) |
+ ('labelcolor=' labelColor=ModelColorArrayList) |
+ ('valuecolor=' valueColor=ModelColorArrayList) |
+ ('iconcolor=' iconColor=ModelColorArrayList) |
+ ('visibility=' visibility=ModelVisibilityRuleList))*;
+
+ModelButtonDefinitionList returns ModelButtonDefinitionList:
+ '[' elements+=ModelButtonDefinition (',' elements+=ModelButtonDefinition)* ']'
+;
+
+ModelButtonDefinition:
row=INT ':' column=INT ':' cmd=Command '=' label=(ID | STRING) ('=' icon=Icon)?;
-Mapping:
+ModelMappingList returns ModelMappingList:
+ '[' elements+=ModelMapping (',' elements+=ModelMapping)* ']'
+;
+
+ModelMapping:
cmd=Command (':' releaseCmd=Command)? '=' label=(ID | STRING) ('=' icon=Icon)?;
-VisibilityRule:
- conditions+=Condition ('AND' conditions+=Condition)*;
+ModelColorArrayList returns ModelColorArrayList:
+ '[' elements+=ModelColorArray (',' elements+=ModelColorArray)* ']'
+;
+
+ModelColorArray:
+ ((conditions+=ModelCondition ('AND' conditions+=ModelCondition)*) '=')? (arg=STRING);
+
+ModelIconRuleList returns ModelIconRuleList:
+ '[' elements+=ModelIconRule (',' elements+=ModelIconRule)* ']'
+;
+
+ModelIconRule:
+ ((conditions+=ModelCondition ('AND' conditions+=ModelCondition)*) '=')? (arg=Icon);
+
+ModelVisibilityRuleList returns ModelVisibilityRuleList:
+ '[' elements+=ModelVisibilityRule (',' elements+=ModelVisibilityRule)* ']'
+;
+
+ModelVisibilityRule:
+ conditions+=ModelCondition ('AND' conditions+=ModelCondition)*;
+
+ModelCondition:
+ (item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState);
ItemRef:
ID;
@@ -247,15 +240,6 @@ IconName:
Period:
(ID '-')? ID;
-ColorArray:
- ((conditions+=Condition ('AND' conditions+=Condition)*) '=')? (arg=STRING);
-
-IconRule:
- ((conditions+=Condition ('AND' conditions+=Condition)*) '=')? (arg=Icon);
-
-Condition:
- (item=ID)? (condition=('==' | '>' | '<' | '>=' | '<=' | '!='))? (sign=('-' | '+'))? (state=XState);
-
Command returns ecore::EString:
Number | ID | STRING;
diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/SitemapProvider.java b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/SitemapProvider.java
deleted file mode 100644
index 8bb8c8fad59..00000000000
--- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/SitemapProvider.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2010-2025 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.core.model.sitemap;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.model.core.ModelRepositoryChangeListener;
-import org.openhab.core.model.sitemap.sitemap.Sitemap;
-
-@NonNullByDefault
-public interface SitemapProvider {
-
- /**
- * This method provides access to sitemap model files, loads them and returns the object model tree.
- *
- * @param sitemapName the name of the sitemap to load
- * @return the object model tree, null if it is not found
- */
- @Nullable
- Sitemap getSitemap(String sitemapName);
-
- /**
- * Returns the names of all available sitemaps
- *
- * @return names of provided sitemaps
- */
- Set getSitemapNames();
-
- /**
- * Add a listener which will be informed subsequently once a model has changed
- *
- * @param listener
- */
- void addModelChangeListener(ModelRepositoryChangeListener listener);
-
- /**
- * Remove a model change listener again
- *
- * @param listener
- */
- void removeModelChangeListener(ModelRepositoryChangeListener listener);
-}
diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java
index 302a1522ff7..cb211c95757 100644
--- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java
+++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/internal/SitemapProviderImpl.java
@@ -12,20 +12,74 @@
*/
package org.openhab.core.model.sitemap.internal;
+import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.stream.Collectors;
+import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.common.registry.AbstractProvider;
+import org.openhab.core.common.registry.ProviderChangeListener;
import org.openhab.core.model.core.EventType;
import org.openhab.core.model.core.ModelRepository;
import org.openhab.core.model.core.ModelRepositoryChangeListener;
-import org.openhab.core.model.sitemap.SitemapProvider;
-import org.openhab.core.model.sitemap.sitemap.Sitemap;
+import org.openhab.core.model.sitemap.sitemap.ModelButton;
+import org.openhab.core.model.sitemap.sitemap.ModelButtonDefinition;
+import org.openhab.core.model.sitemap.sitemap.ModelButtonDefinitionList;
+import org.openhab.core.model.sitemap.sitemap.ModelButtongrid;
+import org.openhab.core.model.sitemap.sitemap.ModelChart;
+import org.openhab.core.model.sitemap.sitemap.ModelColorArray;
+import org.openhab.core.model.sitemap.sitemap.ModelColorArrayList;
+import org.openhab.core.model.sitemap.sitemap.ModelColortemperaturepicker;
+import org.openhab.core.model.sitemap.sitemap.ModelCondition;
+import org.openhab.core.model.sitemap.sitemap.ModelDefault;
+import org.openhab.core.model.sitemap.sitemap.ModelIconRule;
+import org.openhab.core.model.sitemap.sitemap.ModelIconRuleList;
+import org.openhab.core.model.sitemap.sitemap.ModelImage;
+import org.openhab.core.model.sitemap.sitemap.ModelInput;
+import org.openhab.core.model.sitemap.sitemap.ModelLinkableWidget;
+import org.openhab.core.model.sitemap.sitemap.ModelMapping;
+import org.openhab.core.model.sitemap.sitemap.ModelMappingList;
+import org.openhab.core.model.sitemap.sitemap.ModelMapview;
+import org.openhab.core.model.sitemap.sitemap.ModelSelection;
+import org.openhab.core.model.sitemap.sitemap.ModelSetpoint;
+import org.openhab.core.model.sitemap.sitemap.ModelSitemap;
+import org.openhab.core.model.sitemap.sitemap.ModelSlider;
+import org.openhab.core.model.sitemap.sitemap.ModelSwitch;
+import org.openhab.core.model.sitemap.sitemap.ModelVideo;
+import org.openhab.core.model.sitemap.sitemap.ModelVisibilityRule;
+import org.openhab.core.model.sitemap.sitemap.ModelVisibilityRuleList;
+import org.openhab.core.model.sitemap.sitemap.ModelWebview;
+import org.openhab.core.model.sitemap.sitemap.ModelWidget;
+import org.openhab.core.sitemap.Button;
+import org.openhab.core.sitemap.ButtonDefinition;
+import org.openhab.core.sitemap.Buttongrid;
+import org.openhab.core.sitemap.Chart;
+import org.openhab.core.sitemap.Colortemperaturepicker;
+import org.openhab.core.sitemap.Condition;
+import org.openhab.core.sitemap.Default;
+import org.openhab.core.sitemap.Image;
+import org.openhab.core.sitemap.Input;
+import org.openhab.core.sitemap.LinkableWidget;
+import org.openhab.core.sitemap.Mapping;
+import org.openhab.core.sitemap.Mapview;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Rule;
+import org.openhab.core.sitemap.Selection;
+import org.openhab.core.sitemap.Setpoint;
+import org.openhab.core.sitemap.Sitemap;
+import org.openhab.core.sitemap.Slider;
+import org.openhab.core.sitemap.Switch;
+import org.openhab.core.sitemap.Video;
+import org.openhab.core.sitemap.Webview;
+import org.openhab.core.sitemap.Widget;
+import org.openhab.core.sitemap.registry.SitemapFactory;
+import org.openhab.core.sitemap.registry.SitemapProvider;
+import org.openhab.core.sitemap.registry.SitemapRegistry;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
@@ -37,94 +91,337 @@
* This class provides access to the sitemap model files.
*
* @author Kai Kreuzer - Initial contribution
+ * @author Mark Herwege - Separate registry from model
*/
@NonNullByDefault
-@Component(service = SitemapProvider.class)
-public class SitemapProviderImpl implements SitemapProvider, ModelRepositoryChangeListener {
+@Component(service = SitemapProvider.class, immediate = true)
+public class SitemapProviderImpl extends AbstractProvider
+ implements SitemapProvider, ModelRepositoryChangeListener {
private static final String SITEMAP_MODEL_NAME = "sitemap";
protected static final String SITEMAP_FILEEXT = "." + SITEMAP_MODEL_NAME;
+ protected static final String MODEL_TYPE_PREFIX = "Model";
private final Logger logger = LoggerFactory.getLogger(SitemapProviderImpl.class);
private final ModelRepository modelRepo;
+ private final SitemapRegistry sitemapRegistry;
+ private final SitemapFactory sitemapFactory;
- private final Map sitemapModelCache = new ConcurrentHashMap<>();
-
- private final Set modelChangeListeners = new CopyOnWriteArraySet<>();
+ private final Map sitemapCache = new ConcurrentHashMap<>();
@Activate
- public SitemapProviderImpl(final @Reference ModelRepository modelRepo) {
+ public SitemapProviderImpl(final @Reference ModelRepository modelRepo,
+ final @Reference SitemapRegistry sitemapRegistry, final @Reference SitemapFactory sitemapFactory) {
this.modelRepo = modelRepo;
+ this.sitemapRegistry = sitemapRegistry;
+ this.sitemapFactory = sitemapFactory;
refreshSitemapModels();
+ sitemapRegistry.addSitemapProvider(this);
modelRepo.addModelRepositoryChangeListener(this);
}
@Deactivate
protected void deactivate() {
modelRepo.removeModelRepositoryChangeListener(this);
- sitemapModelCache.clear();
+ sitemapRegistry.removeSitemapProvider(this);
+ sitemapCache.clear();
}
@Override
public @Nullable Sitemap getSitemap(String sitemapName) {
- String filename = sitemapName + SITEMAP_FILEEXT;
- Sitemap sitemap = sitemapModelCache.get(filename);
+ Sitemap sitemap = sitemapCache.get(sitemapName);
if (sitemap == null) {
logger.trace("Sitemap {} cannot be found", sitemapName);
- return null;
}
+ return sitemap;
+ }
- if (!sitemap.getName().equals(sitemapName)) {
- logger.warn(
- "Filename `{}` does not match the name `{}` of the sitemap - please fix this as you might see unexpected behavior otherwise.",
- filename, sitemap.getName());
+ private void refreshSitemapModels() {
+ sitemapCache.clear();
+ Iterable sitemapFilenames = modelRepo.getAllModelNamesOfType(SITEMAP_MODEL_NAME);
+ for (String filename : sitemapFilenames) {
+ ModelSitemap modelSitemap = (ModelSitemap) modelRepo.getModel(filename);
+ if (modelSitemap != null) {
+ String sitemapName = filename.substring(0, filename.length() - SITEMAP_FILEEXT.length());
+ if (!modelSitemap.getName().equals(sitemapName)) {
+ logger.warn(
+ "Filename `{}` does not match the name `{}` of the sitemap - please fix this as you might see unexpected behavior otherwise.",
+ filename, modelSitemap.getName());
+ }
+ Sitemap sitemap = parseModelSitemap(modelSitemap);
+ sitemapCache.put(sitemapName, sitemap);
+ }
}
+ }
+
+ private Sitemap parseModelSitemap(ModelSitemap modelSitemap) {
+ Sitemap sitemap = sitemapFactory.createSitemap(modelSitemap.getName());
+ sitemap.setLabel(modelSitemap.getLabel());
+ sitemap.setIcon(modelSitemap.getIcon());
+ List widgets = sitemap.getWidgets();
+ modelSitemap.getChildren().forEach(child -> addWidget(widgets, child, sitemap));
return sitemap;
}
+ private void addWidget(List widgets, ModelWidget modelWidget, Parent parent) {
+ String widgetType = getWidgetType(modelWidget);
+ Widget widget = sitemapFactory.createWidget(widgetType, parent);
+ if (widget != null) {
+ switch (widget) {
+ case Image imageWidget:
+ ModelImage modelImage = (ModelImage) modelWidget;
+ imageWidget.setUrl(modelImage.getUrl());
+ imageWidget.setRefresh(modelImage.getRefresh());
+ break;
+ case Video videoWidget:
+ ModelVideo modelVideo = (ModelVideo) modelWidget;
+ videoWidget.setUrl(modelVideo.getUrl());
+ videoWidget.setEncoding(modelVideo.getEncoding());
+ break;
+ case Chart chartWidget:
+ ModelChart modelChart = (ModelChart) modelWidget;
+ chartWidget.setService(modelChart.getService());
+ chartWidget.setRefresh(modelChart.getRefresh());
+ chartWidget.setPeriod(modelChart.getPeriod());
+ chartWidget.setLegend(modelChart.getLegend());
+ chartWidget.setForceAsItem(modelChart.getForceAsItem());
+ chartWidget.setYAxisDecimalPattern(modelChart.getYAxisDecimalPattern());
+ chartWidget.setInterpolation(modelChart.getInterpolation());
+ break;
+ case Webview webviewWidget:
+ ModelWebview modelWebview = (ModelWebview) modelWidget;
+ webviewWidget.setHeight(modelWebview.getHeight());
+ webviewWidget.setUrl(modelWebview.getUrl());
+ break;
+ case Switch switchWidget:
+ ModelSwitch modelSwitch = (ModelSwitch) modelWidget;
+ addWidgetMappings(switchWidget.getMappings(), modelSwitch.getMappings());
+ break;
+ case Mapview mapviewWidget:
+ ModelMapview modelMapview = (ModelMapview) modelWidget;
+ mapviewWidget.setHeight(modelMapview.getHeight());
+ break;
+ case Slider sliderWidget:
+ ModelSlider modelSlider = (ModelSlider) modelWidget;
+ sliderWidget.setMinValue(modelSlider.getMinValue());
+ sliderWidget.setMaxValue(modelSlider.getMaxValue());
+ sliderWidget.setStep(modelSlider.getStep());
+ sliderWidget.setSwitchEnabled(modelSlider.isSwitchEnabled());
+ sliderWidget.setReleaseOnly(modelSlider.isReleaseOnly());
+ break;
+ case Selection selectionWidget:
+ ModelSelection modelSelection = (ModelSelection) modelWidget;
+ addWidgetMappings(selectionWidget.getMappings(), modelSelection.getMappings());
+ break;
+ case Input inputWidget:
+ ModelInput modelInput = (ModelInput) modelWidget;
+ inputWidget.setInputHint(modelInput.getInputHint());
+ break;
+ case Setpoint setpointWidget:
+ ModelSetpoint modelSetpoint = (ModelSetpoint) modelWidget;
+ setpointWidget.setMinValue(modelSetpoint.getMinValue());
+ setpointWidget.setMaxValue(modelSetpoint.getMaxValue());
+ setpointWidget.setStep(modelSetpoint.getStep());
+ break;
+ case Colortemperaturepicker colortemperaturepickerWidget:
+ ModelColortemperaturepicker modelColortemperaturepicker = (ModelColortemperaturepicker) modelWidget;
+ colortemperaturepickerWidget.setMinValue(modelColortemperaturepicker.getMinValue());
+ colortemperaturepickerWidget.setMaxValue(modelColortemperaturepicker.getMaxValue());
+ break;
+ case Buttongrid buttongridWidget:
+ ModelButtongrid modelButtongrid = (ModelButtongrid) modelWidget;
+ addWidgetButtons(buttongridWidget.getButtons(), modelButtongrid.getButtons());
+ break;
+ case Button buttonWidget:
+ ModelButton modelButton = (ModelButton) modelWidget;
+ buttonWidget.setRow(modelButton.getRow());
+ buttonWidget.setColumn(modelButton.getColumn());
+ buttonWidget.setStateless(modelButton.isStateless());
+ buttonWidget.setCmd(modelButton.getCmd());
+ buttonWidget.setReleaseCmd(modelButton.getReleaseCmd());
+ break;
+ case Default defaultWidget:
+ ModelDefault modelDefault = (ModelDefault) modelWidget;
+ defaultWidget.setHeight(modelDefault.getHeight());
+ break;
+ default:
+ break;
+ }
+
+ widget.setItem(modelWidget.getItem());
+ widget.setLabel(modelWidget.getLabel());
+ String staticIcon = modelWidget.getStaticIcon();
+ if (staticIcon != null && !staticIcon.isEmpty()) {
+ widget.setIcon(staticIcon);
+ widget.setStaticIcon(true);
+ } else {
+ widget.setIcon(modelWidget.getIcon());
+ }
+
+ if (modelWidget instanceof ModelLinkableWidget modelLinkableWidget) {
+ LinkableWidget linkableWidget = (LinkableWidget) widget;
+ List childWidgets = linkableWidget.getWidgets();
+ modelLinkableWidget.getChildren()
+ .forEach(childModelWidget -> addWidget(childWidgets, childModelWidget, linkableWidget));
+ }
+
+ addWidgetVisibilityRules(widget.getVisibility(), modelWidget.getVisibility());
+ addWidgetColorRules(widget.getLabelColor(), modelWidget.getLabelColor());
+ addWidgetColorRules(widget.getValueColor(), modelWidget.getValueColor());
+ addWidgetColorRules(widget.getIconColor(), modelWidget.getIconColor());
+ addWidgetIconRules(widget.getIconRules(), modelWidget.getIconRules());
+
+ widgets.add(widget);
+ }
+ }
+
+ private String getWidgetType(ModelWidget modelWidget) {
+ String instanceTypeName = modelWidget.eClass().getInstanceTypeName();
+ String widgetType = instanceTypeName
+ .substring(instanceTypeName.lastIndexOf("." + MODEL_TYPE_PREFIX) + MODEL_TYPE_PREFIX.length() + 1);
+ return widgetType;
+ }
+
+ private void addWidgetMappings(List mappings, @Nullable ModelMappingList modelMappingList) {
+ if (modelMappingList != null) {
+ EList modelMappings = modelMappingList.getElements();
+ modelMappings.forEach(modelMapping -> {
+ Mapping mapping = sitemapFactory.createMapping();
+ mapping.setCmd(modelMapping.getCmd());
+ mapping.setReleaseCmd(modelMapping.getReleaseCmd());
+ mapping.setLabel(modelMapping.getLabel());
+ mapping.setIcon(modelMapping.getIcon());
+ mappings.add(mapping);
+ });
+ }
+ }
+
+ private void addWidgetButtons(List buttons, @Nullable ModelButtonDefinitionList modelButtonList) {
+ if (modelButtonList != null) {
+ EList modelButtons = modelButtonList.getElements();
+ modelButtons.forEach(modelButton -> {
+ ButtonDefinition button = sitemapFactory.createButtonDefinition();
+ button.setRow(modelButton.getRow());
+ button.setColumn(modelButton.getColumn());
+ button.setCmd(modelButton.getCmd());
+ button.setLabel(modelButton.getLabel());
+ button.setIcon(modelButton.getIcon());
+ buttons.add(button);
+ });
+ }
+ }
+
+ private void addWidgetVisibilityRules(List visibilityRules,
+ @Nullable ModelVisibilityRuleList modelVisibilityRuleList) {
+ if (modelVisibilityRuleList != null) {
+ EList modelVisibilityRules = modelVisibilityRuleList.getElements();
+ modelVisibilityRules.forEach(modelVisibilityRule -> {
+ Rule visibilityRule = sitemapFactory.createRule();
+ addRuleConditions(visibilityRule.getConditions(), modelVisibilityRule.getConditions());
+ visibilityRules.add(visibilityRule);
+ });
+ }
+ }
+
+ private void addWidgetColorRules(List colorRules, @Nullable ModelColorArrayList modelColorRuleList) {
+ if (modelColorRuleList != null) {
+ EList modelColorRules = modelColorRuleList.getElements();
+ modelColorRules.forEach(modelColorRule -> {
+ Rule colorRule = sitemapFactory.createRule();
+ addRuleConditions(colorRule.getConditions(), modelColorRule.getConditions());
+ colorRules.add(colorRule);
+ });
+ }
+ }
+
+ private void addWidgetIconRules(List iconRules, @Nullable ModelIconRuleList modelIconRuleList) {
+ if (modelIconRuleList != null) {
+ EList modelIconRules = modelIconRuleList.getElements();
+ modelIconRules.forEach(modelIconRule -> {
+ Rule iconRule = sitemapFactory.createRule();
+ iconRule.setArgument(modelIconRule.getArg());
+ addRuleConditions(iconRule.getConditions(), modelIconRule.getConditions());
+ iconRules.add(iconRule);
+ });
+ }
+ }
+
+ private void addRuleConditions(List conditions, EList modelConditions) {
+ modelConditions.forEach(modelCondition -> {
+ Condition condition = sitemapFactory.createCondition();
+ condition.setItem(modelCondition.getItem());
+ condition.setCondition(modelCondition.getCondition());
+ String sign = modelCondition.getSign();
+ String value = (sign != null ? sign : "") + modelCondition.getState();
+ condition.setValue(value);
+ conditions.add(condition);
+ });
+ }
+
@Override
public Set getSitemapNames() {
- return sitemapModelCache.keySet().stream()
- .map(name -> name.substring(0, name.length() - SITEMAP_FILEEXT.length())).collect(Collectors.toSet());
+ return sitemapCache.keySet();
}
@Override
public void modelChanged(String modelName, EventType type) {
- if (modelName.endsWith(SITEMAP_FILEEXT)) {
- if (type == EventType.REMOVED) {
- sitemapModelCache.remove(modelName);
- } else {
- EObject sitemap = modelRepo.getModel(modelName);
- // if the sitemap file is empty it will not be in the repo and thus there is no need to cache it here
- if (sitemap instanceof Sitemap sitemap1) {
- sitemapModelCache.put(modelName, sitemap1);
- }
+ if (!modelName.endsWith(SITEMAP_FILEEXT)) {
+ return;
+ }
+
+ Sitemap sitemap = null;
+ String sitemapName = modelName.substring(0, modelName.length() - SITEMAP_FILEEXT.length());
+ Sitemap oldSitemap = sitemapRegistry.get(sitemapName);
+
+ if (type == EventType.REMOVED) {
+ sitemapCache.remove(sitemapName);
+ } else {
+ EObject modelSitemapObject = modelRepo.getModel(modelName);
+ // if the sitemap file is empty it will not be in the repo and thus there is no need to cache it here
+ if (modelSitemapObject instanceof ModelSitemap modelSitemap) {
+ sitemap = parseModelSitemap(modelSitemap);
+ sitemapCache.put(sitemapName, sitemap);
}
}
- for (ModelRepositoryChangeListener listener : modelChangeListeners) {
- listener.modelChanged(modelName, type);
+
+ switch (type) {
+ case EventType.ADDED:
+ if (sitemap != null) {
+ notifyListenersAboutAddedElement(sitemap);
+ }
+ break;
+ case EventType.REMOVED:
+ if (oldSitemap != null) {
+ notifyListenersAboutRemovedElement(oldSitemap);
+ }
+ break;
+ case EventType.MODIFIED:
+ if (sitemap != null && oldSitemap != null) {
+ notifyListenersAboutUpdatedElement(oldSitemap, sitemap);
+ }
+ break;
}
}
- private void refreshSitemapModels() {
- sitemapModelCache.clear();
- Iterable sitemapNames = modelRepo.getAllModelNamesOfType(SITEMAP_MODEL_NAME);
- for (String sitemapName : sitemapNames) {
- Sitemap sitemap = (Sitemap) modelRepo.getModel(sitemapName);
- if (sitemap != null) {
- sitemapModelCache.put(sitemapName, sitemap);
- }
- }
+ @Override
+ public Collection getAll() {
+ return sitemapCache.values();
}
@Override
- public void addModelChangeListener(ModelRepositoryChangeListener listener) {
- modelChangeListeners.add(listener);
+ public void addProviderChangeListener(ProviderChangeListener listener) {
+ super.addProviderChangeListener(listener);
+ getAll().forEach(sitemap -> {
+ notifyListenersAboutAddedElement(sitemap);
+ });
}
@Override
- public void removeModelChangeListener(ModelRepositoryChangeListener listener) {
- modelChangeListeners.remove(listener);
+ public void removeProviderChangeListener(ProviderChangeListener listener) {
+ super.removeProviderChangeListener(listener);
+ getAll().forEach(sitemap -> {
+ notifyListenersAboutRemovedElement(sitemap);
+ });
}
}
diff --git a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend
index 3749f783bc6..6637b6163d4 100644
--- a/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend
+++ b/bundles/org.openhab.core.model.sitemap/src/org/openhab/core/model/sitemap/validation/SitemapValidator.xtend
@@ -15,20 +15,25 @@
*/
package org.openhab.core.model.sitemap.validation
-import org.openhab.core.model.sitemap.sitemap.Button
-import org.openhab.core.model.sitemap.sitemap.Buttongrid
-import org.openhab.core.model.sitemap.sitemap.Colortemperaturepicker
-import org.openhab.core.model.sitemap.sitemap.Frame
-import org.openhab.core.model.sitemap.sitemap.LinkableWidget
-import org.openhab.core.model.sitemap.sitemap.Setpoint
-import org.openhab.core.model.sitemap.sitemap.Sitemap
-import org.openhab.core.model.sitemap.sitemap.SitemapPackage
-import org.openhab.core.model.sitemap.sitemap.Widget
import org.eclipse.xtext.validation.Check
import java.math.BigDecimal
-import org.openhab.core.model.sitemap.sitemap.Input
import org.eclipse.xtext.nodemodel.util.NodeModelUtils
-import org.openhab.core.model.sitemap.sitemap.Chart
+import org.openhab.core.model.sitemap.sitemap.ModelWidget
+import org.openhab.core.model.sitemap.sitemap.ModelFrame
+import org.openhab.core.model.sitemap.sitemap.ModelText
+import org.openhab.core.model.sitemap.sitemap.ModelImage
+import org.openhab.core.model.sitemap.sitemap.ModelVideo
+import org.openhab.core.model.sitemap.sitemap.ModelWebview
+import org.openhab.core.model.sitemap.sitemap.ModelButtongrid
+import org.openhab.core.model.sitemap.sitemap.ModelButton
+import org.openhab.core.model.sitemap.sitemap.ModelLinkableWidget
+import org.openhab.core.model.sitemap.sitemap.ModelSetpoint
+import org.openhab.core.model.sitemap.sitemap.ModelSlider
+import org.openhab.core.model.sitemap.sitemap.ModelColortemperaturepicker
+import org.openhab.core.model.sitemap.sitemap.ModelInput
+import org.openhab.core.model.sitemap.sitemap.ModelChart
+import org.openhab.core.model.sitemap.sitemap.SitemapPackage
+import org.openhab.core.model.sitemap.sitemap.ModelSitemap
//import org.eclipse.xtext.validation.Check
/**
@@ -40,137 +45,207 @@ class SitemapValidator extends AbstractSitemapValidator {
val ALLOWED_HINTS = #["text", "number", "date", "time", "datetime"]
val ALLOWED_INTERPOLATION = #["linear", "step"]
+
+ @Check
+ def void checkWidgetHasItem(ModelWidget w) {
+ if (!(w instanceof ModelFrame || w instanceof ModelText || w instanceof ModelImage || w instanceof ModelVideo || w instanceof ModelWebview || w instanceof ModelButtongrid) && w.item === null) {
+ val node = NodeModelUtils.getNode(w)
+ val line = node.getStartLine()
+ error("'" + w.getClass().getSimpleName() + "' widget doesn't have item defined at line " + line,
+ SitemapPackage.Literals.MODEL_INPUT.getEStructuralFeature(SitemapPackage.MODEL_WIDGET))
+ }
+ }
+
+ @Check
+ def void checkWidgetIcon(ModelWidget w) {
+ if ((w.icon !== null || w.iconRules !== null) && w.staticIcon !== null) {
+ val node = NodeModelUtils.getNode(w)
+ val line = node.getStartLine()
+ error("Widget '" + w.getClass().getSimpleName() + "' has icon '" + w.icon + "' and staticIcon '" + w.staticIcon + "' defined at the same time " + line,
+ SitemapPackage.Literals.MODEL_INPUT.getEStructuralFeature(SitemapPackage.MODEL_WIDGET))
+ }
+ if (w.icon !== null && w.iconRules !== null) {
+ val node = NodeModelUtils.getNode(w)
+ val line = node.getStartLine()
+ error("Widget '" + w.getClass().getSimpleName() + "' has icon '" + w.icon + "' and icon rules '" + w.iconRules + "' defined at the same time " + line,
+ SitemapPackage.Literals.MODEL_INPUT.getEStructuralFeature(SitemapPackage.MODEL_WIDGET))
+ }
+ }
@Check
- def void checkFramesInFrame(Frame frame) {
- for (Widget w : frame.children) {
- if (w instanceof Frame) {
- error("Frames must not contain other frames",
- SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
+ def void checkFramesInFrame(ModelFrame frame) {
+ for (ModelWidget w : frame.children) {
+ val node = NodeModelUtils.getNode(w)
+ val line = node.getStartLine()
+ if (w instanceof ModelFrame) {
+ error("Frames must not contain other frames at line " + line,
+ SitemapPackage.Literals.MODEL_FRAME.getEStructuralFeature(SitemapPackage.MODEL_FRAME__CHILDREN));
return;
}
- if (w instanceof Button) {
- error("Frames should not contain Button, Button is allowed only in Buttongrid",
- SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.FRAME__CHILDREN));
+ if (w instanceof ModelButton) {
+ error("Frames should not contain Button, Button is allowed only in Buttongrid at line " + line,
+ SitemapPackage.Literals.MODEL_FRAME.getEStructuralFeature(SitemapPackage.MODEL_FRAME__CHILDREN));
return;
}
}
}
@Check
- def void checkFramesInWidgetList(Sitemap sitemap) {
+ def void checkFramesInWidgetList(ModelSitemap sitemap) {
var containsFrames = false
var containsOtherWidgets = false
- for (Widget w : sitemap.children) {
- if (w instanceof Button) {
- error("Sitemap should not contain Button, Button is allowed only in Buttongrid",
- SitemapPackage.Literals.SITEMAP.getEStructuralFeature(SitemapPackage.SITEMAP__NAME));
+ for (ModelWidget w : sitemap.children) {
+ val node = NodeModelUtils.getNode(w)
+ val line = node.getStartLine()
+ if (w instanceof ModelButton) {
+ error("Sitemap should not contain Button, Button is allowed only in Buttongrid at line " + line,
+ SitemapPackage.Literals.MODEL_SITEMAP.getEStructuralFeature(SitemapPackage.MODEL_SITEMAP__NAME));
return;
}
- if (w instanceof Frame) {
+ if (w instanceof ModelFrame) {
containsFrames = true
} else {
containsOtherWidgets = true
}
if (containsFrames && containsOtherWidgets) {
- error("Sitemap should contain either only frames or none at all",
- SitemapPackage.Literals.SITEMAP.getEStructuralFeature(SitemapPackage.SITEMAP__NAME));
+ error("Sitemap should contain either only frames or none at all at line " + line,
+ SitemapPackage.Literals.MODEL_SITEMAP.getEStructuralFeature(SitemapPackage.MODEL_SITEMAP__NAME));
return
}
}
}
@Check
- def void checkFramesInWidgetList(LinkableWidget widget) {
- if (widget instanceof Frame) {
+ def void checkFramesInWidgetList(ModelLinkableWidget widget) {
+ if (widget instanceof ModelFrame) {
// we have a dedicated check for frames in place
return;
}
- if (widget instanceof Buttongrid) {
+ if (widget instanceof ModelButtongrid) {
// we have a dedicated check for Buttongrid in place
return;
}
var containsFrames = false
var containsOtherWidgets = false
- for (Widget w : widget.children) {
- if (w instanceof Button) {
- error("Linkable widget should not contain Button, Button is allowed only in Buttongrid",
- SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.LINKABLE_WIDGET__CHILDREN));
+ for (ModelWidget w : widget.children) {
+ val node = NodeModelUtils.getNode(w)
+ val line = node.getStartLine()
+ if (w instanceof ModelButton) {
+ error("Linkable widget should not contain Button, Button is allowed only in Buttongrid at line " + line,
+ SitemapPackage.Literals.MODEL_FRAME.getEStructuralFeature(SitemapPackage.MODEL_LINKABLE_WIDGET__CHILDREN));
return;
}
- if (w instanceof Frame) {
+ if (w instanceof ModelFrame) {
containsFrames = true
} else {
containsOtherWidgets = true
}
if (containsFrames && containsOtherWidgets) {
- error("Linkable widget should contain either only frames or none at all",
- SitemapPackage.Literals.FRAME.getEStructuralFeature(SitemapPackage.LINKABLE_WIDGET__CHILDREN));
+ error("Linkable widget should contain either only frames or none at all at line " + line,
+ SitemapPackage.Literals.MODEL_FRAME.getEStructuralFeature(SitemapPackage.MODEL_LINKABLE_WIDGET__CHILDREN));
return
}
}
}
@Check
- def void checkWidgetsInButtongrid(Buttongrid grid) {
- val nb = grid.getButtons.size()
+ def void checkWidgetsInButtongrid(ModelButtongrid grid) {
+ val nb = grid.getButtons !== null ? grid.getButtons.getElements.size : 0
if (nb > 0 && grid.item === null) {
- error("To use the \"buttons\" parameter in a Buttongrid, the \"item\" parameter is required",
- SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__ITEM));
+ val node = NodeModelUtils.getNode(grid)
+ val line = node.getStartLine()
+ error("To use the \"buttons\" parameter in a Buttongrid, the \"item\" parameter is required at line " + line,
+ SitemapPackage.Literals.MODEL_BUTTONGRID.getEStructuralFeature(SitemapPackage.MODEL_BUTTONGRID__ITEM));
}
- for (Widget w : grid.children) {
- if (!(w instanceof Button)) {
- error("Buttongrid must contain only Button",
- SitemapPackage.Literals.BUTTONGRID.getEStructuralFeature(SitemapPackage.BUTTONGRID__CHILDREN));
+ for (ModelWidget w : grid.children) {
+ if (!(w instanceof ModelButton)) {
+ val node = NodeModelUtils.getNode(w)
+ val line = node.getStartLine()
+ error("Buttongrid must contain only Button at line " + line,
+ SitemapPackage.Literals.MODEL_BUTTONGRID.getEStructuralFeature(SitemapPackage.MODEL_BUTTONGRID__CHILDREN));
return;
}
}
}
@Check
- def void checkSetpoints(Setpoint sp) {
+ def void checkSetpointParameters(ModelSetpoint sp) {
+ val node = NodeModelUtils.getNode(sp)
+ val line = node.getStartLine()
if (BigDecimal.ZERO == sp.step) {
- error("Setpoint on item '" + sp.item + "' has step size of 0",
- SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__STEP));
+ error("Setpoint widget has step size of '0' at line " + line,
+ SitemapPackage.Literals.MODEL_SETPOINT.getEStructuralFeature(SitemapPackage.MODEL_SETPOINT__STEP));
}
-
if (sp.step !== null && sp.step < BigDecimal.ZERO) {
- error("Setpoint on item '" + sp.item + "' has negative step size",
- SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__STEP));
+ error("Setpoint has negative step size of '" + sp.step + "' at line " + line,
+ SitemapPackage.Literals.MODEL_SETPOINT.getEStructuralFeature(SitemapPackage.MODEL_SETPOINT__STEP));
}
-
if (sp.minValue !== null && sp.maxValue !== null && sp.minValue > sp.maxValue) {
- error("Setpoint on item '" + sp.item + "' has larger minValue than maxValue",
- SitemapPackage.Literals.SETPOINT.getEStructuralFeature(SitemapPackage.SETPOINT__MIN_VALUE));
+ error("Setpoint on item has larger minValue '" + sp.minValue + "' than maxValue '" + sp.maxValue + "' at line " + line,
+ SitemapPackage.Literals.MODEL_SETPOINT.getEStructuralFeature(SitemapPackage.MODEL_SETPOINT__MIN_VALUE));
+ }
+ }
+
+ @Check
+ def void checkSliderParameters(ModelSlider s) {
+ val node = NodeModelUtils.getNode(s)
+ val line = node.getStartLine()
+ if (BigDecimal.ZERO == s.step) {
+ error("Slider widget has step size of '0' at line " + line,
+ SitemapPackage.Literals.MODEL_SLIDER.getEStructuralFeature(SitemapPackage.MODEL_SLIDER__STEP));
+ }
+ if (s.step !== null && s.step < BigDecimal.ZERO) {
+ error("Slider has negative step size of '" + s.step + "' at line " + line,
+ SitemapPackage.Literals.MODEL_SLIDER.getEStructuralFeature(SitemapPackage.MODEL_SLIDER__STEP));
+ }
+ if (s.minValue !== null && s.maxValue !== null && s.minValue > s.maxValue) {
+ error("Slider on item has larger minValue '" + s.minValue + "' than maxValue '" + s.maxValue + "' at line " + line,
+ SitemapPackage.Literals.MODEL_SLIDER.getEStructuralFeature(SitemapPackage.MODEL_SLIDER__MIN_VALUE));
}
}
@Check
- def void checkColortemperaturepicker(Colortemperaturepicker ctp) {
+ def void checkColortemperaturepickerParameters(ModelColortemperaturepicker ctp) {
if (ctp.minValue !== null && ctp.maxValue !== null && ctp.minValue > ctp.maxValue) {
- error("Colortemperaturepicker on item '" + ctp.item + "' has larger minValue than maxValue",
- SitemapPackage.Literals.COLORTEMPERATUREPICKER.getEStructuralFeature(SitemapPackage.COLORTEMPERATUREPICKER__MIN_VALUE));
+ val node = NodeModelUtils.getNode(ctp)
+ val line = node.getStartLine()
+ error("Colortemperaturepicker widget has larger minValue '" + ctp.minValue + "' than maxValue '" + ctp.maxValue + "' at line " + line,
+ SitemapPackage.Literals.MODEL_COLORTEMPERATUREPICKER.getEStructuralFeature(SitemapPackage.MODEL_COLORTEMPERATUREPICKER__MIN_VALUE));
}
}
@Check
- def void checkInputHintParameter(Input i) {
+ def void checkInputParameters(ModelInput i) {
if (i.inputHint !== null && !ALLOWED_HINTS.contains(i.inputHint)) {
val node = NodeModelUtils.getNode(i)
val line = node.getStartLine()
- error("Input on item '" + i.item + "' has invalid inputHint '" + i.inputHint + "' at line " + line,
- SitemapPackage.Literals.INPUT.getEStructuralFeature(SitemapPackage.INPUT__INPUT_HINT))
+ error("Input widget has invalid inputHint '" + i.inputHint + "' at line " + line,
+ SitemapPackage.Literals.MODEL_INPUT.getEStructuralFeature(SitemapPackage.MODEL_INPUT__INPUT_HINT))
}
}
@Check
- def void checkInterpolationParameter(Chart i) {
- if (i.interpolation !== null && !ALLOWED_INTERPOLATION.contains(i.interpolation)) {
- val node = NodeModelUtils.getNode(i)
+ def void checkChartParameters(ModelChart c) {
+ val node = NodeModelUtils.getNode(c)
+ val line = node.getStartLine()
+ if (c.interpolation !== null && !ALLOWED_INTERPOLATION.contains(c.interpolation)) {
+ error("Chart widget has invalid interpolation '" + c.interpolation + "' at line " + line,
+ SitemapPackage.Literals.MODEL_INPUT.getEStructuralFeature(SitemapPackage.MODEL_CHART__INTERPOLATION))
+ }
+ if (c.period === null) {
+ error("Chart widget doesn't have period defined at line " + line,
+ SitemapPackage.Literals.MODEL_INPUT.getEStructuralFeature(SitemapPackage.MODEL_CHART__PERIOD))
+ }
+ }
+
+ @Check
+ def void checkVideoParameters(ModelVideo v) {
+ if (v.url === null) {
+ val node = NodeModelUtils.getNode(v)
val line = node.getStartLine()
- error("Input on item '" + i.item + "' has invalid interpolation '" + i.interpolation + "' at line " + line,
- SitemapPackage.Literals.INPUT.getEStructuralFeature(SitemapPackage.CHART__INTERPOLATION))
+ error("Video widget doesn't have url defined at line " + line,
+ SitemapPackage.Literals.MODEL_INPUT.getEStructuralFeature(SitemapPackage.MODEL_VIDEO__URL))
}
}
}
diff --git a/bundles/org.openhab.core.sitemap/.classpath b/bundles/org.openhab.core.sitemap/.classpath
new file mode 100644
index 00000000000..84dfbfdf1df
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/.classpath
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.core.sitemap/.project b/bundles/org.openhab.core.sitemap/.project
new file mode 100644
index 00000000000..584c1430e8c
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.core.sitemap
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/bundles/org.openhab.core.sitemap/NOTICE b/bundles/org.openhab.core.sitemap/NOTICE
new file mode 100644
index 00000000000..6c17d0d8a45
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/NOTICE
@@ -0,0 +1,14 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-core
+
diff --git a/bundles/org.openhab.core.sitemap/pom.xml b/bundles/org.openhab.core.sitemap/pom.xml
new file mode 100644
index 00000000000..b9af5ea135b
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.core.bundles
+ org.openhab.core.reactor.bundles
+ 5.1.0-SNAPSHOT
+
+
+ org.openhab.core.sitemap
+
+ openHAB Core :: Bundles :: Sitemap
+
+
+
+ org.openhab.core.bundles
+ org.openhab.core
+ ${project.version}
+
+
+
+
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Button.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Button.java
new file mode 100644
index 00000000000..6746fbc0629
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Button.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap {@link Button} widget. Button widgets should have a parent {@link Buttongrid} widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Button extends NonLinkableWidget {
+
+ /**
+ * Get button row in grid.
+ *
+ * @return row
+ */
+ int getRow();
+
+ /**
+ * Set button row in grid.
+ *
+ * @param row
+ */
+ void setRow(int row);
+
+ /**
+ * Get button column in grid.
+ *
+ * @return column
+ */
+ int getColumn();
+
+ /**
+ * Set button column in grid.
+ *
+ * @param column
+ */
+ void setColumn(int column);
+
+ /**
+ * True if the button is stateless, by default a button is stateful.
+ *
+ * @return stateless
+ */
+ boolean isStateless();
+
+ /**
+ * Set stateless parameter for button.
+ *
+ * @param stateless
+ */
+ void setStateless(@Nullable Boolean stateless);
+
+ /**
+ * Get button command, will be executed when the button is clicked.
+ *
+ * @return cmd
+ */
+ String getCmd();
+
+ /**
+ * Set button command.
+ *
+ * @param cmd
+ */
+ void setCmd(String cmd);
+
+ /**
+ * Get button release command, will be executed when the button is released.
+ *
+ * @return releaseCmd
+ */
+ @Nullable
+ String getReleaseCmd();
+
+ /**
+ * Set the button release command.
+ *
+ * @param releaseCmd
+ */
+ void setReleaseCmd(@Nullable String releaseCmd);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/ButtonDefinition.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/ButtonDefinition.java
new file mode 100644
index 00000000000..cf97c81a1a1
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/ButtonDefinition.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap {@link Buttongrid} button definition. All buttons will act on the same item defined in
+ * the button grid.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface ButtonDefinition {
+
+ /**
+ * Get button row in grid.
+ *
+ * @return row
+ */
+ int getRow();
+
+ /**
+ * Set button row in grid.
+ *
+ * @param row
+ */
+ void setRow(int row);
+
+ /**
+ * Get button column in grid.
+ *
+ * @return column
+ */
+ int getColumn();
+
+ /**
+ * Set button column in grid.
+ *
+ * @param column
+ */
+ void setColumn(int column);
+
+ /**
+ * Get button command.
+ *
+ * @return cmd
+ */
+ String getCmd();
+
+ /**
+ * Set button command.
+ *
+ * @param cmd
+ */
+ void setCmd(String cmd);
+
+ /**
+ * Get button label.
+ *
+ * @return label
+ */
+ String getLabel();
+
+ /**
+ * Set button label.
+ *
+ * @param label
+ */
+ void setLabel(String label);
+
+ /**
+ * Get button icon.
+ *
+ * @return icon
+ */
+ @Nullable
+ String getIcon();
+
+ /**
+ * Set button icon.
+ *
+ * @param icon
+ */
+ void setIcon(@Nullable String icon);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Buttongrid.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Buttongrid.java
new file mode 100644
index 00000000000..439f6667476
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Buttongrid.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap Buttongrid widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Buttongrid extends LinkableWidget {
+
+ /**
+ * Get the button grid buttons. This method should return a modifiable list, allowing updates to the list of
+ * buttons.
+ *
+ * @return buttons
+ */
+ List getButtons();
+
+ /**
+ * Replace the button grid buttons with a new list of buttons.
+ *
+ * @param buttons
+ */
+ void setButtons(List buttons);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Chart.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Chart.java
new file mode 100644
index 00000000000..4417dbdc73a
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Chart.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Chart widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Chart extends NonLinkableWidget {
+
+ /**
+ * Get the configured persistence service, if no service is configured, the default service should be used.
+ *
+ * @return service
+ */
+ @Nullable
+ String getService();
+
+ /**
+ * Set the persistence service.
+ *
+ * @param service
+ */
+ void setService(String service);
+
+ /**
+ * Get the chart refresh interval in s. If no interval is set, 0 should be returned.
+ *
+ * @return refresh
+ */
+ int getRefresh();
+
+ /**
+ * Set the chart refresh interval in s.
+ *
+ * @param refresh
+ */
+ void setRefresh(@Nullable Integer refresh);
+
+ /**
+ * Get the configured chart time period. See {@link #setPeriod(String)}.
+ *
+ * @return period
+ */
+ String getPeriod();
+
+ /**
+ * Set the chart time axis scale.The time axis can be either entirely in the past ending at the present time,
+ * entirely in the future starting at the present time, or partly in the past and partly in the future around the
+ * present time. To do this, the value can be composed of two parts separated by the "-" character, the value before
+ * the "-" is then the scale in the past and the value after the "-" is the scale in the future. Valid values before
+ * and after the central character "-" are h, 2h, 3h, ..., D, 2D, 3D, ..., W, 2W, 3W, ..., M, 2M, 3M, ..., Y, 2Y,
+ * ... and any valid duration following the ISO8601 duration notation such as P1Y6M for the last year and a half or
+ * PT1H30M for the last hour and a half. If only a period is provided, i.e. without the final "-" character or
+ * without anything after the "-" character, only a period in the past is taken into account.
+ *
+ * @param period
+ */
+ void setPeriod(String period);
+
+ /**
+ * Return true if legend should be shown.
+ *
+ * @return legend
+ */
+ boolean hasLegend();
+
+ /**
+ * Set to true if legend should be shown. If not set, the legend will not be shown if there is only a single series
+ * in the chart.
+ *
+ * @param legend
+ */
+ void setLegend(@Nullable Boolean legend);
+
+ /**
+ * Return true if the group item will be shown instead of items in the group.
+ *
+ * @return forceAsItem
+ */
+ boolean forceAsItem();
+
+ /**
+ * Set to true if group item should be shown in the chart instead of items in the group (default).
+ *
+ * @param forceAsItem
+ */
+ void setForceAsItem(@Nullable Boolean forceAsItem);
+
+ /**
+ * Get the y axis value format pattern.
+ *
+ * @return yAxisDecimalPattern
+ */
+ @Nullable
+ String getYAxisDecimalPattern();
+
+ /**
+ * Set the format for values on the y axis. It accepts {@link java.text.DecimalFormat}. For example with #.## a
+ * number has 2 decimals.
+ *
+ * @param yAxisDecimalPattern
+ */
+ void setYAxisDecimalPattern(@Nullable String yAxisDecimalPattern);
+
+ /**
+ * Gets the interpolation parameter. See {@link #setInterpolation(String)}.
+ *
+ * @return interpolation
+ */
+ @Nullable
+ String getInterpolation();
+
+ /**
+ * Sets the interpolation parameter. The interpolation parameter is used to change how the line is drawn between 2
+ * datapoints. By default, a horizontal line (step) will be drawn between 2 datapoints of Switch or Contact items.
+ * All other item types will have a line (linear) connecting the datapoints. With the "linear" or "step" value for
+ * this parameter, this default behaviour can be changed.
+ *
+ * @param interpolation
+ */
+ void setInterpolation(@Nullable String interpolation);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Colorpicker.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Colorpicker.java
new file mode 100644
index 00000000000..612448f60f4
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Colorpicker.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap Colorpicker widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Colorpicker extends NonLinkableWidget {
+
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Colortemperaturepicker.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Colortemperaturepicker.java
new file mode 100644
index 00000000000..2f11b80f399
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Colortemperaturepicker.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Colortemperaturepicker widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Colortemperaturepicker extends NonLinkableWidget {
+
+ /**
+ * Get minimum color temperature value.
+ *
+ * @return minValue
+ */
+ @Nullable
+ BigDecimal getMinValue();
+
+ /**
+ * Set minimum color temperature value.
+ *
+ * @param minValue
+ */
+ void setMinValue(@Nullable BigDecimal minValue);
+
+ /**
+ * Get maximum color temperature value.
+ *
+ * @return maxValue
+ */
+ @Nullable
+ BigDecimal getMaxValue();
+
+ /**
+ * Set maximum color temperature value.
+ *
+ * @param maxValue
+ */
+ void setMaxValue(@Nullable BigDecimal maxValue);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Condition.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Condition.java
new file mode 100644
index 00000000000..2e13a2d28c6
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Condition.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap rule condition.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Condition {
+
+ /**
+ * Get the item for which the state will be used in the condition evaluation. If no item is set (null returned), the
+ * item of
+ * the widget will be used.
+ *
+ * @return item
+ */
+ @Nullable
+ String getItem();
+
+ /**
+ * Set the item for which the state will be used in the condition evaluation.
+ *
+ * @param item
+ */
+ void setItem(@Nullable String item);
+
+ /**
+ * Get the condition comparator. Valid values are: "==", ">", "<", ">=", "<=", "!=". The item in the
+ * condition will be compared against the value using this comparator. If no condition comparator is set, "==" is
+ * assumed.
+ *
+ * @return condition comparator
+ */
+ @Nullable
+ String getCondition();
+
+ /**
+ * Set the condition comparator, see {@link #getCondition()}.
+ *
+ * @param condition
+ */
+ void setCondition(@Nullable String condition);
+
+ /**
+ * Get the condition comparison value.
+ *
+ * @return value
+ */
+ String getValue();
+
+ /**
+ * Set the condition comparison value.
+ *
+ * @param value
+ */
+ void setValue(String value);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Default.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Default.java
new file mode 100644
index 00000000000..3d0273d63ea
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Default.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Default widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Default extends NonLinkableWidget {
+
+ /**
+ * Get the configured height of the widget. If no height is configured, 0 should be returned.
+ *
+ * @return height
+ */
+ int getHeight();
+
+ /**
+ * Set the height of the widget, null means no height is configured.
+ *
+ * @param height
+ */
+ void setHeight(@Nullable Integer height);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Frame.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Frame.java
new file mode 100644
index 00000000000..2aae836a07a
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Frame.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap Frame widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Frame extends LinkableWidget {
+
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Group.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Group.java
new file mode 100644
index 00000000000..e014f3c4054
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Group.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap Group widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Group extends LinkableWidget {
+
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Image.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Image.java
new file mode 100644
index 00000000000..c73a789f3d7
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Image.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Image widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Image extends LinkableWidget {
+
+ /**
+ * Get the url of the image.
+ *
+ * @return url
+ */
+ @Nullable
+ String getUrl();
+
+ /**
+ * Set the url of the video.
+ *
+ * @param url
+ */
+ void setUrl(@Nullable String url);
+
+ /**
+ * Get the image refresh interval in s. If no interval is set, 0 should be returned.
+ *
+ * @return refresh
+ */
+ int getRefresh();
+
+ /**
+ * Set the image refresh interval in s.
+ *
+ * @param refresh
+ */
+ void setRefresh(@Nullable Integer refresh);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Input.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Input.java
new file mode 100644
index 00000000000..a466ee77356
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Input.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Input widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Input extends NonLinkableWidget {
+
+ /**
+ * Get the input hint. This can be used by a UI to tailor the representation. See {@link #setInputHint(String)}.
+ *
+ * @return input hint
+ */
+ @Nullable
+ String getInputHint();
+
+ /**
+ * Set the input hint, allowed values are: "text", "number", "date", "time", "datetime". This can be used by a UI to
+ * tailor the representation.
+ *
+ * @param inputHint
+ */
+ void setInputHint(@Nullable String inputHint);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/LinkableWidget.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/LinkableWidget.java
new file mode 100644
index 00000000000..1df3de265f8
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/LinkableWidget.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap linkable widget (a widget that can have children).
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface LinkableWidget extends Widget, Parent {
+
+ /**
+ * Get the child {@link Widget}s. This method should return a modifiable list, allowing updates to the child
+ * widgets.
+ *
+ * @return widgets
+ */
+ List getWidgets();
+
+ /**
+ * Replace the child widgets with a new list of widgets.
+ *
+ * @param widgets
+ */
+ void setWidgets(List widgets);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Mapping.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Mapping.java
new file mode 100644
index 00000000000..42aa4c81169
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Mapping.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap widget Mapping.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Mapping {
+
+ /**
+ * Get the click command mapped to the widget item state.
+ *
+ * @return cmd
+ */
+ String getCmd();
+
+ /**
+ * Set the click command mapped to the widget item state.
+ *
+ * @param cmd
+ */
+ void setCmd(String cmd);
+
+ /**
+ *
+ * Get the release command mapped to the widget item state.
+ *
+ * @return releaseCmd
+ */
+ @Nullable
+ String getReleaseCmd();
+
+ /**
+ * Set the release command mapped to the widget item state.
+ *
+ * @param releaseCmd
+ */
+ void setReleaseCmd(@Nullable String releaseCmd);
+
+ /**
+ * Get the label mapped to the widget item state.
+ *
+ * @return label
+ */
+ String getLabel();
+
+ /**
+ * Set the label mapped to the widget item state.
+ *
+ * @param label
+ */
+ void setLabel(String label);
+
+ /**
+ * Get the icon mapped to the widget item state.
+ *
+ * @return icon
+ */
+ @Nullable
+ String getIcon();
+
+ /**
+ * Set the label mapped to the widget item state.
+ *
+ * @param icon
+ */
+ void setIcon(@Nullable String icon);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Mapview.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Mapview.java
new file mode 100644
index 00000000000..fd9445b7746
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Mapview.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Mapview widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Mapview extends NonLinkableWidget {
+
+ /**
+ * Get the configured height of the widget. If no height is configured, 0 should be returned.
+ *
+ * @return height
+ */
+ int getHeight();
+
+ /**
+ * Set the height of the widget, null means no height is configured.
+ *
+ * @param height
+ */
+ void setHeight(@Nullable Integer height);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/NonLinkableWidget.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/NonLinkableWidget.java
new file mode 100644
index 00000000000..78efdd1512c
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/NonLinkableWidget.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap non-linkable widget (no children).
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface NonLinkableWidget extends Widget {
+
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Parent.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Parent.java
new file mode 100644
index 00000000000..36a013491f1
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Parent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Interface representing all sitemap entities that can be parents, should be extended by Sitemap and LinkableWidget.
+ * This is a marker interface.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Parent {
+
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Rule.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Rule.java
new file mode 100644
index 00000000000..ba7972c0f6a
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Rule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap widget icon, color or visibility rule.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Rule {
+
+ /**
+ * Get the rule conditions. This method should return a modifiable list, allowing updates to conditions.
+ *
+ * @return conditions
+ */
+ List getConditions();
+
+ /**
+ * Replace the rule conditions with a new list of conditions.
+ *
+ * @param conditions
+ */
+ void setConditions(List conditions);
+
+ /**
+ * Get the rule argument for icon or color rules. The rule argument is the resulting value if the rule is met.
+ * Visibility rules don't have an argument, always work on the full widget.
+ *
+ * @return argument
+ */
+ @Nullable
+ String getArgument();
+
+ /**
+ * Set the rule argument.
+ *
+ * @param argument
+ */
+ void setArgument(@Nullable String argument);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Selection.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Selection.java
new file mode 100644
index 00000000000..49a27648ba0
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Selection.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap Selection widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Selection extends NonLinkableWidget {
+
+ /**
+ * Get the switch {@link Mapping}s. This method should return a modifiable list, allowing updates to the mappings.
+ *
+ * @return mappings
+ */
+ List getMappings();
+
+ /**
+ * Replace the widget mappings with a new list of mappings.
+ *
+ * @param mappings
+ */
+ void setMappings(List mappings);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Setpoint.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Setpoint.java
new file mode 100644
index 00000000000..eddabb28c2e
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Setpoint.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Setpoint widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Setpoint extends NonLinkableWidget {
+
+ /**
+ * Get minimum setpoint value.
+ *
+ * @return minValue
+ */
+ @Nullable
+ BigDecimal getMinValue();
+
+ /**
+ * Set minimum setpoint value.
+ *
+ * @param minValue
+ */
+ void setMinValue(@Nullable BigDecimal minValue);
+
+ /**
+ * Get maximum setpoint value.
+ *
+ * @return maxValue
+ */
+ @Nullable
+ BigDecimal getMaxValue();
+
+ /**
+ * Set maximum setpoint value.
+ *
+ * @param maxValue
+ */
+ void setMaxValue(@Nullable BigDecimal maxValue);
+
+ /**
+ * Get setpoint step.
+ *
+ * @return step
+ */
+ @Nullable
+ BigDecimal getStep();
+
+ /**
+ * Set setpoint step.
+ *
+ * @param step
+ */
+ void setStep(@Nullable BigDecimal step);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Sitemap.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Sitemap.java
new file mode 100644
index 00000000000..903f23dcebf
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Sitemap.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.common.registry.Identifiable;
+
+/**
+ * A representation of a 'Sitemap'.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Sitemap extends Identifiable, Parent {
+
+ /**
+ * Returns the sitemap name.
+ *
+ * @return sitemap name.
+ */
+ String getName();
+
+ /**
+ * Sets the sitemap name.
+ *
+ * @param name the new sitemap name.
+ */
+ void setName(String name);
+
+ /**
+ * Returns the sitemap label.
+ *
+ * @return sitemap label.
+ */
+ @Nullable
+ String getLabel();
+
+ /**
+ * Sets the sitemap label.
+ *
+ * @param label the new sitemap label.
+ */
+ void setLabel(@Nullable String label);
+
+ /**
+ * Returns the sitemap icon.
+ *
+ * @return sitemap icon.
+ */
+ @Nullable
+ String getIcon();
+
+ /**
+ * Sets the sitemap icon.
+ *
+ * @param icon the new sitemap icon.
+ */
+ void setIcon(@Nullable String icon);
+
+ /**
+ * Returns the top level list of widgets in the sitemap. The returned list is a modifiable list.
+ *
+ * @return list of widgets.
+ */
+ List getWidgets();
+
+ /**
+ * Replace the sitemap child widgets with a new list of widgets.
+ *
+ * @param widgets
+ */
+ void setWidgets(List widgets);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Slider.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Slider.java
new file mode 100644
index 00000000000..72d435b3610
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Slider.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Slider widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Slider extends NonLinkableWidget {
+
+ /**
+ * Return true if the UI should render switch capabilities for the slider.
+ *
+ * @return true if switch enabled
+ */
+ boolean isSwitchEnabled();
+
+ /**
+ * Set switch enabled.
+ *
+ * @param switchEnabled
+ */
+ void setSwitchEnabled(@Nullable Boolean switchEnabled);
+
+ /**
+ * Return true if the UI should only send updates to core on mouse release.
+ *
+ * @return true if release only
+ */
+ boolean isReleaseOnly();
+
+ /**
+ * Set release only.
+ *
+ * @param releaseOnly
+ */
+ void setReleaseOnly(@Nullable Boolean releaseOnly);
+
+ /**
+ * Get minimum slider value.
+ *
+ * @return minValue
+ */
+ @Nullable
+ BigDecimal getMinValue();
+
+ /**
+ * Set minimum slider value.
+ *
+ * @param minValue
+ */
+ void setMinValue(@Nullable BigDecimal minValue);
+
+ /**
+ * Get maximum slider value.
+ *
+ * @return maxValue
+ */
+ @Nullable
+ BigDecimal getMaxValue();
+
+ /**
+ * Set maximum slider value.
+ *
+ * @param maxValue
+ */
+ void setMaxValue(@Nullable BigDecimal maxValue);
+
+ /**
+ * Get slider step.
+ *
+ * @return step
+ */
+ @Nullable
+ BigDecimal getStep();
+
+ /**
+ * Set slider step.
+ *
+ * @param step
+ */
+ void setStep(@Nullable BigDecimal step);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Switch.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Switch.java
new file mode 100644
index 00000000000..b1e039772ae
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Switch.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap Switch widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Switch extends NonLinkableWidget {
+
+ /**
+ * Get the switch {@link Mapping}s. This method should return a modifiable list, allowing updates to the mappings.
+ *
+ * @return mappings
+ */
+ List getMappings();
+
+ /**
+ * Replace the widget mappings with a new list of mappings.
+ *
+ * @param mappings
+ */
+ void setMappings(List mappings);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Text.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Text.java
new file mode 100644
index 00000000000..3cfaa8107f9
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Text.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A representation of a sitemap Text widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Text extends LinkableWidget {
+
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Video.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Video.java
new file mode 100644
index 00000000000..2b8b2cd5be0
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Video.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Video widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Video extends NonLinkableWidget {
+
+ /**
+ * Get the url of the video.
+ *
+ * @return url
+ */
+ String getUrl();
+
+ /**
+ * Set the url of the video.
+ *
+ * @param url
+ */
+ void setUrl(String url);
+
+ /**
+ * Get the configured video encoding.
+ *
+ * @return encoding, null if no encoding is configured
+ */
+ @Nullable
+ String getEncoding();
+
+ /**
+ * Set the video encoding.
+ *
+ * @param encoding
+ */
+ void setEncoding(@Nullable String encoding);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Webview.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Webview.java
new file mode 100644
index 00000000000..10ec91dfdad
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Webview.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap Webview widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+
+public interface Webview extends NonLinkableWidget {
+
+ /**
+ * Get the configured height of the widget. If no height is configured, 0 should be returned.
+ *
+ * @return height
+ */
+ int getHeight();
+
+ /**
+ * Set the height of the widget, null means no height is configured.
+ *
+ * @param height
+ */
+ void setHeight(@Nullable Integer height);
+
+ /**
+ * Get the url to be embedded in the {@link Webview} embedded frame.
+ *
+ * @return url
+ */
+ String getUrl();
+
+ /**
+ * Set the url to be embedded in the {@link Webview} frame.
+ *
+ * @param url
+ */
+ void setUrl(String url);
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Widget.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Widget.java
new file mode 100644
index 00000000000..c0639a62fc6
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/Widget.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A representation of a sitemap widget.
+ *
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public interface Widget {
+
+ /**
+ * Get the direct parent {@link Widget} or {@link Sitemap}.
+ *
+ * @return parent
+ */
+ @Nullable
+ Parent getParent();
+
+ /**
+ * Sets the parent {@link Widget} or {@link Sitemap}.
+ * Widgets in a sitemap should always have a parent. Implementations of {@link Widget} should have a constructor
+ * with {@link Parent} parameter to make building a sitemap easier.
+ *
+ * @param parent
+ */
+ void setParent(Parent parent);
+
+ /**
+ * Gets the item name for the widget. For specific widget type, the item is required and for these widgets, this
+ * method should not return null.
+ *
+ * @return item, or null if no item defined for the widget
+ */
+ @Nullable
+ String getItem();
+
+ /**
+ * Sets the widget item.
+ *
+ * @param item
+ */
+ void setItem(@Nullable String item);
+
+ /**
+ * Get widget label.
+ *
+ * @return label
+ */
+ @Nullable
+ String getLabel();
+
+ /**
+ * Set widget label.
+ *
+ * @param label
+ */
+ void setLabel(@Nullable String label);
+
+ /**
+ * Get widget icon.
+ *
+ * @return icon
+ */
+ @Nullable
+ String getIcon();
+
+ /**
+ * Set widget icon.
+ *
+ * @param icon
+ */
+ void setIcon(@Nullable String icon);
+
+ /**
+ * Get the widget icon rules. This method should return a modifiable list, allowing updates to the icon rules.
+ *
+ * @return icon rules
+ */
+ List getIconRules();
+
+ /**
+ * Replace the widget icon rules with a new list of icon rules.
+ *
+ * @param iconRules
+ */
+ void setIconRules(List iconRules);
+
+ /**
+ * True if the widget icon is static, false otherwise.
+ *
+ * @return static icon
+ */
+ boolean isStaticIcon();
+
+ /**
+ * Set to true if the widget icon is static.
+ *
+ * @param staticIcon
+ */
+ void setStaticIcon(@Nullable Boolean staticIcon);
+
+ /**
+ * Get the widget label color rules. This method should return a modifiable list, allowing updates to the label
+ * color rules.
+ *
+ * @return label color rules
+ */
+ List getLabelColor();
+
+ /**
+ * Replace the widget label color rules with a new list of label color rules.
+ *
+ * @param labelColorRules
+ */
+ void setLabelColor(List labelColorRules);
+
+ /**
+ * Get the widget value color rules. This method should return a modifiable list, allowing updates to the value
+ * color rules.
+ *
+ * @return value color rules
+ */
+ List getValueColor();
+
+ /**
+ * Replace the widget value color rules with a new list of value color rules.
+ *
+ * @param valueColorRules
+ */
+ void setValueColor(List valueColorRules);
+
+ /**
+ * Get the widget icon color rules. This method should return a modifiable list, allowing updates to the icon
+ * color rules.
+ *
+ * @return icon color rules
+ */
+ List getIconColor();
+
+ /**
+ * Replace the widget icon color rules with a new list of icon color rules.
+ *
+ * @param iconColorRules
+ */
+ void setIconColor(List iconColorRules);
+
+ /**
+ * Get the widget visibility rules. This method should return a modifiable list, allowing updates to the visibility
+ * rules.
+ *
+ * @return visibility rules
+ */
+ List getVisibility();
+
+ /**
+ * Replace the widget visibility rules with a new list of visibility rules.
+ *
+ * @param visibilityRules
+ */
+ void setVisibility(List visibilityRules);
+
+ /**
+ * Get type of widget.
+ *
+ * @return widget type
+ */
+ String getWidgetType();
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtonDefinitionImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtonDefinitionImpl.java
new file mode 100644
index 00000000000..dc04b30827c
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtonDefinitionImpl.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.ButtonDefinition;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ButtonDefinitionImpl implements ButtonDefinition {
+
+ private int row;
+ private int column;
+ private String cmd = "";
+ private String label = "";
+ private @Nullable String icon;
+
+ @Override
+ public int getRow() {
+ return row;
+ }
+
+ @Override
+ public void setRow(int row) {
+ this.row = row;
+ }
+
+ @Override
+ public int getColumn() {
+ return column;
+ }
+
+ @Override
+ public void setColumn(int column) {
+ this.column = column;
+ }
+
+ @Override
+ public String getCmd() {
+ return cmd;
+ }
+
+ @Override
+ public void setCmd(String cmd) {
+ this.cmd = cmd;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+ @Override
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public @Nullable String getIcon() {
+ return icon;
+ }
+
+ @Override
+ public void setIcon(@Nullable String icon) {
+ this.icon = icon;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtonImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtonImpl.java
new file mode 100644
index 00000000000..45d28552f08
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtonImpl.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Button;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ButtonImpl extends NonLinkableWidgetImpl implements Button {
+
+ private int row;
+ private int column;
+ private @Nullable Boolean stateless;
+ private String cmd = "";
+ private @Nullable String releaseCmd;
+
+ public ButtonImpl() {
+ super();
+ }
+
+ public ButtonImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public int getRow() {
+ return row;
+ }
+
+ @Override
+ public void setRow(int row) {
+ this.row = row;
+ }
+
+ @Override
+ public int getColumn() {
+ return column;
+ }
+
+ @Override
+ public void setColumn(int column) {
+ this.column = column;
+ }
+
+ @Override
+ public boolean isStateless() {
+ return stateless != null ? stateless : false;
+ }
+
+ @Override
+ public void setStateless(@Nullable Boolean stateless) {
+ this.stateless = stateless;
+ }
+
+ @Override
+ public String getCmd() {
+ return cmd;
+ }
+
+ @Override
+ public void setCmd(String cmd) {
+ this.cmd = cmd;
+ }
+
+ @Override
+ public @Nullable String getReleaseCmd() {
+ return releaseCmd;
+ }
+
+ @Override
+ public void setReleaseCmd(@Nullable String releaseCmd) {
+ this.releaseCmd = releaseCmd;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtongridImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtongridImpl.java
new file mode 100644
index 00000000000..c552b3f4b25
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ButtongridImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.ButtonDefinition;
+import org.openhab.core.sitemap.Buttongrid;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ButtongridImpl extends LinkableWidgetImpl implements Buttongrid {
+
+ private List buttons = new CopyOnWriteArrayList<>();
+
+ public ButtongridImpl() {
+ super();
+ }
+
+ public ButtongridImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public List getButtons() {
+ return buttons;
+ }
+
+ @Override
+ public void setButtons(List buttons) {
+ this.buttons = new CopyOnWriteArrayList<>(buttons);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ChartImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ChartImpl.java
new file mode 100644
index 00000000000..24d35cf8db5
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ChartImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Chart;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ChartImpl extends NonLinkableWidgetImpl implements Chart {
+
+ private @Nullable String service;
+ private @Nullable Integer refresh;
+ private String period = "";
+ private @Nullable Boolean legend;
+ private @Nullable Boolean forceAsItem;
+ private @Nullable String yAxisDecimalPattern;
+ private @Nullable String interpolation;
+
+ public ChartImpl() {
+ super();
+ }
+
+ public ChartImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public @Nullable String getService() {
+ return service;
+ }
+
+ @Override
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ @Override
+ public int getRefresh() {
+ return refresh != null ? refresh : 0;
+ }
+
+ @Override
+ public void setRefresh(@Nullable Integer refresh) {
+ this.refresh = refresh;
+ }
+
+ @Override
+ public String getPeriod() {
+ return period;
+ }
+
+ @Override
+ public void setPeriod(String period) {
+ this.period = period;
+ }
+
+ @Override
+ public boolean hasLegend() {
+ return legend != null ? legend : false;
+ }
+
+ @Override
+ public void setLegend(@Nullable Boolean legend) {
+ this.legend = legend;
+ }
+
+ @Override
+ public boolean forceAsItem() {
+ return forceAsItem != null ? forceAsItem : false;
+ }
+
+ @Override
+ public void setForceAsItem(@Nullable Boolean forceAsItem) {
+ this.forceAsItem = forceAsItem;
+ }
+
+ @Override
+ public @Nullable String getYAxisDecimalPattern() {
+ return yAxisDecimalPattern;
+ }
+
+ @Override
+ public void setYAxisDecimalPattern(@Nullable String yAxisDecimalPattern) {
+ this.yAxisDecimalPattern = yAxisDecimalPattern;
+ }
+
+ @Override
+ public @Nullable String getInterpolation() {
+ return interpolation;
+ }
+
+ @Override
+ public void setInterpolation(@Nullable String interpolation) {
+ this.interpolation = interpolation;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ColorpickerImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ColorpickerImpl.java
new file mode 100644
index 00000000000..3ceab871a3b
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ColorpickerImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.Colorpicker;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ColorpickerImpl extends NonLinkableWidgetImpl implements Colorpicker {
+
+ public ColorpickerImpl() {
+ super();
+ }
+
+ public ColorpickerImpl(Parent parent) {
+ super(parent);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ColortemperaturepickerImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ColortemperaturepickerImpl.java
new file mode 100644
index 00000000000..2fa001d603c
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ColortemperaturepickerImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Colortemperaturepicker;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ColortemperaturepickerImpl extends NonLinkableWidgetImpl implements Colortemperaturepicker {
+
+ private @Nullable BigDecimal minValue;
+ private @Nullable BigDecimal maxValue;
+
+ public ColortemperaturepickerImpl() {
+ super();
+ }
+
+ public ColortemperaturepickerImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public @Nullable BigDecimal getMinValue() {
+ return minValue;
+ }
+
+ @Override
+ public void setMinValue(@Nullable BigDecimal minValue) {
+ this.minValue = minValue;
+ }
+
+ @Override
+ public @Nullable BigDecimal getMaxValue() {
+ return maxValue;
+ }
+
+ @Override
+ public void setMaxValue(@Nullable BigDecimal maxValue) {
+ this.maxValue = maxValue;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ConditionImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ConditionImpl.java
new file mode 100644
index 00000000000..976e99f668e
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ConditionImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Condition;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ConditionImpl implements Condition {
+
+ private @Nullable String item;
+ private @Nullable String condition;
+ private String value = "";
+
+ public ConditionImpl() {
+ }
+
+ public ConditionImpl(Condition condition) {
+ this.item = condition.getItem();
+ this.condition = condition.getCondition();
+ this.value = condition.getValue();
+ }
+
+ @Override
+ public @Nullable String getItem() {
+ return item;
+ }
+
+ @Override
+ public void setItem(@Nullable String item) {
+ this.item = item;
+ }
+
+ @Override
+ public @Nullable String getCondition() {
+ return condition;
+ }
+
+ @Override
+ public void setCondition(@Nullable String condition) {
+ this.condition = condition;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/DefaultImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/DefaultImpl.java
new file mode 100644
index 00000000000..985848e152f
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/DefaultImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Default;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class DefaultImpl extends NonLinkableWidgetImpl implements Default {
+
+ private @Nullable Integer height;
+
+ public DefaultImpl() {
+ super();
+ }
+
+ public DefaultImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public int getHeight() {
+ return height != null ? height : 0;
+ }
+
+ @Override
+ public void setHeight(@Nullable Integer height) {
+ this.height = height;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/FrameImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/FrameImpl.java
new file mode 100644
index 00000000000..f2466d75e16
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/FrameImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.Frame;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class FrameImpl extends LinkableWidgetImpl implements Frame {
+
+ public FrameImpl() {
+ super();
+ }
+
+ public FrameImpl(Parent parent) {
+ super(parent);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/GroupImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/GroupImpl.java
new file mode 100644
index 00000000000..f4c01fb6838
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/GroupImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.Group;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class GroupImpl extends LinkableWidgetImpl implements Group {
+
+ public GroupImpl() {
+ super();
+ }
+
+ public GroupImpl(Parent parent) {
+ super(parent);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ImageImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ImageImpl.java
new file mode 100644
index 00000000000..8e8e86d15fd
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/ImageImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Image;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class ImageImpl extends LinkableWidgetImpl implements Image {
+
+ private @Nullable String url;
+ private @Nullable Integer refresh;
+
+ public ImageImpl() {
+ super();
+ }
+
+ public ImageImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public @Nullable String getUrl() {
+ return url;
+ }
+
+ @Override
+ public void setUrl(@Nullable String url) {
+ this.url = url;
+ }
+
+ @Override
+ public int getRefresh() {
+ return refresh != null ? refresh : 0;
+ }
+
+ @Override
+ public void setRefresh(@Nullable Integer refresh) {
+ this.refresh = refresh;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/InputImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/InputImpl.java
new file mode 100644
index 00000000000..ad4828a722b
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/InputImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Input;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class InputImpl extends NonLinkableWidgetImpl implements Input {
+
+ private @Nullable String inputHint;
+
+ public InputImpl() {
+ super();
+ }
+
+ public InputImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public @Nullable String getInputHint() {
+ return inputHint;
+ }
+
+ @Override
+ public void setInputHint(@Nullable String inputHint) {
+ this.inputHint = inputHint;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/LinkableWidgetImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/LinkableWidgetImpl.java
new file mode 100644
index 00000000000..35236b3e1d4
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/LinkableWidgetImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.LinkableWidget;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Widget;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class LinkableWidgetImpl extends WidgetImpl implements LinkableWidget {
+
+ private List widgets = new CopyOnWriteArrayList<>();
+
+ public LinkableWidgetImpl() {
+ super();
+ }
+
+ public LinkableWidgetImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public List getWidgets() {
+ return widgets;
+ }
+
+ @Override
+ public void setWidgets(List widgets) {
+ this.widgets = new CopyOnWriteArrayList<>(widgets);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/MappingImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/MappingImpl.java
new file mode 100644
index 00000000000..9e8a6ac0a6d
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/MappingImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Mapping;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class MappingImpl implements Mapping {
+
+ private String cmd = "";
+ private @Nullable String releaseCmd;
+ private String label = "";
+ private @Nullable String icon;
+
+ @Override
+ public String getCmd() {
+ return cmd;
+ }
+
+ @Override
+ public void setCmd(String cmd) {
+ this.cmd = cmd;
+ }
+
+ @Override
+ public @Nullable String getReleaseCmd() {
+ return releaseCmd;
+ }
+
+ @Override
+ public void setReleaseCmd(@Nullable String releaseCmd) {
+ this.releaseCmd = releaseCmd;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+ @Override
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public @Nullable String getIcon() {
+ return icon;
+ }
+
+ @Override
+ public void setIcon(@Nullable String icon) {
+ this.icon = icon;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/MapviewImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/MapviewImpl.java
new file mode 100644
index 00000000000..e60df23c08a
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/MapviewImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Mapview;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class MapviewImpl extends NonLinkableWidgetImpl implements Mapview {
+
+ private @Nullable Integer height;
+
+ public MapviewImpl() {
+ super();
+ }
+
+ public MapviewImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public int getHeight() {
+ return height != null ? height : 0;
+ }
+
+ @Override
+ public void setHeight(@Nullable Integer height) {
+ this.height = height;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/NonLinkableWidgetImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/NonLinkableWidgetImpl.java
new file mode 100644
index 00000000000..b7ef6be63d3
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/NonLinkableWidgetImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.NonLinkableWidget;
+import org.openhab.core.sitemap.Parent;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class NonLinkableWidgetImpl extends WidgetImpl implements NonLinkableWidget {
+
+ public NonLinkableWidgetImpl() {
+ super();
+ }
+
+ public NonLinkableWidgetImpl(Parent parent) {
+ super(parent);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/RuleImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/RuleImpl.java
new file mode 100644
index 00000000000..5a8f809c21b
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/RuleImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Condition;
+import org.openhab.core.sitemap.Rule;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class RuleImpl implements Rule {
+
+ private List conditions = new CopyOnWriteArrayList<>();
+ private @Nullable String argument;
+
+ public RuleImpl() {
+ }
+
+ public RuleImpl(Rule rule) {
+ this.conditions = rule.getConditions();
+ this.argument = rule.getArgument();
+ }
+
+ @Override
+ public List getConditions() {
+ return conditions;
+ }
+
+ @Override
+ public void setConditions(List conditions) {
+ this.conditions = new CopyOnWriteArrayList<>(conditions);
+ }
+
+ @Override
+ public @Nullable String getArgument() {
+ return argument;
+ }
+
+ @Override
+ public void setArgument(@Nullable String argument) {
+ this.argument = argument;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SelectionImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SelectionImpl.java
new file mode 100644
index 00000000000..ce507f48d39
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SelectionImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.Mapping;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Selection;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class SelectionImpl extends NonLinkableWidgetImpl implements Selection {
+
+ private List mappings = new CopyOnWriteArrayList<>();
+
+ public SelectionImpl() {
+ super();
+ }
+
+ public SelectionImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public List getMappings() {
+ return mappings;
+ }
+
+ @Override
+ public void setMappings(List mappings) {
+ this.mappings = new CopyOnWriteArrayList<>(mappings);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SetpointImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SetpointImpl.java
new file mode 100644
index 00000000000..bc0ab015c1b
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SetpointImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Setpoint;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class SetpointImpl extends NonLinkableWidgetImpl implements Setpoint {
+
+ private @Nullable BigDecimal minValue;
+ private @Nullable BigDecimal maxValue;
+ private @Nullable BigDecimal step;
+
+ public SetpointImpl() {
+ super();
+ }
+
+ public SetpointImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public @Nullable BigDecimal getMinValue() {
+ return minValue;
+ }
+
+ @Override
+ public void setMinValue(@Nullable BigDecimal minValue) {
+ this.minValue = minValue;
+ }
+
+ @Override
+ public @Nullable BigDecimal getMaxValue() {
+ return maxValue;
+ }
+
+ @Override
+ public void setMaxValue(@Nullable BigDecimal maxValue) {
+ this.maxValue = maxValue;
+ }
+
+ @Override
+ public @Nullable BigDecimal getStep() {
+ return step;
+ }
+
+ @Override
+ public void setStep(@Nullable BigDecimal step) {
+ this.step = step;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SitemapImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SitemapImpl.java
new file mode 100644
index 00000000000..4785436f7e5
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SitemapImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Sitemap;
+import org.openhab.core.sitemap.Widget;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class SitemapImpl implements Sitemap {
+
+ private String name = "";
+ private @Nullable String label;
+ private @Nullable String icon;
+ private List widgets = new CopyOnWriteArrayList<>();
+
+ public SitemapImpl() {
+ }
+
+ public SitemapImpl(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getUID() {
+ return name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public @Nullable String getLabel() {
+ return label;
+ }
+
+ @Override
+ public void setLabel(@Nullable String label) {
+ this.label = label;
+ }
+
+ @Override
+ public @Nullable String getIcon() {
+ return icon;
+ }
+
+ @Override
+ public void setIcon(@Nullable String icon) {
+ this.icon = icon;
+ }
+
+ @Override
+ public List getWidgets() {
+ return widgets;
+ }
+
+ @Override
+ public void setWidgets(List widgets) {
+ this.widgets = new CopyOnWriteArrayList<>(widgets);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SliderImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SliderImpl.java
new file mode 100644
index 00000000000..32ba566cadc
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SliderImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Slider;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class SliderImpl extends NonLinkableWidgetImpl implements Slider {
+
+ private @Nullable Boolean switchEnabled;
+ private @Nullable Boolean releaseOnly;
+ private @Nullable BigDecimal minValue;
+ private @Nullable BigDecimal maxValue;
+ private @Nullable BigDecimal step;
+
+ public SliderImpl() {
+ super();
+ }
+
+ public SliderImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public boolean isSwitchEnabled() {
+ return switchEnabled != null ? switchEnabled : false;
+ }
+
+ @Override
+ public void setSwitchEnabled(@Nullable Boolean switchEnabled) {
+ this.switchEnabled = switchEnabled;
+ }
+
+ @Override
+ public boolean isReleaseOnly() {
+ return releaseOnly != null ? releaseOnly : false;
+ }
+
+ @Override
+ public void setReleaseOnly(@Nullable Boolean releaseOnly) {
+ this.releaseOnly = releaseOnly;
+ }
+
+ @Override
+ public @Nullable BigDecimal getMinValue() {
+ return minValue;
+ }
+
+ @Override
+ public void setMinValue(@Nullable BigDecimal minValue) {
+ this.minValue = minValue;
+ }
+
+ @Override
+ public @Nullable BigDecimal getMaxValue() {
+ return maxValue;
+ }
+
+ @Override
+ public void setMaxValue(@Nullable BigDecimal maxValue) {
+ this.maxValue = maxValue;
+ }
+
+ @Override
+ public @Nullable BigDecimal getStep() {
+ return step;
+ }
+
+ @Override
+ public void setStep(@Nullable BigDecimal step) {
+ this.step = step;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SwitchImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SwitchImpl.java
new file mode 100644
index 00000000000..0d0ae361810
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/SwitchImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.Mapping;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Switch;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class SwitchImpl extends NonLinkableWidgetImpl implements Switch {
+
+ private List mappings = new CopyOnWriteArrayList<>();
+
+ public SwitchImpl() {
+ super();
+ }
+
+ public SwitchImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public List getMappings() {
+ return mappings;
+ }
+
+ @Override
+ public void setMappings(List mappings) {
+ this.mappings = new CopyOnWriteArrayList<>(mappings);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/TextImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/TextImpl.java
new file mode 100644
index 00000000000..ef19158e78a
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/TextImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Text;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class TextImpl extends LinkableWidgetImpl implements Text {
+
+ public TextImpl() {
+ super();
+ }
+
+ public TextImpl(Parent parent) {
+ super(parent);
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/VideoImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/VideoImpl.java
new file mode 100644
index 00000000000..8ed56ccb211
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/VideoImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Video;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class VideoImpl extends NonLinkableWidgetImpl implements Video {
+
+ private String url = "";
+ private @Nullable String encoding;
+
+ public VideoImpl() {
+ super();
+ }
+
+ public VideoImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public String getUrl() {
+ return url;
+ }
+
+ @Override
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ @Override
+ public @Nullable String getEncoding() {
+ return encoding;
+ }
+
+ @Override
+ public void setEncoding(@Nullable String encoding) {
+ this.encoding = encoding;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/WebviewImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/WebviewImpl.java
new file mode 100644
index 00000000000..e8d5f5f040c
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/WebviewImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Webview;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class WebviewImpl extends NonLinkableWidgetImpl implements Webview {
+
+ private @Nullable Integer height;
+ private String url = "";
+
+ public WebviewImpl() {
+ super();
+ }
+
+ public WebviewImpl(Parent parent) {
+ super(parent);
+ }
+
+ @Override
+ public int getHeight() {
+ return height != null ? height : 0;
+ }
+
+ @Override
+ public void setHeight(@Nullable Integer height) {
+ this.height = height;
+ }
+
+ @Override
+ public String getUrl() {
+ return url;
+ }
+
+ @Override
+ public void setUrl(String url) {
+ this.url = url;
+ }
+}
diff --git a/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/WidgetImpl.java b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/WidgetImpl.java
new file mode 100644
index 00000000000..ac1b8e63b90
--- /dev/null
+++ b/bundles/org.openhab.core.sitemap/src/main/java/org/openhab/core/sitemap/internal/WidgetImpl.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.sitemap.internal;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.sitemap.Parent;
+import org.openhab.core.sitemap.Rule;
+import org.openhab.core.sitemap.Widget;
+
+/**
+ * @author Mark Herwege - Initial contribution
+ */
+@NonNullByDefault
+public class WidgetImpl implements Widget {
+
+ private @Nullable Parent parent;
+
+ private @Nullable String item;
+ private @Nullable String label;
+
+ private @Nullable String icon;
+ private List iconRules = new CopyOnWriteArrayList<>();
+ private @Nullable Boolean staticIcon;
+
+ private List labelColorRules = new CopyOnWriteArrayList<>();
+ private List