Skip to content

Commit

Permalink
Merge pull request #539 from eclipse/jnosql-535
Browse files Browse the repository at this point in the history
Create Map type to mapper field
  • Loading branch information
otaviojava authored Aug 8, 2024
2 parents 3e99d08 + 7343c3d commit 3a64137
Show file tree
Hide file tree
Showing 36 changed files with 1,138 additions and 64 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version

- Fix the `Orderby` annotation in the Repository
- Make the JDQL return the correct type when the select is by field
- Invalid deserialization of maps with generic values
- Make sure at the serialization to the field, the API does not return any communication layer, but standard Java types

=== Removed

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
* Copyright (c) 2024 Contributors to the Eclipse Foundation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
Expand Down Expand Up @@ -88,22 +88,46 @@ private <K, V> Map<K, V> getMap(Class<K> keyClass, Class<V> valueClass, Object v
return map;
}
}
if (Entry.class.isInstance(value)) {
Map<K, V> map = new HashMap<>();
convertEntryToMap(value, map);
return map;
}
throw new UnsupportedOperationException("There is not supported convert" + value + " a not Map type.");
}

private <K, V> void convertEntryToMap(Object value, Map<K, V> map) {
Entry entry = Entry.class.cast(value);
Entry entry = (Entry) value;
Object entryValue = entry.value().get();
if (entryValue instanceof Entry) {
Map<String, Object> subMap = new HashMap<>();
Entry subEntry = Entry.class.cast(entryValue);
convertEntryToMap(subEntry, subMap);
map.put((K) entry.name(), (V) subMap);
if (entryValue instanceof Entry subEntry) {
feedEntryValue(map, subEntry, entry);
} else if(entryValue instanceof Iterable<?> iterable) {
feedIterable(map, iterable, entry);
} else {
map.put((K) entry.name(), (V) entryValue);
}
}

private <K, V> void feedIterable(Map<K, V> map, Iterable<?> iterable, Entry entry) {
List<Object> collection = new ArrayList<>();
iterable.forEach(collection::add);
if (collection.isEmpty()) {
map.put((K) entry.name(), (V) Collections.emptyList());
} else if (collection.stream().allMatch(Entry.class::isInstance)) {
Map<K, V> subMap = new HashMap<>();
collection.forEach(e -> convertEntryToMap(e, subMap));
map.put((K) entry.name(), (V) subMap);
} else {
map.put((K) entry.name(), (V) collection);
}
}

private <K, V> void feedEntryValue(Map<K, V> map, Entry subEntry, Entry entry) {
Map<String, Object> subMap = new HashMap<>();
convertEntryToMap(subEntry, subMap);
map.put((K) entry.name(), (V) subMap);
}

private <K, V> Map<K, V> convertToMap(Class<K> keyClass, Class<V> valueClass, Object value) {
Map mapValue = Map.class.cast(value);
return (Map<K, V>) mapValue.keySet().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,67 @@ void shouldConvertSubEntryToMap() {
});
}

@SuppressWarnings("unchecked")
@Test
void shouldConvertUsingObject() {
Entry subEntry = new EntryTest("key", Value.of("value"));
Entry entry = new EntryTest("key", Value.of(subEntry));

Map<String, Object> map = referenceReader.convert(new TypeReference<>() {
}, Collections.singletonList(entry));

Map<String, String> subMap = (Map<String, String>) map.get("key");

assertSoftly(softly -> {
softly.assertThat(map).as("Map is correctly converted").hasSize(1).contains(entry("key", Map.of("key", "value")));
softly.assertThat(subMap).as("SubMap is correctly converted").hasSize(1).contains(entry("key", "value"));
});
}

@Test
void shouldConvertEntry() {
Entry entry = new EntryTest("key", Value.of("value"));

Map<String, String> map = referenceReader.convert(new TypeReference<>() {
}, entry);

assertSoftly(softly -> softly.assertThat(map).as("Map is correctly converted").hasSize(1).contains(entry("key", "value")));
}

