/** * 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.core.items; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import org.eclipse.smarthome.core.common.ThreadPoolManager; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.items.events.ItemEventFactory; import org.eclipse.smarthome.core.library.internal.StateConverterUtil; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.Convertible; import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.StateDescriptionProvider; import org.eclipse.smarthome.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; /** * The abstract base class for all items. It provides all relevant logic * for the infrastructure, such as publishing updates to the event bus * or notifying listeners. * * @author Kai Kreuzer - Initial contribution and API * @author Andre Fuechsel - Added tags * @author Stefan Bußweiler - Migration to new ESH event concept * */ abstract public class GenericItem implements ActiveItem { private final Logger logger = LoggerFactory.getLogger(GenericItem.class); private static final String ITEM_THREADPOOLNAME = "items"; protected EventPublisher eventPublisher; protected Set<StateChangeListener> listeners = new CopyOnWriteArraySet<StateChangeListener>( Collections.newSetFromMap(new WeakHashMap<StateChangeListener, Boolean>())); protected List<String> groupNames = new ArrayList<String>(); protected Set<String> tags = new HashSet<String>(); final protected String name; final protected String type; protected State state = UnDefType.NULL; protected String label; protected String category; private List<StateDescriptionProvider> stateDescriptionProviders; public GenericItem(String type, String name) { this.name = name; this.type = type; } /** * {@inheritDoc} */ @Override public State getState() { return state; } /** * {@inheritDoc} */ @Override public State getStateAs(Class<? extends State> typeClass) { if (state instanceof Convertible) { return ((Convertible) state).as(typeClass); } else { return StateConverterUtil.defaultConversion(state, typeClass); } } /** * {@inheritDoc} */ @Override public String getName() { return name; } /** * {@inheritDoc} */ @Override public String getType() { return type; } /** * {@inheritDoc} */ @Override public List<String> getGroupNames() { return ImmutableList.copyOf(groupNames); } /** * Adds a group name to the {@link GenericItem}. * * @param groupItemName * group item name to add * * @throws IllegalArgumentException if groupItemName is {@code null} */ @Override public void addGroupName(String groupItemName) { if (groupItemName == null) { throw new IllegalArgumentException("Group item name must not be null!"); } if (!groupNames.contains(groupItemName)) { groupNames.add(groupItemName); } } @Override public void addGroupNames(String... groupItemNames) { for (String groupItemName : groupItemNames) { addGroupName(groupItemName); } } @Override public void addGroupNames(List<String> groupItemNames) { for (String groupItemName : groupItemNames) { addGroupName(groupItemName); } } /** * Removes a group item name from the {@link GenericItem}. * * @param groupItemName * group item name to remove * * @throws IllegalArgumentException if groupItemName is {@code null} */ @Override public void removeGroupName(String groupItemName) { if (groupItemName == null) { throw new IllegalArgumentException("Group item name must not be null!"); } groupNames.remove(groupItemName); } public void setEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } public void setStateDescriptionProviders(List<StateDescriptionProvider> stateDescriptionProviders) { this.stateDescriptionProviders = stateDescriptionProviders; } protected void internalSend(Command command) { // try to send the command to the bus if (eventPublisher != null) { eventPublisher.post(ItemEventFactory.createCommandEvent(this.getName(), command)); } } /** * Set a new state. * * Subclasses may override this method in order to do necessary conversions upfront. Afterwards, * {@link #applyState(State)} should be called by classes overriding this method. * * @param state * new state of this item */ public void setState(State state) { applyState(state); } /** * Sets new state, notifies listeners and sends events. * * Classes overriding the {@link #setState(State)} method should call this method in order to actually set the * state, inform listeners and send the event. * * @param state new state of this item */ protected final void applyState(State state) { State oldState = this.state; this.state = state; notifyListeners(oldState, state); if (!oldState.equals(state)) { sendStateChangedEvent(state, oldState); } } private void sendStateChangedEvent(State newState, State oldState) { if (eventPublisher != null) { eventPublisher.post(ItemEventFactory.createStateChangedEvent(this.name, newState, oldState)); } } public void send(RefreshType command) { internalSend(command); } protected void notifyListeners(final State oldState, final State newState) { // if nothing has changed, we send update notifications Set<StateChangeListener> clonedListeners = null; clonedListeners = new CopyOnWriteArraySet<StateChangeListener>(listeners); ExecutorService pool = ThreadPoolManager.getPool(ITEM_THREADPOOLNAME); for (final StateChangeListener listener : clonedListeners) { pool.execute(new Runnable() { @Override public void run() { try { listener.stateUpdated(GenericItem.this, newState); if (newState != null && !newState.equals(oldState)) { listener.stateChanged(GenericItem.this, oldState, newState); } } catch (Exception e) { logger.warn("failed notifying listener '{}' about state update of item {}: {}", new Object[] { listener.toString(), GenericItem.this.getName(), e.getMessage() }, e); } } }); } } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getName()); sb.append(" ("); sb.append("Type="); sb.append(getClass().getSimpleName()); sb.append(", "); sb.append("State="); sb.append(getState()); sb.append(", "); sb.append("Label="); sb.append(getLabel()); sb.append(", "); sb.append("Category="); sb.append(getCategory()); if (!getTags().isEmpty()) { sb.append(", "); sb.append("Tags=["); sb.append(Joiner.on(", ").join(getTags())); sb.append("]"); } if (!getGroupNames().isEmpty()) { sb.append(", "); sb.append("Groups=["); sb.append(Joiner.on(", ").join(getGroupNames())); sb.append("]"); } sb.append(")"); return sb.toString(); } public void addStateChangeListener(StateChangeListener listener) { synchronized (listeners) { listeners.add(listener); } } public void removeStateChangeListener(StateChangeListener listener) { synchronized (listeners) { listeners.remove(listener); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((category == null) ? 0 : category.hashCode()); result = prime * result + ((label == null) ? 0 : label.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((tags == null) ? 0 : tags.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } GenericItem other = (GenericItem) obj; if (category == null) { if (other.category != null) { return false; } } else if (!category.equals(other.category)) { return false; } if (label == null) { if (other.label != null) { return false; } } else if (!label.equals(other.label)) { return false; } if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } if (tags == null) { if (other.tags != null) { return false; } } else if (!tags.equals(other.tags)) { return false; } if (type == null) { if (other.type != null) { return false; } } else if (!type.equals(other.type)) { return false; } return true; } @Override public Set<String> getTags() { return ImmutableSet.copyOf(tags); } @Override public boolean hasTag(String tag) { return (tags.contains(tag)); } @Override public void addTag(String tag) { tags.add(tag); } @Override public void addTags(Collection<String> tags) { this.tags.addAll(tags); } @Override public void addTags(String... tags) { this.tags.addAll(Arrays.asList(tags)); } @Override public void removeTag(String tag) { tags.remove(tag); } @Override public void removeAllTags() { tags.clear(); } @Override public String getLabel() { return this.label; } @Override public void setLabel(String label) { this.label = label; } @Override public String getCategory() { return category; } @Override public void setCategory(String category) { this.category = category; } @Override public StateDescription getStateDescription() { return getStateDescription(null); } @Override public StateDescription getStateDescription(Locale locale) { if (stateDescriptionProviders != null) { for (StateDescriptionProvider stateDescriptionProvider : stateDescriptionProviders) { StateDescription stateDescription = stateDescriptionProvider.getStateDescription(this.name, locale); if (stateDescription != null) { return stateDescription; } } } return null; } }