Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.core.json.DupDetector;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;

Expand Down Expand Up @@ -284,18 +285,21 @@ public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
* This constructor initializes the parser with the given I/O context, parser features,
* and object codec for deserializing XML content into Java objects.
*
* @since 2.20
* @param ctxt I/O context used for handling low-level I/O operations and buffering
* @param genericParserFeatures set of bitmasked parser features to control parsing behavior
* @param codec object codec used for converting between JSON-like structures and Java objects
* @param xmlTokenStream the pre-processed XML token stream to parse from
* @throws IOException if an I/O error occurs during initialization or parsing setup
*
* @since 2.20
*/
public FromXmlParser(IOContext ctxt, int genericParserFeatures, ObjectCodec codec, XmlTokenStream xmlTokenStream) throws IOException {
super(genericParserFeatures, ctxt.streamReadConstraints());
_ioContext = ctxt;
_objectCodec = codec;
_parsingContext = XmlReadContext.createRootContext(-1, -1);
DupDetector dups = JsonParser.Feature.STRICT_DUPLICATE_DETECTION.enabledIn(genericParserFeatures)
? DupDetector.rootDetector(this) : null;
_parsingContext = XmlReadContext.createRootContext(dups, -1, -1);
_xmlTokens = Objects.requireNonNull(xmlTokenStream, "xmlTokenStream cannot be null");
_formatFeatures = xmlTokenStream.getFormatFeatures();
final int firstToken;
Expand Down Expand Up @@ -526,7 +530,12 @@ public void overrideCurrentName(String name)
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
ctxt = ctxt.getParent();
}
ctxt.setCurrentName(name);
// Unfortunate, but since we did not expose exceptions, need to wrap
try {
ctxt.setCurrentName(name);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

@Override
Expand Down Expand Up @@ -1050,7 +1059,7 @@ public String nextTextValue() throws IOException
}


