Skip to content

Commit

Permalink
Set a default presentation pattern for String/Number/Datetime items
Browse files Browse the repository at this point in the history
A default state pattern was previously provided by ChannelStateDescriptionProvider only for String and Number items linked to a channel.
It is now the class DefaultStateDescriptionFragmentProvider which is responsible for providing the default state pattern for items, whether the item is linked to a channel or not.
This new class is the lowest ranked StateDescriptionFragmentProvider so that all other providers have priority in setting the state pattern.

Default pattern for string item: %s
Default pattern for datetime item or group with datetime state: %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
Default pattern for number item or group with number state: %.0f
Default pattern for number+dimension item or group with number+dimension state: %.0f %unit%

Closes openhab#4071
Closes openhab#3835

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
  • Loading branch information
lolodomo committed Apr 11, 2024
1 parent cbb458e commit 7fe0de0
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 17 deletions.
Expand Up @@ -21,7 +21,6 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.Item;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingRegistry;
Expand Down Expand Up @@ -100,22 +99,6 @@ public Integer getRank() {
ChannelType channelType = thingTypeRegistry.getChannelType(channel, locale);
if (channelType != null) {
stateDescription = channelType.getState();
String itemType = channelType.getItemType();
if (itemType != null && (stateDescription == null || stateDescription.getPattern() == null)) {
String pattern = null;
if (CoreItemFactory.STRING.equalsIgnoreCase(itemType)) {
pattern = "%s";
} else if (itemType.startsWith(CoreItemFactory.NUMBER)) {
pattern = "%.0f";
}
if (pattern != null) {
logger.trace("Provide a default pattern {} for item {}", pattern, itemName);
StateDescriptionFragmentBuilder builder = (stateDescription == null)
? StateDescriptionFragmentBuilder.create()
: StateDescriptionFragmentBuilder.create(stateDescription);
stateDescription = builder.withPattern(pattern).build().toStateDescription();
}
}
}
StateDescription dynamicStateDescription = getDynamicStateDescription(channel, stateDescription,
locale);
Expand Down
@@ -0,0 +1,119 @@
/**
* Copyright (c) 2010-2024 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.internal.items;

import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.types.StateDescriptionFragment;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
import org.openhab.core.types.StateDescriptionFragmentProvider;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A {@link StateDescriptionFragment} provider providing a default state pattern for items of type String,
* DateTime and Number (with or without dimension).
*
* @author Laurent Garnier - initial contribution
*
*/
@NonNullByDefault
@Component(service = { StateDescriptionFragmentProvider.class,
DefaultStateDescriptionFragmentProvider.class }, immediate = true, property = { "service.ranking:Integer=-2" })
public class DefaultStateDescriptionFragmentProvider implements StateDescriptionFragmentProvider {

private static final StateDescriptionFragment DEFAULT_STRING = StateDescriptionFragmentBuilder.create()
.withPattern("%s").build();
private static final StateDescriptionFragment DEFAULT_DATETIME = StateDescriptionFragmentBuilder.create()
.withPattern("%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS").build();
private static final StateDescriptionFragment DEFAULT_NUMBER = StateDescriptionFragmentBuilder.create()
.withPattern("%.0f").build();
private static final StateDescriptionFragment DEFAULT_NUMBER_WITH_DIMENSION = StateDescriptionFragmentBuilder
.create().withPattern("%.0f %unit%").build();

private final Logger logger = LoggerFactory.getLogger(DefaultStateDescriptionFragmentProvider.class);

private final Map<String, StateDescriptionFragment> stateDescriptionFragments = new ConcurrentHashMap<>();

private Integer rank = -2; // takes less precedence over all other providers

@Activate
public DefaultStateDescriptionFragmentProvider(Map<String, Object> properties) {
Object serviceRanking = properties.get(Constants.SERVICE_RANKING);
if (serviceRanking instanceof Integer) {
rank = (Integer) serviceRanking;
}
}

@Deactivate
protected void deactivate() {
onAllItemsRemoved();
}

public void onItemAdded(Item item) {
logger.debug("DefaultStateDescriptionFragmentProvider onItemAdded {} {}", item.getName(), item.getType());
if (item instanceof GroupItem group) {
Item baseItem = group.getBaseItem();
if (baseItem != null) {
onItemAdded(baseItem);
}
} else if (item.getType().startsWith(CoreItemFactory.NUMBER + ":")) {
stateDescriptionFragments.put(item.getName(), DEFAULT_NUMBER_WITH_DIMENSION);
} else {
switch (item.getType()) {
case CoreItemFactory.STRING:
stateDescriptionFragments.put(item.getName(), DEFAULT_STRING);
break;
case CoreItemFactory.DATETIME:
stateDescriptionFragments.put(item.getName(), DEFAULT_DATETIME);
break;
case CoreItemFactory.NUMBER:
stateDescriptionFragments.put(item.getName(), DEFAULT_NUMBER);
break;
default:
stateDescriptionFragments.remove(item.getName());
}
}
}

public void onItemRemoved(Item item) {
logger.debug("DefaultStateDescriptionFragmentProvider onItemRemoved {}", item.getName());
stateDescriptionFragments.remove(item.getName());
}

public void onAllItemsRemoved() {
logger.debug("DefaultStateDescriptionFragmentProvider onAllItemsRemoved");
stateDescriptionFragments.clear();
}

@Override
public @Nullable StateDescriptionFragment getStateDescriptionFragment(String itemName, @Nullable Locale locale) {
return stateDescriptionFragments.get(itemName);
}

@Override
public Integer getRank() {
return rank;
}
}
Expand Up @@ -71,6 +71,7 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
private @Nullable StateDescriptionService stateDescriptionService;
private @Nullable CommandDescriptionService commandDescriptionService;
private final MetadataRegistry metadataRegistry;
private @Nullable DefaultStateDescriptionFragmentProvider defaultStateDescriptionFragmentProvider;

private @Nullable ItemStateConverter itemStateConverter;

Expand Down Expand Up @@ -198,6 +199,11 @@ private void initializeItem(Item item) throws IllegalArgumentException {

// add the item to all relevant groups
addToGroupItems(item, item.getGroupNames());

DefaultStateDescriptionFragmentProvider stateDescriptionProvider = defaultStateDescriptionFragmentProvider;
if (stateDescriptionProvider != null) {
stateDescriptionProvider.onItemAdded(item);
}
}

private void injectServices(Item item) {
Expand Down Expand Up @@ -246,6 +252,10 @@ protected void onRemoveElement(Item element) {
genericItem.dispose();
}
removeFromGroupItems(element, element.getGroupNames());
DefaultStateDescriptionFragmentProvider stateDescriptionProvider = defaultStateDescriptionFragmentProvider;
if (stateDescriptionProvider != null) {
stateDescriptionProvider.onItemRemoved(element);
}
}

@Override
Expand All @@ -269,6 +279,12 @@ protected void onUpdateElement(Item oldItem, Item item) {
addMembersToGroupItem(groupItem);
}
injectServices(item);

DefaultStateDescriptionFragmentProvider stateDescriptionProvider = defaultStateDescriptionFragmentProvider;
if (stateDescriptionProvider != null) {
stateDescriptionProvider.onItemRemoved(oldItem);
stateDescriptionProvider.onItemAdded(item);
}
}

