/**
* 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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.eclipse.smarthome.core.items.events.ItemEventFactory;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
public class GroupItem extends GenericItem implements StateChangeListener {
public static final String TYPE = "Group";
private final Logger logger = LoggerFactory.getLogger(GroupItem.class);
protected final GenericItem baseItem;
protected final Set<Item> members;
protected GroupFunction function;
/**
* Creates a plain GroupItem
*
* @param name name of the group
*/
public GroupItem(String name) {
this(name, null, null);
}
public GroupItem(String name, GenericItem baseItem) {
// only baseItem but no function set -> use Equality
this(name, baseItem, new GroupFunction.Equality());
}
/**
* Creates a GroupItem with function
*
* @param name name of the group
* @param baseItem type of items in the group
* @param function function to calculate group status out of member status
*/
public GroupItem(String name, GenericItem baseItem, GroupFunction function) {
super(TYPE, name);
// we only allow GroupItem with BOTH, baseItem AND function set, or NONE of them set
if (baseItem == null || function == null) {
this.baseItem = null;
this.function = null;
} else {
this.function = function;
this.baseItem = baseItem;
}
members = new CopyOnWriteArraySet<Item>();
}
/**
* Returns the base item of this {@link GroupItem}. This method is only
* intended to allow instance checks of the underlying BaseItem. It must
* not be changed in any way.
*
* @return the base item of this GroupItem
*/
public Item getBaseItem() {
return baseItem;
}
/**
* Returns the function of this {@link GroupItem}.
*
* @return the function of this GroupItem
*/
public GroupFunction getFunction() {
return function;
}
/**
* Returns the direct members of this {@link GroupItem} regardless if these
* members are {@link GroupItem}s as well.
*
* @return the direct members of this {@link GroupItem}
*/
public Set<Item> getMembers() {
return ImmutableSet.copyOf(members);
}
/**
* Returns the direct members of this {@link GroupItem} and recursively all
* members of the potentially contained {@link GroupItem}s as well. The {@link GroupItem}s itself aren't contained.
* The returned items are unique.
*
* @return all members of this and all contained {@link GroupItem}s
*/
public Set<Item> getAllMembers() {
Set<Item> allMembers = new HashSet<Item>();
collectMembers(allMembers, members);
return ImmutableSet.copyOf(allMembers);
}
private void collectMembers(Set<Item> allMembers, Set<Item> members) {
for (Item member : members) {
if (member instanceof GroupItem) {
collectMembers(allMembers, ((GroupItem) member).members);
} else {
allMembers.add(member);
}
}
}
/**
* Adds the given item to the members of this group item.
*
* @param item the item to be added (must not be null)
* @throws IllegalArgumentException if the given item is null
*/
public void addMember(Item item) {
if (item == null) {
throw new IllegalArgumentException("Item must not be null!");
}
members.add(item);
if (item instanceof GenericItem) {
GenericItem genericItem = (GenericItem) item;
genericItem.addStateChangeListener(this);
}
}
/**
* Removes the given item from the members of this group item.
*
* @param item the item to be removed (must not be null)
* @throws IllegalArgumentException if the given item is null
*/
public void removeMember(Item item) {
if (item == null) {
throw new IllegalArgumentException("Item must not be null!");
}
members.remove(item);
if (item instanceof GenericItem) {
GenericItem genericItem = (GenericItem) item;
genericItem.removeStateChangeListener(this);
}
}
/**
* The accepted data types of a group item is the same as of the underlying base item.
* If none is defined, the intersection of all sets of accepted data types of all group
* members is used instead.
*
* @return the accepted data types of this group item
*/
@Override
@SuppressWarnings("unchecked")
public List<Class<? extends State>> getAcceptedDataTypes() {
if (baseItem != null) {
return baseItem.getAcceptedDataTypes();
} else {
List<Class<? extends State>> acceptedDataTypes = null;
for (Item item : members) {
if (acceptedDataTypes == null) {
acceptedDataTypes = new ArrayList<>(item.getAcceptedDataTypes());
} else {
acceptedDataTypes.retainAll(item.getAcceptedDataTypes());
}
}
return acceptedDataTypes == null ? Collections.unmodifiableList(Collections.EMPTY_LIST)
: Collections.unmodifiableList(acceptedDataTypes);
}
}
/**
* The accepted command types of a group item is the same as of the underlying base item.
* If none is defined, the intersection of all sets of accepted command types of all group
* members is used instead.
*
* @return the accepted command types of this group item
*/
@Override
@SuppressWarnings("unchecked")
public List<Class<? extends Command>> getAcceptedCommandTypes() {
if (baseItem != null) {
return baseItem.getAcceptedCommandTypes();
} else {
List<Class<? extends Command>> acceptedCommandTypes = null;
for (Item item : members) {
if (acceptedCommandTypes == null) {
acceptedCommandTypes = new ArrayList<>(item.getAcceptedCommandTypes());
} else {
acceptedCommandTypes.retainAll(item.getAcceptedCommandTypes());
}
}
return acceptedCommandTypes == null ? Collections.unmodifiableList(Collections.EMPTY_LIST)
: Collections.unmodifiableList(acceptedCommandTypes);
}
}
public void send(Command command) {
if (getAcceptedCommandTypes().contains(command.getClass())) {
internalSend(command);
} else {
logger.warn("Command '{}' has been ignored for group '{}' as it is not accepted.", command.toString(),
getName());
}
}
/**
* @{inheritDoc
*/
@Override
protected void internalSend(Command command) {
if (eventPublisher != null) {
for (Item member : members) {
// try to send the command to the bus
eventPublisher.post(ItemEventFactory.createCommandEvent(member.getName(), command));
}
}
}
/**
* @{inheritDoc
*/
@Override
public State getStateAs(Class<? extends State> typeClass) {
// if a group does not have a function it cannot have a state
State newState = null;
if (function != null) {
newState = function.getStateAs(getAllMembers(), typeClass);
}
if (newState == null && baseItem != null) {
// we use the transformation method from the base item
baseItem.setState(state);
newState = baseItem.getStateAs(typeClass);
}
if (newState == null) {
newState = super.getStateAs(typeClass);
}
return newState;
}
/**
* @{inheritDoc
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getName());
sb.append(" (");
sb.append("Type=");
sb.append(getClass().getSimpleName());
sb.append(", ");
if (getBaseItem() != null) {
sb.append("BaseType=");
sb.append(baseItem.getClass().getSimpleName());
sb.append(", ");
}
sb.append("Members=");
sb.append(members.size());
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();
}
/**
* @{inheritDoc
*/
@Override
public void stateChanged(Item item, State oldState, State newState) {
}
/**
* @{inheritDoc
*/
@Override
public void stateUpdated(Item item, State state) {
State oldState = this.state;
if (function != null) {
setState(function.calculate(members));
}
if (!oldState.equals(this.state)) {
sendGroupStateChangedEvent(item.getName(), this.state, oldState);
}
}
@Override
public void setState(State state) {
State oldState = this.state;
if (baseItem != null) {
baseItem.setState(state);
this.state = baseItem.getState();
} else {
this.state = state;
}
notifyListeners(oldState, state);
}
private void sendGroupStateChangedEvent(String memberName, State newState, State oldState) {
if (eventPublisher != null) {
eventPublisher.post(
ItemEventFactory.createGroupStateChangedEvent(this.getName(), memberName, newState, oldState));
}
}
}