Skip to content

More frugal enumset #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 13, 2023
Merged
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
101 changes: 101 additions & 0 deletions src/main/java/com/cedarsoftware/util/io/JsonWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,10 @@ public void writeImpl(Object obj, boolean showType, boolean allowRef, boolean al
{
writeArray(obj, showType);
}
else if (obj instanceof EnumSet)
{
writeEnumSet((EnumSet)obj);
}
else if (obj instanceof Collection)
{
writeCollection((Collection) obj, showType);
Expand Down Expand Up @@ -2207,6 +2211,103 @@ else if (neverShowType && MetaUtils.isPrimitive(o.getClass()))
}
}

public void writeEnumSet(final EnumSet<?> enumSet) throws IOException
{
out.write('{');
tabIn();

boolean referenced = objsReferenced.containsKey(enumSet);
if (referenced)
{
writeId(getId(enumSet));
out.write(',');
newLine();
}

writeJsonUtf8String("@enum", out);
out.write(':');

Enum<? extends Enum<?>> ee = null;
if (!enumSet.isEmpty())
{
ee = enumSet.iterator().next();
}
else
{
EnumSet<? extends Enum<?>> complement = EnumSet.complementOf(enumSet);
if (!complement.isEmpty())
{
ee = complement.iterator().next();
}
}

Field elementTypeField = MetaUtils.getField(EnumSet.class, "elementType");
Class<?> elementType = (Class<?>) getValueByReflect(enumSet, elementTypeField);
if ( elementType != null)
{
// nice we got the right to sneak into
}
else if (ee == null)
{
elementType = MetaUtils.Dumpty.class;
}
else
{
elementType = ee.getClass();
}
writeJsonUtf8String(elementType.getName(), out);

if (!enumSet.isEmpty()) {
Map<String, Field> mapOfFields = MetaUtils.getDeepDeclaredFields(elementType);
//Field[] enumFields = elementType.getDeclaredFields();
int enumFieldsCount = mapOfFields.size();

out.write(",");
newLine();

writeJsonUtf8String("@items", out);
out.write(":[");
if (enumFieldsCount > 2)
{
newLine();
}

boolean firstInSet = true;
for (Enum e : enumSet) {
if (!firstInSet)
{
out.write(",");
if (enumFieldsCount > 2)
{
newLine();
}
}
firstInSet = false;

if (enumFieldsCount <= 2)
{
writeJsonUtf8String(e.name(), out);
}
else
{
boolean firstInEntry = true;
out.write('{');
for (Field f : mapOfFields.values())
{
firstInEntry = writeField(e, firstInEntry, f.getName(), f, false);
}
out.write('}');
}
}

out.write("]");
}


tabOut();
out.write('}');
}

/**
* @param obj Object to be written in JSON format
* @param showType boolean true means show the "@type" field, false
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/cedarsoftware/util/io/MetaUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
*/
public class MetaUtils
{
public enum Dumpty {}

private MetaUtils () {}
private static final Map<Class, Map<String, Field>> classMetaCache = new ConcurrentHashMap<>();
private static final Set<Class> prims = new HashSet<>();
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/com/cedarsoftware/util/io/ObjectResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,13 @@ protected void traverseCollection(final Deque<JsonObject<String, Object>> stack,
}
return;
}

Class mayEnumClass = null;
String mayEnumClasName = (String)jsonObj.get("@enum");
if (mayEnumClasName != null) {
mayEnumClass = MetaUtils.classForName(mayEnumClasName, classLoader);
}

final boolean isImmutable = className != null && className.startsWith("java.util.Immutable");
final Collection col = isImmutable ? new ArrayList() : (Collection) jsonObj.target;
final boolean isList = col instanceof List;
Expand All @@ -436,7 +443,10 @@ else if ((special = readIfMatching(element, null, stack)) != null)
}
else if (element instanceof String || element instanceof Boolean || element instanceof Double || element instanceof Long)
{ // Allow Strings, Booleans, Longs, and Doubles to be "inline" without Java object decoration (@id, @type, etc.)
col.add(element);
if (mayEnumClass == null)
col.add(element);
else
col.add(Enum.valueOf(mayEnumClass, (String)element));
}
else if (element.getClass().isArray())
{
Expand Down
72 changes: 64 additions & 8 deletions src/main/java/com/cedarsoftware/util/io/Resolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -313,13 +313,21 @@ protected Object createJavaObjectInstance(Class clazz, JsonObject jsonObj)
String type = jsonObj.type;

// We can't set values to an Object, so well try to use the contained type instead
if ("java.lang.Object".equals(type))
if ("java.lang.Object".equals(type))
{
Object value = jsonObj.get("value");
if (jsonObj.keySet().size() == 1 && value != null)
Object value = jsonObj.get("value");
if (jsonObj.keySet().size() == 1 && value != null)
{
type = value.getClass().getName();
}
type = value.getClass().getName();
}
}
if (type == null)
{
Object mayEnumSpecial = jsonObj.get("@enum");
if (mayEnumSpecial instanceof String)
{
type = "java.util.EnumSet";
}
}