@Test
void shouldConvertEntryWithSubEntry() {
Entry subEntry = new EntryTest("key", Value.of("value"));
Entry entry = new EntryTest("key", Value.of(subEntry));

Map<String, Object> map = referenceReader.convert(new TypeReference<>() {
}, Collections.singletonList(entry));

Map<String, String> subMap = (Map<String, String>) map.get("key");

assertSoftly(softly -> {
softly.assertThat(map).as("Map is correctly converted").hasSize(1).contains(entry("key", Map.of("key", "value")));
softly.assertThat(subMap).as("SubMap is correctly converted").hasSize(1).contains(entry("key", "value"));
});
}

@Test
void shouldConvertEntryWithSubEntryAsList() {
Entry subEntry = new EntryTest("key3", Value.of("value"));
Entry subEntry2 = new EntryTest("key2", Value.of("value2"));
Entry entry = new EntryTest("key", Value.of(List.of(subEntry, subEntry2)));

Map<String, Object> map = referenceReader.convert(new TypeReference<>() {
}, Collections.singletonList(entry));

Map<String, Object> subMap = (Map<String, Object>) map.get("key");

assertSoftly(softly -> {
softly.assertThat(map).as("Map is correctly converted").hasSize(1).containsKey("key");
softly.assertThat(subMap).as("SubMap is correctly converted").hasSize(2).contains(entry("key2", "value2"),
entry("key3", "value"));
});
}

