Skip to content

Commit

Permalink
Add release notes for #184, rework a bit
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Oct 21, 2020
1 parent 1754c4b commit 8cde8aa
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,32 @@

package com.fasterxml.jackson.datatype.jsr310.deser;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.*;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonFormat;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.core.StreamReadCapability;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;


/**
* Deserializer for Java 8 temporal {@link Duration}s.
*
* @author Nick Williams
* @since 2.2.0
* @since 2.2
*/
public class DurationDeserializer extends JSR310DeserializerBase<Duration>
implements ContextualDeserializer
Expand All @@ -57,39 +51,52 @@ public class DurationDeserializer extends JSR310DeserializerBase<Duration>
public static final DurationDeserializer INSTANCE = new DurationDeserializer();

/**
* Since 2.12
* When set, integer values will be deserialized using the specified unit. Using this parser will tipically
* override the value specified in {@link DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS} as it is
* considered that the unit set in {@link JsonFormat#pattern()} has precedence since is more specific.
* When defined (not {@code null}) integer values will be converted into duration
* unit configured for the converter.
* Using this converter will typically override the value specified in
* {@link DeserializationFeature#READ_DATE_TIMESTAMPS_AS_NANOSECONDS} as it is
* considered that the unit set in {@link JsonFormat#pattern()} has precedence
* since it is more specific.
*<p>
* See [jackson-modules-java8#184] for more info.
*
* @see [jackson-modules-java8#184] for more info
* @since 2.12
*/
private DurationUnitParser _durationUnitParser;
protected final DurationUnitConverter _durationUnitConverter;

private DurationDeserializer() {
public DurationDeserializer() {
super(Duration.class);
_durationUnitConverter = null;
}

/**
* Since 2.11
* @since 2.11
*/
protected DurationDeserializer(DurationDeserializer base, Boolean leniency) {
super(base, leniency);
_durationUnitConverter = base._durationUnitConverter;
}

protected DurationDeserializer(DurationDeserializer base, DurationUnitParser durationUnitParser) {
/**
* @since 2.12
*/
protected DurationDeserializer(DurationDeserializer base, DurationUnitConverter converter) {
super(base, base._isLenient);
_durationUnitParser = durationUnitParser;
_durationUnitConverter = converter;
}

@Override
protected DurationDeserializer withLeniency(Boolean leniency) {
return new DurationDeserializer(this, leniency);
}

protected DurationDeserializer withConverter(DurationUnitConverter pattern) {
return new DurationDeserializer(this, pattern);
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
BeanProperty property) throws JsonMappingException
{
JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
DurationDeserializer deser = this;
Expand All @@ -101,18 +108,20 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
}
}
if (format.hasPattern()) {
deser = DurationUnitParser.from(format.getPattern())
.map(deser::withPattern)
.orElse(deser);
final String pattern = format.getPattern();
DurationUnitConverter p = DurationUnitConverter.from(pattern);
if (p == null) {
ctxt.reportBadDefinition(getValueType(ctxt),
String.format(
"Bad 'pattern' definition (\"%s\") for `Duration`: expected one of [%s]",
pattern, DurationUnitConverter.descForAllowed()));
}
deser = deser.withConverter(p);
}
}
return deser;
}

private DurationDeserializer withPattern(DurationUnitParser pattern) {
return new DurationDeserializer(this, pattern);
}

@Override
public Duration deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
Expand All @@ -122,11 +131,7 @@ public Duration deserialize(JsonParser parser, DeserializationContext context) t
BigDecimal value = parser.getDecimalValue();
return DecimalUtils.extractSecondsAndNanos(value, Duration::ofSeconds);
case JsonTokenId.ID_NUMBER_INT:
long intValue = parser.getLongValue();
if (_durationUnitParser != null) {
return _durationUnitParser.parse(intValue);
}
return _fromTimestamp(context, intValue);
return _fromTimestamp(context, parser.getLongValue());
case JsonTokenId.ID_STRING:
return _fromString(parser, context, parser.getText());
// 30-Sep-2020, tatu: New! "Scalar from Object" (mostly for XML)
Expand Down Expand Up @@ -170,14 +175,22 @@ && _isValidTimestampString(value)) {
}

protected Duration _fromTimestamp(DeserializationContext ctxt, long ts) {
if (_durationUnitConverter != null) {
return _durationUnitConverter.convert(ts);
}
// 20-Oct-2020, tatu: This makes absolutely no sense but... somehow
// became the default handling.
if (ctxt.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
return Duration.ofSeconds(ts);
}
return Duration.ofMillis(ts);
}

