// TODO: move these converters in converter package private Map<AdapterKey, Adapter<?, ?>> createJava8Converters(final MapperBuilder builder) { final Map<AdapterKey, Adapter<?, ?>> converters = new HashMap<>(); final TimeZone timeZoneUTC = TimeZone.getTimeZone("UTC"); final ZoneId zoneIDUTC = ZoneId.of("UTC"); // built-in converters not in mapper converters.put( new AdapterKey(Period.class, String.class), new ConverterAdapter<>( new Converter<Period>() { @Override public String toString(final Period instance) { return instance.toString(); } @Override public Period fromString(final String text) { return Period.parse(text); } })); converters.put( new AdapterKey(Duration.class, String.class), new ConverterAdapter<>( new Converter<Duration>() { @Override public String toString(final Duration instance) { return instance.toString(); } @Override public Duration fromString(final String text) { return Duration.parse(text); } })); converters.put( new AdapterKey(Date.class, String.class), new ConverterAdapter<>( new Converter<Date>() { @Override public String toString(final Date instance) { return LocalDateTime.ofInstant(instance.toInstant(), zoneIDUTC).toString(); } @Override public Date fromString(final String text) { return Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC)); } })); converters.put( new AdapterKey(Calendar.class, String.class), new ConverterAdapter<>( new Converter<Calendar>() { @Override public String toString(final Calendar instance) { return ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC).toString(); } @Override public Calendar fromString(final String text) { final Calendar calendar = Calendar.getInstance(); calendar.setTimeZone(timeZoneUTC); calendar.setTimeInMillis(ZonedDateTime.parse(text).toInstant().toEpochMilli()); return calendar; } })); converters.put( new AdapterKey(GregorianCalendar.class, String.class), new ConverterAdapter<>( new Converter<GregorianCalendar>() { @Override public String toString(final GregorianCalendar instance) { return instance.toZonedDateTime().toString(); } @Override public GregorianCalendar fromString(final String text) { final GregorianCalendar calendar = new GregorianCalendar(); calendar.setTimeZone(timeZoneUTC); calendar.setTimeInMillis(ZonedDateTime.parse(text).toInstant().toEpochMilli()); return calendar; } })); converters.put( new AdapterKey(TimeZone.class, String.class), new ConverterAdapter<>( new Converter<TimeZone>() { @Override public String toString(final TimeZone instance) { return instance.getID(); } @Override public TimeZone fromString(final String text) { logIfDeprecatedTimeZone(text); return TimeZone.getTimeZone(text); } })); converters.put( new AdapterKey(ZoneId.class, String.class), new ConverterAdapter<>( new Converter<ZoneId>() { @Override public String toString(final ZoneId instance) { return instance.getId(); } @Override public ZoneId fromString(final String text) { return ZoneId.of(text); } })); converters.put( new AdapterKey(ZoneOffset.class, String.class), new ConverterAdapter<>( new Converter<ZoneOffset>() { @Override public String toString(final ZoneOffset instance) { return instance.getId(); } @Override public ZoneOffset fromString(final String text) { return ZoneOffset.of(text); } })); converters.put( new AdapterKey(SimpleTimeZone.class, String.class), new ConverterAdapter<>( new Converter<SimpleTimeZone>() { @Override public String toString(final SimpleTimeZone instance) { return instance.getID(); } @Override public SimpleTimeZone fromString(final String text) { logIfDeprecatedTimeZone(text); final TimeZone timeZone = TimeZone.getTimeZone(text); return new SimpleTimeZone(timeZone.getRawOffset(), timeZone.getID()); } })); converters.put( new AdapterKey(Instant.class, String.class), new ConverterAdapter<>( new Converter<Instant>() { @Override public String toString(final Instant instance) { return instance.toString(); } @Override public Instant fromString(final String text) { return Instant.parse(text); } })); converters.put( new AdapterKey(LocalDate.class, String.class), new ConverterAdapter<>( new Converter<LocalDate>() { @Override public String toString(final LocalDate instance) { return instance.toString(); } @Override public LocalDate fromString(final String text) { return LocalDate.parse(text); } })); converters.put( new AdapterKey(LocalDateTime.class, String.class), new ConverterAdapter<>( new Converter<LocalDateTime>() { @Override public String toString(final LocalDateTime instance) { return instance.toString(); } @Override public LocalDateTime fromString(final String text) { return LocalDateTime.parse(text); } })); converters.put( new AdapterKey(ZonedDateTime.class, String.class), new ConverterAdapter<>( new Converter<ZonedDateTime>() { @Override public String toString(final ZonedDateTime instance) { return instance.toString(); } @Override public ZonedDateTime fromString(final String text) { return ZonedDateTime.parse(text); } })); converters.put( new AdapterKey(OffsetDateTime.class, String.class), new ConverterAdapter<>( new Converter<OffsetDateTime>() { @Override public String toString(final OffsetDateTime instance) { return instance.toString(); } @Override public OffsetDateTime fromString(final String text) { return OffsetDateTime.parse(text); } })); converters.put( new AdapterKey(OffsetTime.class, String.class), new ConverterAdapter<>( new Converter<OffsetTime>() { @Override public String toString(final OffsetTime instance) { return instance.toString(); } @Override public OffsetTime fromString(final String text) { return OffsetTime.parse(text); } })); addDateFormatConfigConverters(converters, zoneIDUTC); converters.forEach((k, v) -> builder.addAdapter(k.getFrom(), k.getTo(), v)); return converters; }
@Override public Jsonb build() { if (jsonp != null) { builder.setGeneratorFactory(jsonp.createGeneratorFactory(generatorConfig())); builder.setReaderFactory(jsonp.createReaderFactory(emptyMap())); } final Supplier<JsonParserFactory> parserFactoryProvider = new Supplier<JsonParserFactory>() { // thread safety is not mandatory private final AtomicReference<JsonParserFactory> ref = new AtomicReference<>(); @Override public JsonParserFactory get() { JsonParserFactory factory = ref.get(); if (factory == null) { factory = doCreate(); if (!ref.compareAndSet(null, factory)) { factory = ref.get(); } } return factory; } private JsonParserFactory doCreate() { return (jsonp == null ? JsonProvider.provider() : jsonp) .createParserFactory(emptyMap()); } }; if (config == null) { config = new JsonbConfig(); } if (config.getProperty(JsonbConfig.FORMATTING).map(Boolean.class::cast).orElse(false)) { builder.setPretty(true); } config .getProperty(JsonbConfig.ENCODING) .ifPresent(encoding -> builder.setEncoding(String.valueOf(encoding))); config .getProperty(JsonbConfig.NULL_VALUES) .ifPresent(serNulls -> builder.setSkipNull(!Boolean.class.cast(serNulls))); final Optional<Object> namingStrategyValue = config.getProperty(JsonbConfig.PROPERTY_NAMING_STRATEGY); final PropertyNamingStrategy propertyNamingStrategy = new PropertyNamingStrategyFactory(namingStrategyValue.orElse(IDENTITY)).create(); final String orderValue = config .getProperty(JsonbConfig.PROPERTY_ORDER_STRATEGY) .map(String::valueOf) .orElse(LEXICOGRAPHICAL); final PropertyVisibilityStrategy visibilityStrategy = config .getProperty(JsonbConfig.PROPERTY_VISIBILITY_STRATEGY) .map(PropertyVisibilityStrategy.class::cast) .orElse( new PropertyVisibilityStrategy() { private final ConcurrentMap<Class<?>, PropertyVisibilityStrategy> strategies = new ConcurrentHashMap<>(); @Override public boolean isVisible(final Field field) { final PropertyVisibilityStrategy strategy = strategies.computeIfAbsent( field.getDeclaringClass(), this::visibilityStrategy); return strategy == this ? Modifier.isPublic(field.getModifiers()) : strategy.isVisible(field); } @Override public boolean isVisible(final Method method) { final PropertyVisibilityStrategy strategy = strategies.computeIfAbsent( method.getDeclaringClass(), this::visibilityStrategy); return strategy == this ? Modifier.isPublic(method.getModifiers()) : strategy.isVisible(method); } private PropertyVisibilityStrategy visibilityStrategy( final Class<?> type) { // can be cached Package p = type.getPackage(); while (p != null) { final JsonbVisibility visibility = p.getAnnotation(JsonbVisibility.class); if (visibility != null) { try { return visibility.value().newInstance(); } catch (final InstantiationException | IllegalAccessException e) { throw new IllegalArgumentException(e); } } final String name = p.getName(); final int end = name.lastIndexOf('.'); if (end < 0) { break; } p = Package.getPackage(name.substring(0, end)); } return this; } }); config .getProperty("johnzon.attributeOrder") .ifPresent(comp -> builder.setAttributeOrder(Comparator.class.cast(comp))); config .getProperty("johnzon.enforceQuoteString") .map( v -> !Boolean.class.isInstance(v) ? Boolean.parseBoolean(v.toString()) : Boolean.class.cast(v)) .ifPresent(builder::setEnforceQuoteString); config .getProperty("johnzon.primitiveConverters") .map( v -> !Boolean.class.isInstance(v) ? Boolean.parseBoolean(v.toString()) : Boolean.class.cast(v)) .ifPresent(builder::setPrimitiveConverters); final Map<AdapterKey, Adapter<?, ?>> defaultConverters = createJava8Converters(builder); final JohnzonAdapterFactory factory = config .getProperty("johnzon.factory") .map( val -> { if (JohnzonAdapterFactory.class.isInstance(val)) { return JohnzonAdapterFactory.class.cast(val); } if (String.class.isInstance(val)) { try { return JohnzonAdapterFactory.class.cast( tccl().loadClass(val.toString()).newInstance()); } catch (final InstantiationException | ClassNotFoundException | IllegalAccessException e) { throw new IllegalArgumentException(e); } } if (Class.class.isInstance(val)) { try { return JohnzonAdapterFactory.class.cast(Class.class.cast(val).newInstance()); } catch (final InstantiationException | IllegalAccessException e) { throw new IllegalArgumentException(e); } } throw new IllegalArgumentException("Unsupported factory: " + val); }) .orElseGet(this::findFactory); final JsonbAccessMode accessMode = new JsonbAccessMode( propertyNamingStrategy, orderValue, visibilityStrategy, !namingStrategyValue.orElse("").equals(PropertyNamingStrategy.CASE_INSENSITIVE), defaultConverters, factory, parserFactoryProvider); builder.setAccessMode(accessMode); // user adapters config .getProperty(JsonbConfig.ADAPTERS) .ifPresent( adapters -> Stream.of(JsonbAdapter[].class.cast(adapters)) .forEach( adapter -> { final ParameterizedType pt = ParameterizedType.class.cast( Stream.of(adapter.getClass().getGenericInterfaces()) .filter( i -> ParameterizedType.class.isInstance(i) && ParameterizedType.class.cast(i).getRawType() == JsonbAdapter.class) .findFirst() .orElse(null)); if (pt == null) { throw new IllegalArgumentException( adapter + " doesn't implement JsonbAdapter"); } final Type[] args = pt.getActualTypeArguments(); builder.addAdapter( args[0], args[1], new JohnzonJsonbAdapter(adapter, args[0], args[1])); })); config .getProperty(JsonbConfig.STRICT_IJSON) .map(Boolean.class::cast) .ifPresent( ijson -> { // no-op: https://tools.ietf.org/html/rfc7493 the only MUST of the spec should be fine // by default }); config .getProperty(JsonbConfig.BINARY_DATA_STRATEGY) .map(String.class::cast) .ifPresent( bin -> { switch (bin) { case BinaryDataStrategy.BYTE: // no-op: our default break; case BinaryDataStrategy.BASE_64: builder.setTreatByteArrayAsBase64(true); break; case BinaryDataStrategy.BASE_64_URL: // needs j8 builder.addConverter( byte[].class, new Converter<byte[]>() { @Override public String toString(final byte[] instance) { return Base64.getUrlEncoder().encodeToString(instance); } @Override public byte[] fromString(final String text) { return Base64.getUrlDecoder() .decode(text.getBytes(StandardCharsets.UTF_8)); } }); break; default: throw new IllegalArgumentException("Unsupported binary configuration: " + bin); } }); getBeanManager(); // force detection builder.setReadAttributeBeforeWrite( config .getProperty("johnzon.readAttributeBeforeWrite") .map(Boolean.class::cast) .orElse(false)); config .getProperty(JsonbConfig.SERIALIZERS) .map(JsonbSerializer[].class::cast) .ifPresent( serializers -> { Stream.of(serializers) .forEach( s -> { final ParameterizedType pt = findPT(s, JsonbSerializer.class); if (pt == null) { throw new IllegalArgumentException( s + " doesn't implement JsonbSerializer"); } final Type[] args = pt.getActualTypeArguments(); // TODO: support PT in ObjectConverter (list) if (args.length != 1 || !Class.class.isInstance(args[0])) { throw new IllegalArgumentException( "We only support serializer on Class for now"); } builder.addObjectConverter( Class.class.cast(args[0]), (ObjectConverter.Writer) (instance, jsonbGenerator) -> s.serialize( instance, jsonbGenerator.getJsonGenerator(), new JohnzonSerializationContext(jsonbGenerator))); }); }); config .getProperty(JsonbConfig.DESERIALIZERS) .map(JsonbDeserializer[].class::cast) .ifPresent( deserializers -> { Stream.of(deserializers) .forEach( d -> { final ParameterizedType pt = findPT(d, JsonbDeserializer.class); if (pt == null) { throw new IllegalArgumentException( d + " doesn't implement JsonbDeserializer"); } final Type[] args = pt.getActualTypeArguments(); if (args.length != 1 || !Class.class.isInstance(args[0])) { throw new IllegalArgumentException( "We only support deserializer on Class for now"); } // TODO: support PT in ObjectConverter (list) builder.addObjectConverter( Class.class.cast(args[0]), (ObjectConverter.Reader) (jsonObject, targetType, parser) -> d.deserialize( parserFactoryProvider.get().createParser(jsonObject), new JohnzonDeserializationContext(parser), targetType)); }); }); final boolean useCdi = cdiIntegration != null && cdiIntegration.isCanWrite() && config .getProperty("johnzon.cdi.activated") .map(Boolean.class::cast) .orElse(Boolean.TRUE); final Mapper mapper = builder.addCloseable(accessMode).build(); return useCdi ? new JohnsonJsonb(mapper) { { cdiIntegration.track(this); } @Override public void close() { try { super.close(); } finally { if (cdiIntegration.isCanWrite()) { cdiIntegration.untrack(this); } } } } : new JohnsonJsonb(mapper); }