Java tutorial
/** * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * * 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.internal.i18n; import static org.eclipse.smarthome.core.library.unit.MetricPrefix.HECTO; import java.text.MessageFormat; import java.time.DateTimeException; import java.time.ZoneId; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.ResourceBundle; import java.util.TimeZone; import javax.measure.Quantity; import javax.measure.Unit; import javax.measure.quantity.Angle; import javax.measure.quantity.Dimensionless; import javax.measure.quantity.Length; import javax.measure.quantity.Pressure; import javax.measure.quantity.Speed; import javax.measure.quantity.Temperature; import javax.measure.spi.SystemOfUnits; import org.apache.commons.lang.StringUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.i18n.LocaleProvider; import org.eclipse.smarthome.core.i18n.LocationProvider; import org.eclipse.smarthome.core.i18n.TimeZoneProvider; import org.eclipse.smarthome.core.i18n.TranslationProvider; import org.eclipse.smarthome.core.i18n.UnitProvider; import org.eclipse.smarthome.core.library.dimension.Intensity; import org.eclipse.smarthome.core.library.types.PointType; import org.eclipse.smarthome.core.library.unit.ImperialUnits; import org.eclipse.smarthome.core.library.unit.SIUnits; import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; import org.osgi.framework.Bundle; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Modified; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link I18nProviderImpl} is a concrete implementation of the {@link TranslationProvider}, {@link LocaleProvider}, * and {@link LocationProvider} service interfaces. * * * <p> * This implementation uses the i18n mechanism of Java ({@link ResourceBundle}) to translate a given key into text. The * resources must be placed under the specific directory {@link LanguageResourceBundleManager#RESOURCE_DIRECTORY} within * the certain modules. Each module is tracked in the platform by using the {@link ResourceBundleTracker} and managed by * using one certain {@link LanguageResourceBundleManager} which is responsible for the translation. * <p> * <p> * It reads a user defined configuration to set a locale and a location for this installation. * * @author Michael Grammling - Initial Contribution of TranslationProvider * @author Thomas Hfer - Added getText operation with arguments * @author Markus Rathgeb - Initial contribution and API of LocaleProvider * @author Stefan Triller - Initial contribution and API of LocationProvider * @author Erdoan Hadzhiyusein - Added time zone * */ @NonNullByDefault @Component(immediate = true, configurationPid = "org.eclipse.smarthome.core.i18nprovider", property = { "service.pid=org.eclipse.smarthome.core.i18nprovider", "service.config.description.uri:String=system:i18n", "service.config.label:String=Regional Settings", "service.config.category:String=system" }) public class I18nProviderImpl implements TranslationProvider, LocaleProvider, LocationProvider, TimeZoneProvider, UnitProvider { private final Logger logger = LoggerFactory.getLogger(getClass()); // LocaleProvider static final String LANGUAGE = "language"; static final String SCRIPT = "script"; static final String REGION = "region"; static final String VARIANT = "variant"; private @Nullable Locale locale; // TranslationProvider private @NonNullByDefault({}) ResourceBundleTracker resourceBundleTracker; // LocationProvider static final String LOCATION = "location"; private @Nullable PointType location; // TimeZoneProvider static final String TIMEZONE = "timezone"; private @Nullable ZoneId timeZone; // UnitProvider private static final String MEASUREMENT_SYSTEM = "measurementSystem"; private @Nullable SystemOfUnits measurementSystem; private final Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap = new HashMap<>(); @Activate @SuppressWarnings("unchecked") protected void activate(ComponentContext componentContext) { initDimensionMap(); modified((Map<String, Object>) componentContext.getProperties()); this.resourceBundleTracker = new ResourceBundleTracker(componentContext.getBundleContext(), this); this.resourceBundleTracker.open(); } @Deactivate protected void deactivate(ComponentContext componentContext) { this.resourceBundleTracker.close(); } @Modified protected synchronized void modified(Map<String, Object> config) { final String language = toStringOrNull(config.get(LANGUAGE)); final String script = toStringOrNull(config.get(SCRIPT)); final String region = toStringOrNull(config.get(REGION)); final String variant = toStringOrNull(config.get(VARIANT)); final String location = toStringOrNull(config.get(LOCATION)); final String zoneId = toStringOrNull(config.get(TIMEZONE)); final String measurementSystem = toStringOrNull(config.get(MEASUREMENT_SYSTEM)); setTimeZone(zoneId); setLocation(location); setLocale(language, script, region, variant); setMeasurementSystem(measurementSystem); } private void setMeasurementSystem(@Nullable String measurementSystem) { SystemOfUnits oldMeasurementSystem = this.measurementSystem; final String ms; if (measurementSystem == null || measurementSystem.isEmpty()) { ms = ""; } else { ms = measurementSystem; } final SystemOfUnits newMeasurementSystem; switch (ms) { case "SI": newMeasurementSystem = SIUnits.getInstance(); break; case "US": newMeasurementSystem = ImperialUnits.getInstance(); break; default: logger.debug("Error setting measurement system for value '{}'.", measurementSystem); newMeasurementSystem = null; break; } this.measurementSystem = newMeasurementSystem; if (oldMeasurementSystem != null && newMeasurementSystem == null) { logger.info("Measurement system is not set, falling back to locale based system."); } else if (newMeasurementSystem != null && !newMeasurementSystem.equals(oldMeasurementSystem)) { logger.info("Measurement system set to '{}'.", newMeasurementSystem.getName()); } } private void setLocale(@Nullable String language, @Nullable String script, @Nullable String region, @Nullable String variant) { Locale oldLocale = this.locale; if (StringUtils.isEmpty(language)) { // at least the language must be defined otherwise the system default locale is used logger.debug("No language set, setting locale to 'null'."); locale = null; if (oldLocale != null) { logger.info("Locale is not set, falling back to the default locale"); } return; } final Locale.Builder builder = new Locale.Builder(); try { builder.setLanguage(language); } catch (final RuntimeException ex) { logger.warn("Language ({}) is invalid. Cannot create locale, keep old one.", language, ex); return; } try { builder.setScript(script); } catch (final RuntimeException ex) { logger.warn("Script ({}) is invalid. Skip it.", script, ex); return; } try { builder.setRegion(region); } catch (final RuntimeException ex) { logger.warn("Region ({}) is invalid. Skip it.", region, ex); return; } try { builder.setVariant(variant); } catch (final RuntimeException ex) { logger.warn("Variant ({}) is invalid. Skip it.", variant, ex); return; } final Locale newLocale = builder.build(); locale = newLocale; if (!newLocale.equals(oldLocale)) { logger.info("Locale set to '{}'.", newLocale); } } private @Nullable String toStringOrNull(@Nullable Object value) { return value == null ? null : value.toString(); } private void setLocation(final @Nullable String location) { PointType oldLocation = this.location; PointType newLocation; if (location == null || location.isEmpty()) { newLocation = null; } else { try { newLocation = PointType.valueOf(location); } catch (IllegalArgumentException e) { newLocation = oldLocation; // preserve old location or null if none was set before logger.warn("Could not set new location: {}, keeping old one, error message: {}", location, e.getMessage()); } } if (!Objects.equals(newLocation, oldLocation)) { this.location = newLocation; logger.info("Location set to '{}'.", newLocation); } } private void setTimeZone(final @Nullable String zoneId) { ZoneId oldTimeZone = this.timeZone; if (StringUtils.isBlank(zoneId)) { timeZone = null; } else { try { timeZone = ZoneId.of(zoneId); } catch (DateTimeException e) { logger.warn("Error setting time zone '{}', falling back to the default time zone: {}", zoneId, e.getMessage()); timeZone = null; } } if (oldTimeZone != null && this.timeZone == null) { logger.info("Time zone is not set, falling back to the default time zone."); } else if (this.timeZone != null && !this.timeZone.equals(oldTimeZone)) { logger.info("Time zone set to '{}'.", this.timeZone); } } @Override public @Nullable PointType getLocation() { return location; } @Override public ZoneId getTimeZone() { final ZoneId timeZone = this.timeZone; if (timeZone == null) { return TimeZone.getDefault().toZoneId(); } return timeZone; } @Override public Locale getLocale() { final Locale locale = this.locale; if (locale == null) { return Locale.getDefault(); } return locale; } @Override public @Nullable String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, @Nullable Locale locale) { LanguageResourceBundleManager languageResource = this.resourceBundleTracker.getLanguageResource(bundle); if (languageResource != null) { String text = languageResource.getText(key, locale); if (text != null) { return text; } } return defaultText; } @Override public @Nullable String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, @Nullable Locale locale, @Nullable Object @Nullable... arguments) { String text = getText(bundle, key, defaultText, locale); if (text != null) { return MessageFormat.format(text, arguments); } return text; } @SuppressWarnings("unchecked") @Override public <T extends Quantity<T>> @Nullable Unit<T> getUnit(@Nullable Class<T> dimension) { Map<SystemOfUnits, Unit<? extends Quantity<?>>> map = dimensionMap.get(dimension); if (map == null) { return null; } return (Unit<T>) map.get(getMeasurementSystem()); } @Override public SystemOfUnits getMeasurementSystem() { final SystemOfUnits measurementSystem = this.measurementSystem; if (measurementSystem != null) { return measurementSystem; } // Only US and Liberia use the Imperial System. if (Locale.US.equals(locale) || Locale.forLanguageTag("en-LR").equals(locale)) { return ImperialUnits.getInstance(); } return SIUnits.getInstance(); } private void initDimensionMap() { Map<SystemOfUnits, Unit<? extends Quantity<?>>> temperatureMap = new HashMap<>(); temperatureMap.put(SIUnits.getInstance(), SIUnits.CELSIUS); temperatureMap.put(ImperialUnits.getInstance(), ImperialUnits.FAHRENHEIT); dimensionMap.put(Temperature.class, temperatureMap); Map<SystemOfUnits, Unit<? extends Quantity<?>>> pressureMap = new HashMap<>(); pressureMap.put(SIUnits.getInstance(), HECTO(SIUnits.PASCAL)); pressureMap.put(ImperialUnits.getInstance(), ImperialUnits.INCH_OF_MERCURY); dimensionMap.put(Pressure.class, pressureMap); Map<SystemOfUnits, Unit<? extends Quantity<?>>> speedMap = new HashMap<>(); speedMap.put(SIUnits.getInstance(), SIUnits.KILOMETRE_PER_HOUR); speedMap.put(ImperialUnits.getInstance(), ImperialUnits.MILES_PER_HOUR); dimensionMap.put(Speed.class, speedMap); Map<SystemOfUnits, Unit<? extends Quantity<?>>> lengthMap = new HashMap<>(); lengthMap.put(SIUnits.getInstance(), SIUnits.METRE); lengthMap.put(ImperialUnits.getInstance(), ImperialUnits.INCH); dimensionMap.put(Length.class, lengthMap); Map<SystemOfUnits, Unit<? extends Quantity<?>>> intensityMap = new HashMap<>(); intensityMap.put(SIUnits.getInstance(), SmartHomeUnits.IRRADIANCE); intensityMap.put(ImperialUnits.getInstance(), SmartHomeUnits.IRRADIANCE); dimensionMap.put(Intensity.class, intensityMap); Map<SystemOfUnits, Unit<? extends Quantity<?>>> percentMap = new HashMap<>(); percentMap.put(SIUnits.getInstance(), SmartHomeUnits.ONE); percentMap.put(ImperialUnits.getInstance(), SmartHomeUnits.ONE); dimensionMap.put(Dimensionless.class, percentMap); Map<SystemOfUnits, Unit<? extends Quantity<?>>> angleMap = new HashMap<>(); angleMap.put(SIUnits.getInstance(), SmartHomeUnits.DEGREE_ANGLE); angleMap.put(ImperialUnits.getInstance(), SmartHomeUnits.DEGREE_ANGLE); dimensionMap.put(Angle.class, angleMap); } }