Skip to content

Commit 79c6c65

Browse files
authored
Merge pull request #180 from laurgarn/more_frugal_enumset
More frugal enumset
2 parents 43bdfbf + 2fb18e2 commit 79c6c65

File tree

6 files changed

+229
-34
lines changed

6 files changed

+229
-34
lines changed

src/main/java/com/cedarsoftware/util/io/JsonWriter.java

+101
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,10 @@ public void writeImpl(Object obj, boolean showType, boolean allowRef, boolean al
10891089
{
10901090
writeArray(obj, showType);
10911091
}
1092+
else if (obj instanceof EnumSet)
1093+
{
1094+
writeEnumSet((EnumSet)obj);
1095+
}
10921096
else if (obj instanceof Collection)
10931097
{
10941098
writeCollection((Collection) obj, showType);
@@ -2207,6 +2211,103 @@ else if (neverShowType && MetaUtils.isPrimitive(o.getClass()))
22072211
}
22082212
}
22092213

2214+
public void writeEnumSet(final EnumSet<?> enumSet) throws IOException
2215+
{
2216+
out.write('{');
2217+
tabIn();
2218+
2219+
boolean referenced = objsReferenced.containsKey(enumSet);
2220+
if (referenced)
2221+
{
2222+
writeId(getId(enumSet));
2223+
out.write(',');
2224+
newLine();
2225+
}
2226+
2227+
writeJsonUtf8String("@enum", out);
2228+
out.write(':');
2229+
2230+
Enum<? extends Enum<?>> ee = null;
2231+
if (!enumSet.isEmpty())
2232+
{
2233+
ee = enumSet.iterator().next();
2234+
}
2235+
else
2236+
{
2237+
EnumSet<? extends Enum<?>> complement = EnumSet.complementOf(enumSet);
2238+
if (!complement.isEmpty())
2239+
{
2240+
ee = complement.iterator().next();
2241+
}
2242+
}
2243+
2244+
Field elementTypeField = MetaUtils.getField(EnumSet.class, "elementType");
2245+
Class<?> elementType = (Class<?>) getValueByReflect(enumSet, elementTypeField);
2246+
if ( elementType != null)
2247+
{
2248+
// nice we got the right to sneak into
2249+
}
2250+
else if (ee == null)
2251+
{
2252+
elementType = MetaUtils.Dumpty.class;
2253+
}
2254+
else
2255+
{
2256+
elementType = ee.getClass();
2257+
}
2258+
writeJsonUtf8String(elementType.getName(), out);
2259+
2260+
if (!enumSet.isEmpty()) {
2261+
Map<String, Field> mapOfFields = MetaUtils.getDeepDeclaredFields(elementType);
2262+
//Field[] enumFields = elementType.getDeclaredFields();
2263+
int enumFieldsCount = mapOfFields.size();
2264+
2265+
out.write(",");
2266+
newLine();
2267+
2268+
writeJsonUtf8String("@items", out);
2269+
out.write(":[");
2270+
if (enumFieldsCount > 2)
2271+
{
2272+
newLine();
2273+
}
2274+
2275+
boolean firstInSet = true;
2276+
for (Enum e : enumSet) {
2277+
if (!firstInSet)
2278+
{
2279+
out.write(",");
2280+
if (enumFieldsCount > 2)
2281+
{
2282+
newLine();
2283+
}
2284+
}
2285+
firstInSet = false;
2286+
2287+
if (enumFieldsCount <= 2)
2288+
{
2289+
writeJsonUtf8String(e.name(), out);
2290+
}
2291+
else
2292+
{
2293+
boolean firstInEntry = true;
2294+
out.write('{');
2295+
for (Field f : mapOfFields.values())
2296+
{
2297+
firstInEntry = writeField(e, firstInEntry, f.getName(), f, false);
2298+
}
2299+
out.write('}');
2300+
}
2301+
}
2302+
2303+
out.write("]");
2304+
}
2305+
2306+
2307+
tabOut();
2308+
out.write('}');
2309+
}
2310+
22102311
/**
22112312
* @param obj Object to be written in JSON format
22122313
* @param showType boolean true means show the "@type" field, false

src/main/java/com/cedarsoftware/util/io/MetaUtils.java

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
*/
3737
public class MetaUtils
3838
{
39+
public enum Dumpty {}
40+
3941
private MetaUtils () {}
4042
private static final Map<Class, Map<String, Field>> classMetaCache = new ConcurrentHashMap<>();
4143
private static final Set<Class> prims = new HashSet<>();

src/main/java/com/cedarsoftware/util/io/ObjectResolver.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,13 @@ protected void traverseCollection(final Deque<JsonObject<String, Object>> stack,
414414
}
415415
return;
416416
}
417+
418+
Class mayEnumClass = null;
419+
String mayEnumClasName = (String)jsonObj.get("@enum");
420+
if (mayEnumClasName != null) {
421+
mayEnumClass = MetaUtils.classForName(mayEnumClasName, classLoader);
422+
}
423+
417424
final boolean isImmutable = className != null && className.startsWith("java.util.Immutable");
418425
final Collection col = isImmutable ? new ArrayList() : (Collection) jsonObj.target;
419426
final boolean isList = col instanceof List;
@@ -436,7 +443,10 @@ else if ((special = readIfMatching(element, null, stack)) != null)
436443
}
437444
else if (element instanceof String || element instanceof Boolean || element instanceof Double || element instanceof Long)
438445
{ // Allow Strings, Booleans, Longs, and Doubles to be "inline" without Java object decoration (@id, @type, etc.)
439-
col.add(element);
446+
if (mayEnumClass == null)
447+
col.add(element);
448+
else
449+
col.add(Enum.valueOf(mayEnumClass, (String)element));
440450
}
441451
else if (element.getClass().isArray())
442452
{

src/main/java/com/cedarsoftware/util/io/Resolver.java

+64-8
Original file line numberDiff line numberDiff line change
@@ -313,13 +313,21 @@ protected Object createJavaObjectInstance(Class clazz, JsonObject jsonObj)
313313
String type = jsonObj.type;
314314

315315
// We can't set values to an Object, so well try to use the contained type instead
316-
if ("java.lang.Object".equals(type))
316+
if ("java.lang.Object".equals(type))
317317
{
318-
Object value = jsonObj.get("value");
319-
if (jsonObj.keySet().size() == 1 && value != null)
318+
Object value = jsonObj.get("value");
319+
if (jsonObj.keySet().size() == 1 && value != null)
320320
{
321-
type = value.getClass().getName();
322-
}
321+
type = value.getClass().getName();
322+
}
323+
}
324+
if (type == null)
325+
{
326+
Object mayEnumSpecial = jsonObj.get("@enum");
327+
if (mayEnumSpecial instanceof String)
328+
{
329+
type = "java.util.EnumSet";
330+
}
323331
}
324332

