Skip to content

Commit

Permalink
Added i18n feature for profiles (openhab#785)
Browse files Browse the repository at this point in the history
Signed-off-by: Christoph Weitkamp <[email protected]>
  • Loading branch information
cweitkamp authored and maggu2810 committed May 9, 2019
1 parent 1996f69 commit b5f33d3
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@

import static org.eclipse.smarthome.core.thing.profiles.SystemProfiles.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -25,6 +29,7 @@
import org.eclipse.smarthome.core.library.CoreItemFactory;
import org.eclipse.smarthome.core.thing.Channel;
import org.eclipse.smarthome.core.thing.DefaultSystemChannelTypeProvider;
import org.eclipse.smarthome.core.thing.UID;
import org.eclipse.smarthome.core.thing.profiles.Profile;
import org.eclipse.smarthome.core.thing.profiles.ProfileAdvisor;
import org.eclipse.smarthome.core.thing.profiles.ProfileCallback;
Expand All @@ -33,8 +38,12 @@
import org.eclipse.smarthome.core.thing.profiles.ProfileType;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeProvider;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeUID;
import org.eclipse.smarthome.core.thing.profiles.i18n.ProfileTypeI18nLocalizationService;
import org.eclipse.smarthome.core.thing.type.ChannelType;
import org.eclipse.smarthome.core.thing.type.ChannelTypeRegistry;
import org.eclipse.smarthome.core.util.BundleResolver;
import org.osgi.framework.Bundle;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

Expand All @@ -47,12 +56,13 @@
* required profile type.
*
* @author Simon Kaufmann - Initial contribution
* @author Christoph Weitkamp - Added translation for profile labels
*/
@NonNullByDefault
@Component(service = { SystemProfileFactory.class, ProfileTypeProvider.class })
public class SystemProfileFactory implements ProfileFactory, ProfileAdvisor, ProfileTypeProvider {

private @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistry;
private final ChannelTypeRegistry channelTypeRegistry;

private static final Set<ProfileType> SUPPORTED_PROFILE_TYPES = Stream
.of(DEFAULT_TYPE, FOLLOW_TYPE, OFFSET_TYPE, RAWBUTTON_TOGGLE_PLAYER_TYPE, RAWBUTTON_TOGGLE_PLAYER_TYPE,
Expand All @@ -66,6 +76,20 @@ public class SystemProfileFactory implements ProfileFactory, ProfileAdvisor, Pro
RAWROCKER_NEXT_PREVIOUS, RAWROCKER_ON_OFF, RAWROCKER_PLAY_PAUSE, RAWROCKER_REWIND_FASTFORWARD,
RAWROCKER_STOP_MOVE, RAWROCKER_UP_DOWN, TIMESTAMP_CHANGE, TIMESTAMP_UPDATE).collect(Collectors.toSet());

private final Map<LocalizedProfileTypeKey, @Nullable ProfileType> localizedProfileTypeCache = new ConcurrentHashMap<>();

private final ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService;
private final BundleResolver bundleResolver;

@Activate
public SystemProfileFactory(final @Reference ChannelTypeRegistry channelTypeRegistry,
final @Reference ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService,
final @Reference BundleResolver bundleResolver) {
this.channelTypeRegistry = channelTypeRegistry;
this.profileTypeI18nLocalizationService = profileTypeI18nLocalizationService;
this.bundleResolver = bundleResolver;
}

@Override
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
ProfileContext context) {
Expand Down Expand Up @@ -159,21 +183,93 @@ public class SystemProfileFactory implements ProfileFactory, ProfileAdvisor, Pro

@Override
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
return SUPPORTED_PROFILE_TYPES;
final List<ProfileType> allProfileTypes = new ArrayList<>();
final Bundle bundle = bundleResolver.resolveBundle(SystemProfileFactory.class);

for (final ProfileType profileType : SUPPORTED_PROFILE_TYPES) {
allProfileTypes.add(createLocalizedProfileType(bundle, profileType, locale));
}
return allProfileTypes;
}

@Override
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
return SUPPORTED_PROFILE_TYPE_UIDS;
}

