Fix several compiler warnings (#4041)

* Fix several compiler warnings

* Add missing null annotations
* Add missing null checks
* Remove use of deprecated SecurityManager
* Remove redundant null checks
* Remove unused variables
* Fix raw use of parameterized class

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born 2024-01-20 09:55:50 +01:00 committed by GitHub
parent 0b1e1b66ab
commit c2a0739f1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 67 additions and 97 deletions

View File

@ -95,9 +95,7 @@ final class RuleExecutionSimulator {
// Only triggers that are time-based will be considered within the simulation
if (triggerHandler instanceof TimeBasedTriggerHandler handler) {
SchedulerTemporalAdjuster temporalAdjuster = handler.getTemporalAdjuster();
if (temporalAdjuster != null) {
executions.addAll(simulateExecutionsForCronBasedRule(rule, from, until, temporalAdjuster));
}
executions.addAll(simulateExecutionsForCronBasedRule(rule, from, until, temporalAdjuster));
}
}
logger.debug("Created {} rule simulations for rule {}.", executions.size(), rule.getName());

View File

@ -132,11 +132,7 @@ public class AutomationCommandsPluggable extends AutomationCommands implements C
}
String res = super.executeCommand(command, params);
if (res == null) {
console.println(String.format("Unsupported command %s", command));
} else {
console.println(res);
}
console.println(res);
}
@Override

View File