325333
Object mate;
@@ -380,7 +388,7 @@ else if (Enum.class.isAssignableFrom(c)) // anonymous subclass of an enum
380388
}
381389
else if (EnumSet.class.isAssignableFrom(c))
382390
{
383-
mate = getEnumSet(c, jsonObj);
391+
mate = extractEnumSet(c, jsonObj);
384392
}
385393
else if ((mate = coerceCertainTypes(c.getName())) != null)
386394
{ // if coerceCertainTypes() returns non-null, it did the work
@@ -426,7 +434,7 @@ else if (Enum.class.isAssignableFrom(clazz)) // anonymous subclass of an enum
426434
}
427435
else if (EnumSet.class.isAssignableFrom(clazz)) // anonymous subclass of an enum
428436
{
429-
mate = getEnumSet(clazz, jsonObj);
437+
mate = extractEnumSet(clazz, jsonObj);
430438
}
431439
else if ((mate = coerceCertainTypes(clazz.getName())) != null)
432440
{ // if coerceCertainTypes() returns non-null, it did the work
@@ -533,7 +541,7 @@ private static enum OneEnum {
533541
/*
534542
/* java 17 don't allow to call reflect on internal java api like EnumSet's implement, so need to create like this
535543
*/
536-
private Object getEmptyEnumSet() {
544+
private EnumSet getEmptyEnumSet() {
537545
return EnumSet.noneOf(OneEnum.class);
538546
}
539547

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

579+
protected EnumSet<?> extractEnumSet(Class c, JsonObject<String, Object> jsonObj)
580+
{
581+
String enumClassName = (String) jsonObj.get("@enum");
582+
Class enumClass = enumClassName == null ? null
583+
: MetaUtils.classForName(enumClassName, reader.getClassLoader());
584+
Object[] items = jsonObj.getArray();
585+
if (items == null || items.length == 0)
586+
{
587+
if (enumClass != null)
588+
{
589+
return EnumSet.noneOf(enumClass);
590+
}
591+
else
592+
{
593+
return EnumSet.noneOf(MetaUtils.Dumpty.class);
594+
}
595+
}
596+
else if (enumClass == null)
597+
{
598+
throw new JsonIoException("Could not figure out Enum of the not empty set " + jsonObj);
599+
}
600+
601+
EnumSet enumSet = null;
602+
for (Object item : items)
603+
{
604+
Enum enumItem;
605+
if (item instanceof String)
606+
{
607+
enumItem = Enum.valueOf(enumClass, (String)item);
608+
}
609+
else
610+
{
611+
JsonObject jsItem = (JsonObject) item;
612+
enumItem = Enum.valueOf(c, (String) jsItem.get("name"));
613+
}
614+
615+
if (enumSet == null)
616+
{ // Lazy init the EnumSet
617+
enumSet = EnumSet.of(enumItem);
618+
}
619+
else
620+
{
621+
enumSet.add(enumItem);
622+
}
623+
}
624+
return enumSet;
625+
}
626+
571627
/**
572628
* For all fields where the value was "@ref":"n" where 'n' was the id of an object
573629
* that had not yet been encountered in the stream, make the final substitution.

src/test/java/com/cedarsoftware/util/io/TestEmptyEnumSetOnJDK17.java

+38-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package com.cedarsoftware.util.io;
22

3-
import com.google.gson.Gson;
43
import org.junit.Test;
54

6-
import java.util.ArrayList;
75
import java.util.EnumSet;
8-
import java.util.List;
9-
import java.util.Map;
6+
import java.util.Objects;
107

118
/**
129
* @author John DeRegnaucourt ([email protected])
@@ -29,7 +26,31 @@ public class TestEmptyEnumSetOnJDK17
2926
{
3027
static enum TestEnum
3128
{
32-
V1
29+
V1, V2, V3
30+
}
31+
32+
static class MultiVersioned
33+
{
34+
EnumSet<TestEnum> versions;
35+
String dummy;
36+
EnumSet<Thread.State> states;
37+
38+
public MultiVersioned(EnumSet<TestEnum> versions, String dummy, EnumSet<Thread.State> states) {
39+
this.versions = versions;
40+
this.dummy = dummy;
41+
this.states = states;
42+
}
43+
44+
public boolean equals(Object o) {
45+
if (this == o) return true;
46+
if (o == null || getClass() != o.getClass()) return false;
47+
MultiVersioned that = (MultiVersioned) o;
48+
return Objects.equals(versions, that.versions) && Objects.equals(dummy, that.dummy);
49+
}
50+
51+
public int hashCode() {
52+
return Objects.hash(versions, dummy);
53+
}
3354
}
3455

3556
@Test
@@ -46,11 +67,22 @@ public void testEmptyEnumSetOnJDK17()
4667
@Test
4768
public void testEnumSetOnJDK17()
4869
{
49-
EnumSet source = EnumSet.of(TestEnum.V1);
70+
EnumSet source = EnumSet.of(TestEnum.V1, TestEnum.V3);
5071

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

5475
assert source.equals(target);
5576
}
77+
@Test
78+
79+
public void testEnumSetInPoJoOnJDK17()
80+
{
81+
MultiVersioned m = new MultiVersioned(EnumSet.of(TestEnum.V1, TestEnum.V3), "what", EnumSet.of(Thread.State.NEW));
82+
83+
String json = JsonWriter.objectToJson(m);
84+
MultiVersioned target = (MultiVersioned) JsonReader.jsonToJava(json);
85+
86+
assert m.equals(target);
87+
}
5688
}

0 commit comments

Comments
 (0)