Skip to content
Open
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 @@ -41,6 +41,8 @@
import org.infinispan.protostream.containers.IndexedElementContainerAdapter;
import org.infinispan.protostream.containers.IterableElementContainer;
import org.infinispan.protostream.containers.IterableElementContainerAdapter;
import org.infinispan.protostream.containers.MapElementContainer;
import org.infinispan.protostream.containers.MapElementContainerAdapter;
import org.infinispan.protostream.descriptors.JavaType;
import org.infinispan.protostream.descriptors.Type;
import org.infinispan.protostream.impl.Log;
Expand Down Expand Up @@ -70,6 +72,8 @@ public class ProtoMessageTypeMetadata extends ProtoTypeMetadata {

private final boolean isIterableContainer;

private final boolean isMapContainer;

private final boolean isOrderedMarshallable;

private XExecutable factory;
Expand All @@ -90,6 +94,7 @@ protected ProtoMessageTypeMetadata(BaseProtoSchemaGenerator protoSchemaGenerator
this.isAdapter = javaClass != annotatedClass;
this.isIndexedContainer = annotatedClass.isAssignableTo(isAdapter ? IndexedElementContainerAdapter.class : IndexedElementContainer.class);
this.isIterableContainer = annotatedClass.isAssignableTo(isAdapter ? IterableElementContainerAdapter.class : IterableElementContainer.class);
this.isMapContainer = annotatedClass.isAssignableTo(isAdapter ? MapElementContainerAdapter.class : MapElementContainer.class);
this.isOrderedMarshallable = protoSchemaGenerator.orderedMarshaller();

checkInstantiability();
Expand Down Expand Up @@ -125,8 +130,12 @@ public boolean isIterableContainer() {
return isIterableContainer;
}

public boolean isMapContainer() {
return isMapContainer;
}

public boolean isContainer() {
return isIterableContainer || isIndexedContainer;
return isIterableContainer || isIndexedContainer || isMapContainer;
}

public boolean isOrderedMarshallable() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.infinispan.protostream.containers;

import java.util.Map;

/**
* Container adapter interface for {@link Map} implementations.
*
* @author José Bolina
* @since 6.0
*/
public interface MapElementContainer<K, V> extends IterableElementContainer<Map.Entry<K, V>> { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.infinispan.protostream.containers;

import java.util.Map;

/**
* Container adapter interface for {@link Map} implementations.
*
* @author José Bolina
* @since 6.0
*/
public interface MapElementContainerAdapter<K, V, M extends Map<K, V>> extends IterableElementContainerAdapter<M, Map.Entry<K, V>> {

@Override
default void appendElement(M container, Map.Entry<K, V> element) {
container.put(element.getKey(), element.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
final class ContainerObjectWriter extends BaseJsonWriter {

private int containerFields = 1;
private boolean writtenElements = false;

ContainerObjectWriter(ImmutableSerializationContext ctx, List<JsonTokenWriter> ast, FieldDescriptor descriptor) {
super(ctx, ast, descriptor);
Expand Down Expand Up @@ -99,6 +100,7 @@ public void onTag(int fieldNumber, FieldDescriptor fieldDescriptor, Object tagVa
GenericDescriptor descriptor = ctx.getDescriptorByTypeId(WrappedMessage.PROTOBUF_TYPE_ID);
TagHandler delegate = new RootJsonWriter(ctx, ast);
delegate.onStart(descriptor);
writtenElements |= lastToken() == JsonToken.LEFT_BRACKET;
delegate.onTag(fieldNumber, fieldDescriptor, tagValue);
delegate.onEnd();
}
Expand All @@ -111,6 +113,7 @@ protected boolean isRoot() {
}

private void writePrimitiveContainer(FieldDescriptor fieldDescriptor, Object tagValue) {
writtenElements = true;
pushToken(JsonToken.LEFT_BRACE);
pushToken(JsonTokenWriter.string(fieldDescriptor.getTypeName()));
pushToken(JsonToken.COLON);
Expand All @@ -120,6 +123,7 @@ private void writePrimitiveContainer(FieldDescriptor fieldDescriptor, Object tag

@Override
public void onEnd() {
pushToken(JsonToken.RIGHT_BRACKET);
if (writtenElements)
pushToken(JsonToken.RIGHT_BRACKET);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -31,6 +32,7 @@
import org.infinispan.protostream.annotations.impl.types.XTypeFactory;
import org.infinispan.protostream.containers.IndexedElementContainerAdapter;
import org.infinispan.protostream.containers.IterableElementContainerAdapter;
import org.infinispan.protostream.containers.MapElementContainerAdapter;
import org.infinispan.protostream.impl.Log;
import org.infinispan.protostream.processor.types.HasModelElement;

Expand Down Expand Up @@ -184,6 +186,13 @@ private void generateMessageMarshaller(ProtoMessageTypeMetadata pmtm) throws IOE
if (pmtm.isIndexedContainer()) {
elementType = pmtm.getAnnotatedClass().getGenericInterfaceParameterTypes(IndexedElementContainerAdapter.class)[1];
iw.printf(", %s<%s, %s>", IndexedElementContainerAdapter.class.getName(), pmtm.getJavaClassName(), elementType);
} else if (pmtm.isMapContainer()) {
String[] types = pmtm.getAnnotatedClass().getGenericInterfaceParameterTypes(MapElementContainerAdapter.class);
String map = pmtm.getJavaClassName();
String key = types[0];
String value = types[1];
elementType = Map.Entry.class.getCanonicalName();
iw.printf(", %s<%s, %s, %s<%s, %s>>", MapElementContainerAdapter.class.getName(), key, value, map, key, value);
} else if (pmtm.isIterableContainer()) {
elementType = pmtm.getAnnotatedClass().getGenericInterfaceParameterTypes(IterableElementContainerAdapter.class)[1];
iw.printf(", %s<%s, %s>", IterableElementContainerAdapter.class.getName(), pmtm.getJavaClassName(), elementType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.infinispan.protostream.types.java.collections.LinkedHashSetAdapter;
import org.infinispan.protostream.types.java.collections.LinkedListAdapter;
import org.infinispan.protostream.types.java.collections.TreeSetAdapter;
import org.infinispan.protostream.types.java.util.MapAdapters;
import org.infinispan.protostream.types.java.util.MapEntryAdapter;

/**
* Support for marshalling various {@link java.util.Collection} implementations and array or primitives.
Expand Down Expand Up @@ -56,7 +58,21 @@
BoxedFloatArrayAdapter.class,
BoxedDoubleArrayAdapter.class,
StringArrayAdapter.class,
ObjectArrayAdapter.class
ObjectArrayAdapter.class,

// maps
MapEntryAdapter.class,
MapAdapters.HashMapAdapter.class,
MapAdapters.ConcurrentHashMapAdapter.class,
MapAdapters.LinkedHashMapAdapter.class,
MapAdapters.TreeMapAdapter.class,
MapAdapters.WeakHashMapAdapter.class,
MapAdapters.IdentityHashMapAdapter.class,
MapAdapters.ConcurrentSkipListMapAdapter.class,
MapAdapters.HashtableAdapter.class,
MapAdapters.PropertiesAdapter.class,
MapAdapters.CollectionsEmptyMap.class,
MapAdapters.CollectionSingletonMap.class,
}
)
public interface CommonContainerTypes extends GeneratedSchema {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.infinispan.protostream.types.java.util;

import java.util.Iterator;
import java.util.Map;

import org.infinispan.protostream.containers.MapElementContainerAdapter;

/**
* Base adapter for {@link Map} implementations.
*
* @author José Bolina
* @since 6.0
*/
public abstract class AbstractMapAdapter<K, V, M extends Map<K, V>> implements MapElementContainerAdapter<K, V, M> {

abstract public M create(int size);

@Override
public Iterator<Map.Entry<K, V>> getElements(M container) {
return AbstractMapAdapter.toIterator(container);
}

public static <K, V> MapEntryWrapper<K, V> entry(Map.Entry<K, V> entry) {
if (entry instanceof AbstractMapAdapter.MapEntryWrapper) {
return (AbstractMapAdapter.MapEntryWrapper<K, V>) entry;
}
return new MapEntryWrapper<>(entry);
}

static <K, V> Iterator<Map.Entry<K, V>> toIterator(Map<K, V> map) {
Iterator<Map.Entry<K, V>> delegate = map.entrySet().iterator();
return new Iterator<>() {

@Override
public boolean hasNext() {
return delegate.hasNext();
}

@Override
public Map.Entry<K, V> next() {
Map.Entry<K, V> entry = delegate.next();
// Wrap entries in our MapEntryWrapper to provide a consistent serializable type
return MapEntryWrapper.create(entry);
}
};
}

@Override
public final int getNumElements(M container) {
return container.size();
}

/**
* Wrapper for {@link Map.Entry} instances.
* <p>
* This wrapper is necessary because there are several implementations of the {@link Map.Entry} interface
* across different map types (e.g., HashMap.Node, TreeMap.Entry, etc.). Instead of creating a separate
* ProtoStream adapter for each implementation, we wrap all entries in this single, consistent type that
* can be serialized uniformly.
*
* @param <K> the type of the entry key
* @param <V> the type of the entry value
*/
public static class MapEntryWrapper<K, V> implements Map.Entry<K, V> {
private final Map.Entry<K, V> entry;

private MapEntryWrapper(Map.Entry<K, V> entry) {
this.entry = entry;
}

public static <K, V> Map.Entry<K, V> create(K key, V value) {
return new MapEntryWrapper<>(Map.entry(key, value));
}

public static <K, V> Map.Entry<K, V> create(Map.Entry<K, V> entry) {
return new MapEntryWrapper<>(entry);
}

@Override
public K getKey() {
return entry.getKey();
}

@Override
public V getValue() {
return entry.getValue();
}

@Override
public V setValue(V v) {
return entry.setValue(v);
}
}
}
Loading