I use protostuff-runtime to serialize object graphs. Some of these objects have reference to Guava immutable collections, such as ImmutableList and ImmutableSet. Protostuff is unable to deserialize these collections out of the box, because it tries to construct an instance and then "add" elements to it from the inputStream (which fails, since the collections are immutable).
Do you know of any library / protostuff plugin that does that out of the box? If not, is there a best practice to do this myself?
I've investigated, and found that protostuff has a concept of "delegate", that lets you take control of the serialization for specific types. It seems to be the answer to my problem, but I can't seem to get it working.
Here is what I have right now:
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
/**
* This is the POJO I want to serialize. Note that the {#code strings} field refers to an {#link ImmutableList}.
*/
#Immutable
public class Foo {
public static final Schema<Foo> SCHEMA = RuntimeSchema.getSchema(Foo.class);
#Nonnull
private final ImmutableList<String> strings;
public Foo(ImmutableList<String> strings) {
this.strings = Preconditions.checkNotNull(strings);
}
#Nonnull
public ImmutableList<String> getStrings() {
return strings;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Foo) {
Foo that = (Foo) obj;
return this.strings.equals(that.strings);
}
return false;
}
#Override
public int hashCode() {
return strings.hashCode();
}
}
import com.dyuproject.protostuff.*;
import com.dyuproject.protostuff.runtime.Delegate;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.ArrayList;
public class ImmutableListDelegate implements Delegate<ImmutableList<?>> {
private static final Schema<ArrayList> LIST_SCHEMA = RuntimeSchema.getSchema(ArrayList.class);
#Override
public WireFormat.FieldType getFieldType() {
return WireFormat.FieldType.MESSAGE;
}
#Override
public ImmutableList<?> readFrom(Input input) throws IOException {
ArrayList<?> list = LIST_SCHEMA.newMessage();
input.mergeObject(list, LIST_SCHEMA);
return ImmutableList.copyOf(list);
}
#Override
public void writeTo(Output output, int number, ImmutableList<?> value, boolean repeated) throws IOException {
ArrayList<?> list = Lists.newArrayList(value);
output.writeObject(number, list, LIST_SCHEMA, repeated);
LIST_SCHEMA.writeTo(output, list);
}
#Override
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException {
throw new UnsupportedOperationException("TODO");
}
#Override
public Class<?> typeClass() {
return ImmutableList.class;
}
}
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.DefaultIdStrategy;
import com.dyuproject.protostuff.runtime.RuntimeEnv;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ImmutableListDelegateTest {
#Before
public void before() {
// registers the delegate
if (RuntimeEnv.ID_STRATEGY instanceof DefaultIdStrategy) {
((DefaultIdStrategy) RuntimeEnv.ID_STRATEGY).registerDelegate(new ImmutableListDelegate());
}
}
#Test
public void testDelegate() throws IOException {
Foo foo = new Foo(ImmutableList.of("foo"));
Assert.assertEquals(foo, serializeThenDeserialize(foo));
}
private Foo serializeThenDeserialize(Foo fooToSerialize) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ProtostuffIOUtil.writeDelimitedTo(out, fooToSerialize, Foo.SCHEMA, buffer());
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
Foo fooDeserialized = Foo.SCHEMA.newMessage();
ProtostuffIOUtil.mergeDelimitedFrom(in, fooDeserialized, Foo.SCHEMA, buffer());
return fooDeserialized;
}
private LinkedBuffer buffer() {
return LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
}
}
The test fails with the following exception, which seems to mean that my delegate only deserializes null values:
java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:191)
at com.google.common.collect.SingletonImmutableList.<init>(SingletonImmutableList.java:40)
at com.google.common.collect.ImmutableList.asImmutableList(ImmutableList.java:305)
at com.google.common.collect.ImmutableList.copyFromCollection(ImmutableList.java:314)
at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:253)
at test.ImmutableListDelegate.readFrom(ImmutableListDelegate.java:25)
at test.ImmutableListDelegate.readFrom(ImmutableListDelegate.java:12)
at com.dyuproject.protostuff.runtime.RuntimeUnsafeFieldFactory$19$1.mergeFrom(RuntimeUnsafeFieldFactory.java:1111)
at com.dyuproject.protostuff.runtime.MappedSchema.mergeFrom(MappedSchema.java:188)
at com.dyuproject.protostuff.IOUtil.mergeDelimitedFrom(IOUtil.java:109)
at com.dyuproject.protostuff.ProtostuffIOUtil.mergeDelimitedFrom(ProtostuffIOUtil.java:151)
at test.ImmutableListDelegateTest.serializeThenDeserialize(ImmutableListDelegateTest.java:38)
at test.ImmutableListDelegateTest.testDelegate(ImmutableListDelegateTest.java:30)
Is this the right approach? What am I missing?
This is not a duplicate of the What is a Null Pointer Exception, and how do I fix it? question, which makes no sense. The fact that I mentioned that an NPE is thrown when trying to use a Protostuff delegate to de-serialize immutable collections, doesn't mean that this duplicates the "What is a NPE?" question in any way, shape, or form.
Everything looks fine and the
java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:191)
at com.google.common.collect.SingletonImmutableList.<init>(SingletonImmutableList.java:40)
says that you're trying to put null into an ImmutableList, which is forbidden. To be sure, inspect you list just before the failing line. Make sure your input json doesn't look like [null].
Related
Problem I am trying to solve
I am trying to implement enum mapping for Hibernate. So far I have researched available options, and both the #Enumerated(EnumType.ORDINAL) and #Enumerated(EnumType.STRING) seemed inadequate for my needs. The #Enumerated(EnumType.ORDINAL) seems to be very error-prone, as a mere reordering of enum constants can mess the mapping up, and the #Enumerated(EnumType.STRING) does not suffice too, as the database I work with is already full of values to be mapped, and these values are not what I would like my enum constants to be named like (the values are foreign language strings / integers).
Currently, all these values are being mapped to String / Integer properties. At the same time the properties should only allow for a restricted sets of values (imagine meetingStatus property allowing for Strings: PLANNED, CANCELED, and DONE. Or another property allowing for a restricted set of Integer values: 1, 2, 3, 4, 5).
My idea was to replace the implementation with enums to improve the type safety of the code. A good example where the String / Integer implementation could cause errors is String method parameter representing such value - with String, anything goes there. Having an Enum parameter type on the other hand introduces compile time safety.
My best approach so far
The only solution that seemed to fulfill my needs was to implement custom javax.persistence.AttributeConverter with #Converter annotation for every enum. As my model would require quite a few enums, writing custom converter for each of them started to seem like a madness really quickly. So I searched for a generic solution to the problem -> how to write a generic converter for any type of enum. The following answer was of big help here: https://stackoverflow.com/a/23564597/7024402. The code example in the answer provides for somewhat generic implementation, yet for every enum there is still a separate converter class needed. The author of the answer also continues:
"The alternative would be to define a custom annotation, patch the JPA provider to recognize this annotation. That way, you could examine the field type as you build the mapping information and feed the necessary enum type into a purely generic converter."
And that's what I think I would be interested in. I could, unfortunately, not find any more information about that, and I would need a little more guidance to understand what needs to be done and how would it work with this approach.
Current Implementation
public interface PersistableEnum<T> {
T getValue();
}
public enum IntegerEnum implements PersistableEnum<Integer> {
ONE(1),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6);
private int value;
IntegerEnum(int value) {
this.value = value;
}
#Override
public Integer getValue() {
return value;
}
}
public abstract class PersistableEnumConverter<E extends PersistableEnum<T>, T> implements AttributeConverter<E, T> {
private Class<E> enumType;
public PersistableEnumConverter(Class<E> enumType) {
this.enumType = enumType;
}
#Override
public T convertToDatabaseColumn(E attribute) {
return attribute.getValue();
}
#Override
public E convertToEntityAttribute(T dbData) {
for (E enumConstant : enumType.getEnumConstants()) {
if (enumConstant.getValue().equals(dbData)) {
return enumConstant;
}
}
throw new EnumConversionException(enumType, dbData);
}
}
#Converter
public class IntegerEnumConverter extends PersistableEnumConverter<IntegerEnum, Integer> {
public IntegerEnumConverter() {
super(IntegerEnum.class);
}
}
This is how I was able to achieve the partially generic converter implementation.
GOAL: Getting rid of the need to create new converter class for every enum.
Luckily, you should not patch the hibernate for this.
You can declare an annotation like the following:
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.sql.Types;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({METHOD, FIELD})
#Retention(RUNTIME)
public #interface EnumConverter
{
Class<? extends PersistableEnum<?>> enumClass() default IntegerEnum.class;
int sqlType() default Types.INTEGER;
}
A hibernate user type like the following:
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.UserType;
public class PersistableEnumType implements UserType, DynamicParameterizedType
{
private int sqlType;
private Class<? extends PersistableEnum<?>> clazz;
#Override
public void setParameterValues(Properties parameters)
{
ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
EnumConverter converter = getEnumConverter(reader);
sqlType = converter.sqlType();
clazz = converter.enumClass();
}
private EnumConverter getEnumConverter(ParameterType reader)
{
for (Annotation annotation : reader.getAnnotationsMethod()){
if (annotation instanceof EnumConverter) {
return (EnumConverter) annotation;
}
}
throw new IllegalStateException("The PersistableEnumType should be used with #EnumConverter annotation.");
}
#Override
public int[] sqlTypes()
{
return new int[] {sqlType};
}
#Override
public Class<?> returnedClass()
{
return clazz;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException
{
return Objects.equals(x, y);
}
#Override
public int hashCode(Object x) throws HibernateException
{
return Objects.hashCode(x);
}
#Override
public Object nullSafeGet(ResultSet rs,
String[] names,
SharedSessionContractImplementor session,
Object owner) throws HibernateException, SQLException
{
Object val = null;
if (sqlType == Types.INTEGER) val = rs.getInt(names[0]);
if (sqlType == Types.VARCHAR) val = rs.getString(names[0]);
if (rs.wasNull()) return null;
for (PersistableEnum<?> pEnum : clazz.getEnumConstants())
{
if (Objects.equals(pEnum.getValue(), val)) return pEnum;
}
throw new IllegalArgumentException("Can not convert " + val + " to enum " + clazz.getName());
}
#Override
public void nullSafeSet(PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session) throws HibernateException, SQLException
{
if (value == null) {
st.setNull(index, sqlType);
}
else {
PersistableEnum<?> pEnum = (PersistableEnum<?>) value;
if (sqlType == Types.INTEGER) st.setInt(index, (Integer) pEnum.getValue());
if (sqlType == Types.VARCHAR) st.setString(index, (String) pEnum.getValue());
}
}
#Override
public Object deepCopy(Object value) throws HibernateException
{
return value;
}
#Override
public boolean isMutable()
{
return false;
}
#Override
public Serializable disassemble(Object value) throws HibernateException
{
return Objects.toString(value);
}
#Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return cached;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return original;
}
}
And then, you can use it:
import org.hibernate.annotations.Type;
#Entity
#Table(name="TST_DATA")
public class TestData
{
...
#EnumConverter(enumClass = IntegerEnum.class, sqlType = Types.INTEGER)
#Type(type = "com.example.converter.PersistableEnumType")
#Column(name="INT_VAL")
public IntegerEnum getIntValue()
...
#EnumConverter(enumClass = StringEnum.class, sqlType = Types.VARCHAR)
#Type(type = "com.example.converter.PersistableEnumType")
#Column(name="STR_VAL")
public StringEnum getStrValue()
...
}
See also the chapter 5.3.3 Extending Hibernate with UserTypes at the excellent book "Java Persistence with Hibernate" by Bauer, King, Gregory.
Simplifying:
import com.pismo.apirest.mvc.enums.OperationType;
import com.pismo.apirest.mvc.enums.support.PersistableEnum;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
#SuppressWarnings("unused")
public interface EnumsConverters {
#RequiredArgsConstructor
abstract class AbstractPersistableEnumConverter<E extends Enum<E> & PersistableEnum<I>, I> implements AttributeConverter<E, I> {
private final E[] enumConstants;
public AbstractPersistableEnumConverter(#NonNull Class<E> enumType) {
enumConstants = enumType.getEnumConstants();
}
#Override
public I convertToDatabaseColumn(E attribute) {
return Objects.isNull(attribute) ? null : attribute.getId();
}
#Override
public E convertToEntityAttribute(I dbData) {
return fromId(dbData, enumConstants);
}
public E fromId(I idValue) {
return fromId(idValue, enumConstants);
}
public static <E extends Enum<E> & PersistableEnum<I>, I> E fromId(I idValue, E[] enumConstants) {
return Objects.isNull(idValue) ? null : Stream.of(enumConstants)
.filter(e -> e.getId().equals(idValue))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(
String.format("Does not exist %s with ID: %s", enumConstants[0].getClass().getSimpleName(), idValue)));
}
}
#Converter(autoApply = true)
class OperationTypeConverter extends AbstractPersistableEnumConverter<OperationType, Integer> {
public OperationTypeConverter() {
super(OperationType.class);
}
}
}
I tried 1000 times create something same.
Generate converter for each enum on the fly - not problem, but then they will be have same class. Main problem there: org.hibernate.boot.internal.MetadataBuilderImpl#applyAttributeConverter(java.lang.Class<? extends javax.persistence.AttributeConverter>, boolean).
If converter already registered we got exception.
public void addAttributeConverterInfo(AttributeConverterInfo info) {
if ( this.attributeConverterInfoMap == null ) {
this.attributeConverterInfoMap = new HashMap<>();
}
final Object old = this.attributeConverterInfoMap.put( info.getConverterClass(), info );
if ( old != null ) {
throw new AssertionFailure(
String.format(
"AttributeConverter class [%s] registered multiple times",
info.getConverterClass()
)
);
}
}
Perhaps we can change org.hibernate.boot.internal.BootstrapContext Impl, but I'm sure it's create too complex and non-flexible code.
Hi I am new to Javers .
I have an Entity , for which I am implementing a custom comparator in order to compare using Javers .
My entity :-
package com.devyansh.entity;
import java.util.List;
import org.javers.core.metamodel.annotation.Entity;
#Entity
public class Ent {
String value;
List<String> values;
public Ent(String value, List<String> values) {
super();
this.value = value;
this.values = values;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
}
Below I am registering the new Comparator :-
public void comp(){
Javers javers = JaversBuilder.javers().registerCustomComparator(new FunnyStringComparator(), Ent.class).build();
Diff diff = javers.compare(new Ent("aaa", new ArrayList<String>()), new Ent("aaa",new ArrayList<String>()));
System.out.println(diff.getChanges());
}
My Comparator Implementation :-
package com.devyansh.javers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import com.devyansh.entity.Ent;
import com.google.common.collect.Sets;
import org.javers.core.diff.changetype.PropertyChangeMetadata;
import org.javers.core.diff.changetype.container.ContainerElementChange;
import org.javers.core.diff.changetype.container.SetChange;
import org.javers.core.diff.changetype.container.ValueAdded;
import org.javers.core.diff.changetype.container.ValueRemoved;
import org.javers.core.diff.custom.CustomPropertyComparator;
import org.javers.core.metamodel.property.Property;
public class FunnyStringComparator implements CustomPropertyComparator<Ent, SetChange> {
#Override
public Optional<SetChange> compare(Ent left, Ent right, PropertyChangeMetadata metadata,
Property property) {
// TODO Auto-generated method stub
System.out.println("Hello!!!!!");
return null ;
}
#Override
public boolean equals(Ent a, Ent b) {
// TODO Auto-generated method stub
return false;
}
}
PROBLEM AREA :-
WHENEVER I TRY TO COMPARE USING THE BELOW LINE :-
Diff diff = javers.compare(new Ent("aaa", new ArrayList()), new Ent("aaa",new ArrayList()));
I get the following error :-
MANAGED_CLASS_MAPPING_ERROR: given javaClass 'class com.devyansh.entity.Ent' is mapped to CustomType, expected ManagedType
Can anybody please let me know what I am doing wrong ??? I tried to debug the Javers api , below function is called :-
JaversType get(Type javaType) {
return mappedTypes.get(javaType.toString());
}
here mappedTypes is a concurrentHashMap , and it reutrns a customType for user defined entities ?
How can we resolve this ?
Currently, CustomTypes objects are supported only when they are inside ObjectGraphs.
When CustomTypes objects are passed directly to Javers.compare(), Javers fails on genertating GlobalId for them.
I have created the issue for this case https://github.com/javers/javers/issues/882, contributions are welcome.
Using gson, I use this cumbersome approach to make sure a required property has a desired value:
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class ScratchSpace {
public static void main(String[] args) {
// create json object from source data - in my real code, this is sourced externally
JsonObject json = new JsonParser().parse("{ \"product\": \"foobar\"}").getAsJsonObject();
// does this object have a key called product, which is a string, and equal to our expected value?
boolean correctProduct = false;
if (json.has("product")) {
JsonElement productElement = json.get("product");
if (productElement.isJsonPrimitive()) {
String product = productElement.getAsString();
if ("foobar".equals(product)) {
correctProduct = true;
}
}
}
System.out.println("correctProduct = " + correctProduct);
}
}
I'm almost certain I'm doing this suboptimally. Is there a simple, readable, short-ish one-liner to achieve the same?
Edit: if possible, I'd like to keep using gson.
Using java.util.Optional, the following works:
final boolean correctProduct = Optional.ofNullable(json.get("product"))
.filter(JsonPrimitive.class::isInstance)
.map(JsonPrimitive.class::cast)
.map(JsonPrimitive::getAsString)
.filter("foobar"::equals)
.isPresent();
You can write a custom deserializer like this, register it, and use fromJson method to obtain object directly from json string. In this way, you can return null or throw exception in deserializer if the json string is not in expected format.
Note that you don't have to set each field seperately. After performing custom checks, you can use default deserialization from context.
EDIT: If you want to obtain just true/false instead of the complete object, then you can write a MyBoolean class, holding a simple boolean value and use fromJson method to deserialize to MyBoolean class. The custom deserializer will perform only the desired checks and set the content of MyBoolean instance appropriately. Furthermore, (I guess) if you extend this MyBoolean from Boolean, you can use it as boolean too.
EDIT 2: I didn't have to time to include a sample code before. Here is what I suggest:
package io.ram.ram;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
public class Tester {
public static class MyBoolean {
private boolean value;
public void set(boolean value) {
this.value = value;
}
public boolean get() {
return value;
}
}
public static class MyAdapter implements JsonDeserializer<MyBoolean> {
public MyBoolean deserialize(JsonElement json, Type type, JsonDeserializationContext context)
throws JsonParseException {
MyBoolean result = new MyBoolean();
result.set(false);
try {
result.set(json.getAsJsonObject().get("product").getAsString().equals("foobar"));
} catch (Exception e) {
}
return result;
}
}
public static void main(String[] args) {
Gson gson = new GsonBuilder().registerTypeAdapter(MyBoolean.class, new MyAdapter()).create();
System.out.println(gson.fromJson("{\"product\": \"foobar\"}", MyBoolean.class).get());
System.out.println(gson.fromJson("{\"product\": \"foobaz\"}", MyBoolean.class).get());
}
}
As I said, after you register a type adapter for custom serialization, you can achieve what you want with a single line. (Note: Boolean class is final, so we cannot extend it. Sorry for this wrong information.)
You can parametrize MyAdapter for strings "product" and "foobar" of course, thus you don't have to create such classes for every possible cases.
I know you said GSON, but the pojo based approach jackson offers makes what you want to do just too convenient to not to post:
Simple pojo:
public class FooPojo {
#JsonProperty
private String product;
public String getProduct() {
return product;
}
public void setProduct(String product) {
this.product = product;
}
public boolean isProductEqualTo(String check) {
return product.equals(check);
}
}
Parse and check:
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
FooPojo fooPojo = objectMapper.readValue("{ \"product\": \"foobar\"}", FooPojo.class);
System.out.println(fooPojo.isProductEqualTo("foobar"));
}
I am new to parameterized feature of JUnit 4.x and having a problem. My parameterized test consists of 3 integer arrays and I am having difficulty of how to declare them. What I have below generates run-time error:
testGeneral[0] caused an ERROR: argument type mismatch
argument type mismatch
java.lang.IllegalArgumentException
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
testGeneral[1] caused an ERROR: argument type mismatch
argument type mismatch
java.lang.IllegalArgumentException
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
Here is my code:
#RunWith(Parameterized.class)
public class MyArrayTest {
private Integer[] inputList1;
private Integer[] inputList2;
private Integer[] expectedList;
public MyArrayTest(Integer[] li1, Integer[] li2, Integer[] expected) {
// ========> Runtime error happens here. <=========
this.inputList1 = li1;
this.inputList2 = li2;
this.expectedList = expected;
}
#Parameterized.Parameters
public static Collection testCases() {
return Arrays.asList(new Object[][][] {
{{1,1,1}, {2,2,2}, {3,3,3}},
{{2,2,2}, {3,3,3}, {4,4,4}}
});
}
#Test
public void testGeneral() {
// Do some test with this.inputList1, this.inputList2,
// and verify with this.expectedList
// I am not even getting here yet.
}
}
I appreciate your help to correctly passing the three arrays to my tests.
The reason why it is failing is because your test expects Integer arrays whereas you are passing Object type. So you are expanding the type. Try this:
#Parameterized.Parameters
public static Collection testCases() {
return Arrays.asList(new Integer[][][] {
{{1,1,1}, {2,2,2}, {3,3,3}},
{{2,2,2}, {3,3,3}, {4,4,4}}
});
}
This solution uses junitparams, implements junitparams.converters.Converter and parses list of long values as parameters.
package example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import junitparams.converters.ConversionFailedException;
import junitparams.converters.Converter;
import junitparams.converters.Param;
#RunWith(JUnitParamsRunner.class)
public class LongArrayParameterTest {
#Parameters({ "0|10", "1|10;20;30" })
#Test
public void test(final long otherParameter, #LongArrayParam final long[] expected) {
System.out.println(Arrays.stream(expected).boxed().map(l -> Long.toString(l)).collect(Collectors.toList()));
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
#Param(converter = LongArrayConverter.class)
public #interface LongArrayParam {
}
public static class LongArrayConverter implements Converter<LongArrayParam, long[]> {
#Override
public void initialize(final LongArrayParam annotation) {
}
#Override
public long[] convert(final Object param) throws ConversionFailedException {
final String str = (String) param;
final String[] longStrings = str.split(";");
return Arrays.stream(longStrings).mapToLong(s -> Long.parseLong(s)).toArray();
}
}
}
This parser does not support empty list.
I am trying to serialize some objects of class InstitutionResultView which is basically wrapper to guava's TreeMultimap:
import java.io.Serializable;
import java.text.Collator;
import java.util.Comparator;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.Maps.EntryTransformer;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
public class InstitutionResultView implements Serializable {
private static final long serialVersionUID = -8110992296090587657L;
private final SortedSetMultimap<String, Institution> nameToInstitutionsMapping = TreeMultimap.create(
Ordering.from(StringComparators.AS_IS), // insertion order
Ordering.natural() // <----- this works when serializing object
// Ordering.from(Collator.getInstance()) // <----- when used instead previous line gives an exception when serializing
.nullsFirst().onResultOf(StringInstitutionFunctions.BY_NAME) // sort by name
.compound(
Ordering.natural().nullsFirst().onResultOf(IntegerInstitutionFunctions.BY_ID) // sort by id
));
public SortedSetMultimap<String, Institution> institutions() {
return nameToInstitutionsMapping;
}
public void setInstitutions(final Multimap<String, Institution> institutions) {
this.nameToInstitutionsMapping.clear();
this.nameToInstitutionsMapping.putAll(institutions);
}
#Override
public String toString() {
return Objects.toStringHelper(this)
.add("nameToInstitutionsMapping", Multimaps.transformEntries(nameToInstitutionsMapping, EntryTransformers.TO_NAME_WITH_CATEGORY))
.toString();
}
During serialization I get exception:
java.io.NotSerializableException: java.text.RuleBasedCollator at
java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
at
...
com.google.common.collect.TreeMultimap.writeObject(TreeMultimap.java:180)
...
I found this bug from Sun's bug database which covers very similar case to mine, but it doesn't point to any resolution. I tried adding transient Collator instance field:
private transient Collator collatorInstance = Collator.getInstance();
and using it as Ordering.from(collatorInstance) but still it doesn't work.
I'd be glad if anybody could give me some idea what to do to solve this problem.
EDIT - That's what worked for me (thanks to #Puce and his answer):
class CollatorWrapper implements Comparator<String>, Serializable {
private static final long serialVersionUID = 1L;
private transient Collator collatorInstance;
public CollatorWrapper() {
super();
initCollatorInstance();
}
#Override
public int compare(final String o1, final String o2) {
return collatorInstance.compare(o1, o2);
}
private void initCollatorInstance() {
collatorInstance = Collator.getInstance();
}
private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
initCollatorInstance();
}
}
I think you might be able to write a wrapper, which
implements Comparator
implements Serialzable (and the necessary methods such as readObject, writeObject)
wraps a transient Collator
remembers the Locale (if you need that)