AbstractCopyOnWriteArrayConverter.java
/*
* Copyright (C) 2026 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 18. February 2026 by Joerg Schaible
*/
package com.thoughtworks.xstream.converters.collections;
import java.util.ArrayList;
import java.util.Collection;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* Base class for converters of the CopyOnWriteArray types.
*
* @author Joerg Schaible
* @since upcoming
*/
public abstract class AbstractCopyOnWriteArrayConverter extends CollectionConverter {
protected static final String ELEMENT_DEFAULT = "default";
protected static final String ATTRIBUTE_SERIALIZATION = "serialization";
protected static final String ATTRIBUTE_VALUE_CUSTOM = "custom";
/**
* Constructs a CopyOnWriteArraySetConverter for a CopyOnWriteArraySet or derived type.
*
* @param mapper the mapper
* @param type the compatible CopyOnWriteArraySet type to handle
* @author Jörg Schaible
* @since upcoming
*/
protected AbstractCopyOnWriteArrayConverter(
final Mapper mapper, @SuppressWarnings("rawtypes") final Class<? extends Collection> type) {
super(mapper, type);
}
@Override
public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
@SuppressWarnings("unchecked")
final Collection<Object> collection = (Collection<Object>)createCollection(context.getRequiredType());
int up = skipLegacyFormat(reader);
final Collection<?> list = new ArrayList<>();
populateCollection(reader, context, list);
while (up-- > 0) {
reader.moveUp();
}
collection.addAll(list); // is internally optimized for adding ArrayList
return collection;
}
/**
* Skip the initial structure of the legacy format. The method must move down and forward in the stream directly to
* the first element of the array and return the immersion depth.
*
* @param reader the stream reader
* @return the immersion depth
* @since upcoming
*/
abstract protected int skipLegacyFormat(final HierarchicalStreamReader reader);
/**
* Throw {@link ConversionException} for unhandled serialization type.
*
* @param serialization the serialization type
* @since upcoming
*/
protected void throwForUnhandledSerializationType(final String serialization) {
final ConversionException ex = new ConversionException("Unmarshalling of unexpected legacy format failed.");
ex.add("expected-serialization-attribute-value", ATTRIBUTE_VALUE_CUSTOM);
ex.add("serialization-attribute-value", serialization);
throw ex;
}
/**
* Check and skip the legacy serialization format of a CopyOnWriteArraySet instance.
*
* @param reader the stream reader
* @param firstElement name of the first element or {@code null} to skip the check
* @return the immersion depth
* @since upcoming
*/
protected int checkSerializableArraySetFormat(final HierarchicalStreamReader reader, final String firstElement) {
int up = firstElement == null ? 1 : checkElement(firstElement, reader, false);
up += checkElement(null, reader, false);
up += checkElement(ELEMENT_DEFAULT, reader, true);
up += checkElement(mapper().serializedClass(int.class), reader, true);
return up;
}
/**
* Check and skip the next element.
*
* @param expected the expected element name
* @param reader the stream reader
* @param up <code>true</code> if the element should be left
* @return 1 of the element was not left, else 0
* @since upcoming
*/
protected int checkElement(final String expected, final HierarchicalStreamReader reader, final boolean up) {
reader.moveDown();
final String actual = reader.getNodeName();
if (expected != null && !expected.equals(actual)) {
final ConversionException ex = new ConversionException("Unmarshalling of unexpected legacy format failed.");
ex.add("expected-element", expected);
ex.add("element", actual);
throw ex;
}
if (up) {
reader.moveUp();
}
return up ? 0 : 1;
}
}