@Override
Expand Down Expand Up @@ -469,6 +485,22 @@ public void unsetCommandDescriptionService(CommandDescriptionService commandDesc
}
}

@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
public void setDefaultStateDescriptionFragmentProvider(
DefaultStateDescriptionFragmentProvider defaultStateDescriptionFragmentProvider) {
this.defaultStateDescriptionFragmentProvider = defaultStateDescriptionFragmentProvider;

defaultStateDescriptionFragmentProvider.onAllItemsRemoved();
for (Item item : getItems()) {
defaultStateDescriptionFragmentProvider.onItemAdded(item);
}
}

public void unsetDefaultStateDescriptionFragmentProvider(
DefaultStateDescriptionFragmentProvider defaultStateDescriptionFragmentProvider) {
this.defaultStateDescriptionFragmentProvider = null;
}

@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
protected void setManagedProvider(ManagedItemProvider provider) {
super.setManagedProvider(provider);
Expand Down
Expand Up @@ -159,6 +159,20 @@ public void testOptionsWhenTwoDescriptionProvidersHigherRankingDoesntProvideOpti
assertThat(stateDescription.getOptions(), is(stateDescriptionFragment2.getOptions()));
}

@Test
public void testPatternWhenTwoDescriptionProvidersHigherRankingDoesntProvidePattern() {
StateDescriptionFragment stateDescriptionFragment1 = StateDescriptionFragmentBuilder.create().build();
registerStateDescriptionFragmentProvider(stateDescriptionFragment1, -1);

StateDescriptionFragment stateDescriptionFragment2 = StateDescriptionFragmentBuilder.create()
.withPattern("pattern").build();
registerStateDescriptionFragmentProvider(stateDescriptionFragment2, -2);

StateDescription stateDescription = Objects.requireNonNull(item.getStateDescription());

assertThat(stateDescription.getPattern(), is(stateDescriptionFragment2.getPattern()));
}

@Test
public void testFragmentsAreMergedInProviderOrder() {
final List<StateOption> options = List.of(new StateOption("value", "label"));
Expand Down

0 comments on commit 7fe0de0

Please sign in to comment.