diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..9777d66d7 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,58 @@ +# +# Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, +# or the Eclipse Distribution License v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +name: Yasson + +on: [push, pull_request] + +jobs: + build: + name: Test on JDK ${{ matrix.java_version }} + runs-on: ubuntu-latest + + strategy: + matrix: + java_version: [ 11, 16 ] + + steps: + - name: Checkout for build + uses: actions/checkout@v2.3.4 + with: + fetch-depth: 0 + - name: Set up compile JDK + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: 16 + - name: Maven cache + uses: actions/cache@v2 + env: + cache-name: maven-cache + with: + path: + ~/.m2 + key: build-${{ env.cache-name }} + - name: Checkstyle + run: mvn -B -Pstaging checkstyle:checkstyle + - name: Copyright + run: bash etc/copyright.sh + - name: Yasson install + run: mvn -U -C -Pstaging clean install -DskipTests + - name: Set up JDK for tests + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: ${{ matrix.java_version }} + - name: Yasson tests + run: mvn -U -C -Pstaging test + - name: JSONB-API TCK + run: cd yasson-tck && mvn -U -B test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 64ab60efa..000000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License v. 2.0 which is available at -# http://www.eclipse.org/legal/epl-2.0, -# or the Eclipse Distribution License v. 1.0 which is available at -# http://www.eclipse.org/org/documents/edl-v10.php. -# -# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause -# - -language: java - -jdk: - - oraclejdk11 - -cache: - directories: - - .autoconf - - $HOME/.m2 - -install: true - -jobs: - include: - - stage: checkstyle - script: mvn -B -Pstaging checkstyle:checkstyle - - stage: copyright - script: bash etc/copyright.sh - - stage: install-yasson - script: mvn -U -C -Pstaging clean install - - stage: tck-run - script: cd yasson-tck && mvn -U -B -Pstaging test - diff --git a/README.md b/README.md index 62ff05b25..f39c3a8d7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/org.eclipse/yasson.svg)](https://oss.sonatype.org/content/repositories/snapshots/org/eclipse/yasson/) [![Gitter](https://badges.gitter.im/eclipse/yasson.svg)](https://gitter.im/eclipse/yasson) [![Javadocs](https://www.javadoc.io/badge/org.eclipse/yasson.svg)](https://www.javadoc.io/doc/org.eclipse/yasson) -[![Build Status](https://travis-ci.org/eclipse-ee4j/yasson.svg?branch=master)](https://travis-ci.org/eclipse-ee4j/yasson) +[![Build Status](https://github.com/eclipse-ee4j/yasson/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/eclipse-ee4j/yasson/actions/workflows/maven.yml?branch=master) [![License](https://img.shields.io/badge/License-EPL%201.0-green.svg)](https://opensource.org/licenses/EPL-1.0) Yasson is a Java framework which provides a standard binding layer between Java classes and JSON documents. This is similar to what JAXB is doing in the XML world. Yasson is an official reference implementation of JSON Binding ([JSR-367](https://jcp.org/en/jsr/detail?id=367)). diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/AbstractNumberDeserializer.java b/src/main/java/org/eclipse/yasson/internal/serializer/AbstractNumberDeserializer.java index 67e39e045..07984bb67 100644 --- a/src/main/java/org/eclipse/yasson/internal/serializer/AbstractNumberDeserializer.java +++ b/src/main/java/org/eclipse/yasson/internal/serializer/AbstractNumberDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,8 +13,10 @@ package org.eclipse.yasson.internal.serializer; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.ParseException; +import java.util.Locale; import java.util.Optional; import jakarta.json.bind.JsonbException; @@ -56,14 +58,24 @@ protected final Optional deserializeFormatted(String jsonValue, boolean final JsonbNumberFormatter numberFormat = getCustomization().getDeserializeNumberFormatter(); //consider synchronizing on format instance or per thread cache. - final NumberFormat format = NumberFormat - .getInstance(jsonbContext.getConfigProperties().getLocale(numberFormat.getLocale())); + Locale locale = jsonbContext.getConfigProperties().getLocale(numberFormat.getLocale()); + final NumberFormat format = NumberFormat.getInstance(locale); ((DecimalFormat) format).applyPattern(numberFormat.getFormat()); format.setParseIntegerOnly(integerOnly); try { - return Optional.of(format.parse(jsonValue)); + return Optional.of(format.parse(compatibilityChanger(jsonValue, locale))); } catch (ParseException e) { throw new JsonbException(Messages.getMessage(MessageKeys.PARSING_NUMBER, jsonValue, numberFormat.getFormat())); } } + + private String compatibilityChanger(String value, Locale locale) { + char beforeJdk13GroupSeparator = '\u00A0'; + char frenchGroupingSeparator = DecimalFormatSymbols.getInstance(Locale.FRENCH).getGroupingSeparator(); + if (locale.getLanguage().equals(Locale.FRENCH.getLanguage()) && beforeJdk13GroupSeparator != frenchGroupingSeparator) { + //JDK-8225245 + return value.replace(beforeJdk13GroupSeparator, frenchGroupingSeparator); + } + return value; + } } diff --git a/yasson-tck/pom.xml b/yasson-tck/pom.xml index 2907cb2da..bf2429647 100644 --- a/yasson-tck/pom.xml +++ b/yasson-tck/pom.xml @@ -12,6 +12,8 @@ 2.0.0-SNAPSHOT 2.0.1-SNAPSHOT + 1.8 + 1.8 @@ -28,13 +30,17 @@ jakarta.json.bind jakarta.json.bind-tck ${jsonb.tck.version} - test + + org.eclipse yasson ${yasson.version} - test + + org.jboss.arquillian.container @@ -45,7 +51,7 @@ org.jboss.weld.se weld-se-core - 4.0.0.Beta1 + 4.0.2.Final test diff --git a/yasson-tck/src/main/java/jakarta/json/bind/tck/customizedmapping/numberformat/NumberFormatCustomizationTest.java b/yasson-tck/src/main/java/jakarta/json/bind/tck/customizedmapping/numberformat/NumberFormatCustomizationTest.java new file mode 100644 index 000000000..3501d43ce --- /dev/null +++ b/yasson-tck/src/main/java/jakarta/json/bind/tck/customizedmapping/numberformat/NumberFormatCustomizationTest.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/* + * $Id$ + */ + +package jakarta.json.bind.tck.customizedmapping.numberformat; + +import static org.junit.Assert.fail; + +import java.lang.invoke.MethodHandles; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Test; +import org.junit.runner.RunWith; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.tck.customizedmapping.numberformat.model.AccessorCustomizedDoubleContainer; +import jakarta.json.bind.tck.customizedmapping.numberformat.model.FieldCustomizedDoubleContainer; +import jakarta.json.bind.tck.customizedmapping.numberformat.model.TypeCustomizedDoubleContainer; +import jakarta.json.bind.tck.customizedmapping.numberformat.model.TypeCustomizedFieldOverriddenDoubleContainer; +import jakarta.json.bind.tck.customizedmapping.numberformat.model.customized.PackageCustomizedDoubleContainer; +import jakarta.json.bind.tck.customizedmapping.numberformat.model.customized.PackageCustomizedTypeOverriddenDoubleContainer; +import jakarta.json.bind.tck.customizedmapping.numberformat.model.customized.PackageCustomizedTypeOverriddenFieldOverriddenDoubleContainer; + +/** + * This is just temporal work around for failing test testNumberFormatField. + * + * It is failing due to changed FR number format separator. + **/ +@RunWith(Arquillian.class) +public class NumberFormatCustomizationTest { + + @Deployment + public static WebArchive createTestArchive() { + return ShrinkWrap.create(WebArchive.class) + .addPackages(true, MethodHandles.lookup().lookupClass().getPackage().getName()); + } + + private static final String FRENCH_NUMBER = "\"123\\u00a0456,789\""; + + private final Jsonb jsonb = JsonbBuilder.create(); + + /* + * @testName: testNumberFormatPackage + * + * @assertion_ids: JSONB:SPEC:JSB-4.9-1 + * + * @test_Strategy: Assert that package annotation with JsonbNumberFormat is + * correctly applied + */ + @Test + public void testNumberFormatPackage() { + String jsonString = jsonb.toJson(new PackageCustomizedDoubleContainer() { + { + setInstance(123456.789); + } + }); + if (!jsonString + .matches("\\{\\s*\"instance\"\\s*:\\s*\"123.456,8\"\\s*\\}")) { + fail( + "Failed to correctly customize number format during marshalling using JsonbNumberFormat annotation on package."); + } + + PackageCustomizedDoubleContainer unmarshalledObject = jsonb.fromJson( + "{ \"instance\" : \"123.456,789\" }", + PackageCustomizedDoubleContainer.class); + if (unmarshalledObject.getInstance() != 123456.789) { + fail( + "Failed to correctly customize number format during unmarshalling using JsonbNumberFormat annotation on package."); + } + + return; // passed + } + + /* + * @testName: testNumberFormatType + * + * @assertion_ids: JSONB:SPEC:JSB-4.9-1 + * + * @test_Strategy: Assert that type annotation with JsonbNumberFormat is + * correctly applied + */ + @Test + public void testNumberFormatType() { + String jsonString = jsonb.toJson(new TypeCustomizedDoubleContainer() { + { + setInstance(123456.789); + } + }); + if (!jsonString + .matches("\\{\\s*\"instance\"\\s*:\\s*\"123,456.79\"\\s*\\}")) { + fail( + "Failed to correctly customize number format during marshalling using JsonbNumberFormat annotation on type."); + } + + TypeCustomizedDoubleContainer unmarshalledObject = jsonb.fromJson( + "{ \"instance\" : \"123,456.789\" }", + TypeCustomizedDoubleContainer.class); + if (unmarshalledObject.getInstance() != 123456.789) { + fail( + "Failed to correctly customize number format during unmarshalling using JsonbNumberFormat annotation on type."); + } + + return; // passed + } + + /* + * @testName: testNumberFormatField + * + * @assertion_ids: JSONB:SPEC:JSB-4.9-1 + * + * @test_Strategy: Assert that field annotation with JsonbNumberFormat is + * correctly applied + */ + @Test + public void testNumberFormatField() { + //Franch group separator has been changed in JDK 13 and it is now backwords incompatible. + char frenchGroupingSeparator = DecimalFormatSymbols.getInstance(Locale.FRENCH).getGroupingSeparator(); + String jsonString = jsonb.toJson(new FieldCustomizedDoubleContainer() { + { + setInstance(123456.789); + } + }); + if (!jsonString + .matches("\\{\\s*\"instance\"\\s*:\\s*\"123"+frenchGroupingSeparator+"456,789\"\\s*\\}")) { + fail( + "Failed to correctly customize number format during marshalling using JsonbNumberFormat annotation on field."); + } + + FieldCustomizedDoubleContainer unmarshalledObject = jsonb.fromJson( + "{ \"instance\" : " + FRENCH_NUMBER + " }", + FieldCustomizedDoubleContainer.class); + if (unmarshalledObject.getInstance() != 123456.789) { + fail( + "Failed to correctly customize number format during unmarshalling using JsonbNumberFormat annotation on field."); + } + + return; // passed + } + + /* + * @testName: testNumberFormatAccessors + * + * @assertion_ids: JSONB:SPEC:JSB-4.9-1 + * + * @test_Strategy: Assert that accessor annotation with JsonbNumberFormat is + * correctly individually applied for marshalling and unmarshalling + */ + @Test + public void testNumberFormatAccessors() { + String jsonString = jsonb.toJson(new AccessorCustomizedDoubleContainer() { + { + setInstance(123456.789); + } + }); + if (!jsonString + .matches("\\{\\s*\"instance\"\\s*:\\s*\"123,456.79\"\\s*\\}")) { + fail( + "Failed to correctly customize number format during marshalling using JsonbNumberFormat annotation on getter."); + } + + AccessorCustomizedDoubleContainer unmarshalledObject = jsonb.fromJson( + "{ \"instance\" : " + FRENCH_NUMBER + " }", + AccessorCustomizedDoubleContainer.class); + if (unmarshalledObject.getInstance() != 123456.789) { + fail( + "Failed to correctly customize number format during unmarshalling using JsonbNumberFormat annotation on setter."); + } + + return; // passed + } + + /* + * @testName: testNumberFormatPackageTypeOverride + * + * @assertion_ids: JSONB:SPEC:JSB-4.9-1; JSONB:SPEC:JSB-4.9-2 + * + * @test_Strategy: Assert that package annotation with JsonbNumberFormat is + * correctly overridden by type annotation with JsonbNumberFormat + */ + @Test + public void testNumberFormatPackageTypeOverride() { + String jsonString = jsonb + .toJson(new PackageCustomizedTypeOverriddenDoubleContainer() { + { + setInstance(123456.789); + } + }); + if (!jsonString + .matches("\\{\\s*\"instance\"\\s*:\\s*\"123,456.79\"\\s*\\}")) { + fail( + "Failed to correctly override number format customization using JsonbNumberFormat annotation on package during marshalling using JsonbNumberFormat annotation on type."); + } + + PackageCustomizedTypeOverriddenDoubleContainer unmarshalledObject = jsonb + .fromJson("{ \"instance\" : \"123,456.789\" }", + PackageCustomizedTypeOverriddenDoubleContainer.class); + if (unmarshalledObject.getInstance() != 123456.789) { + fail( + "Failed to correctly override number format customization using JsonbNumberFormat annotation on package during unmarshalling using JsonbNumberFormat annotation on type."); + } + + return; // passed + } + + /* + * @testName: testNumberFormatTypeFieldOverride + * + * @assertion_ids: JSONB:SPEC:JSB-4.9-1; JSONB:SPEC:JSB-4.9-2 + * + * @test_Strategy: Assert that type annotation with JsonbNumberFormat is + * correctly overridden by field annotation with JsonbNumberFormat + */ + @Test + public void testNumberFormatTypeFieldOverride() { + String jsonString = jsonb + .toJson(new TypeCustomizedFieldOverriddenDoubleContainer() { + { + setInstance(123456.789); + } + }); + if (!jsonString + .matches("\\{\\s*\"instance\"\\s*:\\s*\"123,456.8\"\\s*\\}")) { + fail( + "Failed to correctly customize number format during marshalling using JsonbNumberFormat annotation on type."); + } + + TypeCustomizedFieldOverriddenDoubleContainer unmarshalledObject = jsonb + .fromJson("{ \"instance\" : \"123,456.789\" }", + TypeCustomizedFieldOverriddenDoubleContainer.class); + if (unmarshalledObject.getInstance() != 123456.789) { + fail( + "Failed to correctly customize number format during unmarshalling using JsonbNumberFormat annotation on type."); + } + + return; // passed + } + + /* + * @testName: testNumberFormatPackageTypeOverrideFieldOverride + * + * @assertion_ids: JSONB:SPEC:JSB-4.9-1; JSONB:SPEC:JSB-4.9-2 + * + * @test_Strategy: Assert that package and type annotation with + * JsonbNumberFormat is correctly overridden by field annotation with + * JsonbNumberFormat + */ + @Test + public void testNumberFormatPackageTypeOverrideFieldOverride() { + String jsonString = jsonb.toJson( + new PackageCustomizedTypeOverriddenFieldOverriddenDoubleContainer() { + { + setInstance(123456.789); + } + }); + if (!jsonString + .matches("\\{\\s*\"instance\"\\s*:\\s*\"123.456,789\"\\s*\\}")) { + fail( + "Failed to correctly override number format customization using JsonbNumberFormat annotation on package during marshalling using JsonbNumberFormat annotation on type."); + } + + PackageCustomizedTypeOverriddenFieldOverriddenDoubleContainer unmarshalledObject = jsonb + .fromJson("{ \"instance\" : \"123.456,789\" }", + PackageCustomizedTypeOverriddenFieldOverriddenDoubleContainer.class); + if (unmarshalledObject.getInstance() != 123456.789) { + fail( + "Failed to correctly override number format customization using JsonbNumberFormat annotation on package during unmarshalling using JsonbNumberFormat annotation on type."); + } + + return; // passed + } +}