static Stream<Arguments> compatibleTypeReferences() {
return Stream.of(
arguments(new TypeReference<Map<String, String>>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*
* @see ParameterMetaData
*/
public interface GenericFieldMetadata extends FieldMetadata {
public interface CollectionFieldMetadata extends FieldMetadata {
/**
* Returns true if it is the element has either Entity or Embeddable annotations
* @return true if the element has Entity or Embeddable annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
import java.util.Collection;

/**
* The GenericParameterMetaData interface extends the {@link ParameterMetaData} interface and provides
* additional information about a parameter with a generic type.
* The CollectionParameterMetaData interface extends the {@link ParameterMetaData} interface and provides
* additional information about a parameter with a generic type for collections.
*
* <p>This interface is used to represent parameters of generic types, where the type may be a collection
* or array containing elements of a specific type.</p>
*
* @see ParameterMetaData
*/
public interface GenericParameterMetaData extends ParameterMetaData {
public interface CollectionParameterMetaData extends ParameterMetaData {

/**
* Returns the {@link Class} representing the type of elements in the collection or array.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
*
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
*
* Otavio Santana
*/
package org.eclipse.jnosql.mapping.metadata;

/**
* The MapFieldMetadata interface extends the {@link FieldMetadata} interface and provides
* additional information about a parameter with a map type.
*
* <p>This interface is used to represent parameters of map types, where the type may be a map
* containing keys and values of specific types.</p>
*
* @see ParameterMetaData
*/
public interface MapFieldMetadata extends FieldMetadata {
/**
* Returns true if either the key or value has Entity or Embeddable annotations
* @return true if the key or value has Entity or Embeddable annotations
*/
boolean isEmbeddable();

/**
* Returns the {@link Class} representing the type of keys in the map.
*
* @return the key type of the map parameter
*/
Class<?> keyType();

/**
* Returns the {@link Class} representing the type of values in the map.
*
* @return the value type of the map parameter
*/
Class<?> valueType();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
*
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
*
* Otavio Santana
*/
package org.eclipse.jnosql.mapping.metadata;


import org.eclipse.jnosql.communication.Value;

/**
* The MapParameterMetaData interface extends the {@link ParameterMetaData} interface and provides
* additional information about a parameter with a generic type for maps.
*
* <p>This interface is used to represent parameters of generic types, where the type may be a map
* containing keys and values of specific types.</p>
*
* @see ParameterMetaData
*/
public interface MapParameterMetaData extends ParameterMetaData {

/**
* Returns the {@link Class} representing the type of keys in the map.
*
* @return the key type of the map parameter
*/
Class<?> keyType();

/**
* Returns the {@link Class} representing the type of values in the map.
*
* @return the value type of the map parameter
*/
Class<?> valueType();

/**
* Returns the object from the field type.
*
* @param value the value {@link Value}
* @return the instance from the field type
*/
Object value(Value value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import jakarta.nosql.AttributeConverter;
import jakarta.nosql.Embeddable;
import org.eclipse.jnosql.mapping.metadata.CollectionSupplier;
import org.eclipse.jnosql.mapping.metadata.GenericFieldMetadata;
import org.eclipse.jnosql.mapping.metadata.CollectionFieldMetadata;
import org.eclipse.jnosql.mapping.metadata.MappingType;

import java.lang.reflect.Field;
Expand All @@ -30,13 +30,13 @@
import java.util.Objects;
import java.util.ServiceLoader;

final class DefaultGenericFieldMetadata extends AbstractFieldMetadata implements GenericFieldMetadata {
final class DefaultCollectionFieldMetadata extends AbstractFieldMetadata implements CollectionFieldMetadata {

private final TypeSupplier<?> typeSupplier;

DefaultGenericFieldMetadata(MappingType type, Field field, String name, TypeSupplier<?> typeSupplier,
Class<? extends AttributeConverter<?, ?>> converter,
FieldReader reader, FieldWriter writer, String udt) {
DefaultCollectionFieldMetadata(MappingType type, Field field, String name, TypeSupplier<?> typeSupplier,
Class<? extends AttributeConverter<?, ?>> converter,
FieldReader reader, FieldWriter writer, String udt) {
super(type, field, name, converter, reader, writer, udt);
this.typeSupplier = typeSupplier;
}
Expand All @@ -63,7 +63,7 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
DefaultGenericFieldMetadata that = (DefaultGenericFieldMetadata) o;
DefaultCollectionFieldMetadata that = (DefaultCollectionFieldMetadata) o;
return mappingType == that.mappingType &&
Objects.equals(field, that.field) &&
Objects.equals(typeSupplier, that.typeSupplier) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,31 @@
import org.eclipse.jnosql.communication.TypeSupplier;
import jakarta.nosql.AttributeConverter;
import org.eclipse.jnosql.mapping.metadata.CollectionSupplier;
import org.eclipse.jnosql.mapping.metadata.GenericParameterMetaData;
import org.eclipse.jnosql.mapping.metadata.CollectionParameterMetaData;
import org.eclipse.jnosql.mapping.metadata.MappingType;

import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.ServiceLoader;

class DefaultGenericParameterMetaData extends DefaultParameterMetaData implements GenericParameterMetaData {
class DefaultCollectionParameterMetaData extends DefaultParameterMetaData implements CollectionParameterMetaData {


private final TypeSupplier<?> typeSupplier;

DefaultGenericParameterMetaData(String name, Class<?> type, boolean id,
Class<? extends AttributeConverter<?, ?>> converter,
MappingType mappingType, TypeSupplier<?> typeSupplier) {
DefaultCollectionParameterMetaData(String name, Class<?> type, boolean id,
Class<? extends AttributeConverter<?, ?>> converter,
MappingType mappingType, TypeSupplier<?> typeSupplier) {
super(name, type, id, converter, mappingType);
this.typeSupplier = typeSupplier;
}

@Override
public Class<?> elementType() {
return (Class<?>) ((ParameterizedType) typeSupplier.get()).getActualTypeArguments()[0];
}

@Override
public Collection<?> collectionInstance() {
Class<?> type = type();
final CollectionSupplier supplier = ServiceLoader.load(CollectionSupplier.class)
Expand Down
Loading

0 comments on commit 3a64137

Please sign in to comment.