@Reference
protected void setChannelTypeRegistry(ChannelTypeRegistry channelTypeRegistry) {
this.channelTypeRegistry = channelTypeRegistry;
private ProfileType createLocalizedProfileType(Bundle bundle, ProfileType profileType, @Nullable Locale locale) {
LocalizedProfileTypeKey localizedProfileTypeKey = getLocalizedProfileTypeKey(profileType.getUID(), locale);

ProfileType cachedEntry = localizedProfileTypeCache.get(localizedProfileTypeKey);
if (cachedEntry != null) {
return cachedEntry;
}

ProfileType localizedProfileType = localize(bundle, profileType, locale);
if (localizedProfileType != null) {
localizedProfileTypeCache.put(localizedProfileTypeKey, localizedProfileType);
return localizedProfileType;
} else {
return profileType;
}
}

protected void unsetChannelTypeRegistry(ChannelTypeRegistry channelTypeRegistry) {
this.channelTypeRegistry = null;
private @Nullable ProfileType localize(Bundle bundle, ProfileType profileType, @Nullable Locale locale) {
if (profileTypeI18nLocalizationService == null) {
return null;
}
return profileTypeI18nLocalizationService.createLocalizedProfileType(bundle, profileType, locale);
}

private LocalizedProfileTypeKey getLocalizedProfileTypeKey(UID uid, @Nullable Locale locale) {
return new LocalizedProfileTypeKey(uid, locale != null ? locale.toLanguageTag() : null);
}

private static class LocalizedProfileTypeKey {
public final UID uid;
public final @Nullable String locale;

public LocalizedProfileTypeKey(UID uid, @Nullable String locale) {
this.uid = uid;
this.locale = locale;
}

@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
LocalizedProfileTypeKey other = (LocalizedProfileTypeKey) obj;
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 + ((locale != null) ? locale.hashCode() : 0);
result = prime * result + ((uid == null) ? 0 : uid.hashCode());
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.core.thing.internal.profiles.i18n;

import java.util.Locale;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.i18n.I18nUtil;
import org.eclipse.smarthome.core.i18n.TranslationProvider;
import org.eclipse.smarthome.core.thing.profiles.Profile;
import org.eclipse.smarthome.core.thing.profiles.ProfileType;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeUID;
import org.eclipse.smarthome.core.thing.profiles.i18n.ProfileTypeI18nLocalizationService;
import org.osgi.framework.Bundle;

/**
* A utility service which localizes {@link Profile}s.
* Falls back to a localized {@link ProfileType} for label and description when not given otherwise.
*
* @see {@link ProfileTypeI18nLocalizationService}
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class ProfileI18nUtil {

private final TranslationProvider i18nProvider;

/**
* Create a new util instance and pass the appropriate dependencies.
*
* @param i18nProvider an instance of {@link TranslationProvider}.
*/
public ProfileI18nUtil(TranslationProvider i18nProvider) {
this.i18nProvider = i18nProvider;
}

public @Nullable String getProfileLabel(Bundle bundle, ProfileTypeUID profileTypeUID, String defaultLabel,
@Nullable Locale locale) {
String key = I18nUtil.stripConstantOr(defaultLabel, () -> inferProfileTypeKey(profileTypeUID, "label"));
return i18nProvider.getText(bundle, key, defaultLabel, locale);
}

private String inferProfileTypeKey(ProfileTypeUID profileTypeUID, String lastSegment) {
return "profile-type." + profileTypeUID.getBindingId() + "." + profileTypeUID.getId() + "." + lastSegment;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@
/**
* A {@link ProfileTypeProvider} is responsible for providing {@link ProfileType}s.
*
* @author Simon Kaufmann - initial contribution and API.
*
* @author Simon Kaufmann - Initial contribution
*/
@NonNullByDefault
public interface ProfileTypeProvider {

/**
* Returns all profile types for the given {@link Locale}.
*
* @param locale (can be null)
* @return all profile types or empty list if no profile type exists
*/
Collection<ProfileType> getProfileTypes(@Nullable Locale locale);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.core.thing.profiles.i18n;

import java.util.Locale;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.i18n.TranslationProvider;
import org.eclipse.smarthome.core.thing.internal.profiles.i18n.ProfileI18nUtil;
import org.eclipse.smarthome.core.thing.profiles.ProfileType;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeBuilder;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeUID;
import org.eclipse.smarthome.core.thing.profiles.StateProfileType;
import org.eclipse.smarthome.core.thing.profiles.TriggerProfileType;
import org.osgi.framework.Bundle;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* This OSGi service could be used to localize a {@link ProfileType} using the i18n mechanism of the openHAB framework.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(service = ProfileTypeI18nLocalizationService.class)
@NonNullByDefault
public class ProfileTypeI18nLocalizationService {

private final ProfileI18nUtil profileI18nUtil;

@Activate
public ProfileTypeI18nLocalizationService(final @Reference TranslationProvider i18nProvider) {
this.profileI18nUtil = new ProfileI18nUtil(i18nProvider);
}

public ProfileType createLocalizedProfileType(Bundle bundle, ProfileType profileType, @Nullable Locale locale) {
ProfileTypeUID profileTypeUID = profileType.getUID();
String defaultLabel = profileType.getLabel();
String label = profileI18nUtil.getProfileLabel(bundle, profileTypeUID, defaultLabel, locale);

if (profileType instanceof StateProfileType) {
return ProfileTypeBuilder.newTrigger(profileTypeUID, label != null ? label : defaultLabel)
.withSupportedItemTypes(profileType.getSupportedItemTypes())
.withSupportedItemTypesOfChannel(((StateProfileType) profileType).getSupportedItemTypesOfChannel())
.build();
} else if (profileType instanceof TriggerProfileType) {
return ProfileTypeBuilder.newTrigger(profileTypeUID, label != null ? label : defaultLabel)
.withSupportedItemTypes(profileType.getSupportedItemTypes())
.withSupportedChannelTypeUIDs(((TriggerProfileType) profileType).getSupportedChannelTypeUIDs())
.build();
} else {
return profileType;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
profile-type.system.default.label = Standard
profile-type.system.follow.label = Folgen
profile-type.system.offset.label = Versatz
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.eclipse.smarthome.core.thing.profiles.ProfileCallback;
import org.eclipse.smarthome.core.thing.profiles.ProfileContext;
import org.eclipse.smarthome.core.thing.profiles.ProfileFactory;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeProvider;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeUID;
import org.eclipse.smarthome.core.thing.profiles.StateProfile;
import org.eclipse.smarthome.core.thing.profiles.TriggerProfile;
Expand All @@ -79,10 +80,9 @@

/**
*
* @author Simon Kaufmann - initial contribution and API.
*
* @author Simon Kaufmann - Initial contribution
*/
public class CommunicationManagerTest extends JavaOSGiTest {
public class CommunicationManagerOSGiTest extends JavaOSGiTest {

private class ItemChannelLinkRegistryAdvanced extends ItemChannelLinkRegistry {
@Override
Expand Down Expand Up @@ -167,9 +167,12 @@ public void setup() {
safeCaller = getService(SafeCaller.class);
assertNotNull(safeCaller);

SystemProfileFactory profileFactory = getService(ProfileTypeProvider.class, SystemProfileFactory.class);
assertNotNull(profileFactory);

manager = new CommunicationManager();
manager.setEventPublisher(eventPublisher);
manager.setDefaultProfileFactory(new SystemProfileFactory());
manager.setDefaultProfileFactory(profileFactory);
manager.setSafeCaller(safeCaller);

doAnswer(invocation -> {
Expand Down
Loading

0 comments on commit b5f33d3

Please sign in to comment.