blob: 36c761614222683fd71b4764a826310b07e85926 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.johnzon.mapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import jakarta.json.JsonValue;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
@RunWith(Parameterized.class)
public class ObjectTypeTest {
@Parameterized.Parameter
public String accessMode;
@Parameterized.Parameters(name = "{0}")
public static Iterable<String> modes() {
return Arrays.asList("field", "method", "both", "strict-method");
}
@Test
public void testObjectConverterMapper() {
Mapper mapper = new MapperBuilder()
.setAccessModeName(accessMode)
.addObjectConverter(Dog.class, new TestWithTypeConverter())
.setAttributeOrder(String.CASE_INSENSITIVE_ORDER)
.build();
String expectedJsonString = getJson();
Mutt snoopie = getJavaObject();
String json = mapper.writeObjectAsString(snoopie);
Assert.assertNotNull(json);
Assert.assertEquals(expectedJsonString, json);
}
@Test
public void testReadWithObjectConverter() {
Mapper mapper = new MapperBuilder().setAccessModeName(accessMode)
.addObjectConverter(Dog.class, new TestWithTypeConverter())
.build();
Dog dog = mapper.readObject(getJson(), Dog.class);
Assert.assertNotNull(dog);
Assert.assertEquals(getJavaObject(), dog);
}
@Test
public void testWriteWithAdvancedObjectConverter() {
String expectedJson = "[{\"poodleName\":\"Poodle1\"},{\"poodleName\":\"Poodle2\"}]";
Mapper mapper = new MapperBuilder().setAccessModeName(accessMode)
.addObjectConverter(Poodle.class, new DBAccessPoodleConverter())
.build();
String json = mapper.writeObjectAsString(new ArrayList<Poodle>(DBAccessPoodleConverter.POODLES.values()));
Assert.assertNotNull(json);
Assert.assertEquals(expectedJson, json);
}
@Test
public void testReadWithAdvancedObjectConverter() {
Mapper mapper = new MapperBuilder().setAccessModeName(accessMode)
.addObjectConverter(Poodle.class, new DBAccessPoodleConverter())
.build();
List<Poodle> poodles = mapper.readObject("[{\"poodleName\":\"Poodle1\"},{\"poodleName\":\"Poodle2\"}]", new ParameterizedType() {
@Override
public Type[] getActualTypeArguments() {
return new Type[]{Poodle.class};
}
@Override
public Type getRawType() {
return List.class;
}
@Override
public Type getOwnerType() {
return null;
}
});
Assert.assertNotNull(poodles);
Assert.assertEquals(2, poodles.size());
for (Poodle poodle : poodles) {
Assert.assertEquals(DBAccessPoodleConverter.POODLES.get(poodle.getName()), poodle);
}
}
@Test
public void testGenericList() {
assumeFalse("field".equals(accessMode) /*we need setType*/);
final Multiple multiple = new Multiple();
Poodle poodle = new Poodle();
poodle.setHairCut(true);
Beagle beagle = new Beagle();
beagle.setColor("brown");
multiple.dogs = asList(poodle, beagle);
final Mapper mapper = new MapperBuilder()
.setAccessModeName(accessMode)
.setReadAttributeBeforeWrite(true)
.setAttributeOrder(new Comparator<String>() {
@Override
public int compare(final String o1, final String o2) {
return o1.compareTo(o2); // type before value
}
}).build();
final String json = "{\"dogs\":[" +
"{\"type\":\"org.apache.johnzon.mapper.ObjectTypeTest$Poodle\",\"value\":{\"hairCut\":true}}," +
"{\"type\":\"org.apache.johnzon.mapper.ObjectTypeTest$Beagle\",\"value\":{\"color\":\"brown\"}}]}";
assertEquals(json, mapper.writeObjectAsString(multiple));
final Multiple deser = mapper.readObject(json, Multiple.class);
assertEquals(2, deser.dogs.size());
assertTrue(Poodle.class.isInstance(deser.dogs.get(0)));
assertFalse(Beagle.class.isInstance(deser.dogs.get(0)));
assertTrue(Beagle.class.isInstance(deser.dogs.get(1)));
}
private Mutt getJavaObject() {
Poodle mum = new Poodle();
mum.setName("Rosa");
mum.setHairCut(true);
Beagle dad = new Beagle();
dad.setName("Gnarl");
Beagle grandPa = new Beagle();
grandPa.setName("Wuffi");
dad.setFather(grandPa);
Mutt snoopie = new Mutt();
snoopie.setName("Snoopie");
snoopie.setFather(dad);
snoopie.setMother(mum);
return snoopie;
}
private String getJson() {
return "{" +
"\"//javaType\":\"org.apache.johnzon.mapper.ObjectTypeTest$Mutt\"," +
"\"father\":{" +
"\"//javaType\":\"org.apache.johnzon.mapper.ObjectTypeTest$Beagle\"," +
"\"father\":{" +
"\"//javaType\":\"org.apache.johnzon.mapper.ObjectTypeTest$Beagle\"," +
"\"name\":\"Wuffi\"" +
"}," +
"\"name\":\"Gnarl\"}," +
"\"mother\":{" +
"\"//javaType\":\"org.apache.johnzon.mapper.ObjectTypeTest$Poodle\"," +
"\"hairCut\":true," +
"\"name\":\"Rosa\"}," +
"\"name\":\"Snoopie\"" +
"}";
}
public static class TestWithTypeConverter implements ObjectConverter.Codec<Dog> {
@Override
public void writeJson(Dog instance, MappingGenerator mappingGenerator) {
mappingGenerator.getJsonGenerator().write("//javaType", instance.getClass().getName());
mappingGenerator.writeObject(instance, mappingGenerator.getJsonGenerator());
}
@Override
public Dog fromJson(JsonValue jsonObject, Type targetType, MappingParser parser) {
String javaType = jsonObject.asJsonObject().getString("//javaType");
Class targetClass = javaType != null ? getSubClass(targetType, javaType) : (Class) targetType;
return parser.readObject(jsonObject, targetClass);
}
/**
* Helper method to check that javaType is really a subclass of targetType.
* Might get moved to a utility class
*/
private Class getSubClass(Type targetType, String javaType) {
if (javaType != null) {
// the following should get extracted in a utility class.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = targetType.getClass().getClassLoader();
}
try {
Class subClass = cl.loadClass(javaType);
Class targetClass = null;
if (targetType instanceof Class) {
targetClass = (Class) targetType;
} else if (targetType instanceof ParameterizedType) {
targetClass = (Class) ((ParameterizedType) targetType).getRawType();
}
if (targetClass != null && targetClass.isAssignableFrom(subClass)) {
return subClass;
}
} catch (ClassNotFoundException e) {
// continue without better class match
}
}
return (Class) targetType;
}
}
public static class Multiple {
@JohnzonConverter(TypeAdapter.class)
private List<Dog> dogs;
@JohnzonConverter(TypeAdapter.class)
public List<Dog> getDogs() {
return dogs;
}
@JohnzonConverter(TypeAdapter.class)
public void setDogs(final List<Dog> dogs) {
this.dogs = dogs;
}
}
public static class TypeAdapter implements TypeAwareAdapter<Dog, TypeInstance> {
@Override
public Dog to(final TypeInstance typeInstance) {
return typeInstance.value;
}
@Override
public TypeInstance from(final Dog dog) {
final TypeInstance typeInstance = new TypeInstance();
typeInstance.type = dog.getClass().getName();
typeInstance.value = dog;
return typeInstance;
}
@Override
public Type getTo() {
return TypeInstance.class;
}
@Override
public Type getFrom() {
return Dog.class;
}
}
public static class TypeInstance {
private String type;
private Dog value;
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
try {
this.value = Dog.class.cast(Thread.currentThread().getContextClassLoader().loadClass(type).newInstance());
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
}
public Dog getValue() {
return value;
}
public void setValue(final Dog value) {
this.value = value;
}
}
public static abstract class Dog {
private String name;
private Dog father;
private Dog mother;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dog getFather() {
return father;
}
public void setFather(Dog father) {
this.father = father;
}
public Dog getMother() {
return mother;
}
public void setMother(Dog mother) {
this.mother = mother;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Dog dog = (Dog) o;
if (name != null ? !name.equals(dog.name) : dog.name != null) {
return false;
}
if (father != null ? !father.equals(dog.father) : dog.father != null) {
return false;
}
return mother != null ? mother.equals(dog.mother) : dog.mother == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (father != null ? father.hashCode() : 0);
result = 31 * result + (mother != null ? mother.hashCode() : 0);
return result;
}
}
public static class Beagle extends Dog {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public static class Poodle extends Dog {
private boolean hairCut = false;
public boolean isHairCut() {
return hairCut;
}
public void setHairCut(boolean hairCut) {
this.hairCut = hairCut;
}
}
public static class Mutt extends Dog {
}
public static class DBAccessPoodleConverter implements ObjectConverter.Codec<Poodle> {
public static final String POODLE_1_NAME = "Poodle1";
public static final String POODLE_2_NAME = "Poodle2";
public static final Map<String, Poodle> POODLES = new LinkedHashMap<String, Poodle>(2);
static {
Poodle poodle1 = new Poodle();
poodle1.setHairCut(true);
poodle1.setName(POODLE_1_NAME);
Poodle poodle2 = new Poodle();
poodle2.setHairCut(false);
poodle2.setName(POODLE_2_NAME);
POODLES.put(poodle1.getName(), poodle1);
POODLES.put(poodle2.getName(), poodle2);
}
@Override
public void writeJson(Poodle instance, MappingGenerator jsonbGenerator) {
jsonbGenerator.getJsonGenerator().write("poodleName", instance.getName());
}
@Override
public Poodle fromJson(JsonValue jsonObject, Type targetType, MappingParser parser) {
return POODLES.get(jsonObject.asJsonObject().getString("poodleName"));
}
}
public static class DogOwner {
private String name;
private List<Dog> dogs;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Dog> getDogs() {
return dogs;
}
public void setDogs(List<Dog> dogs) {
this.dogs = dogs;
}
}
}