Object mate;
Expand Down Expand Up @@ -380,7 +388,7 @@ else if (Enum.class.isAssignableFrom(c)) // anonymous subclass of an enum
}
else if (EnumSet.class.isAssignableFrom(c))
{
mate = getEnumSet(c, jsonObj);
mate = extractEnumSet(c, jsonObj);
}
else if ((mate = coerceCertainTypes(c.getName())) != null)
{ // if coerceCertainTypes() returns non-null, it did the work
Expand Down Expand Up @@ -426,7 +434,7 @@ else if (Enum.class.isAssignableFrom(clazz)) // anonymous subclass of an enum
}
else if (EnumSet.class.isAssignableFrom(clazz)) // anonymous subclass of an enum
{
mate = getEnumSet(clazz, jsonObj);
mate = extractEnumSet(clazz, jsonObj);
}
else if ((mate = coerceCertainTypes(clazz.getName())) != null)
{ // if coerceCertainTypes() returns non-null, it did the work
Expand Down Expand Up @@ -533,7 +541,7 @@ private static enum OneEnum {
/*
/* java 17 don't allow to call reflect on internal java api like EnumSet's implement, so need to create like this
*/
private Object getEmptyEnumSet() {
private EnumSet getEmptyEnumSet() {
return EnumSet.noneOf(OneEnum.class);
}

Expand Down Expand Up @@ -568,6 +576,54 @@ private Object getEnumSet(Class c, JsonObject<String, Object> jsonObj)
return enumSet;
}

protected EnumSet<?> extractEnumSet(Class c, JsonObject<String, Object> jsonObj)
{
String enumClassName = (String) jsonObj.get("@enum");
Class enumClass = enumClassName == null ? null
: MetaUtils.classForName(enumClassName, reader.getClassLoader());
Object[] items = jsonObj.getArray();
if (items == null || items.length == 0)
{
if (enumClass != null)
{
return EnumSet.noneOf(enumClass);
}
else
{
return EnumSet.noneOf(MetaUtils.Dumpty.class);
}
}
else if (enumClass == null)
{
throw new JsonIoException("Could not figure out Enum of the not empty set " + jsonObj);
}

EnumSet enumSet = null;
for (Object item : items)
{
Enum enumItem;
if (item instanceof String)
{
enumItem = Enum.valueOf(enumClass, (String)item);
}
else
{
JsonObject jsItem = (JsonObject) item;
enumItem = Enum.valueOf(c, (String) jsItem.get("name"));
}

if (enumSet == null)
{ // Lazy init the EnumSet
enumSet = EnumSet.of(enumItem);
}
else
{
enumSet.add(enumItem);
}
}
return enumSet;
}

/**
* For all fields where the value was "@ref":"n" where 'n' was the id of an object
* that had not yet been encountered in the stream, make the final substitution.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package com.cedarsoftware.util.io;

import com.google.gson.Gson;
import org.junit.Test;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
* @author John DeRegnaucourt ([email protected])
Expand All @@ -29,7 +26,31 @@ public class TestEmptyEnumSetOnJDK17
{
static enum TestEnum
{
V1
V1, V2, V3
}

static class MultiVersioned
{
EnumSet<TestEnum> versions;
String dummy;
EnumSet<Thread.State> states;

public MultiVersioned(EnumSet<TestEnum> versions, String dummy, EnumSet<Thread.State> states) {
this.versions = versions;
this.dummy = dummy;
this.states = states;
}

public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MultiVersioned that = (MultiVersioned) o;
return Objects.equals(versions, that.versions) && Objects.equals(dummy, that.dummy);
}

public int hashCode() {
return Objects.hash(versions, dummy);
}
}

@Test
Expand All @@ -46,11 +67,22 @@ public void testEmptyEnumSetOnJDK17()
@Test
public void testEnumSetOnJDK17()
{
EnumSet source = EnumSet.of(TestEnum.V1);
EnumSet source = EnumSet.of(TestEnum.V1, TestEnum.V3);

String json = JsonWriter.objectToJson(source);
EnumSet target = (EnumSet) JsonReader.jsonToJava(json);

assert source.equals(target);
}
@Test

public void testEnumSetInPoJoOnJDK17()
{
MultiVersioned m = new MultiVersioned(EnumSet.of(TestEnum.V1, TestEnum.V3), "what", EnumSet.of(Thread.State.NEW));

String json = JsonWriter.objectToJson(m);
MultiVersioned target = (MultiVersioned) JsonReader.jsonToJava(json);

assert m.equals(target);
}
}
Loading