-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[hdpowerview] Refactor dynamic channels (#11853)
* Extract dynamic channel creation to separate classes. * Avoid double list allocations. * Add test coverage for scenarios with no channels built. * Extract common builder stuff to super class. * Fix grammar. * Reduce constructor access modifiers. * Removed unneeded this keyword for protected method. * Fix null annotation issues. Signed-off-by: Jacob Laursen <[email protected]>
- Loading branch information
Showing
13 changed files
with
1,123 additions
and
172 deletions.
There are no files selected for viewing
252 changes: 252 additions & 0 deletions
252
...main/java/org/openhab/binding/hdpowerview/internal/builders/AutomationChannelBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
/** | ||
* Copyright (c) 2010-2022 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.binding.hdpowerview.internal.builders; | ||
|
||
import java.time.DayOfWeek; | ||
import java.time.LocalTime; | ||
import java.time.format.TextStyle; | ||
import java.util.EnumSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.StringJoiner; | ||
import java.util.stream.Collectors; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; | ||
import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; | ||
import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection; | ||
import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene; | ||
import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents; | ||
import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents.ScheduledEvent; | ||
import org.openhab.core.library.CoreItemFactory; | ||
import org.openhab.core.thing.Channel; | ||
import org.openhab.core.thing.ChannelGroupUID; | ||
import org.openhab.core.thing.ChannelUID; | ||
import org.openhab.core.thing.binding.builder.ChannelBuilder; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* The {@link AutomationChannelBuilder} class creates automation channels | ||
* from structured scheduled event data. | ||
* | ||
* @author Jacob Laursen - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class AutomationChannelBuilder extends BaseChannelBuilder { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(AutomationChannelBuilder.class); | ||
|
||
@Nullable | ||
private Map<Integer, Scene> scenes; | ||
@Nullable | ||
private Map<Integer, SceneCollection> sceneCollections; | ||
@Nullable | ||
private List<ScheduledEvent> scheduledEvents; | ||
|
||
private AutomationChannelBuilder(HDPowerViewTranslationProvider translationProvider, | ||
ChannelGroupUID channelGroupUid) { | ||
super(translationProvider, channelGroupUid, HDPowerViewBindingConstants.CHANNELTYPE_AUTOMATION_ENABLED); | ||
} | ||
|
||
/** | ||
* Creates an {@link AutomationChannelBuilder} for the given {@link HDPowerViewTranslationProvider} and | ||
* {@link ChannelGroupUID}. | ||
* | ||
* @param translationProvider the {@link HDPowerViewTranslationProvider} | ||
* @param channelGroupUid parent {@link ChannelGroupUID} for created channels | ||
* @return channel builder | ||
*/ | ||
public static AutomationChannelBuilder create(HDPowerViewTranslationProvider translationProvider, | ||
ChannelGroupUID channelGroupUid) { | ||
return new AutomationChannelBuilder(translationProvider, channelGroupUid); | ||
} | ||
|
||
/** | ||
* Adds created channels to existing list. | ||
* | ||
* @param channels list that channels will be added to | ||
* @return channel builder | ||
*/ | ||
public AutomationChannelBuilder withChannels(List<Channel> channels) { | ||
this.channels = channels; | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the scenes. | ||
* | ||
* @param scenes the scenes | ||
* @return channel builder | ||
*/ | ||
public AutomationChannelBuilder withScenes(List<Scene> scenes) { | ||
this.scenes = scenes.stream().collect(Collectors.toMap(scene -> scene.id, scene -> scene)); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the scene collections. | ||
* | ||
* @param sceneCollections the scene collections | ||
* @return channel builder | ||
*/ | ||
public AutomationChannelBuilder withSceneCollections(List<SceneCollection> sceneCollections) { | ||
this.sceneCollections = sceneCollections.stream() | ||
.collect(Collectors.toMap(sceneCollection -> sceneCollection.id, sceneCollection -> sceneCollection)); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the scheduled events. | ||
* | ||
* @param scheduledEvents the sceduled events | ||
* @return channel builder | ||
*/ | ||
public AutomationChannelBuilder withScheduledEvents(List<ScheduledEvent> scheduledEvents) { | ||
this.scheduledEvents = scheduledEvents; | ||
return this; | ||
} | ||
|
||
/** | ||
* Builds and returns the channels. | ||
* | ||
* @return the {@link Channel} list | ||
*/ | ||
public List<Channel> build() { | ||
List<ScheduledEvent> scheduledEvents = this.scheduledEvents; | ||
if (scheduledEvents == null || (scenes == null && sceneCollections == null)) { | ||
return getChannelList(0); | ||
} | ||
List<Channel> channels = getChannelList(scheduledEvents.size()); | ||
scheduledEvents.stream().forEach(scheduledEvent -> { | ||
Channel channel = createChannel(scheduledEvent); | ||
if (channel != null) { | ||
channels.add(channel); | ||
} | ||
}); | ||
|
||
return channels; | ||
} | ||
|
||
private @Nullable Channel createChannel(ScheduledEvent scheduledEvent) { | ||
String referencedName = getReferencedSceneOrSceneCollectionName(scheduledEvent); | ||
if (referencedName == null) { | ||
return null; | ||
} | ||
ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(scheduledEvent.id)); | ||
String label = getScheduledEventName(referencedName, scheduledEvent); | ||
String description = translationProvider.getText("dynamic-channel.automation-enabled.description", | ||
referencedName); | ||
Channel channel = ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(channelTypeUid) | ||
.withLabel(label).withDescription(description).build(); | ||
|
||
return channel; | ||
} | ||
|
||
private @Nullable String getReferencedSceneOrSceneCollectionName(ScheduledEvent scheduledEvent) { | ||
if (scheduledEvent.sceneId > 0) { | ||
Map<Integer, Scene> scenes = this.scenes; | ||
if (scenes == null) { | ||
logger.warn("Scheduled event '{}' references scene '{}', but no scenes are loaded", scheduledEvent.id, | ||
scheduledEvent.sceneId); | ||
return null; | ||
} | ||
Scene scene = scenes.get(scheduledEvent.sceneId); | ||
if (scene != null) { | ||
return scene.getName(); | ||
} | ||
logger.warn("Scene '{}' was not found for scheduled event '{}'", scheduledEvent.sceneId, scheduledEvent.id); | ||
return null; | ||
} else if (scheduledEvent.sceneCollectionId > 0) { | ||
Map<Integer, SceneCollection> sceneCollections = this.sceneCollections; | ||
if (sceneCollections == null) { | ||
logger.warn( | ||
"Scheduled event '{}' references scene collection '{}', but no scene collections are loaded", | ||
scheduledEvent.id, scheduledEvent.sceneCollectionId); | ||
return null; | ||
} | ||
SceneCollection sceneCollection = sceneCollections.get(scheduledEvent.sceneCollectionId); | ||
if (sceneCollection != null) { | ||
return sceneCollection.getName(); | ||
} | ||
logger.warn("Scene collection '{}' was not found for scheduled event '{}'", | ||
scheduledEvent.sceneCollectionId, scheduledEvent.id); | ||
return null; | ||
} else { | ||
logger.warn("Scheduled event '{}'' not related to any scene or scene collection", scheduledEvent.id); | ||
return null; | ||
} | ||
} | ||
|
||
private String getScheduledEventName(String sceneName, ScheduledEvent scheduledEvent) { | ||
String timeString, daysString; | ||
|
||
switch (scheduledEvent.eventType) { | ||
case ScheduledEvents.SCHEDULED_EVENT_TYPE_TIME: | ||
timeString = LocalTime.of(scheduledEvent.hour, scheduledEvent.minute).toString(); | ||
break; | ||
case ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE: | ||
if (scheduledEvent.minute == 0) { | ||
timeString = translationProvider.getText("dynamic-channel.automation.at-sunrise"); | ||
} else if (scheduledEvent.minute < 0) { | ||
timeString = translationProvider.getText("dynamic-channel.automation.before-sunrise", | ||
getFormattedTimeOffset(-scheduledEvent.minute)); | ||
} else { | ||
timeString = translationProvider.getText("dynamic-channel.automation.after-sunrise", | ||
getFormattedTimeOffset(scheduledEvent.minute)); | ||
} | ||
break; | ||
case ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNSET: | ||
if (scheduledEvent.minute == 0) { | ||
timeString = translationProvider.getText("dynamic-channel.automation.at-sunset"); | ||
} else if (scheduledEvent.minute < 0) { | ||
timeString = translationProvider.getText("dynamic-channel.automation.before-sunset", | ||
getFormattedTimeOffset(-scheduledEvent.minute)); | ||
} else { | ||
timeString = translationProvider.getText("dynamic-channel.automation.after-sunset", | ||
getFormattedTimeOffset(scheduledEvent.minute)); | ||
} | ||
break; | ||
default: | ||
return sceneName; | ||
} | ||
|
||
EnumSet<DayOfWeek> days = scheduledEvent.getDays(); | ||
if (EnumSet.allOf(DayOfWeek.class).equals(days)) { | ||
daysString = translationProvider.getText("dynamic-channel.automation.all-days"); | ||
} else if (ScheduledEvents.WEEKDAYS.equals(days)) { | ||
daysString = translationProvider.getText("dynamic-channel.automation.weekdays"); | ||
} else if (ScheduledEvents.WEEKENDS.equals(days)) { | ||
daysString = translationProvider.getText("dynamic-channel.automation.weekends"); | ||
} else { | ||
StringJoiner joiner = new StringJoiner(", "); | ||
days.forEach(day -> joiner.add(day.getDisplayName(TextStyle.SHORT, translationProvider.getLocale()))); | ||
daysString = joiner.toString(); | ||
} | ||
|
||
return translationProvider.getText("dynamic-channel.automation-enabled.label", sceneName, timeString, | ||
daysString); | ||
} | ||
|
||
private String getFormattedTimeOffset(int minutes) { | ||
if (minutes >= 60) { | ||
int remainder = minutes % 60; | ||
if (remainder == 0) { | ||
return translationProvider.getText("dynamic-channel.automation.hour", minutes / 60); | ||
} | ||
return translationProvider.getText("dynamic-channel.automation.hour-minute", minutes / 60, remainder); | ||
} | ||
return translationProvider.getText("dynamic-channel.automation.minute", minutes); | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
...w/src/main/java/org/openhab/binding/hdpowerview/internal/builders/BaseChannelBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/** | ||
* Copyright (c) 2010-2022 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.binding.hdpowerview.internal.builders; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; | ||
import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; | ||
import org.openhab.core.thing.Channel; | ||
import org.openhab.core.thing.ChannelGroupUID; | ||
import org.openhab.core.thing.type.ChannelTypeUID; | ||
|
||
/** | ||
* The {@link BaseChannelBuilder} class is super class for | ||
* channel builders. | ||
* | ||
* @author Jacob Laursen - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class BaseChannelBuilder { | ||
|
||
protected final HDPowerViewTranslationProvider translationProvider; | ||
protected final ChannelGroupUID channelGroupUid; | ||
protected final ChannelTypeUID channelTypeUid; | ||
|
||
@Nullable | ||
protected List<Channel> channels; | ||
|
||
protected BaseChannelBuilder(HDPowerViewTranslationProvider translationProvider, ChannelGroupUID channelGroupUid, | ||
String channelTypeId) { | ||
this.translationProvider = translationProvider; | ||
this.channelGroupUid = channelGroupUid; | ||
this.channelTypeUid = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID, channelTypeId); | ||
} | ||
|
||
protected List<Channel> getChannelList(int initialCapacity) { | ||
List<Channel> channels = this.channels; | ||
return channels != null ? channels : new ArrayList<>(initialCapacity); | ||
} | ||
} |
Oops, something went wrong.