diff --git a/Examples/Java/Sources/Everything.java b/Examples/Java/Sources/Everything.java index 87239241..bef23077 100644 --- a/Examples/Java/Sources/Everything.java +++ b/Examples/Java/Sources/Everything.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.annotations.SerializedName; @@ -25,30 +26,6 @@ import java.util.Objects; import java.util.Set; -interface EverythingMapPolymorphicValuesMatcher { - R match(@Nullable User value0); - R match(@Nullable Board value1); - R match(@Nullable Image value2); - R match(@Nullable Pin value3); - R match(@Nullable Everything value4); - R match(@Nullable List value5); - R match(@Nullable Map value6); -} - -interface EverythingPolymorphicPropMatcher { - R match(@Nullable User value0); - R match(@Nullable Board value1); - R match(@Nullable Image value2); - R match(@Nullable Pin value3); - R match(@Nullable Everything value4); - R match(@Nullable String value5); - R match(@Nullable Boolean value6); - R match(@Nullable Integer value7); - R match(@Nullable Double value8); - R match(@Nullable Date value9); - R match(@Nullable String value10); -} - public class Everything { public enum EverythingCharEnum { @@ -1958,24 +1935,7 @@ public Everything read(@NonNull JsonReader reader) throws IOException { } } - public static final class EverythingMapPolymorphicValues { - - public enum InternalStorage { - USER(0), - BOARD(1), - IMAGE(2), - PIN(3), - EVERYTHING(4), - LISTOBJECT(5), - MAPSTRING_OBJECT(6); - private final int value; - InternalStorage(int value) { - this.value = value; - } - public int getValue() { - return this.value; - } - } + public static final class EverythingMapPolymorphicValues { private @Nullable User value0; private @Nullable Board value1; @@ -1985,40 +1945,161 @@ public int getValue() { private @Nullable List value5; private @Nullable Map value6; - private static InternalStorage internalStorage; - private EverythingMapPolymorphicValues() { } - public R matchEverythingMapPolymorphicValues(EverythingMapPolymorphicValuesMatcher matcher) { - // TODO: Implement this! + public EverythingMapPolymorphicValues(@NonNull User value) { + this.value0 = value; + } + + public EverythingMapPolymorphicValues(@NonNull Board value) { + this.value1 = value; + } + + public EverythingMapPolymorphicValues(@NonNull Image value) { + this.value2 = value; + } + + public EverythingMapPolymorphicValues(@NonNull Pin value) { + this.value3 = value; + } + + public EverythingMapPolymorphicValues(@NonNull Everything value) { + this.value4 = value; + } + + public EverythingMapPolymorphicValues(@NonNull List value) { + this.value5 = value; + } + + public EverythingMapPolymorphicValues(@NonNull Map value) { + this.value6 = value; + } + + @Nullable + public R matchEverythingMapPolymorphicValues(EverythingMapPolymorphicValuesMatcher matcher) { + if (value0 != null) { + return matcher.match(value0); + } + if (value1 != null) { + return matcher.match(value1); + } + if (value2 != null) { + return matcher.match(value2); + } + if (value3 != null) { + return matcher.match(value3); + } + if (value4 != null) { + return matcher.match(value4); + } + if (value5 != null) { + return matcher.match(value5); + } + if (value6 != null) { + return matcher.match(value6); + } return null; } - } - public static final class EverythingPolymorphicProp { + public static class EverythingMapPolymorphicValuesTypeAdapterFactory implements TypeAdapterFactory { - public enum InternalStorage { - USER(0), - BOARD(1), - IMAGE(2), - PIN(3), - EVERYTHING(4), - STRING(5), - BOOLEAN(6), - INTEGER(7), - DOUBLE(8), - DATE(9), - STRING(10); - private final int value; - InternalStorage(int value) { - this.value = value; + @Nullable + @Override + public TypeAdapter create(@NonNull Gson gson, @NonNull TypeToken typeToken) { + if (!EverythingMapPolymorphicValues.class.isAssignableFrom(typeToken.getRawType())) { + return null; + } + return (TypeAdapter) new EverythingMapPolymorphicValuesTypeAdapter(gson); } - public int getValue() { - return this.value; + } + + private static class EverythingMapPolymorphicValuesTypeAdapter extends TypeAdapter { + + private final Gson gson; + private TypeAdapter userTypeAdapter; + private TypeAdapter boardTypeAdapter; + private TypeAdapter imageTypeAdapter; + private TypeAdapter pinTypeAdapter; + private TypeAdapter everythingTypeAdapter; + private TypeAdapter> list_Object_TypeAdapter; + private TypeAdapter> map_String__Object_TypeAdapter; + + public EverythingMapPolymorphicValuesTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(@NonNull JsonWriter writer, EverythingMapPolymorphicValues value) throws IOException { + writer.nullValue(); + } + + @Nullable + @Override + public EverythingMapPolymorphicValues read(@NonNull JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + if (reader.peek() == JsonToken.BEGIN_OBJECT) { + JsonObject jsonObject = this.gson.fromJson(reader, JsonObject.class); + String type; + try { + type = jsonObject.get("type").getAsString(); + } catch (Exception e) { + return new EverythingMapPolymorphicValues(); + } + if (type == null) { + return new EverythingMapPolymorphicValues(); + } + switch (type) { + case ("user"): + if (this.userTypeAdapter == null) { + this.userTypeAdapter = this.gson.getAdapter(User.class).nullSafe(); + } + return new EverythingMapPolymorphicValues(userTypeAdapter.fromJsonTree(jsonObject)); + case ("board"): + if (this.boardTypeAdapter == null) { + this.boardTypeAdapter = this.gson.getAdapter(Board.class).nullSafe(); + } + return new EverythingMapPolymorphicValues(boardTypeAdapter.fromJsonTree(jsonObject)); + case ("image"): + if (this.imageTypeAdapter == null) { + this.imageTypeAdapter = this.gson.getAdapter(Image.class).nullSafe(); + } + return new EverythingMapPolymorphicValues(imageTypeAdapter.fromJsonTree(jsonObject)); + case ("pin"): + if (this.pinTypeAdapter == null) { + this.pinTypeAdapter = this.gson.getAdapter(Pin.class).nullSafe(); + } + return new EverythingMapPolymorphicValues(pinTypeAdapter.fromJsonTree(jsonObject)); + case ("everything"): + if (this.everythingTypeAdapter == null) { + this.everythingTypeAdapter = this.gson.getAdapter(Everything.class).nullSafe(); + } + return new EverythingMapPolymorphicValues(everythingTypeAdapter.fromJsonTree(jsonObject)); + default: + return new EverythingMapPolymorphicValues(); + } + } + reader.skipValue(); + return new EverythingMapPolymorphicValues(); } } + public interface EverythingMapPolymorphicValuesMatcher { + R match(@NonNull User value0); + R match(@NonNull Board value1); + R match(@NonNull Image value2); + R match(@NonNull Pin value3); + R match(@NonNull Everything value4); + R match(@NonNull List value5); + R match(@NonNull Map value6); + } + } + + public static final class EverythingPolymorphicProp { + private @Nullable User value0; private @Nullable Board value1; private @Nullable Image value2; @@ -2031,14 +2112,192 @@ public int getValue() { private @Nullable Date value9; private @Nullable String value10; - private static InternalStorage internalStorage; - private EverythingPolymorphicProp() { } - public R matchEverythingPolymorphicProp(EverythingPolymorphicPropMatcher matcher) { - // TODO: Implement this! + public EverythingPolymorphicProp(@NonNull User value) { + this.value0 = value; + } + + public EverythingPolymorphicProp(@NonNull Board value) { + this.value1 = value; + } + + public EverythingPolymorphicProp(@NonNull Image value) { + this.value2 = value; + } + + public EverythingPolymorphicProp(@NonNull Pin value) { + this.value3 = value; + } + + public EverythingPolymorphicProp(@NonNull Everything value) { + this.value4 = value; + } + + public EverythingPolymorphicProp(@NonNull String value) { + this.value5 = value; + } + + public EverythingPolymorphicProp(@NonNull Boolean value) { + this.value6 = value; + } + + public EverythingPolymorphicProp(@NonNull Integer value) { + this.value7 = value; + } + + public EverythingPolymorphicProp(@NonNull Double value) { + this.value8 = value; + } + + public EverythingPolymorphicProp(@NonNull Date value) { + this.value9 = value; + } + + public EverythingPolymorphicProp(@NonNull String value) { + this.value10 = value; + } + + @Nullable + public R matchEverythingPolymorphicProp(EverythingPolymorphicPropMatcher matcher) { + if (value0 != null) { + return matcher.match(value0); + } + if (value1 != null) { + return matcher.match(value1); + } + if (value2 != null) { + return matcher.match(value2); + } + if (value3 != null) { + return matcher.match(value3); + } + if (value4 != null) { + return matcher.match(value4); + } + if (value5 != null) { + return matcher.match(value5); + } + if (value6 != null) { + return matcher.match(value6); + } + if (value7 != null) { + return matcher.match(value7); + } + if (value8 != null) { + return matcher.match(value8); + } + if (value9 != null) { + return matcher.match(value9); + } + if (value10 != null) { + return matcher.match(value10); + } return null; } + + public static class EverythingPolymorphicPropTypeAdapterFactory implements TypeAdapterFactory { + + @Nullable + @Override + public TypeAdapter create(@NonNull Gson gson, @NonNull TypeToken typeToken) { + if (!EverythingPolymorphicProp.class.isAssignableFrom(typeToken.getRawType())) { + return null; + } + return (TypeAdapter) new EverythingPolymorphicPropTypeAdapter(gson); + } + } + + private static class EverythingPolymorphicPropTypeAdapter extends TypeAdapter { + + private final Gson gson; + private TypeAdapter userTypeAdapter; + private TypeAdapter boardTypeAdapter; + private TypeAdapter imageTypeAdapter; + private TypeAdapter pinTypeAdapter; + private TypeAdapter everythingTypeAdapter; + private TypeAdapter stringTypeAdapter; + private TypeAdapter booleanTypeAdapter; + private TypeAdapter integerTypeAdapter; + private TypeAdapter doubleTypeAdapter; + private TypeAdapter dateTypeAdapter; + private TypeAdapter stringTypeAdapter; + + public EverythingPolymorphicPropTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(@NonNull JsonWriter writer, EverythingPolymorphicProp value) throws IOException { + writer.nullValue(); + } + + @Nullable + @Override + public EverythingPolymorphicProp read(@NonNull JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + if (reader.peek() == JsonToken.BEGIN_OBJECT) { + JsonObject jsonObject = this.gson.fromJson(reader, JsonObject.class); + String type; + try { + type = jsonObject.get("type").getAsString(); + } catch (Exception e) { + return new EverythingPolymorphicProp(); + } + if (type == null) { + return new EverythingPolymorphicProp(); + } + switch (type) { + case ("user"): + if (this.userTypeAdapter == null) { + this.userTypeAdapter = this.gson.getAdapter(User.class).nullSafe(); + } + return new EverythingPolymorphicProp(userTypeAdapter.fromJsonTree(jsonObject)); + case ("board"): + if (this.boardTypeAdapter == null) { + this.boardTypeAdapter = this.gson.getAdapter(Board.class).nullSafe(); + } + return new EverythingPolymorphicProp(boardTypeAdapter.fromJsonTree(jsonObject)); + case ("image"): + if (this.imageTypeAdapter == null) { + this.imageTypeAdapter = this.gson.getAdapter(Image.class).nullSafe(); + } + return new EverythingPolymorphicProp(imageTypeAdapter.fromJsonTree(jsonObject)); + case ("pin"): + if (this.pinTypeAdapter == null) { + this.pinTypeAdapter = this.gson.getAdapter(Pin.class).nullSafe(); + } + return new EverythingPolymorphicProp(pinTypeAdapter.fromJsonTree(jsonObject)); + case ("everything"): + if (this.everythingTypeAdapter == null) { + this.everythingTypeAdapter = this.gson.getAdapter(Everything.class).nullSafe(); + } + return new EverythingPolymorphicProp(everythingTypeAdapter.fromJsonTree(jsonObject)); + default: + return new EverythingPolymorphicProp(); + } + } + reader.skipValue(); + return new EverythingPolymorphicProp(); + } + } + + public interface EverythingPolymorphicPropMatcher { + R match(@NonNull User value0); + R match(@NonNull Board value1); + R match(@NonNull Image value2); + R match(@NonNull Pin value3); + R match(@NonNull Everything value4); + R match(@NonNull String value5); + R match(@NonNull Boolean value6); + R match(@NonNull Integer value7); + R match(@NonNull Double value8); + R match(@NonNull Date value9); + R match(@NonNull String value10); + } } } diff --git a/Examples/Java/Sources/Pin.java b/Examples/Java/Sources/Pin.java index cc7f12b6..df5713a7 100644 --- a/Examples/Java/Sources/Pin.java +++ b/Examples/Java/Sources/Pin.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.annotations.SerializedName; @@ -24,11 +25,6 @@ import java.util.Map; import java.util.Objects; -interface PinAttributionObjectsMatcher { - R match(@Nullable Board value0); - R match(@Nullable User value1); -} - public class Pin { public enum PinInStock { @@ -957,31 +953,101 @@ public Pin read(@NonNull JsonReader reader) throws IOException { } } - public static final class PinAttributionObjects { + public static final class PinAttributionObjects { + + private @Nullable Board value0; + private @Nullable User value1; + + private PinAttributionObjects() { + } + + public PinAttributionObjects(@NonNull Board value) { + this.value0 = value; + } + + public PinAttributionObjects(@NonNull User value) { + this.value1 = value; + } - public enum InternalStorage { - BOARD(0), - USER(1); - private final int value; - InternalStorage(int value) { - this.value = value; + @Nullable + public R matchPinAttributionObjects(PinAttributionObjectsMatcher matcher) { + if (value0 != null) { + return matcher.match(value0); } - public int getValue() { - return this.value; + if (value1 != null) { + return matcher.match(value1); } + return null; } - private @Nullable Board value0; - private @Nullable User value1; + public static class PinAttributionObjectsTypeAdapterFactory implements TypeAdapterFactory { - private static InternalStorage internalStorage; + @Nullable + @Override + public TypeAdapter create(@NonNull Gson gson, @NonNull TypeToken typeToken) { + if (!PinAttributionObjects.class.isAssignableFrom(typeToken.getRawType())) { + return null; + } + return (TypeAdapter) new PinAttributionObjectsTypeAdapter(gson); + } + } - private PinAttributionObjects() { + private static class PinAttributionObjectsTypeAdapter extends TypeAdapter { + + private final Gson gson; + private TypeAdapter boardTypeAdapter; + private TypeAdapter userTypeAdapter; + + public PinAttributionObjectsTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(@NonNull JsonWriter writer, PinAttributionObjects value) throws IOException { + writer.nullValue(); + } + + @Nullable + @Override + public PinAttributionObjects read(@NonNull JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + if (reader.peek() == JsonToken.BEGIN_OBJECT) { + JsonObject jsonObject = this.gson.fromJson(reader, JsonObject.class); + String type; + try { + type = jsonObject.get("type").getAsString(); + } catch (Exception e) { + return new PinAttributionObjects(); + } + if (type == null) { + return new PinAttributionObjects(); + } + switch (type) { + case ("board"): + if (this.boardTypeAdapter == null) { + this.boardTypeAdapter = this.gson.getAdapter(Board.class).nullSafe(); + } + return new PinAttributionObjects(boardTypeAdapter.fromJsonTree(jsonObject)); + case ("user"): + if (this.userTypeAdapter == null) { + this.userTypeAdapter = this.gson.getAdapter(User.class).nullSafe(); + } + return new PinAttributionObjects(userTypeAdapter.fromJsonTree(jsonObject)); + default: + return new PinAttributionObjects(); + } + } + reader.skipValue(); + return new PinAttributionObjects(); + } } - public R matchPinAttributionObjects(PinAttributionObjectsMatcher matcher) { - // TODO: Implement this! - return null; + public interface PinAttributionObjectsMatcher { + R match(@NonNull Board value0); + R match(@NonNull User value1); } } } diff --git a/Sources/Core/JavaADTRenderer.swift b/Sources/Core/JavaADTRenderer.swift index 1584fc31..91375c58 100644 --- a/Sources/Core/JavaADTRenderer.swift +++ b/Sources/Core/JavaADTRenderer.swift @@ -21,66 +21,198 @@ extension JavaModelRenderer { } */ func adtRootsForSchema(property: String, schemas: [SchemaObjectProperty]) -> [JavaIR.Root] { - // Do we need to create a custom runtime type adapter factory? let adtName = "\(rootSchema.name)_\(property)" let formattedADTName = Languages.java.snakeCaseToCamelCase(adtName) - let privateInit = JavaIR.method([.private], "\(formattedADTName)()") { [] } func interfaceMethods() -> [JavaIR.Method] { return schemas.enumerated() - .map { (typeFromSchema("", $0.element), $0.offset) } - .map { JavaIR.method([], "R match(\($0.0) value\($0.1))") { [] } } + .map { (unwrappedTypeFromSchema("", $0.element.schema), $0.offset) } + .map { JavaIR.method([], "R match(@NonNull \($0.0) value\($0.1))") { [] } } } - let matcherInterface = JavaIR.Interface(modifiers: [], + let matcherInterface = JavaIR.Interface(modifiers: [.public], extends: nil, name: "\(formattedADTName)Matcher", methods: interfaceMethods()) - let matcherMethod = JavaIR.method([.public], "R match\(formattedADTName)(\(formattedADTName)Matcher matcher)") { [ - "// TODO: Implement this!", - "return null;", - ] } + let matcherMethod = JavaIR.method(annotations: [.nullable], [.public], " R match\(formattedADTName)(\(formattedADTName)Matcher matcher)") { + schemas.enumerated().map { index, _ in + JavaIR.ifBlock(condition: "value\(index) != null") { + ["return matcher.match(value\(index));"] + } + } + + ["return null;"] + } + + let emptyConstructor = JavaIR.method([.private], "\(formattedADTName)()") { [] } + let typeConstructors = schemas.enumerated().map { index, schemaObj in + JavaIR.method([.public], "\(formattedADTName)(@NonNull \(unwrappedTypeFromSchema("", schemaObj.schema)) value)") { [ + "this.value\(index) = value;", + ] } + } let internalProperties = schemas.enumerated() .map { (typeFromSchema("", $0.element), $0.offset) } .map { JavaIR.Property(annotations: [], modifiers: [.private], type: $0.0, name: "value\($0.1)", initialValue: "") } - let enumOptions = schemas.enumerated() - .map { (typeFromSchema("", $0.element.schema.unknownNullabilityProperty()) - .split(separator: " ") - .filter { !String($0).hasPrefix("@") } - .map { $0.trimmingCharacters(in: .whitespaces) } - .map { $0.replacingOccurrences(of: "<", with: "") } - .map { $0.replacingOccurrences(of: ">", with: "") } - .map { $0.replacingOccurrences(of: ",", with: "") } - .filter { $0 != "" } - .joined(separator: "_"), $0.offset) } - .map { EnumValue(defaultValue: $0.1, description: $0.0) } - - let internalStorageEnum = JavaIR.Enum(name: "InternalStorage", values: .integer(enumOptions)) - - let internalStorageProp = JavaIR.Property(annotations: [], modifiers: [.private, .static], type: "InternalStorage", name: "internalStorage", initialValue: "") let cls = JavaIR.Class(annotations: [], modifiers: [.public, .static, .final], extends: nil, implements: nil, - name: "\(formattedADTName)", - methods: [ - privateInit, - matcherMethod, + name: "\(formattedADTName)", + methods: [emptyConstructor] + typeConstructors + [matcherMethod], + enums: [], + innerClasses: [ + adtTypeAdapterFactory(property: property, schemas: schemas), + adtTypeAdapter(property: property, schemas: schemas), ], - enums: [internalStorageEnum], - innerClasses: [], - properties: [internalProperties, [internalStorageProp]]) - return [ - // Interface - JavaIR.Root.interfaceDecl(aInterface: matcherInterface), - // Class - JavaIR.Root.classDecl(aClass: cls), - // - Properties - // - Private Constructor - // - Match method + interfaces: [matcherInterface], + properties: [internalProperties]) + + return [JavaIR.Root.classDecl(aClass: cls)] + } + + func adtTypeAdapterFactory(property: String, schemas _: [SchemaObjectProperty]) -> JavaIR.Class { + let adtName = "\(rootSchema.name)_\(property)" + let formattedADTName = Languages.java.snakeCaseToCamelCase(adtName) + + let createMethod = JavaIR.method(annotations: [.nullable, JavaAnnotation.override], [.public], " TypeAdapter create(@NonNull Gson gson, @NonNull TypeToken typeToken)") { [ + JavaIR.ifBlock(condition: "!" + formattedADTName + ".class.isAssignableFrom(typeToken.getRawType())") { [ + "return null;", + ] }, + "return (TypeAdapter) new " + formattedADTName + "TypeAdapter(gson);", + ] } + + return JavaIR.Class( + annotations: [], + modifiers: [.public, .static], + extends: nil, + implements: ["TypeAdapterFactory"], + name: formattedADTName + "TypeAdapterFactory", + methods: [createMethod], + enums: [], + innerClasses: [], + interfaces: [], + properties: [] + ) + } + + func adtTypeAdapter(property: String, schemas: [SchemaObjectProperty]) -> JavaIR.Class { + let adtName = "\(rootSchema.name)_\(property)" + let formattedADTName = Languages.java.snakeCaseToCamelCase(adtName) + + return JavaIR.Class( + annotations: [], + modifiers: [.private, .static], + extends: "TypeAdapter<\(formattedADTName)>", + implements: [], + name: formattedADTName + "TypeAdapter", + methods: adtTypeAdapterMethods(property: property, schemas: schemas), + enums: [], + innerClasses: [], + interfaces: [], + properties: adtTypeAdapterProperties(schemas: schemas) + ) + } + + func adtTypeAdapterVariableNameForType(_ type: String) -> String { + return type.replacingNonAlphaNumericsWith("_").lowercaseFirst + "TypeAdapter" + } + + func adtTypeAdapterProperties(schemas: [SchemaObjectProperty]) -> [[JavaIR.Property]] { + let typeAdapters = schemas.map { schemaObj in + JavaIR.Property( + annotations: [], + modifiers: [.private], + type: "TypeAdapter<\(unwrappedTypeFromSchema("", schemaObj.schema))>", + name: typeAdapterVariableNameForType(unwrappedTypeFromSchema("", schemaObj.schema)), + initialValue: "" + ) + } + + let gson = JavaIR.Property( + annotations: [], + modifiers: [.final, .private], + type: "Gson", + name: "gson", + initialValue: "" + ) + + return [[gson] + typeAdapters] + } + + func adtTypeAdapterMethods(property: String, schemas: [SchemaObjectProperty]) -> [JavaIR.Method] { + let adtName = "\(rootSchema.name)_\(property)" + let formattedADTName = Languages.java.snakeCaseToCamelCase(adtName) + + let constructor = JavaIR.method( + annotations: [], + [.public], + formattedADTName + "TypeAdapter(Gson gson)" + ) { [ + "this.gson = gson;", ] + } + + let write = JavaIR.methodThatThrows( + annotations: [JavaAnnotation.override], + [.public], + "void write(@NonNull JsonWriter writer, " + formattedADTName + " value)", + ["IOException"] + ) { [ + "writer.nullValue();", + ] } + + let read = JavaIR.methodThatThrows( + annotations: [.nullable, JavaAnnotation.override], + [.public], + formattedADTName + " read(@NonNull JsonReader reader)", + ["IOException"] + ) { [ + JavaIR.ifBlock(condition: "reader.peek() == JsonToken.NULL") { [ + "reader.nextNull();", + "return null;", + ] }, + + JavaIR.ifBlock(condition: "reader.peek() == JsonToken.BEGIN_OBJECT") { [ + "JsonObject jsonObject = this.gson.fromJson(reader, JsonObject.class);", + "String type;", + JavaIR.tryCatch(try: ["type = jsonObject.get(\"type\").getAsString();"], catch: JavaIR.Catch(argument: "Exception e", body: ["return new \(formattedADTName)();"])), + JavaIR.ifBlock(condition: "type == null") { [ + "return new \(formattedADTName)();", + ] }, + JavaIR.switchBlock(variableToCheck: "type", defaultBody: ["return new \(formattedADTName)();"]) { + schemas.enumerated().compactMap { _, schemaObj in + switch schemaObj.schema { + case let .reference(with: ref): + switch ref.force() { + case let .some(.object(schemaRoot)): + let typeAdapterVariableName = typeAdapterVariableNameForType(unwrappedTypeFromSchema("", schemaObj.schema)) + return JavaIR.Case( + variableEquals: "\"" + schemaRoot.typeIdentifier + "\"", + body: [ + // Creates TypeAdapter if necessary + JavaIR.ifBlock(condition: "this.\(typeAdapterVariableName) == null") { [ + "this.\(typeAdapterVariableName) = this.gson.getAdapter(\(unwrappedTypeFromSchema("", schemaObj.schema)).class).nullSafe();", + ] }, + "return new \(formattedADTName)(\(typeAdapterVariableName).fromJsonTree(jsonObject));", + ], + shouldBreak: false + ) + default: + return nil + } + default: + return nil + } + } + }, + ] }, + + "reader.skipValue();", + "return new \(formattedADTName)();", + ] } + + return [constructor, write, read] } } diff --git a/Sources/Core/JavaFileRenderer.swift b/Sources/Core/JavaFileRenderer.swift index bef23fda..725d8e36 100644 --- a/Sources/Core/JavaFileRenderer.swift +++ b/Sources/Core/JavaFileRenderer.swift @@ -51,7 +51,7 @@ extension JavaFileRenderer { } } - func unwrappedTypeFromSchema(_ param: String, _ schema: Schema) -> String { +func unwrappedTypeFromSchema(_ param: String, _ schema: Schema) -> String { switch schema { case .array(itemType: .none): return "List" diff --git a/Sources/Core/JavaIR.swift b/Sources/Core/JavaIR.swift index b8336a12..142b7f47 100644 --- a/Sources/Core/JavaIR.swift +++ b/Sources/Core/JavaIR.swift @@ -323,6 +323,21 @@ public struct JavaIR { ].joined(separator: "\n") } + static func tryCatch(try: [String], catch: Catch) -> String { + return [ + "try {", + -->`try`, + "} catch (\(`catch`.argument)) {", // TODO allow for multiple catches + -->`catch`.body, + "}", + ].joined(separator: "\n") + } + + struct Catch { + let argument: String + let body: [String] + } + static func switchBlock(variableToCheck: String, defaultBody: [String], cases: () -> [Case]) -> String { return [ "switch (" + variableToCheck + ") {", @@ -332,11 +347,18 @@ public struct JavaIR { ].joined(separator: "\n") } + struct Case { let variableEquals: String let body: [String] - let shouldBreak: Bool = true - + let shouldBreak: Bool + + init(variableEquals: String, body: [String], shouldBreak: Bool = true) { + self.variableEquals = variableEquals + self.body = body + self.shouldBreak = shouldBreak + } + func render() -> [String] { var lines = [ "case (" + variableEquals + "):", @@ -396,6 +418,7 @@ public struct JavaIR { let methods: [JavaIR.Method] let enums: [Enum] let innerClasses: [JavaIR.Class] + let interfaces: [JavaIR.Interface] let properties: [[JavaIR.Property]] func render() -> [String] { @@ -423,6 +446,10 @@ public struct JavaIR { if !innerClasses.isEmpty { lines.append(-->innerClasses.flatMap { [""] + $0.render() }) } + + if !interfaces.isEmpty { + lines.append(-->interfaces.flatMap { [""] + $0.render() }) + } lines.append("}") diff --git a/Sources/Core/JavaModelRenderer.swift b/Sources/Core/JavaModelRenderer.swift index 533e3dd4..21f06b74 100644 --- a/Sources/Core/JavaModelRenderer.swift +++ b/Sources/Core/JavaModelRenderer.swift @@ -408,18 +408,39 @@ public struct JavaModelRenderer: JavaFileRenderer { fatalError("java_nullability_annotation_type must be either android-support or androidx. Invalid type provided: " + params[.javaNullabilityAnnotationType]!) } - let propertyTypeImports = transitiveProperties.compactMap { (_, prop) -> String? in + let propertyTypeImports: [String] = transitiveProperties.compactMap { (_, prop) -> [String] in switch prop.schema { - case .array: return "java.util.List" - case .map: return "java.util.Map" - case .set: return "java.util.Set" - case .string(format: .some(.dateTime)): return "java.util.Date" - default: return nil + case .array(.none): + return ["java.util.List"] + case let .array(itemType: .some(itemType)): + switch itemType { + case .oneOf: + return ["java.util.List", "com.google.gson.JsonObject"] + default: + return ["java.util.List"] + } + case .map(.none): + return ["java.util.Map"] + case let .map(valueType: .some(valueType)): + switch valueType { + case .oneOf: + return ["java.util.Map", "com.google.gson.JsonObject"] + default: + return ["java.util.Map"] + } + case .set: return ["java.util.Set"] + case .string(format: .some(.dateTime)): return ["java.util.Date"] + default: return [] } + }.reduce(into: []) { + $0.append(contentsOf: $1) } - - let additionalImports = propertyTypeImports + uriType.imports + (unknownPropertyLogging?.imports ?? []) + (decorations.imports ?? []) - + + var additionalImports = propertyTypeImports + additionalImports += uriType.imports + additionalImports += (unknownPropertyLogging?.imports ?? []) + additionalImports += (decorations.imports ?? []) + let imports = [ JavaIR.Root.imports(names: Set([ "com.google.gson.Gson", @@ -494,6 +515,7 @@ public struct JavaModelRenderer: JavaFileRenderer { ], enums: [], innerClasses: [], + interfaces: [], properties: renderBuilderProperties() ) @@ -506,6 +528,7 @@ public struct JavaModelRenderer: JavaFileRenderer { methods: renderTypeAdapterFactoryMethods(), enums: [], innerClasses: [], + interfaces: [], properties: [] ) @@ -518,6 +541,7 @@ public struct JavaModelRenderer: JavaFileRenderer { methods: renderTypeAdapterMethods(), enums: [], innerClasses: [], + interfaces: [], properties: renderTypeAdapterProperties() ) @@ -544,6 +568,7 @@ public struct JavaModelRenderer: JavaFileRenderer { typeAdapterFactoryClass, typeAdapterClass, ] + adtClasses, + interfaces: [], properties: renderModelProperties() ) ) diff --git a/Sources/Core/Schema.swift b/Sources/Core/Schema.swift index 78bb6a41..8d53a168 100644 --- a/Sources/Core/Schema.swift +++ b/Sources/Core/Schema.swift @@ -104,7 +104,7 @@ public enum Nullability: String { // or if it would be better to have a protocol (i.e. NullabilityProperty) that we would pattern // match on to detect nullable constraints. public struct SchemaObjectProperty { - let schema: Schema +let schema: Schema let nullability: Nullability? // Nullability does not apply for primitive types init(schema aSchema: Schema, nullability aNullability: Nullability?) {