/**
* 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.thing.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.collections.iterators.ArrayIterator;
import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.Channel;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.thing.UID;
import org.eclipse.smarthome.core.thing.binding.builder.BridgeBuilder;
import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder;
import org.eclipse.smarthome.core.thing.dto.ChannelDTO;
import org.eclipse.smarthome.core.thing.dto.ChannelDTOMapper;
import org.eclipse.smarthome.core.thing.dto.ThingDTO;
import org.eclipse.smarthome.core.thing.internal.BridgeImpl;
import org.eclipse.smarthome.core.thing.internal.ThingImpl;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
/**
* {@link ThingHelper} provides a utility method to create and bind items.
*
* @author Oliver Libutzki - Initial contribution
* @author Andre Fuechsel - graceful creation of items and links
* @author Benedikt Niehues - Fix ESH Bug 450236
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=450236 - Considering
* ThingTypeDescription
* @author Dennis Nobel - Removed createAndBindItems method
* @author Kai Kreuzer - Added merge method
*/
public class ThingHelper {
/**
* Indicates whether two {@link Thing}s are technical equal.
*
* @param a
* Thing object
* @param b
* another Thing object
* @return true whether a and b are equal, otherwise false
*/
public static boolean equals(Thing a, Thing b) {
if (!a.getUID().equals(b.getUID())) {
return false;
}
// bridge
if (!Objects.equal(a.getBridgeUID(), b.getBridgeUID())) {
return false;
}
// configuration
if (!Objects.equal(a.getConfiguration(), b.getConfiguration())) {
return false;
}
// label
if (!Objects.equal(a.getLabel(), b.getLabel())) {
return false;
}
// location
if (!Objects.equal(a.getLocation(), b.getLocation())) {
return false;
}
// channels
List<Channel> channelsOfA = a.getChannels();
List<Channel> channelsOfB = b.getChannels();
if (channelsOfA.size() != channelsOfB.size()) {
return false;
}
if (!toString(channelsOfA).equals(toString(channelsOfB))) {
return false;
}
return true;
}
private static String toString(List<Channel> channels) {
List<String> strings = new ArrayList<>(channels.size());
for (Channel channel : channels) {
strings.add(channel.getUID().toString() + '#' + channel.getAcceptedItemType() + '#' + channel.getKind());
}
Collections.sort(strings);
return Joiner.on(',').join(strings);
}
public static void addChannelsToThing(Thing thing, Collection<Channel> channels) {
List<Channel> mutableChannels = ((ThingImpl) thing).getChannelsMutable();
ensureUniqueChannels(mutableChannels, channels);
mutableChannels.addAll(channels);
}
public static void ensureUnique(Collection<Channel> channels) {
HashSet<UID> ids = new HashSet<>();
for (Channel channel : channels) {
if (!ids.add(channel.getUID())) {
throw new IllegalArgumentException("Duplicate channels " + channel.getUID().getAsString());
}
}
}
/**
* Ensures that there are no duplicate channels in the array (i.e. not using the same ChannelUID)
*
* @param channels the channels to check
* @throws IllegalArgumentException in case there are duplicate channels found
*/
public static void ensureUniqueChannels(final Channel[] channels) {
@SuppressWarnings("unchecked")
final Iterator<Channel> it = new ArrayIterator(channels);
ensureUniqueChannels(it, new HashSet<UID>(channels.length));
}
/**
* Ensures that there are no duplicate channels in the collection (i.e. not using the same ChannelUID)
*
* @param channels the channels to check
* @throws IllegalArgumentException in case there are duplicate channels found
*/
public static void ensureUniqueChannels(final Collection<Channel> channels) {
ensureUniqueChannels(channels.iterator(), new HashSet<UID>(channels.size()));
}
/**
* Ensures that there are no duplicate channels in the collection plus the additional one (i.e. not using the same
* ChannelUID)
*
* @param channels the {@link List} of channels to check
* @param channel an additional channel
* @throws IllegalArgumentException in case there are duplicate channels found
*/
public static void ensureUniqueChannels(final Collection<Channel> channels, final Channel channel) {
ensureUniqueChannels(channels, Collections.singleton(channel));
}
private static void ensureUniqueChannels(final Collection<Channel> channels1, final Collection<Channel> channels2) {
ensureUniqueChannels(channels1.iterator(),
ensureUniqueChannels(channels2.iterator(), new HashSet<UID>(channels1.size() + channels2.size())));
}
private static HashSet<UID> ensureUniqueChannels(final Iterator<Channel> channels, final HashSet<UID> ids) {
while (channels.hasNext()) {
final Channel channel = channels.next();
if (!ids.add(channel.getUID())) {
throw new IllegalArgumentException("Duplicate channels " + channel.getUID().getAsString());
}
}
return ids;
}
/**
* Merges the content of a ThingDTO with an existing Thing.
* Where ever the DTO has null values, the content of the original Thing is kept.
* Where ever the DTO has non-null values, these are used.
* In consequence, care must be taken when the content of a list (like configuration, properties or channels) is to
* be updated - the DTO must contain the full list, otherwise entries will be deleted.
*
* @param thing the Thing instance to merge the new content into
* @param updatedContents a DTO which carries the updated content
*
* @return A Thing instance, which is the result of the merge
*/
public static Thing merge(Thing thing, ThingDTO updatedContents) {
ThingBuilder builder;
if (thing instanceof Bridge) {
builder = BridgeBuilder.create(thing.getThingTypeUID(), thing.getUID());
} else {
builder = ThingBuilder.create(thing.getThingTypeUID(), thing.getUID());
}
// Update the label
if (updatedContents.label != null) {
builder.withLabel(updatedContents.label);
} else {
builder.withLabel(thing.getLabel());
}
// Update the location
if (updatedContents.location != null) {
builder.withLocation(updatedContents.location);
} else {
builder.withLocation(thing.getLocation());
}
// update bridge UID
if (updatedContents.bridgeUID != null) {
builder.withBridge(new ThingUID(updatedContents.bridgeUID));
} else {
builder.withBridge(thing.getBridgeUID());
}
// update thing configuration
if (updatedContents.configuration != null && !updatedContents.configuration.keySet().isEmpty()) {
builder.withConfiguration(new Configuration(updatedContents.configuration));
} else {
builder.withConfiguration(thing.getConfiguration());
}
// update thing properties
if (updatedContents.properties != null) {
builder.withProperties(updatedContents.properties);
} else {
builder.withProperties(thing.getProperties());
}
// Update the channels
if (updatedContents.channels != null) {
for (ChannelDTO channelDTO : updatedContents.channels) {
builder.withChannel(ChannelDTOMapper.map(channelDTO));
}
} else {
builder.withChannels(thing.getChannels());
}
if (updatedContents.location != null) {
builder.withLocation(updatedContents.location);
} else {
builder.withLocation(thing.getLocation());
}
Thing mergedThing = builder.build();
// keep all child things in place on a merged bridge
if (mergedThing instanceof BridgeImpl && thing instanceof Bridge) {
Bridge bridge = (Bridge) thing;
BridgeImpl mergedBridge = (BridgeImpl) mergedThing;
for (Thing child : bridge.getThings()) {
mergedBridge.addThing(child);
}
}
return mergedThing;
}
}