protected static class DurationUnitParser {
final static Set<ChronoUnit> PARSEABLE_UNITS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
protected static class DurationUnitConverter {
private final static Map<String, ChronoUnit> PARSEABLE_UNITS;
static {
Map<String, ChronoUnit> units = new LinkedHashMap<>();
for (ChronoUnit unit : new ChronoUnit[] {
ChronoUnit.NANOS,
ChronoUnit.MICROS,
ChronoUnit.MILLIS,
Expand All @@ -186,22 +199,35 @@ protected static class DurationUnitParser {
ChronoUnit.HOURS,
ChronoUnit.HALF_DAYS,
ChronoUnit.DAYS
)));
}) {
units.put(unit.name(), unit);
}
PARSEABLE_UNITS = units;
}

final TemporalUnit unit;

DurationUnitParser(TemporalUnit unit) {
DurationUnitConverter(TemporalUnit unit) {
this.unit = unit;
}

Duration parse(long value) {
public Duration convert(long value) {
return Duration.of(value, unit);
}

static Optional<DurationUnitParser> from(String unit) {
return PARSEABLE_UNITS.stream()
.filter(u -> u.name().equals(unit))
.map(DurationUnitParser::new)
.findFirst();
/**
* @return Description of all allowed valued as a sequence of
* double-quoted values separated by comma
*/
public static String descForAllowed() {
return "\"" + PARSEABLE_UNITS.keySet().stream()
.collect(Collectors.joining("\", \""))
+"\"";
}

static DurationUnitConverter from(String unit) {
ChronoUnit chr = PARSEABLE_UNITS.get(unit);
return (chr == null) ? null : new DurationUnitConverter(chr);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ protected <R> R _handleUnexpectedToken(DeserializationContext context,

@SuppressWarnings("unchecked")
protected T _failForNotLenient(JsonParser p, DeserializationContext ctxt,
JsonToken expToken) throws IOException
JsonToken expToken) throws IOException
{
return (T) ctxt.handleUnexpectedToken(handledType(), expToken, p,
"Cannot deserialize instance of %s out of %s token: not allowed because 'strict' mode set for property or type (enable 'lenient' handling to allow)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
Expand Down Expand Up @@ -429,6 +430,12 @@ public void testStrictDeserializeFromEmptyString() throws Exception {
objectReader.readValue(valueFromEmptyStr);
}

/*
/**********************************************************
/* Tests for custom patterns (modules-java8#184)
/**********************************************************
*/

@Test
public void shouldDeserializeInNanos_whenNanosUnitAsPattern_andValueIsInteger() throws Exception {
ObjectMapper mapper = newMapper();
Expand Down Expand Up @@ -550,15 +557,19 @@ public void shouldIgnoreUnitPattern_whenValueIsString() throws Exception {
}

@Test
public void shouldIgnoreUnitPattern_whenUnitPatternDoesNotMatchExactly() throws Exception {
public void shouldFailForInvalidPattern() throws Exception {
ObjectMapper mapper = newMapper();
mapper.configOverride(Duration.class)
.setFormat(JsonFormat.Value.forPattern("Nanos"));
ObjectReader reader = mapper.readerFor(MAP_TYPE_REF);

Wrapper wrapper = reader.readValue(wrapperPayload(25), Wrapper.class);

assertEquals(Duration.ofSeconds(25), wrapper.value);
try {
/*Wrapper wrapper =*/ reader.readValue(wrapperPayload(25), Wrapper.class);
fail("Should not allow invalid 'pattern'");
} catch (InvalidDefinitionException e) {
verifyException(e, "Bad 'pattern' definition (\"Nanos\")");
verifyException(e, "expected one of [");
}
}

private String wrapperPayload(Number number) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.fasterxml.jackson.datatype.jsr310.deser;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.time.temporal.ChronoUnit;

import org.junit.Test;

import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
import com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer.DurationUnitConverter;

public class DurationUnitConverterTest
extends ModuleTestBase
{
@Test
public void shouldMapToTemporalUnit() {
for (ChronoUnit inputUnit : new ChronoUnit[] {
ChronoUnit.NANOS,
ChronoUnit.MICROS,
ChronoUnit.MILLIS,
ChronoUnit.SECONDS,
ChronoUnit.MINUTES,
ChronoUnit.HOURS,
ChronoUnit.HALF_DAYS,
ChronoUnit.DAYS,
}) {
DurationUnitConverter conv = DurationUnitConverter.from(inputUnit.name());
assertNotNull(conv);
assertEquals(inputUnit, conv.unit);
// is case-sensitive:
assertNull(DurationUnitConverter.from(inputUnit.name().toLowerCase()));
}
}

@Test
public void shouldNotMapToTemporalUnit() {
for (String invalid : new String[] {
// Inaccurate units not (yet?) supported
"WEEKS",
"MONTHS",
"YEARS",
"DECADES",
"CENTURIES",
"MILLENNIA",
"ERAS",
"FOREVER",

// Not matching at all
"DOESNOTMATCH", "", " "
}) {
assertNull("Should not map pattern '"+invalid+"'",
DurationUnitConverter.from(invalid));
}
}
}

This file was deleted.

Loading

0 comments on commit 8cde8aa

Please sign in to comment.