/**
* 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.xml.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.smarthome.core.i18n.I18nProvider;
import org.eclipse.smarthome.core.thing.UID;
import org.eclipse.smarthome.core.thing.i18n.ThingTypeI18nUtil;
import org.eclipse.smarthome.core.thing.type.ChannelGroupType;
import org.eclipse.smarthome.core.thing.type.ChannelGroupTypeUID;
import org.eclipse.smarthome.core.thing.type.ChannelType;
import org.eclipse.smarthome.core.thing.type.ChannelTypeProvider;
import org.eclipse.smarthome.core.thing.type.ChannelTypeUID;
import org.eclipse.smarthome.core.types.StateDescription;
import org.eclipse.smarthome.core.types.StateOption;
import org.osgi.framework.Bundle;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* {@link XmlChannelTypeProvider} provides channel types from XML files.
*
* @author Dennis Nobel - Initial contribution
* @author Kai Kreuzer - fixed concurrency issues
*/
@Component(immediate = true, property = { "esh.scope=core.xml" })
public class XmlChannelTypeProvider implements ChannelTypeProvider {
private class LocalizedChannelTypeKey {
public String locale;
public UID uid;
public LocalizedChannelTypeKey(UID uid, String locale) {
this.uid = uid;
this.locale = locale;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
LocalizedChannelTypeKey other = (LocalizedChannelTypeKey) obj;
if (!getOuterType().equals(other.getOuterType())) {
return false;
}
if (locale == null) {
if (other.locale != null) {
return false;
}
} else if (!locale.equals(other.locale)) {
return false;
}
if (uid == null) {
if (other.uid != null) {
return false;
}
} else if (!uid.equals(other.uid)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((locale == null) ? 0 : locale.hashCode());
result = prime * result + ((uid == null) ? 0 : uid.hashCode());
return result;
}
private XmlChannelTypeProvider getOuterType() {
return XmlChannelTypeProvider.this;
}
}
private final Map<Bundle, List<ChannelGroupType>> bundleChannelGroupTypesMap = new ConcurrentHashMap<>();
private final Map<Bundle, List<ChannelType>> bundleChannelTypesMap = new ConcurrentHashMap<>();
private final Map<LocalizedChannelTypeKey, ChannelGroupType> localizedChannelGroupTypeCache = new ConcurrentHashMap<>();
private final Map<LocalizedChannelTypeKey, ChannelType> localizedChannelTypeCache = new ConcurrentHashMap<>();
private ThingTypeI18nUtil thingTypeI18nUtil;
public synchronized void addChannelGroupType(Bundle bundle, ChannelGroupType channelGroupType) {
if (channelGroupType != null) {
List<ChannelGroupType> channelGroupTypes = acquireChannelGroupTypes(bundle);
if (channelGroupTypes != null) {
channelGroupTypes.add(channelGroupType);
// just make sure no old entry remains in the cache
removeCachedChannelGroupTypes(channelGroupType);
}
}
}
public synchronized void addChannelType(Bundle bundle, ChannelType channelType) {
if (channelType != null) {
List<ChannelType> channelTypes = acquireChannelTypes(bundle);
if (channelTypes != null) {
channelTypes.add(channelType);
// just make sure no old entry remains in the cache
removeCachedChannelTypes(channelType);
}
}
}
@Override
public ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID, Locale locale) {
Collection<Entry<Bundle, List<ChannelGroupType>>> channelGroupTypesList = this.bundleChannelGroupTypesMap
.entrySet();
if (channelGroupTypesList != null) {
for (Entry<Bundle, List<ChannelGroupType>> channelGroupTypes : channelGroupTypesList) {
for (ChannelGroupType channelGroupType : channelGroupTypes.getValue()) {
if (channelGroupType.getUID().equals(channelGroupTypeUID)) {
return createLocalizedChannelGroupType(channelGroupTypes.getKey(), channelGroupType, locale);
}
}
}
}
return null;
}
@Override
public Collection<ChannelGroupType> getChannelGroupTypes(Locale locale) {
List<ChannelGroupType> allChannelGroupTypes = new ArrayList<>(10);
Collection<Entry<Bundle, List<ChannelGroupType>>> channelGroupTypesList = this.bundleChannelGroupTypesMap
.entrySet();
if (channelGroupTypesList != null) {
for (Entry<Bundle, List<ChannelGroupType>> channelGroupTypes : channelGroupTypesList) {
for (ChannelGroupType channelGroupType : channelGroupTypes.getValue()) {
ChannelGroupType localizedChannelType = createLocalizedChannelGroupType(channelGroupTypes.getKey(),
channelGroupType, locale);
allChannelGroupTypes.add(localizedChannelType);
}
}
}
return allChannelGroupTypes;
}
@Override
public ChannelType getChannelType(ChannelTypeUID channelTypeUID, Locale locale) {
Collection<Entry<Bundle, List<ChannelType>>> channelTypesList = this.bundleChannelTypesMap.entrySet();
if (channelTypesList != null) {
for (Entry<Bundle, List<ChannelType>> channelTypes : channelTypesList) {
for (ChannelType channelType : channelTypes.getValue()) {
if (channelType.getUID().equals(channelTypeUID)) {
return createLocalizedChannelType(channelTypes.getKey(), channelType, locale);
}
}
}
} else {
}
return null;
}
@Override
public synchronized Collection<ChannelType> getChannelTypes(Locale locale) {
List<ChannelType> allChannelTypes = new ArrayList<>(10);
Collection<Entry<Bundle, List<ChannelType>>> channelTypesList = this.bundleChannelTypesMap.entrySet();
if (channelTypesList != null) {
for (Entry<Bundle, List<ChannelType>> channelTypes : channelTypesList) {
for (ChannelType channelType : channelTypes.getValue()) {
ChannelType localizedChannelType = createLocalizedChannelType(channelTypes.getKey(), channelType,
locale);
allChannelTypes.add(localizedChannelType);
}
}
}
return allChannelTypes;
}
public synchronized void removeAllChannelGroupTypes(Bundle bundle) {
if (bundle != null) {
List<ChannelGroupType> channelGroupTypes = this.bundleChannelGroupTypesMap.get(bundle);
if (channelGroupTypes != null) {
this.bundleChannelGroupTypesMap.remove(bundle);
removeCachedChannelGroupTypes(channelGroupTypes);
}
}
}
public synchronized void removeAllChannelTypes(Bundle bundle) {
if (bundle != null) {
List<ChannelType> channelTypes = this.bundleChannelTypesMap.get(bundle);
if (channelTypes != null) {
this.bundleChannelTypesMap.remove(bundle);
removeCachedChannelTypes(channelTypes);
}
}
}
@Reference
public void setI18nProvider(I18nProvider i18nProvider) {
this.thingTypeI18nUtil = new ThingTypeI18nUtil(i18nProvider);
}
public void unsetI18nProvider(I18nProvider i18nProvider) {
this.thingTypeI18nUtil = null;
}
private List<ChannelGroupType> acquireChannelGroupTypes(Bundle bundle) {
if (bundle != null) {
List<ChannelGroupType> channelGroupTypes = this.bundleChannelGroupTypesMap.get(bundle);
if (channelGroupTypes == null) {
channelGroupTypes = new ArrayList<ChannelGroupType>(10);
this.bundleChannelGroupTypesMap.put(bundle, channelGroupTypes);
}
return channelGroupTypes;
}
return null;
}
private List<ChannelType> acquireChannelTypes(Bundle bundle) {
if (bundle != null) {
List<ChannelType> channelTypes = this.bundleChannelTypesMap.get(bundle);
if (channelTypes == null) {
channelTypes = new ArrayList<ChannelType>(10);
this.bundleChannelTypesMap.put(bundle, channelTypes);
}
return channelTypes;
}
return null;
}
private ChannelGroupType createLocalizedChannelGroupType(Bundle bundle, ChannelGroupType channelGroupType,
Locale locale) {
LocalizedChannelTypeKey localizedChannelTypeKey = getLocalizedChannelTypeKey(channelGroupType.getUID(), locale);
ChannelGroupType cachedEntry = localizedChannelGroupTypeCache.get(localizedChannelTypeKey);
if (cachedEntry != null) {
return cachedEntry;
}
if (this.thingTypeI18nUtil != null) {
ChannelGroupTypeUID channelGroupTypeUID = channelGroupType.getUID();
String label = this.thingTypeI18nUtil.getChannelGroupLabel(bundle, channelGroupTypeUID,
channelGroupType.getLabel(), locale);
String description = this.thingTypeI18nUtil.getChannelGroupDescription(bundle, channelGroupTypeUID,
channelGroupType.getDescription(), locale);
ChannelGroupType localizedChannelType = new ChannelGroupType(channelGroupTypeUID,
channelGroupType.isAdvanced(), label, description, channelGroupType.getChannelDefinitions());
localizedChannelGroupTypeCache.put(localizedChannelTypeKey, localizedChannelType);
return localizedChannelType;
}
return channelGroupType;
}
private StateDescription createLocalizedChannelState(Bundle bundle, ChannelType channelType,
ChannelTypeUID channelTypeUID, Locale locale) {
StateDescription state = channelType.getState();
if (state != null) {
String pattern = this.thingTypeI18nUtil.getChannelStatePattern(bundle, channelTypeUID, state.getPattern(),
locale);
List<StateOption> localizedOptions = new ArrayList<>();
List<StateOption> options = state.getOptions();
for (StateOption stateOption : options) {
String optionLabel = this.thingTypeI18nUtil.getChannelStateOption(bundle, channelTypeUID,
stateOption.getValue(), stateOption.getLabel(), locale);
localizedOptions.add(new StateOption(stateOption.getValue(), optionLabel));
}
return new StateDescription(state.getMinimum(), state.getMaximum(), state.getStep(), pattern,
state.isReadOnly(), localizedOptions);
}
return null;
}
private ChannelType createLocalizedChannelType(Bundle bundle, ChannelType channelType, Locale locale) {
LocalizedChannelTypeKey localizedChannelTypeKey = getLocalizedChannelTypeKey(channelType.getUID(), locale);
ChannelType cachedEntry = localizedChannelTypeCache.get(localizedChannelTypeKey);
if (cachedEntry != null) {
return cachedEntry;
}
if (this.thingTypeI18nUtil != null) {
ChannelTypeUID channelTypeUID = channelType.getUID();
String label = this.thingTypeI18nUtil.getChannelLabel(bundle, channelTypeUID, channelType.getLabel(),
locale);
String description = this.thingTypeI18nUtil.getChannelDescription(bundle, channelTypeUID,
channelType.getDescription(), locale);
StateDescription state = createLocalizedChannelState(bundle, channelType, channelTypeUID, locale);
ChannelType localizedChannelType = new ChannelType(channelTypeUID, channelType.isAdvanced(),
channelType.getItemType(), channelType.getKind(), label, description, channelType.getCategory(),
channelType.getTags(), state, channelType.getEvent(), channelType.getConfigDescriptionURI());
localizedChannelTypeCache.put(localizedChannelTypeKey, localizedChannelType);
return localizedChannelType;
}
return channelType;
}
private LocalizedChannelTypeKey getLocalizedChannelTypeKey(UID uid, Locale locale) {
String localeString = locale != null ? locale.toLanguageTag() : null;
LocalizedChannelTypeKey localizedChannelTypeKey = new LocalizedChannelTypeKey(uid,
locale != null ? localeString : null);
return localizedChannelTypeKey;
}
private void removeCachedChannelGroupTypes(ChannelGroupType channelGroupType) {
for (Iterator<Entry<LocalizedChannelTypeKey, ChannelGroupType>> iterator = this.localizedChannelGroupTypeCache
.entrySet().iterator(); iterator.hasNext();) {
Entry<LocalizedChannelTypeKey, ChannelGroupType> entry = iterator.next();
if (entry.getKey().uid.equals(channelGroupType.getUID())) {
iterator.remove();
}
}
}
private void removeCachedChannelGroupTypes(List<ChannelGroupType> channelGroupTypes) {
for (ChannelGroupType channelGroupType : channelGroupTypes) {
removeCachedChannelGroupTypes(channelGroupType);
}
}
private void removeCachedChannelTypes(ChannelType channelType) {
for (Iterator<Entry<LocalizedChannelTypeKey, ChannelType>> iterator = this.localizedChannelTypeCache.entrySet()
.iterator(); iterator.hasNext();) {
Entry<LocalizedChannelTypeKey, ChannelType> entry = iterator.next();
if (entry.getKey().uid.equals(channelType.getUID())) {
iterator.remove();
}
}
}
private void removeCachedChannelTypes(List<ChannelType> channelTypes) {
for (ChannelType channelType : channelTypes) {
removeCachedChannelTypes(channelType);
}
}
}