@ -12,6 +12,7 @@
*/
package org.openhab.core.automation.internal.ruleengine;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.Module;
@ -23,7 +24,7 @@ import org.openhab.core.automation.handler.ModuleHandler;
* @author Markus Rathgeb - Initial contribution
*/
@NonNullByDefault
public class WrappedModule<M extends Module, H extends ModuleHandler> {
public class WrappedModule<@NonNull M extends Module, H extends ModuleHandler> {
private final M module;
private @Nullable H handler;

View File

@ -12,7 +12,7 @@
*/
package org.openhab.core.config.core;
import static java.util.Collections.*;
import static java.util.Collections.synchronizedMap;
import static org.openhab.core.config.core.ConfigUtil.normalizeTypes;
import java.util.ArrayList;

View File

@ -29,6 +29,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
@ -125,7 +126,7 @@ public class PersistenceManagerTest {
private @NonNullByDefault({}) @Mock ScheduledCompletableFuture<Void> scheduledFutureMock;
private @NonNullByDefault({}) @Mock ItemRegistry itemRegistryMock;
private @NonNullByDefault({}) @Mock SafeCaller safeCallerMock;
private @NonNullByDefault({}) @Mock SafeCallerBuilder<QueryablePersistenceService> safeCallerBuilderMock;
private @NonNullByDefault({}) @Mock SafeCallerBuilder<@NonNull QueryablePersistenceService> safeCallerBuilderMock;
private @NonNullByDefault({}) @Mock ReadyService readyServiceMock;
private @NonNullByDefault({}) @Mock PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistryMock;

View File

@ -17,6 +17,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.type.ChannelType;
@ -30,7 +31,7 @@ import org.openhab.core.thing.type.ChannelTypeUID;
*/
@NonNullByDefault
@SuppressWarnings("unchecked")
abstract class AbstractChannelTypeBuilder<T extends ChannelTypeBuilder<T>> implements ChannelTypeBuilder<T> {
abstract class AbstractChannelTypeBuilder<@NonNull T extends ChannelTypeBuilder<T>> implements ChannelTypeBuilder<T> {
protected final ChannelTypeUID channelTypeUID;
protected final String label;

View File

@ -16,6 +16,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.internal.profiles.StateProfileTypeImpl;
import org.openhab.core.thing.internal.profiles.TriggerProfileTypeImpl;
@ -31,7 +32,7 @@ import org.openhab.core.thing.type.ChannelTypeUID;
* @param <T> the concrete {@link ProfileType} sub-interface.
*/
@NonNullByDefault
public final class ProfileTypeBuilder<T extends ProfileType> {
public final class ProfileTypeBuilder<@NonNull T extends ProfileType> {
@FunctionalInterface
private interface ProfileTypeFactory<T extends ProfileType> {

View File

@ -15,6 +15,7 @@ package org.openhab.core.thing.type;
import java.net.URI;
import java.util.Collection;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.internal.type.StateChannelTypeBuilderImpl;
import org.openhab.core.thing.internal.type.TriggerChannelTypeBuilderImpl;
@ -25,7 +26,7 @@ import org.openhab.core.thing.internal.type.TriggerChannelTypeBuilderImpl;
* @author Stefan Triller - Initial contribution
*/
@NonNullByDefault
public interface ChannelTypeBuilder<T extends ChannelTypeBuilder<T>> {
public interface ChannelTypeBuilder<@NonNull T extends ChannelTypeBuilder<T>> {
/**
* Specify whether this is an advanced channel, default is false
*

View File

@ -77,9 +77,7 @@ public class NamedThreadFactory implements ThreadFactory {
this.daemonize = daemonize;
this.priority = priority;
this.namePrefix = "OH-" + id + "-";
final SecurityManager securityManager = System.getSecurityManager();
this.group = securityManager != null ? securityManager.getThreadGroup()
: Thread.currentThread().getThreadGroup();
this.group = Thread.currentThread().getThreadGroup();
}
@Override

View File

@ -167,10 +167,6 @@ public class QueueingThreadPoolExecutor extends ThreadPoolExecutor {
if (taskQueue.isEmpty()) {
super.execute(command);
} else {
if (command == null) {
throw new IllegalArgumentException("Command can not be null.");
}
// ignore incoming tasks when the executor is shutdown
if (!isShutdown()) {
addToQueue(command);
@ -240,8 +236,7 @@ public class QueueingThreadPoolExecutor extends ThreadPoolExecutor {
public CommonThreadFactory(String name) {
this.name = name;
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
group = Thread.currentThread().getThreadGroup();
}
@Override

View File

@ -14,6 +14,7 @@ package org.openhab.core.common;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
@ -39,5 +40,5 @@ public interface SafeCaller {
* @param interfaceType the interface which defines the relevant methods
* @return a safe call builder instance.
*/
<T> SafeCallerBuilder<T> create(T target, Class<T> interfaceType);
<T> SafeCallerBuilder<@NonNull T> create(T target, Class<T> interfaceType);
}

View File

@ -14,6 +14,7 @@ package org.openhab.core.common;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
@ -24,7 +25,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @param <T>
*/
@NonNullByDefault
public interface SafeCallerBuilder<T> {
public interface SafeCallerBuilder<@NonNull T> {
/**
* Creates a dynamic proxy with the according properties which guards the caller from hanging implementations in the

View File

@ -188,11 +188,14 @@ public abstract class AbstractRegistry<@NonNull E extends Identifiable<K>, @NonN
*/
private boolean added(Provider<E> provider, E element, Collection<E> providerElements) {
final K uid = element.getUID();
if (identifierToElement.containsKey(uid)) {
@Nullable
E existingElement = identifierToElement.get(uid);
if (existingElement != null) {
Provider<E> existingElementProvider = elementToProvider.get(existingElement);
logger.debug(
"Cannot add \"{}\" with key \"{}\". It exists already from provider \"{}\"! Failed to add a second with the same UID from provider \"{}\"!",
element.getClass().getSimpleName(), uid,
elementToProvider.get(identifierToElement.get(uid)).getClass().getSimpleName(),
existingElementProvider != null ? existingElementProvider.getClass().getSimpleName() : null,
provider.getClass().getSimpleName());
return false;
}
@ -245,7 +248,7 @@ public abstract class AbstractRegistry<@NonNull E extends Identifiable<K>, @NonN
return;
}
Provider<E> elementProvider = elementToProvider.get(existingElement);
if (!elementProvider.equals(provider)) {
if (elementProvider != null && !elementProvider.equals(provider)) {
logger.error(
"Provider '{}' is not allowed to remove element '{}' with key '{}' from the registry because it was added by provider '{}'.",
provider.getClass().getSimpleName(), element.getClass().getSimpleName(), uid,
@ -439,7 +442,7 @@ public abstract class AbstractRegistry<@NonNull E extends Identifiable<K>, @NonN
}
elementsAdded.forEach(this::notifyListenersAboutAddedElement);
if (provider instanceof ManagedProvider && readyService != null) {
if (provider instanceof ManagedProvider && providerClazz != null && readyService != null) {
readyService.markReady(
new ReadyMarker("managed", providerClazz.getSimpleName().replace("Provider", "").toLowerCase()));
}

View File

@ -18,6 +18,7 @@ import java.util.Arrays;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.SafeCaller;
@ -31,7 +32,7 @@ import org.openhab.core.common.SafeCallerBuilder;
* @param <T>
*/
@NonNullByDefault
public class SafeCallerBuilderImpl<T> implements SafeCallerBuilder<T> {
public class SafeCallerBuilderImpl<@NonNull T> implements SafeCallerBuilder<T> {
private final T target;
private final Class<?>[] interfaceTypes;

View File

@ -17,6 +17,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.SafeCaller;
@ -62,7 +63,7 @@ public class SafeCallerImpl implements SafeCaller {
}
@Override
public <T> SafeCallerBuilder<T> create(T target, Class<T> interfaceType) {
public <T> SafeCallerBuilder<@NonNull T> create(T target, Class<T> interfaceType) {
return new SafeCallerBuilderImpl<>(target, new Class<?>[] { interfaceType }, manager);
}

View File

@ -60,8 +60,9 @@ public class MetadataCommandDescriptionProvider implements CommandDescriptionPro
if (metadata != null) {
try {
CommandDescriptionImpl commandDescription = new CommandDescriptionImpl();
if (metadata.getConfiguration().containsKey("options")) {
Stream.of(metadata.getConfiguration().get("options").toString().split(",")).forEach(o -> {
Object options = metadata.getConfiguration().get("options");
if (options != null) {
Stream.of(options.toString().split(",")).forEach(o -> {
if (o.contains("=")) {
var pair = parseValueLabelPair(o.trim());
commandDescription.addCommandOption(new CommandOption(pair[0], pair[1]));

View File

@ -131,9 +131,6 @@ public abstract class GenericItem implements ActiveItem {
*/
@Override
public void addGroupName(String groupItemName) {
if (groupItemName == null) {
throw new IllegalArgumentException("Group item name must not be null!");
}
if (!groupNames.contains(groupItemName)) {
groupNames.add(groupItemName);
}
@ -161,9 +158,6 @@ public abstract class GenericItem implements ActiveItem {
*/
@Override
public void removeGroupName(String groupItemName) {
if (groupItemName == null) {
throw new IllegalArgumentException("Group item name must not be null!");
}
groupNames.remove(groupItemName);
}

View File

@ -163,10 +163,6 @@ public class GroupItem extends GenericItem implements StateChangeListener, Metad
* @throws IllegalArgumentException if the given item is null
*/
public void addMember(Item item) {
if (item == null) {
throw new IllegalArgumentException("Item must not be null!");
}
boolean added = members.addIfAbsent(item);
// in case membership is constructed programmatically this sanitizes
@ -190,9 +186,6 @@ public class GroupItem extends GenericItem implements StateChangeListener, Metad
}
public void replaceMember(Item oldItem, Item newItem) {
if (oldItem == null || newItem == null) {
throw new IllegalArgumentException("Items must not be null!");
}
int index = members.indexOf(oldItem);
if (index > -1) {
Item old = members.set(index, newItem);
@ -208,9 +201,6 @@ public class GroupItem extends GenericItem implements StateChangeListener, Metad
* @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);
unregisterStateListener(item);
}
@ -287,6 +277,7 @@ public class GroupItem extends GenericItem implements StateChangeListener, Metad
@Override
protected void internalSend(Command command) {
EventPublisher eventPublisher = this.eventPublisher;
if (eventPublisher != null) {
for (Item member : members) {
// try to send the command to the bus
@ -304,7 +295,8 @@ public class GroupItem extends GenericItem implements StateChangeListener, Metad
newState = function.getStateAs(getStateMembers(getMembers()), typeClass);
}
if (newState == null && baseItem != null && baseItem instanceof GenericItem item) {
Item baseItem = this.baseItem;
if (newState == null && baseItem instanceof GenericItem item) {
// we use the transformation method from the base item
item.setState(state);
newState = baseItem.getStateAs(typeClass);
@ -363,6 +355,7 @@ public class GroupItem extends GenericItem implements StateChangeListener, Metad
public void stateUpdated(Item item, State state) {
State oldState = this.state;
State newState = oldState;
ItemStateConverter itemStateConverter = this.itemStateConverter;
if (function != null && baseItem != null && itemStateConverter != null) {
State calculatedState = function.calculate(getStateMembers(getMembers()));
newState = itemStateConverter.convertToAcceptedState(calculatedState, baseItem);
@ -377,7 +370,8 @@ public class GroupItem extends GenericItem implements StateChangeListener, Metad
@Override
public void setState(State state) {
State oldState = this.state;
if (baseItem != null && baseItem instanceof GenericItem item) {
Item baseItem = this.baseItem;
if (baseItem instanceof GenericItem item) {
item.setState(state);
this.state = baseItem.getState();
} else {

View File

@ -12,12 +12,8 @@
*/
package org.openhab.core.library.unit;
import static org.eclipse.jdt.annotation.DefaultLocation.FIELD;
import static org.eclipse.jdt.annotation.DefaultLocation.PARAMETER;
import static org.eclipse.jdt.annotation.DefaultLocation.RETURN_TYPE;
import static org.eclipse.jdt.annotation.DefaultLocation.TYPE_BOUND;
import static org.eclipse.jdt.annotation.DefaultLocation.*;
import static org.openhab.core.library.unit.CurrencyUnits.BASE_CURRENCY;
import static tech.units.indriya.AbstractUnit.ONE;
import java.math.BigDecimal;
import java.math.MathContext;
@ -70,6 +66,7 @@ public final class CurrencyUnit extends AbstractUnit<Currency> {
this.name = name;
}
@Override
public UnitConverter getSystemConverter() {
return internalGetConverterTo(getSystemUnit());
}
@ -94,6 +91,7 @@ public final class CurrencyUnit extends AbstractUnit<Currency> {
return DIMENSION;
}
@Override
public void setName(@NonNullByDefault({}) String name) {
this.name = name;
}
@ -108,6 +106,7 @@ public final class CurrencyUnit extends AbstractUnit<Currency> {
return symbol;
}
@Override
public void setSymbol(@Nullable String s) {
this.symbol = s;
}

View File

@ -99,7 +99,7 @@ public class I18nProviderImplTest {
public void assertThatConfigurationWasSet() {
i18nProviderImpl.modified((Map<String, Object>) initialConfig);
PointType location = i18nProviderImpl.getLocation();
PointType location = Objects.requireNonNull(i18nProviderImpl.getLocation());
Locale setLocale = i18nProviderImpl.getLocale();
assertThat(location.toString(), is(LOCATION_ZERO));
@ -129,7 +129,7 @@ public class I18nProviderImplTest {
conf.put(LOCATION, LOCATION_DARMSTADT);
i18nProviderImpl.modified(conf);
PointType location = i18nProviderImpl.getLocation();
PointType location = Objects.requireNonNull(i18nProviderImpl.getLocation());
Locale setLocale = i18nProviderImpl.getLocale();
assertThat(location.toString(), is(LOCATION_DARMSTADT));
@ -138,7 +138,7 @@ public class I18nProviderImplTest {
@Test
public void assertThatActivateSetsLocaleAndLocation() {
PointType location = i18nProviderImpl.getLocation();
PointType location = Objects.requireNonNull(i18nProviderImpl.getLocation());
Locale setLocale = i18nProviderImpl.getLocale();
assertThat(location.toString(), is(LOCATION_ZERO));
@ -162,7 +162,7 @@ public class I18nProviderImplTest {
public void assertThatConfigurationChangeWorks() {
i18nProviderImpl.modified(buildRUConfig());
PointType location = i18nProviderImpl.getLocation();
PointType location = Objects.requireNonNull(i18nProviderImpl.getLocation());
Locale setLocale = i18nProviderImpl.getLocale();
assertThat(location.toString(), is(LOCATION_HAMBURG));

View File

@ -146,7 +146,7 @@ public class ItemStateConverterImplTest {
State originalState = new QuantityType<>("153 mired");
State convertedState = itemStateConverter.convertToAcceptedState(originalState, item);
assertThat(((QuantityType) convertedState).intValue(), is(6535));
assertThat(((QuantityType<?>) convertedState).intValue(), is(6535));
}
@ParameterizedTest

View File

@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@ -106,25 +105,6 @@ public class GenericItemTest {
assertEquals(ItemStateUpdatedEvent.TYPE, updated.getType());
}
@Test
public void testAddGroupNameWithNull() {
TestItem item = new TestItem("member1");
assertThrows(IllegalArgumentException.class, () -> item.addGroupName(toNull()));
}
@Test
public void testAddGroupNamesWithNull() {
TestItem item = new TestItem("member1");
assertThrows(IllegalArgumentException.class,
() -> item.addGroupNames(Arrays.asList("group-a", toNull(), "group-b")));
}
@Test
public void testRemoveGroupNameWithNull() {
TestItem item = new TestItem("member1");
assertThrows(IllegalArgumentException.class, () -> item.removeGroupName(toNull()));
}
@Test
public void testGetStateAsWithSameType() {
TestItem item = new TestItem("member1");

View File

@ -30,7 +30,7 @@ public class DataAmountTest {
@Test
public void testBToMB() {
QuantityType<DataAmount> quantityType = new QuantityType("1000 B");
QuantityType<DataAmount> quantityType = new QuantityType<>("1000 B");
QuantityType<DataAmount> converted = quantityType.toUnit("MB");
assertThat(converted.toString(), is(equalTo("0.001 MB")));
@ -38,7 +38,7 @@ public class DataAmountTest {
@Test
public void testKBToMB() {
QuantityType<DataAmount> quantityType = new QuantityType("1000 kB");
QuantityType<DataAmount> quantityType = new QuantityType<>("1000 kB");
QuantityType<DataAmount> converted = quantityType.toUnit("MB");
assertThat(converted.toString(), is(equalTo("1 MB")));
@ -46,7 +46,7 @@ public class DataAmountTest {
@Test
public void testInvertibleUnit() {
QuantityType<DataAmount> quantityType = new QuantityType("1000 kB");
QuantityType<DataAmount> quantityType = new QuantityType<>("1000 kB");
QuantityType<?> inverted = quantityType.toInvertibleUnit(Units.MEGABYTE);
assertThat(quantityType, is(equalTo(inverted)));

View File

@ -27,6 +27,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@ -301,12 +302,7 @@ public class DateTimeTypeTest {
TimeZone inputTimeZone = parameterSet.inputTimeZone;
String inputTimeString = parameterSet.inputTimeString;
if (inputTimeMap != null && inputTimeZone != null) {
int durationInNano = (int) TimeUnit.NANOSECONDS.convert(inputTimeMap.get("milliseconds"),
TimeUnit.MILLISECONDS);
LocalDateTime dateTime = LocalDateTime.of(inputTimeMap.get("year"), inputTimeMap.get("month") + 1,
inputTimeMap.get("date"), inputTimeMap.get("hourOfDay"), inputTimeMap.get("minute"),
inputTimeMap.get("second"), durationInNano);
LocalDateTime dateTime = createLocalDateTimeFromInput(inputTimeMap);
ZonedDateTime zonedDate = ZonedDateTime.of(dateTime, inputTimeZone.toZoneId());
dt1 = new DateTimeType(zonedDate);
dt3 = new DateTimeType(
@ -368,12 +364,7 @@ public class DateTimeTypeTest {
TimeZone inputTimeZone = parameterSet.inputTimeZone;
String inputTimeString = parameterSet.inputTimeString;
if (inputTimeMap != null && inputTimeZone != null) {
int durationInNano = (int) TimeUnit.NANOSECONDS.convert(inputTimeMap.get("milliseconds"),
TimeUnit.MILLISECONDS);
LocalDateTime dateTime = LocalDateTime.of(inputTimeMap.get("year"), inputTimeMap.get("month") + 1,
inputTimeMap.get("date"), inputTimeMap.get("hourOfDay"), inputTimeMap.get("minute"),
inputTimeMap.get("second"), durationInNano);
LocalDateTime dateTime = createLocalDateTimeFromInput(inputTimeMap);
ZonedDateTime zonedDate = ZonedDateTime.of(dateTime, inputTimeZone.toZoneId());
return new DateTimeType(zonedDate);
} else if (inputTimeString != null) {
@ -381,4 +372,17 @@ public class DateTimeTypeTest {
}
throw new DateTimeException("Invalid inputs in parameter set");
}
private LocalDateTime createLocalDateTimeFromInput(Map<String, Integer> inputTimeMap) {
Integer year = Objects.requireNonNull(inputTimeMap.get("year"));
Integer month = Objects.requireNonNull(inputTimeMap.get("month"));
Integer dayOfMonth = Objects.requireNonNull(inputTimeMap.get("date"));
Integer hourOfDay = Objects.requireNonNull(inputTimeMap.get("hourOfDay"));
Integer minute = Objects.requireNonNull(inputTimeMap.get("minute"));
Integer second = Objects.requireNonNull(inputTimeMap.get("second"));
Integer milliseconds = Objects.requireNonNull(inputTimeMap.get("milliseconds"));
int durationInNano = (int) TimeUnit.NANOSECONDS.convert(milliseconds, TimeUnit.MILLISECONDS);
return LocalDateTime.of(year, month + 1, dayOfMonth, hourOfDay, minute, second, durationInNano);
}
}

View File

@ -91,7 +91,6 @@ public class PercentTypeTest {
// Construction for each locale should always return the same result regardless of the current default locale
Stream.of(Locale.ENGLISH, Locale.GERMAN).forEach(locale -> {
char ds = DecimalFormatSymbols.getInstance(locale).getDecimalSeparator();
char gs = DecimalFormatSymbols.getInstance(locale).getGroupingSeparator();
assertEquals(new PercentType("0"), new PercentType("0", locale));
assertEquals(new PercentType("0.000"), new PercentType(String.format("0%s000", ds), locale));