/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.io.rest.sitemap.internal; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.emf.common.util.EList; import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.GroupItem; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemNotFoundException; import org.eclipse.smarthome.core.items.StateChangeListener; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.io.rest.core.item.EnrichedItemDTOMapper; import org.eclipse.smarthome.io.rest.sitemap.SitemapSubscriptionService.SitemapSubscriptionCallback; import org.eclipse.smarthome.model.sitemap.Frame; import org.eclipse.smarthome.model.sitemap.VisibilityRule; import org.eclipse.smarthome.model.sitemap.Widget; import org.eclipse.smarthome.ui.items.ItemUIRegistry; /** * This is a class that listens on item state change events and creates sitemap events for a dedicated sitemap page. * * @author Kai Kreuzer - Initial contribution and API * */ public class PageChangeListener implements StateChangeListener { private final String sitemapName; private final String pageId; private final ItemUIRegistry itemUIRegistry; private EList<Widget> widgets; private Set<Item> items; private final List<SitemapSubscriptionCallback> callbacks = Collections .synchronizedList(new ArrayList<SitemapSubscriptionCallback>()); private Set<SitemapSubscriptionCallback> distinctCallbacks = Collections.emptySet(); /** * Creates a new instance. * * @param sitemapName the sitemap name of the page * @param pageId the id of the page for which events are created * @param itemUIRegistry the ItemUIRegistry which is needed for the functionality * @param widgets the list of widgets that are part of the page. */ public PageChangeListener(String sitemapName, String pageId, ItemUIRegistry itemUIRegistry, EList<Widget> widgets) { this.sitemapName = sitemapName; this.pageId = pageId; this.itemUIRegistry = itemUIRegistry; updateItemsAndWidgets(widgets); } private void updateItemsAndWidgets(EList<Widget> widgets) { if (this.widgets != null) { // cleanup statechange listeners in case widgets were removed items = getAllItems(this.widgets); for (Item item : items) { if (item instanceof GenericItem) { ((GenericItem) item).removeStateChangeListener(this); } } } this.widgets = widgets; items = getAllItems(widgets); for (Item item : items) { if (item instanceof GenericItem) { ((GenericItem) item).addStateChangeListener(this); } } } public String getSitemapName() { return sitemapName; } public String getPageId() { return pageId; } public void addCallback(SitemapSubscriptionCallback callback) { callbacks.add(callback); // we transform the list of callbacks to a set in order to remove duplicates distinctCallbacks = new HashSet<>(callbacks); } public void removeCallback(SitemapSubscriptionCallback callback) { callbacks.remove(callback); distinctCallbacks = new HashSet<>(callbacks); } /** * Disposes this instance and releases all resources. */ public void dispose() { for (Item item : items) { if (item instanceof GenericItem) { ((GenericItem) item).removeStateChangeListener(this); } else if (item instanceof GroupItem) { ((GroupItem) item).removeStateChangeListener(this); } } } /** * Collects all items that are represented by a given list of widgets * * @param widgets * 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<Item> getAllItems(EList<Widget> widgets) { Set<Item> items = new HashSet<Item>(); if (itemUIRegistry != null) { for (Widget widget : widgets) { String itemName = widget.getItem(); if (itemName != null) { addItemWithName(items, itemName); } else { if (widget instanceof Frame) { items.addAll(getAllItems(((Frame) widget).getChildren())); } } // now scan visibility rules for (VisibilityRule vr : widget.getVisibility()) { String ruleItemName = vr.getItem(); addItemWithName(items, ruleItemName); } } } return items; } private void addItemWithName(Set<Item> items, String itemName) { if (itemName != null) { try { Item item = itemUIRegistry.getItem(itemName); items.add(item); } catch (ItemNotFoundException e) { // ignore } } } @Override public void stateChanged(Item item, State oldState, State newState) { // For all items except group, send an event only when the event state is changed. if (item instanceof GroupItem) { return; } Set<SitemapEvent> events = constructSitemapEvents(item, widgets); for (SitemapEvent event : events) { for (SitemapSubscriptionCallback callback : distinctCallbacks) { callback.onEvent(event); } } } @Override public void stateUpdated(Item item, State state) { // For group item only, send an event each time the event state is updated. // It allows updating the group label while the group state is unchanged, // for example the count in label for Group:Switch:OR if (!(item instanceof GroupItem)) { return; } Set<SitemapEvent> events = constructSitemapEvents(item, widgets); for (SitemapEvent event : events) { for (SitemapSubscriptionCallback callback : distinctCallbacks) { callback.onEvent(event); } } } private Set<SitemapEvent> constructSitemapEvents(Item item, List<Widget> widgets) { Set<SitemapEvent> events = new HashSet<>(); for (Widget w : widgets) { if (w instanceof Frame) { events.addAll(constructSitemapEvents(item, ((Frame) w).getChildren())); } if ((w.getItem() != null && w.getItem().equals(item.getName())) || definesVisibility(w, item.getName())) { SitemapWidgetEvent event = new SitemapWidgetEvent(); event.sitemapName = sitemapName; event.pageId = pageId; event.label = itemUIRegistry.getLabel(w); event.labelcolor = itemUIRegistry.getLabelColor(w); event.valuecolor = itemUIRegistry.getValueColor(w); event.widgetId = itemUIRegistry.getWidgetId(w); event.visibility = itemUIRegistry.getVisiblity(w); event.item = EnrichedItemDTOMapper.map(item, false, null, null); // adjust the state according to the widget type event.item.state = itemUIRegistry.getState(w).toFullString(); events.add(event); } } return events; } private boolean definesVisibility(Widget w, String name) { for (VisibilityRule vr : w.getVisibility()) { if (name.equals(vr.getItem())) { return true; } } return false; } public void sitemapContentChanged(EList<Widget> widgets) { updateItemsAndWidgets(widgets); } }