private void _updateState(JsonToken t)
private void _updateState(JsonToken t) throws JsonProcessingException
{
switch (t) {
case START_OBJECT:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.CharTypes;
import com.fasterxml.jackson.core.io.ContentReference;
import com.fasterxml.jackson.core.json.DupDetector;

/**
* Extension of {@link JsonStreamContext}, which implements
Expand All @@ -23,6 +24,14 @@ public final class XmlReadContext

protected final XmlReadContext _parent;

/**
* Object used for checking for duplicate field names, if enabled
* (null if not enabled)
*
* @since 2.21
*/
protected final DupDetector _dups;

// // // Location information (minus source reference)

protected int _lineNr;
Expand Down Expand Up @@ -59,28 +68,26 @@ public final class XmlReadContext
*/

/**
* @since 2.18
* @since 2.21
*/
public XmlReadContext(XmlReadContext parent, int nestingDepth,
public XmlReadContext(XmlReadContext parent, DupDetector dups,
int nestingDepth,
int type, int lineNr, int colNr)
{
super();
_type = type;
_parent = parent;
_dups = dups;
_lineNr = lineNr;
_columnNr = colNr;
_index = -1;
_nestingDepth = nestingDepth;
}

/**
* @deprecated Since 2.18
*/
@Deprecated // since 2.18
public XmlReadContext(XmlReadContext parent, int type, int lineNr, int colNr)
{
this(parent, (parent == null) ? 0 : parent._nestingDepth + 1,
type, lineNr, colNr);
@Deprecated // @since 2.21
public XmlReadContext(XmlReadContext parent, int nestingDepth,
int type, int lineNr, int colNr) {
this(parent, null, nestingDepth, type, lineNr, colNr);
}

protected final void reset(int type, int lineNr, int colNr)
Expand All @@ -92,7 +99,10 @@ protected final void reset(int type, int lineNr, int colNr)
_currentName = null;
_currentValue = null;
_namesToWrap = null;
// _nestingDepth fine as is, same level for reuse
if (_dups != null) {
_dups.reset();
}
// _nestingDepth fine as-is, same level for reuse
}

@Override
Expand All @@ -111,20 +121,28 @@ public void setCurrentValue(Object v) {
/**********************************************************************
*/

public static XmlReadContext createRootContext(DupDetector dups, int lineNr, int colNr) {
return new XmlReadContext(null, dups, 0, TYPE_ROOT, lineNr, colNr);
}

@Deprecated // @since 2.21
public static XmlReadContext createRootContext(int lineNr, int colNr) {
return new XmlReadContext(null, 0, TYPE_ROOT, lineNr, colNr);
return createRootContext(null, lineNr, colNr);
}

@Deprecated // @since 2.21
public static XmlReadContext createRootContext() {
return new XmlReadContext(null, 0, TYPE_ROOT, 1, 0);
return createRootContext(null, 1, 0);
}

public final XmlReadContext createChildArrayContext(int lineNr, int colNr)
{
++_index; // not needed for Object, but does not hurt so no need to check curr type
XmlReadContext ctxt = _child;
if (ctxt == null) {
_child = ctxt = new XmlReadContext(this, _nestingDepth+1, TYPE_ARRAY, lineNr, colNr);
_child = ctxt = new XmlReadContext(this,
(_dups == null) ? null : _dups.child(),
_nestingDepth+1, TYPE_ARRAY, lineNr, colNr);
return ctxt;
}
ctxt.reset(TYPE_ARRAY, lineNr, colNr);
Expand All @@ -136,10 +154,12 @@ public final XmlReadContext createChildObjectContext(int lineNr, int colNr)
++_index; // not needed for Object, but does not hurt so no need to check curr type
XmlReadContext ctxt = _child;
if (ctxt == null) {
_child = ctxt = new XmlReadContext(this, TYPE_OBJECT, lineNr, colNr);
return ctxt;
_child = ctxt = new XmlReadContext(this,
(_dups == null) ? null : _dups.child(),
_nestingDepth+1, TYPE_OBJECT, lineNr, colNr);
} else {
ctxt.reset(TYPE_OBJECT, lineNr, colNr);
}
ctxt.reset(TYPE_OBJECT, lineNr, colNr);
return ctxt;
}

Expand Down Expand Up @@ -186,10 +206,22 @@ public final void valueStarted() {
++_index;
}

public void setCurrentName(String name) {
public void setCurrentName(String name) throws JsonProcessingException {
_currentName = name;
if (_dups != null) {
_checkDup(_dups, name);
}
}

// @since 2.21
private static void _checkDup(DupDetector dd, String name) throws JsonProcessingException
{
if (dd.isDup(name)) {
throw new JsonParseException(null,
"Duplicate field '"+name+"'", dd.findLocation());
}
}

public void setNamesToWrap(Set<String> namesToWrap) {
_namesToWrap = namesToWrap;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.fasterxml.jackson.dataformat.xml.deser;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.XmlTestUtil;

import static org.junit.jupiter.api.Assertions.*;

/**
* Tests for [dataformat-xml#114]: Support for STRICT_DUPLICATE_DETECTION
*
* @since 2.21
*/
public class StrictDuplicateDetection114Test extends XmlTestUtil
{
static class TestBean114 {
public String field1;
public String field2;
}

private final XmlMapper STRICT_MAPPER = XmlMapper.builder()
.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION)
.build();

// [dataformat-xml#114]
@Test
public void testStrictDuplicateDetectionWithPOJO() throws Exception
{
// Test XML mapper should also reject duplicates
final String xmlWithDup = "<TestBean><field1>value1</field1><field1>value2</field1></TestBean>";

StreamReadException e = assertThrows(StreamReadException.class, () -> {
STRICT_MAPPER.readValue(xmlWithDup, TestBean114.class);
});
assertTrue(e.getMessage().contains("Duplicate field"),
"Expected 'Duplicate field' error, got: " + e.getMessage());
}

@Test
public void testNoDuplicatesShouldWork() throws Exception
{
final String xml = "<TestBean><field1>value1</field1><field2>value2</field2></TestBean>";

TestBean114 bean = STRICT_MAPPER.readValue(xml, TestBean114.class);
assertNotNull(bean);
assertEquals("value1", bean.field1);
assertEquals("value2", bean.field2);
}

@Test
public void testDuplicateDetectionDisabledByDefault() throws Exception
{
XmlMapper mapper = newMapper(); // default mapper without strict duplicate detection

// Should allow duplicates by default (last value wins)
final String xmlWithDup = "<TestBean><field1>value1</field1><field1>value2</field1></TestBean>";

TestBean114 bean = mapper.readValue(xmlWithDup, TestBean114.class);
assertNotNull(bean);
assertEquals("value2", bean.field1); // last value wins
}
}