forked from eclipse-archived/smarthome
/
PageChangeListener.java
271 lines (246 loc) · 10.3 KB
/
PageChangeListener.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/**
* Copyright (c) 2014,2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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.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 java.util.function.Predicate;
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.library.CoreItemFactory;
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.Chart;
import org.eclipse.smarthome.model.sitemap.ColorArray;
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) {
addItemWithName(items, widget.getItem());
if (widget instanceof Frame) {
items.addAll(getAllItems(((Frame) widget).getChildren()));
}
// now scan visibility rules
for (VisibilityRule rule : widget.getVisibility()) {
addItemWithName(items, rule.getItem());
}
// now scan label color rules
for (ColorArray rule : widget.getLabelColor()) {
addItemWithName(items, rule.getItem());
}
// now scan value color rules
for (ColorArray rule : widget.getValueColor()) {
addItemWithName(items, rule.getItem());
}
}
}
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, itemUIRegistry.getChildren((Frame) w)));
}
if (w.getItem() != null && w.getItem().equals(item.getName())) {
// We skip the chart widgets having a refresh argument
boolean skipWidget = false;
if (w instanceof Chart) {
Chart chartWidget = (Chart) w;
skipWidget = chartWidget.getRefresh() > 0;
}
if (!skipWidget || definesVisibilityOrColor(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 contains data from the item including its state (in event.item.state)
String widgetTypeName = w.eClass().getInstanceTypeName()
.substring(w.eClass().getInstanceTypeName().lastIndexOf(".") + 1);
boolean drillDown = "mapview".equalsIgnoreCase(widgetTypeName);
Predicate<Item> itemFilter = (i -> i.getType().equals(CoreItemFactory.LOCATION));
event.item = EnrichedItemDTOMapper.map(item, drillDown, itemFilter, null, null);
// event.state is an adjustment of the item state to the widget type.
event.state = itemUIRegistry.getState(w).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;
}
events.add(event);
}
}
}
return events;
}
private boolean definesVisibilityOrColor(Widget w, String name) {
for (VisibilityRule rule : w.getVisibility()) {
if (name.equals(rule.getItem())) {
return true;
}
}
for (ColorArray rule : w.getLabelColor()) {
if (name.equals(rule.getItem())) {
return true;
}
}
for (ColorArray rule : w.getValueColor()) {
if (name.equals(rule.getItem())) {
return true;
}
}
return false;
}
public void sitemapContentChanged(EList<Widget> widgets) {
updateItemsAndWidgets(widgets);
SitemapChangedEvent changeEvent = new SitemapChangedEvent();
changeEvent.pageId = pageId;
changeEvent.sitemapName = sitemapName;
for (SitemapSubscriptionCallback callback : distinctCallbacks) {
callback.onEvent(changeEvent);
}
}
}